Skip to content

Commit

Permalink
✨ feat: plugin default use iframe render (lobehub#141)
Browse files Browse the repository at this point in the history
* 🌐 style: update i18n

* 🐛 fix: 修正 functions 可能会重复的问题

* ✨ feat: 支持 iframe 模式的插件加载

* 🐛 fix: 优化默认规则
  • Loading branch information
arvinxx committed Sep 3, 2023
1 parent 7036f29 commit 35a3a16
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 16 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ config.rules['unicorn/explicit-length-check'] = 0;
config.rules['unicorn/prefer-code-point'] = 0;
config.rules['no-extra-boolean-cast'] = 0;
config.rules['unicorn/no-useless-undefined'] = 0;
config.rules['react/no-unknown-property'] = 0;

module.exports = config;
10 changes: 8 additions & 2 deletions locales/en_US/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@
"desc": "LobeChat will install the plugin using this link",
"invalid": "The input manifest link is invalid or does not comply with the specification",
"label": "Plugin Manifest URL",
"urlError": "Please enter a valid URL"
"urlError": "Please enter a valid URL",
"jsonInvalid": "The manifest is not valid, validation result: \n\n {{error}}",
"preview": "Preview Manifest",
"refresh": "Refresh",
"requestError": "Failed to request the link, please enter a valid link and check if the link allows cross-origin access"
},
"title": {
"desc": "The title of the plugin",
Expand All @@ -73,7 +77,9 @@
"manifest": "Function Description Manifest (Manifest)",
"meta": "Plugin Metadata"
},
"title": "Add Custom Plugin"
"title": "Add Custom Plugin",
"update": "Update",
"updateSuccess": "Plugin settings updated successfully"
},
"list": {
"item": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"@emoji-mart/data": "^1",
"@emoji-mart/react": "^1",
"@icons-pack/react-simple-icons": "^9",
"@lobehub/chat-plugin-sdk": "^1.13.1",
"@lobehub/chat-plugin-sdk": "^1.15.0",
"@lobehub/chat-plugins-gateway": "^1.5.0",
"@lobehub/ui": "latest",
"@vercel/analytics": "^1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useEffect } from 'react';

import { onPluginFetchMessage, onPluginReady } from './utils';

export const useOnPluginReady = (onReady: () => void) => {
useEffect(() => {
const fn = (e: MessageEvent) => {
onPluginReady(e, onReady);
};

window.addEventListener('message', fn);
return () => {
window.removeEventListener('message', fn);
};
}, []);
};

export const useOnPluginFetchMessage = (onRequest: (data: any) => void, deps: any[] = []) => {
useEffect(() => {
const fn = (e: MessageEvent) => {
onPluginFetchMessage(e, onRequest);
};

window.addEventListener('message', fn);
return () => {
window.removeEventListener('message', fn);
};
}, deps);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { PluginRenderProps } from '@lobehub/chat-plugin-sdk';
import { Skeleton } from 'antd';
import { memo, useEffect, useRef, useState } from 'react';

import { useOnPluginFetchMessage, useOnPluginReady } from './hooks';
import { sendMessageToPlugin } from './utils';

interface IFrameRenderProps extends PluginRenderProps {
height?: number;
url: string;
width?: number;
}

const IFrameRender = memo<IFrameRenderProps>(({ url, width = 800, height = 300, ...props }) => {
const [loading, setLoading] = useState(true);
const [readyForRender, setReady] = useState(false);
const iframeRef = useRef<HTMLIFrameElement>(null);

useOnPluginReady(() => setReady(true));

// 当 props 发生变化时,主动向 iframe 发送数据
useEffect(() => {
const iframeWin = iframeRef.current?.contentWindow;

if (iframeWin && readyForRender) {
sendMessageToPlugin(iframeWin, props);
}
}, [readyForRender, props]);

// 当接收到来自 iframe 的请求时,触发发送数据
useOnPluginFetchMessage(() => {
const iframeWin = iframeRef.current?.contentWindow;
if (iframeWin) {
sendMessageToPlugin(iframeWin, props);
}
}, [props]);

return (
<>
{loading && <Skeleton active style={{ width }} />}
<iframe
// @ts-ignore
allowtransparency="true"
height={height}
hidden={loading}
onLoad={() => {
setLoading(false);
}}
ref={iframeRef}
src={url}
style={{
border: 0,
// iframe 在 color-scheme:dark 模式下无法透明
// refs: https://www.jianshu.com/p/bc5a37bb6a7b
colorScheme: 'light',
}}
width={width}
/>
</>
);
});
export default IFrameRender;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { PluginChannel } from '@lobehub/chat-plugin-sdk';

export const onPluginReady = (e: MessageEvent, onReady: () => void) => {
if (e.data.type === PluginChannel.pluginReadyForRender) {
onReady();
}
};

export const onPluginFetchMessage = (e: MessageEvent, onRequest: (data: any) => void) => {
if (e.data.type === PluginChannel.fetchPluginMessage) {
onRequest(e.data);
}
};

export const sendMessageToPlugin = (window: Window, props: any) => {
window.postMessage({ props, type: PluginChannel.renderPlugin }, '*');
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Loading3QuartersOutlined } from '@ant-design/icons';
import { memo } from 'react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';

import { usePluginStore } from '@/store/plugin';
import { ChatMessage } from '@/types/chatMessage';

import CustomRender from './CustomRender';
import IFrameRender from './IFrameRender';
import SystemJsRender from './SystemJsRender';

export interface FunctionMessageProps extends ChatMessage {
loading?: boolean;
Expand All @@ -23,6 +24,8 @@ const PluginMessage = memo<FunctionMessageProps>(({ content, name }) => {
isJSON = false;
}

const contentObj = useMemo(() => (isJSON ? JSON.parse(content) : content), [content]);

// if (!loading)

if (!isJSON) {
Expand All @@ -36,10 +39,23 @@ const PluginMessage = memo<FunctionMessageProps>(({ content, name }) => {
);
}

if (!manifest?.ui?.url) return;
if (!manifest?.ui) return;

const ui = manifest.ui;

if (!ui.url) return;

if (ui.mode === 'module')
return <SystemJsRender content={contentObj} name={name || 'unknown'} url={ui.url} />;

return (
<CustomRender content={JSON.parse(content)} name={name || 'unknown'} url={manifest.ui?.url} />
<IFrameRender
content={contentObj}
height={ui.height}
name={name || 'unknown'}
url={ui.url}
width={ui.width}
/>
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { PluginRender, PluginRenderProps } from '@lobehub/chat-plugin-sdk';
import { Skeleton } from 'antd';
import { memo, useEffect, useState } from 'react';

import { system } from './dynamticLoader';
import { system } from './utils';

interface CustomRenderProps extends PluginRenderProps {
interface SystemJsRenderProps extends PluginRenderProps {
url: string;
}
const CustomRender = memo<CustomRenderProps>(({ url, ...props }) => {

const SystemJsRender = memo<SystemJsRenderProps>(({ url, ...props }) => {
const [component, setComp] = useState<PluginRender | null>(null);

useEffect(() => {
Expand All @@ -31,4 +32,4 @@ const CustomRender = memo<CustomRenderProps>(({ url, ...props }) => {

return <Render {...props} />;
});
export default CustomRender;
export default SystemJsRender;
15 changes: 10 additions & 5 deletions src/store/plugin/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { uniqBy } from 'lodash-es';
import { ChatCompletionFunctions } from 'openai-edge/types/api';

import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
import { pluginHelpers } from '@/store/plugin/helpers';

import { PluginStoreState } from './initialState';

const enabledSchema =
(enabledPlugins: string[] = []) =>
(s: PluginStoreState) => {
return Object.values(s.pluginManifestMap)
.filter((p) => {
// 如果不存在 enabledPlugins,那么全部不启用
if (!enabledPlugins) return false;
(s: PluginStoreState): ChatCompletionFunctions[] => {
// 如果不存在 enabledPlugins,那么全部不启用
if (!enabledPlugins) return [];

const list = Object.values(s.pluginManifestMap)
.filter((p) => {
// 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
return enabledPlugins.includes(p.identifier);
})
Expand All @@ -21,6 +24,8 @@ const enabledSchema =
name: manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + m.name,
})),
);

return uniqBy(list, 'name');
};

const pluginList = (s: PluginStoreState) => [...s.pluginList, ...s.customPluginList];
Expand Down

0 comments on commit 35a3a16

Please sign in to comment.