@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.
- package/.github/workflows/pr-welcome.yml +1 -1
- package/CHANGELOG.md +35 -0
- package/package.json +4 -4
- package/src/app/chat/(desktop)/features/ChatInput.tsx +0 -1
- package/src/app/chat/features/Conversation/ChatList/Error/Plugin/PluginSettings.tsx +1 -1
- package/src/app/chat/features/Conversation/ChatList/Messages/Assistant.tsx +12 -15
- package/src/app/chat/features/Conversation/ChatList/Messages/Function.tsx +26 -15
- package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +36 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/{FunctionCall.tsx → Inspector/index.tsx} +53 -25
- package/src/app/chat/features/Conversation/ChatList/Plugins/{IFrameRender → Render/DefaultType/IFrameRender}/index.tsx +5 -16
- package/src/app/chat/features/Conversation/ChatList/Plugins/{PluginMessage.tsx → Render/DefaultType/index.tsx} +3 -3
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx +136 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/index.tsx +28 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/index.tsx +28 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/iframeOnReady.ts +23 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/listenToPlugin.ts +65 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/pluginSettings.ts +17 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/pluginState.ts +20 -0
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/utils/postMessage.ts +17 -0
- package/src/const/plugin.ts +1 -1
- package/src/features/PluginSettings/index.tsx +5 -3
- package/src/features/PluginSettingsModal/index.tsx +45 -0
- package/src/store/plugin/selectors.ts +10 -4
- package/src/store/session/slices/chat/actions/index.ts +4 -1
- package/src/store/session/slices/chat/actions/message.ts +102 -117
- package/src/store/session/slices/chat/actions/plugin.ts +109 -0
- package/src/store/session/slices/chat/reducers/message.ts +31 -1
- package/src/store/session/slices/chat/selectors/chat.ts +17 -20
- package/src/store/session/slices/chat/selectors/index.ts +4 -2
- package/src/types/chatMessage.ts +1 -0
- package/src/utils/message.ts +8 -1
- package/src/app/chat/features/Conversation/ChatList/Plugins/IFrameRender/hooks.ts +0 -29
- package/src/app/chat/features/Conversation/ChatList/Plugins/IFrameRender/utils.ts +0 -17
- /package/src/app/chat/features/Conversation/ChatList/Plugins/{PluginResultRender.tsx → Inspector/PluginResultJSON.tsx} +0 -0
- /package/src/app/chat/features/Conversation/ChatList/Plugins/{style.ts → Inspector/style.ts} +0 -0
- /package/src/app/chat/features/Conversation/ChatList/Plugins/{SystemJsRender → Render/DefaultType/SystemJsRender}/index.tsx +0 -0
- /package/src/app/chat/features/Conversation/ChatList/Plugins/{SystemJsRender → Render/DefaultType/SystemJsRender}/utils.ts +0 -0
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
|
+
[](#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.
|
|
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
|
|
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": "^
|
|
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": "^
|
|
144
|
+
"commitlint": "^18",
|
|
145
145
|
"consola": "^3",
|
|
146
146
|
"dpdm": "^3",
|
|
147
147
|
"eslint": "^8",
|
|
@@ -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}
|
|
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 {
|
|
6
|
+
import { isFunctionMessageAtStart } from '@/utils/message';
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import Inspector from '../Plugins/Inspector';
|
|
9
9
|
import { DefaultMessage } from './Default';
|
|
10
10
|
|
|
11
|
-
export const AssistantMessage: RenderMessage = memo(
|
|
12
|
-
({ id, plugin
|
|
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
|
-
|
|
14
|
+
if (!isFunctionMessageAtStart(content))
|
|
15
|
+
return <DefaultMessage content={content} id={id} {...props} />;
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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
|
|
9
|
-
import
|
|
9
|
+
import Inspector from '../Plugins/Inspector';
|
|
10
|
+
import PluginRender from '../Plugins/Render';
|
|
10
11
|
|
|
11
|
-
export const FunctionMessage: RenderMessage = memo(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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;
|
package/src/app/chat/features/Conversation/ChatList/Plugins/{FunctionCall.tsx → Inspector/index.tsx}
RENAMED
|
@@ -1,26 +1,40 @@
|
|
|
1
1
|
import { LoadingOutlined } from '@ant-design/icons';
|
|
2
|
-
import {
|
|
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 './
|
|
12
|
+
import PluginResult from './PluginResultJSON';
|
|
13
|
+
import Settings from './Settings';
|
|
12
14
|
import { useStyles } from './style';
|
|
13
15
|
|
|
14
|
-
export interface
|
|
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
|
|
23
|
-
({
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
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,
|
|
3
|
+
import { memo, useRef, useState } from 'react';
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { sendMessageToPlugin } from '
|
|
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
|
-
|
|
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
|
|
14
|
+
export interface PluginDefaultTypeProps {
|
|
15
15
|
content: string;
|
|
16
16
|
loading?: boolean;
|
|
17
17
|
name?: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const
|
|
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
|
|
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
|
+
};
|