mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
✨ feat: support plugin state and settings
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { RenderMessage } from '@lobehub/ui';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { memo } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { useSessionStore } from '@/store/session';
|
||||
@@ -14,11 +14,21 @@ export const FunctionMessage: RenderMessage = memo(({ id, content, plugin, name
|
||||
chatSelectors.getFunctionMessageProps({ content, id, plugin }),
|
||||
isEqual,
|
||||
);
|
||||
const [showRender, setShow] = useState(true);
|
||||
|
||||
return (
|
||||
<Flexbox gap={12} id={id}>
|
||||
<Inspector {...fcProps} />
|
||||
<PluginRender content={content} loading={fcProps.loading} name={name} type={fcProps.type} />
|
||||
<Inspector showRender={showRender} {...fcProps} setShow={setShow} />
|
||||
{showRender && (
|
||||
<PluginRender
|
||||
content={content}
|
||||
id={id}
|
||||
loading={fcProps.loading}
|
||||
name={name}
|
||||
payload={fcProps.command}
|
||||
type={fcProps.type}
|
||||
/>
|
||||
)}
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { Avatar, Highlighter, Icon } from '@lobehub/ui';
|
||||
import { LobePluginType } from '@lobehub/chat-plugin-sdk';
|
||||
import { ActionIcon, Avatar, Highlighter, Icon } from '@lobehub/ui';
|
||||
import { Tabs } from 'antd';
|
||||
import { LucideChevronDown, LucideChevronUp, LucideToyBrick } from 'lucide-react';
|
||||
import { LucideChevronDown, LucideChevronUp, LucideOrbit, LucideToyBrick } from 'lucide-react';
|
||||
import { memo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { pluginHelpers, pluginSelectors, usePluginStore } from '@/store/plugin';
|
||||
|
||||
import { useStyles } from '../style';
|
||||
import PluginResult from './PluginResultJSON';
|
||||
import { useStyles } from './style';
|
||||
|
||||
export interface InspectorProps {
|
||||
arguments?: string;
|
||||
@@ -17,10 +18,22 @@ export interface InspectorProps {
|
||||
content: string;
|
||||
id?: string;
|
||||
loading?: boolean;
|
||||
setShow?: (showRender: boolean) => void;
|
||||
showRender?: boolean;
|
||||
type?: LobePluginType;
|
||||
}
|
||||
|
||||
const Inspector = memo<InspectorProps>(
|
||||
({ arguments: requestArgs = '{}', command, loading, content, id = 'unknown' }) => {
|
||||
({
|
||||
arguments: requestArgs = '{}',
|
||||
command,
|
||||
showRender,
|
||||
loading,
|
||||
setShow,
|
||||
content,
|
||||
id = 'unknown',
|
||||
type,
|
||||
}) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const { styles } = useStyles();
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -41,24 +54,37 @@ const Inspector = memo<InspectorProps>(
|
||||
|
||||
return (
|
||||
<Flexbox gap={8}>
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={styles.container}
|
||||
gap={8}
|
||||
horizontal
|
||||
onClick={() => {
|
||||
setOpen(!open);
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<div>
|
||||
<LoadingOutlined />
|
||||
</div>
|
||||
) : (
|
||||
avatar
|
||||
)}
|
||||
{pluginTitle ?? t('plugins.unknown')}
|
||||
<Icon icon={open ? LucideChevronUp : LucideChevronDown} />
|
||||
<Flexbox align={'center'} distribution={'space-between'} gap={24} horizontal>
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
className={styles.container}
|
||||
gap={8}
|
||||
horizontal
|
||||
onClick={() => {
|
||||
setOpen(!open);
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<div>
|
||||
<LoadingOutlined />
|
||||
</div>
|
||||
) : (
|
||||
avatar
|
||||
)}
|
||||
{pluginTitle ?? t('plugins.unknown')}
|
||||
<Icon icon={open ? LucideChevronUp : LucideChevronDown} />
|
||||
</Flexbox>
|
||||
<Flexbox horizontal>
|
||||
{type === 'standalone' && <ActionIcon icon={LucideOrbit} />}
|
||||
{setShow && (
|
||||
<ActionIcon
|
||||
icon={showRender ? LucideChevronUp : LucideChevronDown}
|
||||
onClick={() => {
|
||||
setShow(!showRender);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
{open && (
|
||||
<Tabs
|
||||
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
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);
|
||||
};
|
||||
+5
-16
@@ -1,9 +1,9 @@
|
||||
import { PluginRenderProps } from '@lobehub/chat-plugin-sdk/client';
|
||||
import { Skeleton } from 'antd';
|
||||
import { memo, useEffect, useRef, useState } from 'react';
|
||||
import { memo, useRef, useState } from 'react';
|
||||
|
||||
import { useOnPluginFetchMessage, useOnPluginReady } from './hooks';
|
||||
import { sendMessageToPlugin } from './utils';
|
||||
import { useOnPluginReadyForInteraction } from '../../utils/iframeOnReady';
|
||||
import { sendMessageToPlugin } from '../../utils/postMessage';
|
||||
|
||||
interface IFrameRenderProps extends PluginRenderProps {
|
||||
height?: number;
|
||||
@@ -12,24 +12,13 @@ interface IFrameRenderProps extends PluginRenderProps {
|
||||
}
|
||||
|
||||
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));
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// 当 props 发生变化时,主动向 iframe 发送数据
|
||||
useEffect(() => {
|
||||
useOnPluginReadyForInteraction(() => {
|
||||
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);
|
||||
}
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
||||
|
||||
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 }, '*');
|
||||
};
|
||||
+98
-1
@@ -1,17 +1,114 @@
|
||||
import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
|
||||
import { Skeleton } from 'antd';
|
||||
import { memo, useRef, useState } from 'react';
|
||||
|
||||
import { useOnPluginSettingsUpdate } from '@/app/chat/features/Conversation/ChatList/Plugins/Render/utils/pluginSettings';
|
||||
import { useOnPluginStateUpdate } from '@/app/chat/features/Conversation/ChatList/Plugins/Render/utils/pluginState';
|
||||
import { pluginSelectors, usePluginStore } from '@/store/plugin';
|
||||
import { useSessionStore } from '@/store/session';
|
||||
import { chatSelectors } from '@/store/session/selectors';
|
||||
|
||||
import { useOnPluginReadyForInteraction } from '../utils/iframeOnReady';
|
||||
import {
|
||||
useOnPluginFetchMessage,
|
||||
useOnPluginFetchPluginSettings,
|
||||
useOnPluginFetchPluginState,
|
||||
useOnPluginFillContent,
|
||||
} from '../utils/listenToPlugin';
|
||||
import {
|
||||
sendMessageToPlugin,
|
||||
sendPayloadToPlugin,
|
||||
sendPluginSettingsToPlugin,
|
||||
sendPluginStateToPlugin,
|
||||
} from '../utils/postMessage';
|
||||
|
||||
interface IFrameRenderProps {
|
||||
height?: number;
|
||||
id: string;
|
||||
payload?: PluginRequestPayload;
|
||||
url: string;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
const IFrameRender = memo<IFrameRenderProps>(({ url, width = 600, height = 300 }) => {
|
||||
const IFrameRender = memo<IFrameRenderProps>(({ url, id, payload, width = 600, height = 300 }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
|
||||
// when payload change,send content to plugin
|
||||
useOnPluginReadyForInteraction(() => {
|
||||
const iframeWin = iframeRef.current?.contentWindow;
|
||||
if (iframeWin && payload) {
|
||||
sendPayloadToPlugin(iframeWin, payload);
|
||||
}
|
||||
}, [payload]);
|
||||
|
||||
// when plugin wants to get message content, send it to plugin
|
||||
useOnPluginFetchMessage(() => {
|
||||
const iframeWin = iframeRef.current?.contentWindow;
|
||||
|
||||
if (iframeWin) {
|
||||
const message = chatSelectors.getMessageById(id)(useSessionStore.getState());
|
||||
if (!message) return;
|
||||
const props = { content: '' };
|
||||
|
||||
try {
|
||||
props.content = JSON.parse(message.content || '{}');
|
||||
} catch {
|
||||
props.content = message.content || '';
|
||||
}
|
||||
|
||||
sendMessageToPlugin(iframeWin, props);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// when plugin try to send back message, we should fill it to the message content
|
||||
const fillPluginContent = useSessionStore((s) => s.fillPluginMessageContent);
|
||||
useOnPluginFillContent((content) => {
|
||||
fillPluginContent(id, content);
|
||||
});
|
||||
|
||||
// when plugin wants to get plugin state, send it to plugin
|
||||
useOnPluginFetchPluginState((key) => {
|
||||
const iframeWin = iframeRef.current?.contentWindow;
|
||||
|
||||
if (iframeWin) {
|
||||
const message = chatSelectors.getMessageById(id)(useSessionStore.getState());
|
||||
if (!message) return;
|
||||
|
||||
sendPluginStateToPlugin(iframeWin, key, message.pluginState?.[key]);
|
||||
}
|
||||
});
|
||||
|
||||
// when plugin update state, we should update it to the message pluginState key
|
||||
const updatePluginState = useSessionStore((s) => s.updatePluginState);
|
||||
useOnPluginStateUpdate((key, value) => {
|
||||
updatePluginState(id, key, value);
|
||||
});
|
||||
|
||||
// when plugin wants to get plugin settings, send it to plugin
|
||||
useOnPluginFetchPluginSettings(() => {
|
||||
const iframeWin = iframeRef.current?.contentWindow;
|
||||
|
||||
if (iframeWin) {
|
||||
if (!payload?.identifier) return;
|
||||
|
||||
const settings = pluginSelectors.getPluginSettingsById(payload?.identifier)(
|
||||
usePluginStore.getState(),
|
||||
);
|
||||
|
||||
sendPluginSettingsToPlugin(iframeWin, settings);
|
||||
}
|
||||
});
|
||||
|
||||
// when plugin update settings, we should update it to the plugin settings
|
||||
const updatePluginSettings = usePluginStore((s) => s.updatePluginSettings);
|
||||
useOnPluginSettingsUpdate((value) => {
|
||||
if (!payload?.identifier) return;
|
||||
|
||||
updatePluginSettings(payload?.identifier, value);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && <Skeleton active style={{ width }} />}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { usePluginStore } from '@/store/plugin';
|
||||
@@ -5,10 +6,12 @@ import { usePluginStore } from '@/store/plugin';
|
||||
import IFrameRender from './Iframe';
|
||||
|
||||
export interface PluginStandaloneTypeProps {
|
||||
id: string;
|
||||
name?: string;
|
||||
payload?: PluginRequestPayload;
|
||||
}
|
||||
|
||||
const PluginDefaultType = memo<PluginStandaloneTypeProps>(({ name = 'unknown' }) => {
|
||||
const PluginDefaultType = memo<PluginStandaloneTypeProps>(({ payload, id, name = 'unknown' }) => {
|
||||
const manifest = usePluginStore((s) => s.pluginManifestMap[name]);
|
||||
|
||||
if (!manifest?.ui) return;
|
||||
@@ -17,7 +20,9 @@ const PluginDefaultType = memo<PluginStandaloneTypeProps>(({ name = 'unknown' })
|
||||
|
||||
if (!ui.url) return;
|
||||
|
||||
return <IFrameRender height={ui.height} url={ui.url} width={ui.width} />;
|
||||
return (
|
||||
<IFrameRender height={ui.height} id={id} payload={payload} url={ui.url} width={ui.width} />
|
||||
);
|
||||
});
|
||||
|
||||
export default PluginDefaultType;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LobePluginType } from '@lobehub/chat-plugin-sdk';
|
||||
import { LobePluginType, PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
|
||||
import { memo } from 'react';
|
||||
|
||||
import DefaultType from './DefaultType';
|
||||
@@ -6,15 +6,17 @@ import Standalone from './StandaloneType';
|
||||
|
||||
export interface PluginRenderProps {
|
||||
content: string;
|
||||
id: string;
|
||||
loading?: boolean;
|
||||
name?: string;
|
||||
payload?: PluginRequestPayload;
|
||||
type?: LobePluginType;
|
||||
}
|
||||
|
||||
const PluginRender = memo<PluginRenderProps>(({ content, name, type }) => {
|
||||
const PluginRender = memo<PluginRenderProps>(({ content, id, payload, name, type }) => {
|
||||
switch (type) {
|
||||
case 'standalone': {
|
||||
return <Standalone name={name} />;
|
||||
return <Standalone id={id} name={name} payload={payload} />;
|
||||
}
|
||||
|
||||
default: {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const useOnPluginReadyForInteraction = (onReady: () => void, deps: any[] = []) => {
|
||||
const [readyForRender, setReady] = useState(false);
|
||||
useEffect(() => {
|
||||
const fn = (e: MessageEvent) => {
|
||||
if (e.data.type === PluginChannel.pluginReadyForRender) {
|
||||
setReady(true);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', fn);
|
||||
return () => {
|
||||
window.removeEventListener('message', fn);
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (readyForRender) {
|
||||
onReady();
|
||||
}
|
||||
}, [readyForRender, ...deps]);
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const useOnPluginFetchMessage = (onRequest: (data: any) => void, deps: any[] = []) => {
|
||||
useEffect(() => {
|
||||
const fn = (e: MessageEvent) => {
|
||||
if (e.data.type === PluginChannel.fetchPluginMessage) {
|
||||
onRequest(e.data);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', fn);
|
||||
return () => {
|
||||
window.removeEventListener('message', fn);
|
||||
};
|
||||
}, deps);
|
||||
};
|
||||
|
||||
export const useOnPluginFetchPluginState = (onRequest: (key: string) => void) => {
|
||||
useEffect(() => {
|
||||
const fn = (e: MessageEvent) => {
|
||||
if (e.data.type === PluginChannel.fetchPluginState) {
|
||||
onRequest(e.data.key);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', fn);
|
||||
return () => {
|
||||
window.removeEventListener('message', fn);
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const useOnPluginFillContent = (callback: (content: string) => void) => {
|
||||
useEffect(() => {
|
||||
const fn = (e: MessageEvent) => {
|
||||
if (e.data.type === PluginChannel.fillStandalonePluginContent) {
|
||||
const data = e.data.content;
|
||||
const content = typeof data !== 'string' ? JSON.stringify(data) : data;
|
||||
|
||||
callback(content);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', fn);
|
||||
return () => {
|
||||
window.removeEventListener('message', fn);
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const useOnPluginFetchPluginSettings = (onRequest: () => void) => {
|
||||
useEffect(() => {
|
||||
const fn = (e: MessageEvent) => {
|
||||
if (e.data.type === PluginChannel.fetchPluginSettings) {
|
||||
onRequest();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', fn);
|
||||
return () => {
|
||||
window.removeEventListener('message', fn);
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const useOnPluginSettingsUpdate = (callback: (settings: any) => void) => {
|
||||
useEffect(() => {
|
||||
const fn = (e: MessageEvent) => {
|
||||
if (e.data.type === PluginChannel.updatePluginSettings) {
|
||||
callback(e.data.value);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', fn);
|
||||
return () => {
|
||||
window.removeEventListener('message', fn);
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const useOnPluginStateUpdate = (callback: (key: string, value: any) => void) => {
|
||||
useEffect(() => {
|
||||
const fn = (e: MessageEvent) => {
|
||||
if (e.data.type === PluginChannel.updatePluginState) {
|
||||
const key = e.data.key;
|
||||
const value = e.data.value;
|
||||
|
||||
callback(key, value);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', fn);
|
||||
return () => {
|
||||
window.removeEventListener('message', fn);
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
||||
|
||||
export const sendMessageToPlugin = (window: Window, props: any) => {
|
||||
window.postMessage({ props, type: PluginChannel.renderPlugin }, '*');
|
||||
};
|
||||
|
||||
export const sendPayloadToPlugin = (window: Window, props: any) => {
|
||||
window.postMessage({ props, type: PluginChannel.initStandalonePlugin }, '*');
|
||||
};
|
||||
|
||||
export const sendPluginStateToPlugin = (window: Window, key: string, value: any) => {
|
||||
window.postMessage({ key, type: PluginChannel.renderPluginState, value }, '*');
|
||||
};
|
||||
|
||||
export const sendPluginSettingsToPlugin = (window: Window, settings: any) => {
|
||||
window.postMessage({ type: PluginChannel.renderPluginState, value: settings }, '*');
|
||||
};
|
||||
@@ -16,9 +16,10 @@ const t = setNamespace('chat/plugin');
|
||||
* 插件方法
|
||||
*/
|
||||
export interface ChatPluginAction {
|
||||
fillPluginMessageContent: (id: string, content: string) => Promise<void>;
|
||||
runPluginDefaultType: (id: string, payload: any) => Promise<void>;
|
||||
runPluginStandaloneType: (id: string, payload: any) => Promise<void>;
|
||||
triggerFunctionCall: (id: string) => Promise<void>;
|
||||
updatePluginState: (id: string, key: string, value: any) => void;
|
||||
}
|
||||
|
||||
export const chatPlugin: StateCreator<
|
||||
@@ -27,6 +28,14 @@ export const chatPlugin: StateCreator<
|
||||
[],
|
||||
ChatPluginAction
|
||||
> = (set, get) => ({
|
||||
fillPluginMessageContent: async (id, content) => {
|
||||
const { dispatchMessage, coreProcessMessage } = get();
|
||||
|
||||
dispatchMessage({ id, key: 'content', type: 'updateMessage', value: content });
|
||||
|
||||
const chats = chatSelectors.currentChats(get());
|
||||
await coreProcessMessage(chats, id);
|
||||
},
|
||||
runPluginDefaultType: async (id, payload) => {
|
||||
const { dispatchMessage, coreProcessMessage, toggleChatLoading } = get();
|
||||
let data: string;
|
||||
@@ -47,11 +56,8 @@ export const chatPlugin: StateCreator<
|
||||
const chats = chatSelectors.currentChats(get());
|
||||
await coreProcessMessage(chats, id);
|
||||
},
|
||||
runPluginStandaloneType: async (id, payload) => {
|
||||
console.log('触发standalone', id, payload);
|
||||
},
|
||||
triggerFunctionCall: async (id) => {
|
||||
const { dispatchMessage, runPluginDefaultType, runPluginStandaloneType } = get();
|
||||
const { dispatchMessage, runPluginDefaultType } = get();
|
||||
const session = sessionSelectors.currentSession(get());
|
||||
|
||||
if (!session) return;
|
||||
@@ -91,7 +97,13 @@ export const chatPlugin: StateCreator<
|
||||
dispatchMessage({ id, key: 'name', type: 'updateMessage', value: payload.identifier });
|
||||
dispatchMessage({ id, key: 'plugin', type: 'updateMessage', value: payload });
|
||||
|
||||
if (payload.type === 'standalone') runPluginStandaloneType(id, payload);
|
||||
else runPluginDefaultType(id, payload);
|
||||
if (payload.type === 'standalone') {
|
||||
// nothing to do
|
||||
} else runPluginDefaultType(id, payload);
|
||||
},
|
||||
updatePluginState: (id, key, value) => {
|
||||
const { dispatchMessage } = get();
|
||||
|
||||
dispatchMessage({ id, key, type: 'updatePluginState', value });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { produce } from 'immer';
|
||||
|
||||
import { ChatMessage, ChatMessageMap } from '@/types/chatMessage';
|
||||
import { LLMRoleType } from '@/types/llm';
|
||||
import { MetaData } from '@/types/meta';
|
||||
import { merge } from '@/utils/merge';
|
||||
import { nanoid } from '@/utils/uuid';
|
||||
|
||||
interface AddMessage {
|
||||
@@ -38,12 +40,20 @@ interface UpdateMessageExtra {
|
||||
value: any;
|
||||
}
|
||||
|
||||
interface UpdatePluginState {
|
||||
id: string;
|
||||
key: string;
|
||||
type: 'updatePluginState';
|
||||
value: any;
|
||||
}
|
||||
|
||||
export type MessageDispatch =
|
||||
| AddMessage
|
||||
| DeleteMessage
|
||||
| ResetMessages
|
||||
| UpdateMessage
|
||||
| UpdateMessageExtra;
|
||||
| UpdateMessageExtra
|
||||
| UpdatePluginState;
|
||||
|
||||
export const messagesReducer = (
|
||||
state: ChatMessageMap,
|
||||
@@ -101,6 +111,26 @@ export const messagesReducer = (
|
||||
});
|
||||
}
|
||||
|
||||
case 'updatePluginState': {
|
||||
return produce(state, (draftState) => {
|
||||
const { id, key, value } = payload;
|
||||
const message = draftState[id];
|
||||
if (!message) return;
|
||||
|
||||
let newState;
|
||||
if (!message.pluginState) {
|
||||
newState = { [key]: value } as any;
|
||||
} else {
|
||||
newState = merge(message.pluginState, { [key]: value });
|
||||
}
|
||||
|
||||
if (isEqual(message.pluginState, newState)) return;
|
||||
|
||||
message.pluginState = newState;
|
||||
message.updateAt = Date.now();
|
||||
});
|
||||
}
|
||||
|
||||
case 'resetMessages': {
|
||||
return produce(state, (draftState) => {
|
||||
const { topicId } = payload;
|
||||
|
||||
@@ -102,3 +102,9 @@ export const getFunctionMessageProps =
|
||||
loading: id === s.chatLoadingId,
|
||||
type: plugin?.type as LobePluginType,
|
||||
});
|
||||
|
||||
export const getMessageById = (id: string) => (s: SessionStore) => {
|
||||
for (const e of Object.values(s.sessions)) {
|
||||
if (e.chats[id]) return e.chats[id];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
currentChatsWithHistoryConfig,
|
||||
getChatsById,
|
||||
getFunctionMessageProps,
|
||||
getMessageById,
|
||||
} from './chat';
|
||||
import { currentTopics, getTopicMessages } from './topic';
|
||||
|
||||
@@ -15,6 +16,7 @@ export const chatSelectors = {
|
||||
currentChatsWithHistoryConfig,
|
||||
getChatsById,
|
||||
getFunctionMessageProps,
|
||||
getMessageById,
|
||||
};
|
||||
|
||||
export const topicSelectors = {
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface ChatMessage extends BaseDataModel {
|
||||
parentId?: string;
|
||||
|
||||
plugin?: PluginRequestPayload;
|
||||
pluginState?: any;
|
||||
|
||||
// 引用
|
||||
quotaId?: string;
|
||||
|
||||
Reference in New Issue
Block a user