@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
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
export const useOnPluginFetchMessage = (onRequest: (data: any) => void, deps: any[] = []) => {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const fn = (e: MessageEvent) => {
|
|
7
|
+
if (e.data.type === PluginChannel.fetchPluginMessage) {
|
|
8
|
+
onRequest(e.data);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
window.addEventListener('message', fn);
|
|
13
|
+
return () => {
|
|
14
|
+
window.removeEventListener('message', fn);
|
|
15
|
+
};
|
|
16
|
+
}, deps);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const useOnPluginFetchPluginState = (onRequest: (key: string) => void) => {
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const fn = (e: MessageEvent) => {
|
|
22
|
+
if (e.data.type === PluginChannel.fetchPluginState) {
|
|
23
|
+
onRequest(e.data.key);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
window.addEventListener('message', fn);
|
|
28
|
+
return () => {
|
|
29
|
+
window.removeEventListener('message', fn);
|
|
30
|
+
};
|
|
31
|
+
}, []);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const useOnPluginFillContent = (callback: (content: string) => void) => {
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const fn = (e: MessageEvent) => {
|
|
37
|
+
if (e.data.type === PluginChannel.fillStandalonePluginContent) {
|
|
38
|
+
const data = e.data.content;
|
|
39
|
+
const content = typeof data !== 'string' ? JSON.stringify(data) : data;
|
|
40
|
+
|
|
41
|
+
callback(content);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
window.addEventListener('message', fn);
|
|
46
|
+
return () => {
|
|
47
|
+
window.removeEventListener('message', fn);
|
|
48
|
+
};
|
|
49
|
+
}, []);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const useOnPluginFetchPluginSettings = (onRequest: () => void) => {
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const fn = (e: MessageEvent) => {
|
|
55
|
+
if (e.data.type === PluginChannel.fetchPluginSettings) {
|
|
56
|
+
onRequest();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
window.addEventListener('message', fn);
|
|
61
|
+
return () => {
|
|
62
|
+
window.removeEventListener('message', fn);
|
|
63
|
+
};
|
|
64
|
+
}, []);
|
|
65
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
export const useOnPluginSettingsUpdate = (callback: (settings: any) => void) => {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const fn = (e: MessageEvent) => {
|
|
7
|
+
if (e.data.type === PluginChannel.updatePluginSettings) {
|
|
8
|
+
callback(e.data.value);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
window.addEventListener('message', fn);
|
|
13
|
+
return () => {
|
|
14
|
+
window.removeEventListener('message', fn);
|
|
15
|
+
};
|
|
16
|
+
}, []);
|
|
17
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
export const useOnPluginStateUpdate = (callback: (key: string, value: any) => void) => {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const fn = (e: MessageEvent) => {
|
|
7
|
+
if (e.data.type === PluginChannel.updatePluginState) {
|
|
8
|
+
const key = e.data.key;
|
|
9
|
+
const value = e.data.value;
|
|
10
|
+
|
|
11
|
+
callback(key, value);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
window.addEventListener('message', fn);
|
|
16
|
+
return () => {
|
|
17
|
+
window.removeEventListener('message', fn);
|
|
18
|
+
};
|
|
19
|
+
}, []);
|
|
20
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
|
|
2
|
+
|
|
3
|
+
export const sendMessageToPlugin = (window: Window, props: any) => {
|
|
4
|
+
window.postMessage({ props, type: PluginChannel.renderPlugin }, '*');
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const sendPayloadToPlugin = (window: Window, props: any) => {
|
|
8
|
+
window.postMessage({ props, type: PluginChannel.initStandalonePlugin }, '*');
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const sendPluginStateToPlugin = (window: Window, key: string, value: any) => {
|
|
12
|
+
window.postMessage({ key, type: PluginChannel.renderPluginState, value }, '*');
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const sendPluginSettingsToPlugin = (window: Window, settings: any) => {
|
|
16
|
+
window.postMessage({ type: PluginChannel.renderPluginState, value: settings }, '*');
|
|
17
|
+
};
|
package/src/const/plugin.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PLUGIN_SCHEMA_SEPARATOR = '
|
|
1
|
+
export const PLUGIN_SCHEMA_SEPARATOR = '____';
|
|
@@ -9,6 +9,8 @@ import { usePluginStore } from '@/store/plugin';
|
|
|
9
9
|
import PluginSettingRender from './PluginSettingRender';
|
|
10
10
|
|
|
11
11
|
export const transformPluginSettings = (pluginSettings: PluginSchema) => {
|
|
12
|
+
if (!pluginSettings?.properties) return [];
|
|
13
|
+
|
|
12
14
|
return Object.entries(pluginSettings.properties).map(([name, i]) => ({
|
|
13
15
|
desc: i.description,
|
|
14
16
|
format: i.format,
|
|
@@ -21,7 +23,7 @@ export const transformPluginSettings = (pluginSettings: PluginSchema) => {
|
|
|
21
23
|
|
|
22
24
|
interface PluginSettingsConfigProps {
|
|
23
25
|
id: string;
|
|
24
|
-
|
|
26
|
+
schema: PluginSchema;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
const useStyles = createStyles(({ css, token }) => ({
|
|
@@ -32,8 +34,8 @@ const useStyles = createStyles(({ css, token }) => ({
|
|
|
32
34
|
`,
|
|
33
35
|
}));
|
|
34
36
|
|
|
35
|
-
const PluginSettingsConfig = memo<PluginSettingsConfigProps>(({
|
|
36
|
-
const items = transformPluginSettings(
|
|
37
|
+
const PluginSettingsConfig = memo<PluginSettingsConfigProps>(({ schema, id }) => {
|
|
38
|
+
const items = transformPluginSettings(schema);
|
|
37
39
|
|
|
38
40
|
const { styles } = useStyles();
|
|
39
41
|
const [updatePluginSettings] = usePluginStore((s) => [s.updatePluginSettings]);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Avatar, Modal } from '@lobehub/ui';
|
|
2
|
+
import { Divider, Typography } from 'antd';
|
|
3
|
+
import { useTheme } from 'antd-style';
|
|
4
|
+
import isEqual from 'fast-deep-equal';
|
|
5
|
+
import { memo } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
|
8
|
+
|
|
9
|
+
import PluginSettingsConfig from '@/features/PluginSettings';
|
|
10
|
+
import { pluginHelpers, pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
11
|
+
|
|
12
|
+
interface PluginSettingsModalProps {
|
|
13
|
+
id: string;
|
|
14
|
+
onClose: () => void;
|
|
15
|
+
open?: boolean;
|
|
16
|
+
schema: any;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const PluginSettingsModal = memo<PluginSettingsModalProps>(({ schema, onClose, id, open }) => {
|
|
20
|
+
const pluginMeta = usePluginStore(pluginSelectors.getPluginMetaById(id), isEqual);
|
|
21
|
+
|
|
22
|
+
const { t } = useTranslation('plugin');
|
|
23
|
+
const theme = useTheme();
|
|
24
|
+
return (
|
|
25
|
+
<Modal onCancel={onClose} open={open} title={t('setting')} width={600}>
|
|
26
|
+
<Center gap={16}>
|
|
27
|
+
<Avatar
|
|
28
|
+
avatar={pluginHelpers.getPluginAvatar(pluginMeta?.meta) || '⚙️'}
|
|
29
|
+
background={theme.colorFillContent}
|
|
30
|
+
gap={12}
|
|
31
|
+
size={64}
|
|
32
|
+
/>
|
|
33
|
+
|
|
34
|
+
<Flexbox style={{ fontSize: 20 }}>{pluginHelpers.getPluginTitle(pluginMeta?.meta)}</Flexbox>
|
|
35
|
+
<Typography.Text type={'secondary'}>
|
|
36
|
+
{pluginHelpers.getPluginDesc(pluginMeta?.meta)}
|
|
37
|
+
</Typography.Text>
|
|
38
|
+
<Divider style={{ marginBottom: 0, marginTop: 8 }} />
|
|
39
|
+
{schema && <PluginSettingsConfig id={id} schema={schema} />}
|
|
40
|
+
</Center>
|
|
41
|
+
</Modal>
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export default PluginSettingsModal;
|
|
@@ -18,11 +18,17 @@ const enabledSchema =
|
|
|
18
18
|
return enabledPlugins.includes(p.identifier);
|
|
19
19
|
})
|
|
20
20
|
.flatMap((manifest) =>
|
|
21
|
-
manifest.api.map((m) =>
|
|
22
|
-
|
|
21
|
+
manifest.api.map((m) => {
|
|
22
|
+
const pluginType = manifest.type ? `${PLUGIN_SCHEMA_SEPARATOR + manifest.type}` : '';
|
|
23
|
+
|
|
23
24
|
// 将插件的 identifier 作为前缀,避免重复
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
const apiName = manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + m.name + pluginType;
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
...m,
|
|
29
|
+
name: apiName,
|
|
30
|
+
};
|
|
31
|
+
}),
|
|
26
32
|
);
|
|
27
33
|
|
|
28
34
|
return uniqBy(list, 'name');
|
|
@@ -3,6 +3,7 @@ import { StateCreator } from 'zustand/vanilla';
|
|
|
3
3
|
import { SessionStore } from '@/store/session';
|
|
4
4
|
|
|
5
5
|
import { ChatMessageAction, chatMessage } from './message';
|
|
6
|
+
import { ChatPluginAction, chatPlugin } from './plugin';
|
|
6
7
|
import { ShareAction, chatShare } from './share';
|
|
7
8
|
import { ChatTopicAction, chatTopic } from './topic';
|
|
8
9
|
import { ChatTranslateAction, chatTranslate } from './translate';
|
|
@@ -14,7 +15,8 @@ export interface ChatAction
|
|
|
14
15
|
extends ChatTopicAction,
|
|
15
16
|
ChatMessageAction,
|
|
16
17
|
ShareAction,
|
|
17
|
-
ChatTranslateAction
|
|
18
|
+
ChatTranslateAction,
|
|
19
|
+
ChatPluginAction {}
|
|
18
20
|
|
|
19
21
|
export const createChatSlice: StateCreator<
|
|
20
22
|
SessionStore,
|
|
@@ -26,4 +28,5 @@ export const createChatSlice: StateCreator<
|
|
|
26
28
|
...chatMessage(...params),
|
|
27
29
|
...chatShare(...params),
|
|
28
30
|
...chatTranslate(...params),
|
|
31
|
+
...chatPlugin(...params),
|
|
29
32
|
});
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
|
|
2
1
|
import { template } from 'lodash-es';
|
|
3
2
|
import { StateCreator } from 'zustand/vanilla';
|
|
4
3
|
|
|
5
4
|
import { LOADING_FLAT } from '@/const/message';
|
|
6
|
-
import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
|
|
7
5
|
import { fetchChatModel } from '@/services/chatModel';
|
|
8
|
-
import { fetchPlugin } from '@/services/plugin';
|
|
9
6
|
import { SessionStore } from '@/store/session';
|
|
10
|
-
import { ChatMessage
|
|
7
|
+
import { ChatMessage } from '@/types/chatMessage';
|
|
11
8
|
import { fetchSSE } from '@/utils/fetch';
|
|
12
|
-
import {
|
|
9
|
+
import { isFunctionMessageAtStart, testFunctionMessageAtEnd } from '@/utils/message';
|
|
13
10
|
import { setNamespace } from '@/utils/storeDebug';
|
|
14
11
|
import { nanoid } from '@/utils/uuid';
|
|
15
12
|
|
|
@@ -29,6 +26,12 @@ export interface ChatMessageAction {
|
|
|
29
26
|
* 清除消息
|
|
30
27
|
*/
|
|
31
28
|
clearMessage: () => void;
|
|
29
|
+
/**
|
|
30
|
+
* 处理 ai 消息的核心逻辑(包含前处理与后处理)
|
|
31
|
+
* @param messages - 聊天消息数组
|
|
32
|
+
* @param parentId - 父消息 ID,可选
|
|
33
|
+
*/
|
|
34
|
+
coreProcessMessage: (messages: ChatMessage[], parentId: string) => Promise<void>;
|
|
32
35
|
/**
|
|
33
36
|
* 删除消息
|
|
34
37
|
* @param id - 消息 ID
|
|
@@ -40,21 +43,19 @@ export interface ChatMessageAction {
|
|
|
40
43
|
*/
|
|
41
44
|
dispatchMessage: (payload: MessageDispatch) => void;
|
|
42
45
|
/**
|
|
43
|
-
*
|
|
46
|
+
* 实际获取 AI 响应
|
|
44
47
|
* @param messages - 聊天消息数组
|
|
45
48
|
* @param options - 获取 SSE 选项
|
|
46
49
|
*/
|
|
47
|
-
|
|
50
|
+
fetchAIChatMessage: (
|
|
48
51
|
messages: ChatMessage[],
|
|
49
52
|
assistantMessageId: string,
|
|
50
|
-
) => Promise<{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
*/
|
|
57
|
-
realFetchAIResponse: (messages: ChatMessage[], parentId: string) => Promise<void>;
|
|
53
|
+
) => Promise<{
|
|
54
|
+
content: string;
|
|
55
|
+
functionCallAtEnd: boolean;
|
|
56
|
+
functionCallContent: string;
|
|
57
|
+
isFunctionCall: boolean;
|
|
58
|
+
}>;
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
61
|
* 重新发送消息
|
|
@@ -73,8 +74,6 @@ export interface ChatMessageAction {
|
|
|
73
74
|
id?: string,
|
|
74
75
|
action?: string,
|
|
75
76
|
) => AbortController | undefined;
|
|
76
|
-
|
|
77
|
-
triggerFunctionCall: (id: string) => Promise<void>;
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
export const chatMessage: StateCreator<
|
|
@@ -93,10 +92,74 @@ export const chatMessage: StateCreator<
|
|
|
93
92
|
}
|
|
94
93
|
},
|
|
95
94
|
|
|
95
|
+
coreProcessMessage: async (messages, userMessageId) => {
|
|
96
|
+
const { dispatchMessage, fetchAIChatMessage, triggerFunctionCall, activeTopicId } = get();
|
|
97
|
+
|
|
98
|
+
const { model } = agentSelectors.currentAgentConfig(get());
|
|
99
|
+
|
|
100
|
+
// 添加一个空的信息用于放置 ai 响应,注意顺序不能反
|
|
101
|
+
// 因为如果顺序反了,messages 中将包含新增的 ai message
|
|
102
|
+
const mid = nanoid();
|
|
103
|
+
|
|
104
|
+
dispatchMessage({
|
|
105
|
+
id: mid,
|
|
106
|
+
message: LOADING_FLAT,
|
|
107
|
+
parentId: userMessageId,
|
|
108
|
+
role: 'assistant',
|
|
109
|
+
type: 'addMessage',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// 如果有 activeTopicId,则添加 topicId
|
|
113
|
+
if (activeTopicId) {
|
|
114
|
+
dispatchMessage({ id: mid, key: 'topicId', type: 'updateMessage', value: activeTopicId });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 为模型添加 fromModel 的额外信息
|
|
118
|
+
dispatchMessage({ id: mid, key: 'fromModel', type: 'updateMessageExtra', value: model });
|
|
119
|
+
|
|
120
|
+
// 生成 ai message
|
|
121
|
+
const { isFunctionCall, content, functionCallAtEnd, functionCallContent } =
|
|
122
|
+
await fetchAIChatMessage(messages, mid);
|
|
123
|
+
|
|
124
|
+
// 如果是 function,则发送函数调用方法
|
|
125
|
+
if (isFunctionCall) {
|
|
126
|
+
let functionId = mid;
|
|
127
|
+
|
|
128
|
+
if (functionCallAtEnd) {
|
|
129
|
+
// create a new separate message and remove the function call from the prev message
|
|
130
|
+
dispatchMessage({
|
|
131
|
+
id: mid,
|
|
132
|
+
key: 'content',
|
|
133
|
+
type: 'updateMessage',
|
|
134
|
+
value: content.replace(functionCallContent, ''),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
functionId = nanoid();
|
|
138
|
+
dispatchMessage({
|
|
139
|
+
id: functionId,
|
|
140
|
+
message: functionCallContent,
|
|
141
|
+
parentId: userMessageId,
|
|
142
|
+
role: 'assistant',
|
|
143
|
+
type: 'addMessage',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// also add activeTopicId
|
|
147
|
+
if (activeTopicId)
|
|
148
|
+
dispatchMessage({
|
|
149
|
+
id: functionId,
|
|
150
|
+
key: 'topicId',
|
|
151
|
+
type: 'updateMessage',
|
|
152
|
+
value: activeTopicId,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
triggerFunctionCall(functionId);
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
|
|
96
160
|
deleteMessage: (id) => {
|
|
97
161
|
get().dispatchMessage({ id, type: 'deleteMessage' });
|
|
98
162
|
},
|
|
99
|
-
|
|
100
163
|
dispatchMessage: (payload) => {
|
|
101
164
|
const { activeId } = get();
|
|
102
165
|
const session = sessionSelectors.currentSession(get());
|
|
@@ -106,7 +169,8 @@ export const chatMessage: StateCreator<
|
|
|
106
169
|
|
|
107
170
|
get().dispatchSession({ chats, id: activeId, type: 'updateSessionChat' });
|
|
108
171
|
},
|
|
109
|
-
|
|
172
|
+
|
|
173
|
+
fetchAIChatMessage: async (messages, assistantId) => {
|
|
110
174
|
const { dispatchMessage, toggleChatLoading } = get();
|
|
111
175
|
|
|
112
176
|
const abortController = toggleChatLoading(
|
|
@@ -160,6 +224,8 @@ export const chatMessage: StateCreator<
|
|
|
160
224
|
|
|
161
225
|
let output = '';
|
|
162
226
|
let isFunctionCall = false;
|
|
227
|
+
let functionCallAtEnd = false;
|
|
228
|
+
let functionCallContent = '';
|
|
163
229
|
|
|
164
230
|
await fetchSSE(fetcher, {
|
|
165
231
|
onErrorHandle: (error) => {
|
|
@@ -168,15 +234,10 @@ export const chatMessage: StateCreator<
|
|
|
168
234
|
onMessageHandle: (text) => {
|
|
169
235
|
output += text;
|
|
170
236
|
|
|
171
|
-
dispatchMessage({
|
|
172
|
-
id: assistantId,
|
|
173
|
-
key: 'content',
|
|
174
|
-
type: 'updateMessage',
|
|
175
|
-
value: output,
|
|
176
|
-
});
|
|
237
|
+
dispatchMessage({ id: assistantId, key: 'content', type: 'updateMessage', value: output });
|
|
177
238
|
|
|
178
|
-
//
|
|
179
|
-
if (
|
|
239
|
+
// is this message is just a function call
|
|
240
|
+
if (isFunctionMessageAtStart(output)) {
|
|
180
241
|
isFunctionCall = true;
|
|
181
242
|
}
|
|
182
243
|
},
|
|
@@ -184,41 +245,19 @@ export const chatMessage: StateCreator<
|
|
|
184
245
|
|
|
185
246
|
toggleChatLoading(false, undefined, t('generateMessage(end)') as string);
|
|
186
247
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
realFetchAIResponse: async (messages, userMessageId) => {
|
|
191
|
-
const { dispatchMessage, generateMessage, triggerFunctionCall, activeTopicId } = get();
|
|
192
|
-
|
|
193
|
-
const { model } = agentSelectors.currentAgentConfig(get());
|
|
248
|
+
// also exist message like this: 请稍等,我帮您查询一下。{"function_call": {"name": "plugin-identifier____recommendClothes____standalone", "arguments": "{\n "mood": "",\n "gender": "man"\n}"}}
|
|
249
|
+
if (!isFunctionCall) {
|
|
250
|
+
const { content, valid } = testFunctionMessageAtEnd(output);
|
|
194
251
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
message: LOADING_FLAT,
|
|
202
|
-
parentId: userMessageId,
|
|
203
|
-
role: 'assistant',
|
|
204
|
-
type: 'addMessage',
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// 如果有 activeTopicId,则添加 topicId
|
|
208
|
-
if (activeTopicId) {
|
|
209
|
-
dispatchMessage({ id: mid, key: 'topicId', type: 'updateMessage', value: activeTopicId });
|
|
252
|
+
// if fc at end, replace the message
|
|
253
|
+
if (valid) {
|
|
254
|
+
isFunctionCall = true;
|
|
255
|
+
functionCallAtEnd = true;
|
|
256
|
+
functionCallContent = content;
|
|
257
|
+
}
|
|
210
258
|
}
|
|
211
259
|
|
|
212
|
-
|
|
213
|
-
dispatchMessage({ id: mid, key: 'fromModel', type: 'updateMessageExtra', value: model });
|
|
214
|
-
|
|
215
|
-
// 生成 ai message
|
|
216
|
-
const { isFunctionCall } = await generateMessage(messages, mid);
|
|
217
|
-
|
|
218
|
-
// 如果是 function,则发送函数调用方法
|
|
219
|
-
if (isFunctionCall) {
|
|
220
|
-
triggerFunctionCall(mid);
|
|
221
|
-
}
|
|
260
|
+
return { content: output, functionCallAtEnd, functionCallContent, isFunctionCall };
|
|
222
261
|
},
|
|
223
262
|
|
|
224
263
|
resendMessage: async (messageId) => {
|
|
@@ -254,17 +293,17 @@ export const chatMessage: StateCreator<
|
|
|
254
293
|
|
|
255
294
|
if (contextMessages.length <= 0) return;
|
|
256
295
|
|
|
257
|
-
const {
|
|
296
|
+
const { coreProcessMessage } = get();
|
|
258
297
|
|
|
259
298
|
const latestMsg = contextMessages.filter((s) => s.role === 'user').at(-1);
|
|
260
299
|
|
|
261
300
|
if (!latestMsg) return;
|
|
262
301
|
|
|
263
|
-
await
|
|
302
|
+
await coreProcessMessage(contextMessages, latestMsg.id);
|
|
264
303
|
},
|
|
265
304
|
|
|
266
305
|
sendMessage: async (message) => {
|
|
267
|
-
const { dispatchMessage,
|
|
306
|
+
const { dispatchMessage, coreProcessMessage, activeTopicId } = get();
|
|
268
307
|
const session = sessionSelectors.currentSession(get());
|
|
269
308
|
if (!session || !message) return;
|
|
270
309
|
|
|
@@ -279,7 +318,7 @@ export const chatMessage: StateCreator<
|
|
|
279
318
|
// Get the current messages to generate AI response
|
|
280
319
|
const messages = chatSelectors.currentChats(get());
|
|
281
320
|
|
|
282
|
-
await
|
|
321
|
+
await coreProcessMessage(messages, userId);
|
|
283
322
|
|
|
284
323
|
// check activeTopic and then auto create topic
|
|
285
324
|
const chats = chatSelectors.currentChats(get());
|
|
@@ -308,58 +347,4 @@ export const chatMessage: StateCreator<
|
|
|
308
347
|
set({ abortController: undefined, chatLoadingId: undefined }, false, action);
|
|
309
348
|
}
|
|
310
349
|
},
|
|
311
|
-
|
|
312
|
-
triggerFunctionCall: async (id) => {
|
|
313
|
-
const { dispatchMessage, realFetchAIResponse, toggleChatLoading } = get();
|
|
314
|
-
const session = sessionSelectors.currentSession(get());
|
|
315
|
-
|
|
316
|
-
if (!session) return;
|
|
317
|
-
|
|
318
|
-
const message = session.chats[id];
|
|
319
|
-
if (!message) return;
|
|
320
|
-
|
|
321
|
-
let payload: PluginRequestPayload = { apiName: '', identifier: '' };
|
|
322
|
-
// 识别到内容是 function_call 的情况下
|
|
323
|
-
// 将 function_call 转换为 plugin request payload
|
|
324
|
-
if (message.content) {
|
|
325
|
-
const { function_call } = JSON.parse(message.content) as {
|
|
326
|
-
function_call: OpenAIFunctionCall;
|
|
327
|
-
};
|
|
328
|
-
|
|
329
|
-
const [identifier, apiName] = function_call.name.split(PLUGIN_SCHEMA_SEPARATOR);
|
|
330
|
-
payload = { apiName, arguments: function_call.arguments, identifier };
|
|
331
|
-
|
|
332
|
-
dispatchMessage({ id, key: 'plugin', type: 'updateMessage', value: payload });
|
|
333
|
-
dispatchMessage({ id, key: 'content', type: 'updateMessage', value: '' });
|
|
334
|
-
} else {
|
|
335
|
-
if (message.plugin) {
|
|
336
|
-
payload = message.plugin;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (!payload.apiName) return;
|
|
341
|
-
|
|
342
|
-
dispatchMessage({ id, key: 'role', type: 'updateMessage', value: 'function' });
|
|
343
|
-
dispatchMessage({ id, key: 'name', type: 'updateMessage', value: payload.identifier });
|
|
344
|
-
dispatchMessage({ id, key: 'plugin', type: 'updateMessage', value: payload });
|
|
345
|
-
|
|
346
|
-
let data: string;
|
|
347
|
-
try {
|
|
348
|
-
const abortController = toggleChatLoading(true, id);
|
|
349
|
-
data = await fetchPlugin(payload, { signal: abortController?.signal });
|
|
350
|
-
} catch (error) {
|
|
351
|
-
dispatchMessage({ id, key: 'error', type: 'updateMessage', value: error });
|
|
352
|
-
|
|
353
|
-
data = '';
|
|
354
|
-
}
|
|
355
|
-
toggleChatLoading(false);
|
|
356
|
-
// 如果报错则结束了
|
|
357
|
-
if (!data) return;
|
|
358
|
-
|
|
359
|
-
dispatchMessage({ id, key: 'content', type: 'updateMessage', value: data });
|
|
360
|
-
|
|
361
|
-
const chats = chatSelectors.currentChats(get());
|
|
362
|
-
|
|
363
|
-
await realFetchAIResponse(chats, message.id);
|
|
364
|
-
},
|
|
365
350
|
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
3
|
+
|
|
4
|
+
import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
|
|
5
|
+
import { fetchPlugin } from '@/services/plugin';
|
|
6
|
+
import { SessionStore } from '@/store/session';
|
|
7
|
+
import { OpenAIFunctionCall } from '@/types/chatMessage';
|
|
8
|
+
import { setNamespace } from '@/utils/storeDebug';
|
|
9
|
+
|
|
10
|
+
import { sessionSelectors } from '../../session/selectors';
|
|
11
|
+
import { chatSelectors } from '../selectors';
|
|
12
|
+
|
|
13
|
+
const t = setNamespace('chat/plugin');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 插件方法
|
|
17
|
+
*/
|
|
18
|
+
export interface ChatPluginAction {
|
|
19
|
+
fillPluginMessageContent: (id: string, content: string) => Promise<void>;
|
|
20
|
+
runPluginDefaultType: (id: string, payload: any) => Promise<void>;
|
|
21
|
+
triggerFunctionCall: (id: string) => Promise<void>;
|
|
22
|
+
updatePluginState: (id: string, key: string, value: any) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const chatPlugin: StateCreator<
|
|
26
|
+
SessionStore,
|
|
27
|
+
[['zustand/devtools', never]],
|
|
28
|
+
[],
|
|
29
|
+
ChatPluginAction
|
|
30
|
+
> = (set, get) => ({
|
|
31
|
+
fillPluginMessageContent: async (id, content) => {
|
|
32
|
+
const { dispatchMessage, coreProcessMessage } = get();
|
|
33
|
+
|
|
34
|
+
dispatchMessage({ id, key: 'content', type: 'updateMessage', value: content });
|
|
35
|
+
|
|
36
|
+
const chats = chatSelectors.currentChats(get());
|
|
37
|
+
await coreProcessMessage(chats, id);
|
|
38
|
+
},
|
|
39
|
+
runPluginDefaultType: async (id, payload) => {
|
|
40
|
+
const { dispatchMessage, coreProcessMessage, toggleChatLoading } = get();
|
|
41
|
+
let data: string;
|
|
42
|
+
try {
|
|
43
|
+
const abortController = toggleChatLoading(true, id, t('fetchPlugin') as string);
|
|
44
|
+
data = await fetchPlugin(payload, { signal: abortController?.signal });
|
|
45
|
+
} catch (error) {
|
|
46
|
+
dispatchMessage({ id, key: 'error', type: 'updateMessage', value: error });
|
|
47
|
+
|
|
48
|
+
data = '';
|
|
49
|
+
}
|
|
50
|
+
toggleChatLoading(false);
|
|
51
|
+
// 如果报错则结束了
|
|
52
|
+
if (!data) return;
|
|
53
|
+
|
|
54
|
+
dispatchMessage({ id, key: 'content', type: 'updateMessage', value: data });
|
|
55
|
+
|
|
56
|
+
const chats = chatSelectors.currentChats(get());
|
|
57
|
+
await coreProcessMessage(chats, id);
|
|
58
|
+
},
|
|
59
|
+
triggerFunctionCall: async (id) => {
|
|
60
|
+
const { dispatchMessage, runPluginDefaultType } = get();
|
|
61
|
+
const session = sessionSelectors.currentSession(get());
|
|
62
|
+
|
|
63
|
+
if (!session) return;
|
|
64
|
+
|
|
65
|
+
const message = session.chats[id];
|
|
66
|
+
if (!message) return;
|
|
67
|
+
|
|
68
|
+
let payload: PluginRequestPayload = { apiName: '', identifier: '' };
|
|
69
|
+
|
|
70
|
+
// 识别到内容是 function_call 的情况下
|
|
71
|
+
// 将 function_call 转换为 plugin request payload
|
|
72
|
+
if (message.content) {
|
|
73
|
+
const { function_call } = JSON.parse(message.content) as {
|
|
74
|
+
function_call: OpenAIFunctionCall;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const [identifier, apiName, type] = function_call.name.split(PLUGIN_SCHEMA_SEPARATOR);
|
|
78
|
+
|
|
79
|
+
payload = {
|
|
80
|
+
apiName,
|
|
81
|
+
arguments: function_call.arguments,
|
|
82
|
+
identifier,
|
|
83
|
+
type: type ?? 'default',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
dispatchMessage({ id, key: 'plugin', type: 'updateMessage', value: payload });
|
|
87
|
+
dispatchMessage({ id, key: 'content', type: 'updateMessage', value: '' });
|
|
88
|
+
} else {
|
|
89
|
+
if (message.plugin) {
|
|
90
|
+
payload = message.plugin;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!payload.apiName) return;
|
|
95
|
+
|
|
96
|
+
dispatchMessage({ id, key: 'role', type: 'updateMessage', value: 'function' });
|
|
97
|
+
dispatchMessage({ id, key: 'name', type: 'updateMessage', value: payload.identifier });
|
|
98
|
+
dispatchMessage({ id, key: 'plugin', type: 'updateMessage', value: payload });
|
|
99
|
+
|
|
100
|
+
if (payload.type === 'standalone') {
|
|
101
|
+
// nothing to do
|
|
102
|
+
} else runPluginDefaultType(id, payload);
|
|
103
|
+
},
|
|
104
|
+
updatePluginState: (id, key, value) => {
|
|
105
|
+
const { dispatchMessage } = get();
|
|
106
|
+
|
|
107
|
+
dispatchMessage({ id, key, type: 'updatePluginState', value });
|
|
108
|
+
},
|
|
109
|
+
});
|