@lobehub/chat 0.54.3 → 0.55.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/.eslintrc.js +1 -0
- package/CHANGELOG.md +81 -0
- package/package.json +6 -3
- package/src/components/SelectWithImg/styles.ts +8 -7
- package/src/const/url.ts +4 -0
- package/src/features/AgentSetting/AgentPlugin/index.tsx +83 -20
- package/src/layout/index.tsx +13 -8
- package/src/locales/default/plugin.ts +0 -16
- package/src/pages/api/openai.ts +4 -20
- package/src/pages/api/plugins.api.ts +161 -8
- package/src/pages/chat/[id]/layout.tsx +4 -0
- package/src/pages/chat/features/Conversation/ChatList/Plugins/CustomRender.tsx +32 -0
- package/src/pages/chat/features/Conversation/ChatList/Plugins/FunctionCall.tsx +4 -5
- package/src/pages/chat/features/Conversation/ChatList/Plugins/PluginMessage.tsx +36 -27
- package/src/pages/chat/features/Conversation/ChatList/Plugins/dynamticLoader.ts +26 -0
- package/src/pages/chat/features/Header/PluginTag.tsx +13 -8
- package/src/services/chatModel.ts +11 -2
- package/src/services/plugin.ts +11 -1
- package/src/store/global/slices/agent.ts +2 -2
- package/src/store/global/slices/settings.ts +1 -1
- package/src/store/plugin/action.ts +121 -0
- package/src/store/plugin/hooks/useSessionHydrated.ts +21 -0
- package/src/store/plugin/index.ts +2 -0
- package/src/store/plugin/initialState.ts +19 -0
- package/src/store/plugin/reducers/manifest.ts +36 -0
- package/src/store/plugin/selectors.ts +23 -0
- package/src/store/plugin/store.ts +48 -0
- package/src/store/session/hooks/useOnFinishHydrationSession.ts +6 -3
- package/src/store/session/hooks/useSessionHydrated.ts +0 -1
- package/src/store/session/slices/agentConfig/action.ts +2 -6
- package/src/store/session/slices/chat/selectors/utils.test.ts +15 -1
- package/src/store/session/slices/chat/selectors/utils.ts +5 -3
- package/src/store/session/store.ts +0 -1
- package/src/types/plugin.ts +3 -4
- package/src/plugins/Render.ts +0 -11
- package/src/plugins/index.ts +0 -11
- package/src/plugins/searchEngine/Render.tsx +0 -92
- package/src/plugins/searchEngine/index.ts +0 -26
- package/src/plugins/searchEngine/type.ts +0 -82
- package/src/plugins/type.ts +0 -45
- package/src/plugins/weather/Render.tsx +0 -53
- package/src/plugins/weather/index.ts +0 -24
- package/src/plugins/weather/type.ts +0 -36
- package/src/plugins/webCrawler/index.ts +0 -20
- package/src/plugins/webCrawler/type.ts +0 -35
|
@@ -3,41 +3,50 @@ import { memo } from 'react';
|
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { Flexbox } from 'react-layout-kit';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { usePluginStore } from '@/store/plugin';
|
|
7
7
|
import { ChatMessage } from '@/types/chatMessage';
|
|
8
8
|
|
|
9
|
+
import CustomRender from './CustomRender';
|
|
10
|
+
|
|
9
11
|
export interface FunctionMessageProps extends ChatMessage {
|
|
10
12
|
loading?: boolean;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
const PluginMessage = memo<FunctionMessageProps>(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
isJSON =
|
|
23
|
-
|
|
15
|
+
const PluginMessage = memo<FunctionMessageProps>(
|
|
16
|
+
({
|
|
17
|
+
content,
|
|
18
|
+
// loading,
|
|
19
|
+
name,
|
|
20
|
+
}) => {
|
|
21
|
+
const { t } = useTranslation('plugin');
|
|
22
|
+
|
|
23
|
+
const manifest = usePluginStore((s) => s.pluginManifestMap[name || '']);
|
|
24
|
+
let isJSON = true;
|
|
25
|
+
try {
|
|
26
|
+
JSON.parse(content);
|
|
27
|
+
} catch {
|
|
28
|
+
isJSON = false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// if (!loading)
|
|
32
|
+
|
|
33
|
+
if (!isJSON) {
|
|
34
|
+
return (
|
|
35
|
+
<Flexbox gap={8} horizontal>
|
|
36
|
+
<div>
|
|
37
|
+
<Loading3QuartersOutlined spin />
|
|
38
|
+
</div>
|
|
39
|
+
{t('loading.content')}
|
|
40
|
+
</Flexbox>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!manifest?.ui?.url) return;
|
|
24
45
|
|
|
25
|
-
if (!isJSON) {
|
|
26
46
|
return (
|
|
27
|
-
<
|
|
28
|
-
<div>
|
|
29
|
-
<Loading3QuartersOutlined spin />
|
|
30
|
-
</div>
|
|
31
|
-
{t('loading.content')}
|
|
32
|
-
</Flexbox>
|
|
47
|
+
<CustomRender content={JSON.parse(content)} name={name || 'unknown'} url={manifest.ui?.url} />
|
|
33
48
|
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (Render) {
|
|
37
|
-
return <Render content={JSON.parse(content)} name={name || 'unknown'} />;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return null;
|
|
41
|
-
});
|
|
49
|
+
},
|
|
50
|
+
);
|
|
42
51
|
|
|
43
52
|
export default PluginMessage;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* eslint-disable no-undef */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 本动态加载模块使用 SystemJS 实现,在 Lobe Chat 中缓存了 React、ReactDOM、antd、antd-style 四个模块。
|
|
5
|
+
*/
|
|
6
|
+
import * as antd from 'antd';
|
|
7
|
+
import * as AntdStyle from 'antd-style';
|
|
8
|
+
import * as React from 'react';
|
|
9
|
+
import * as ReactDOM from 'react-dom';
|
|
10
|
+
import 'systemjs';
|
|
11
|
+
|
|
12
|
+
System.addImportMap({
|
|
13
|
+
imports: {
|
|
14
|
+
'React': 'app:React',
|
|
15
|
+
'ReactDOM': 'app:ReactDOM',
|
|
16
|
+
'antd': 'app:antd',
|
|
17
|
+
'antd-style': 'app:antd-style',
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
System.set('app:React', { default: React, ...React });
|
|
22
|
+
System.set('app:ReactDOM', { __useDefault: true, ...ReactDOM });
|
|
23
|
+
System.set('app:antd', antd);
|
|
24
|
+
System.set('app:antd-style', AntdStyle);
|
|
25
|
+
|
|
26
|
+
export const system = System;
|
|
@@ -5,7 +5,7 @@ import { LucideToyBrick } from 'lucide-react';
|
|
|
5
5
|
import { memo } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { usePluginStore } from '@/store/plugin';
|
|
9
9
|
|
|
10
10
|
export interface PluginTagProps {
|
|
11
11
|
plugins: string[];
|
|
@@ -14,15 +14,20 @@ export interface PluginTagProps {
|
|
|
14
14
|
const PluginTag = memo<PluginTagProps>(({ plugins }) => {
|
|
15
15
|
const { t } = useTranslation('plugin');
|
|
16
16
|
|
|
17
|
+
const list = usePluginStore((s) => s.pluginList);
|
|
18
|
+
|
|
17
19
|
if (plugins.length === 0) return null;
|
|
18
20
|
|
|
19
|
-
const items: MenuProps['items'] = plugins.map((id) =>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const items: MenuProps['items'] = plugins.map((id) => {
|
|
22
|
+
const item = list?.find((p) => p.name === id);
|
|
23
|
+
return {
|
|
24
|
+
icon: (
|
|
25
|
+
<Avatar avatar={item?.meta.avatar} size={24} style={{ marginLeft: -6, marginRight: 2 }} />
|
|
26
|
+
),
|
|
27
|
+
key: id,
|
|
28
|
+
label: t(`plugins.${id}` as any),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
26
31
|
const count = plugins.length;
|
|
27
32
|
|
|
28
33
|
return (
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { merge } from 'lodash-es';
|
|
2
|
+
import { ChatCompletionFunctions } from 'openai-edge/types/api';
|
|
2
3
|
|
|
3
4
|
import { LOBE_CHAT_ACCESS_CODE, OPENAI_API_KEY_HEADER_KEY, OPENAI_END_POINT } from '@/const/fetch';
|
|
4
5
|
import { useGlobalStore } from '@/store/global';
|
|
6
|
+
import { pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
5
7
|
import { initialLobeAgentConfig } from '@/store/session/initialState';
|
|
6
8
|
import type { OpenAIStreamPayload } from '@/types/openai';
|
|
7
9
|
|
|
@@ -15,7 +17,7 @@ interface FetchChatModelOptions {
|
|
|
15
17
|
* 专门用于对话的 fetch
|
|
16
18
|
*/
|
|
17
19
|
export const fetchChatModel = (
|
|
18
|
-
params: Partial<OpenAIStreamPayload>,
|
|
20
|
+
{ plugins: enabledPlugins, ...params }: Partial<OpenAIStreamPayload>,
|
|
19
21
|
options?: FetchChatModelOptions,
|
|
20
22
|
) => {
|
|
21
23
|
const payload = merge(
|
|
@@ -26,9 +28,16 @@ export const fetchChatModel = (
|
|
|
26
28
|
},
|
|
27
29
|
params,
|
|
28
30
|
);
|
|
31
|
+
// ============ 1. 前置处理 functions ============ //
|
|
32
|
+
|
|
33
|
+
const filterFunctions: ChatCompletionFunctions[] = pluginSelectors.enabledSchema(enabledPlugins)(
|
|
34
|
+
usePluginStore.getState(),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const functions = filterFunctions.length === 0 ? undefined : filterFunctions;
|
|
29
38
|
|
|
30
39
|
return fetch(URLS.openai, {
|
|
31
|
-
body: JSON.stringify(payload),
|
|
40
|
+
body: JSON.stringify({ ...payload, functions }),
|
|
32
41
|
headers: {
|
|
33
42
|
'Content-Type': 'application/json',
|
|
34
43
|
[LOBE_CHAT_ACCESS_CODE]: useGlobalStore.getState().settings.password || '',
|
package/src/services/plugin.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
|
|
2
2
|
|
|
3
3
|
import { LOBE_CHAT_ACCESS_CODE } from '@/const/fetch';
|
|
4
|
+
import { PLUGINS_INDEX_URL } from '@/const/url';
|
|
4
5
|
import { useGlobalStore } from '@/store/global';
|
|
5
6
|
|
|
6
7
|
import { URLS } from './url';
|
|
@@ -11,7 +12,7 @@ interface FetchChatModelOptions {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
*
|
|
15
|
+
* 请求插件结果
|
|
15
16
|
*/
|
|
16
17
|
export const fetchPlugin = async (
|
|
17
18
|
params: PluginRequestPayload,
|
|
@@ -29,3 +30,12 @@ export const fetchPlugin = async (
|
|
|
29
30
|
|
|
30
31
|
return await res.text();
|
|
31
32
|
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 请求插件列表
|
|
36
|
+
*/
|
|
37
|
+
export const getPluginList = async () => {
|
|
38
|
+
const res = await fetch(PLUGINS_INDEX_URL);
|
|
39
|
+
|
|
40
|
+
return res.json();
|
|
41
|
+
};
|
|
@@ -52,7 +52,7 @@ export const createAgentSlice: StateCreator<
|
|
|
52
52
|
setAgentConfig: (config) => {
|
|
53
53
|
const settings = produce(get().settings, (draft: GlobalSettings) => {
|
|
54
54
|
const oldConfig = draft.defaultAgent.config as LobeAgentConfig;
|
|
55
|
-
draft.defaultAgent.config = merge(oldConfig, config);
|
|
55
|
+
draft.defaultAgent.config = merge({}, oldConfig, config);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
set({ settings }, false, t('setAgentConfig', config));
|
|
@@ -60,7 +60,7 @@ export const createAgentSlice: StateCreator<
|
|
|
60
60
|
setAgentMeta: (meta) => {
|
|
61
61
|
const settings = produce(get().settings, (draft) => {
|
|
62
62
|
const oldMeta = draft.defaultAgent.meta as MetaData;
|
|
63
|
-
draft.defaultAgent.meta = merge(oldMeta, meta);
|
|
63
|
+
draft.defaultAgent.meta = merge({}, oldMeta, meta);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
set({ settings }, false, t('setAgentMeta', meta));
|
|
@@ -61,7 +61,7 @@ export const createSettingsSlice: StateCreator<
|
|
|
61
61
|
|
|
62
62
|
setSettings: (settings) => {
|
|
63
63
|
const oldSetting = get().settings;
|
|
64
|
-
set({ settings: merge(oldSetting, settings) }, false, t('setSettings', settings));
|
|
64
|
+
set({ settings: merge({}, oldSetting, settings) }, false, t('setSettings', settings));
|
|
65
65
|
},
|
|
66
66
|
switchThemeMode: (themeMode) => {
|
|
67
67
|
get().setSettings({ themeMode });
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LobeChatPlugin,
|
|
3
|
+
LobeChatPluginsMarketIndex,
|
|
4
|
+
pluginManifestSchema,
|
|
5
|
+
} from '@lobehub/chat-plugin-sdk';
|
|
6
|
+
import { message } from 'antd';
|
|
7
|
+
import { produce } from 'immer';
|
|
8
|
+
import { uniq } from 'lodash-es';
|
|
9
|
+
import useSWR, { SWRResponse } from 'swr';
|
|
10
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
11
|
+
|
|
12
|
+
import { getPluginList } from '@/services/plugin';
|
|
13
|
+
import { LobeSessions } from '@/types/session';
|
|
14
|
+
import { setNamespace } from '@/utils/storeDebug';
|
|
15
|
+
|
|
16
|
+
import { PluginDispatch, pluginManifestReducer } from './reducers/manifest';
|
|
17
|
+
import { PluginStore } from './store';
|
|
18
|
+
|
|
19
|
+
const t = setNamespace('plugin');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 代理行为接口
|
|
23
|
+
*/
|
|
24
|
+
export interface PluginAction {
|
|
25
|
+
checkLocalEnabledPlugins: (sessions: LobeSessions) => void;
|
|
26
|
+
dispatchPluginManifest: (payload: PluginDispatch) => void;
|
|
27
|
+
fetchPluginManifest: (name: string) => Promise<void>;
|
|
28
|
+
updateManifestLoadingState: (key: string, value: boolean | undefined) => void;
|
|
29
|
+
useFetchPluginList: () => SWRResponse<LobeChatPluginsMarketIndex>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const createPluginSlice: StateCreator<
|
|
33
|
+
PluginStore,
|
|
34
|
+
[['zustand/devtools', never]],
|
|
35
|
+
[],
|
|
36
|
+
PluginAction
|
|
37
|
+
> = (set, get) => ({
|
|
38
|
+
checkLocalEnabledPlugins: async (sessions) => {
|
|
39
|
+
const { fetchPluginManifest } = get();
|
|
40
|
+
|
|
41
|
+
let enabledPlugins: string[] = [];
|
|
42
|
+
|
|
43
|
+
for (const session of Object.values(sessions)) {
|
|
44
|
+
const plugins = session.config.plugins;
|
|
45
|
+
if (!plugins || plugins.length === 0) continue;
|
|
46
|
+
|
|
47
|
+
enabledPlugins = [...enabledPlugins, ...plugins];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const plugins = uniq(enabledPlugins);
|
|
51
|
+
|
|
52
|
+
await Promise.all(plugins.map((name) => fetchPluginManifest(name)));
|
|
53
|
+
|
|
54
|
+
console.log('fetched');
|
|
55
|
+
set({ manifestPrepared: true }, false, t('checkLocalEnabledPlugins'));
|
|
56
|
+
},
|
|
57
|
+
dispatchPluginManifest: (payload) => {
|
|
58
|
+
const { pluginManifestMap } = get();
|
|
59
|
+
const nextManifest = pluginManifestReducer(pluginManifestMap, payload);
|
|
60
|
+
|
|
61
|
+
set({ pluginManifestMap: nextManifest }, false, t('dispatchPluginManifest', payload));
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
fetchPluginManifest: async (name) => {
|
|
65
|
+
const plugin = get().pluginList.find((plugin) => plugin.name === name);
|
|
66
|
+
// 1. 校验文件
|
|
67
|
+
|
|
68
|
+
if (!plugin) return;
|
|
69
|
+
|
|
70
|
+
if (!plugin.manifest) {
|
|
71
|
+
message.error('插件未配置 描述文件');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 2. 发送请求
|
|
76
|
+
get().updateManifestLoadingState(name, true);
|
|
77
|
+
let data: LobeChatPlugin | null;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const res = await fetch(plugin.manifest);
|
|
81
|
+
|
|
82
|
+
data = await res.json();
|
|
83
|
+
} catch (error) {
|
|
84
|
+
data = null;
|
|
85
|
+
console.error(error);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get().updateManifestLoadingState(name, undefined);
|
|
89
|
+
if (!data) {
|
|
90
|
+
message.error('插件描述文件请求失败');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 3. 校验插件文件格式规范
|
|
95
|
+
const { success } = pluginManifestSchema.safeParse(data);
|
|
96
|
+
|
|
97
|
+
if (!success) {
|
|
98
|
+
message.error('插件描述文件格式错误');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 4. 存储 manifest 信息
|
|
103
|
+
get().dispatchPluginManifest({ id: plugin.name, plugin: data, type: 'addManifest' });
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
updateManifestLoadingState: (key, value) => {
|
|
107
|
+
set(
|
|
108
|
+
produce((draft) => {
|
|
109
|
+
draft.pluginManifestLoading[key] = value;
|
|
110
|
+
}),
|
|
111
|
+
false,
|
|
112
|
+
t('updateManifestLoadingState'),
|
|
113
|
+
);
|
|
114
|
+
},
|
|
115
|
+
useFetchPluginList: () =>
|
|
116
|
+
useSWR<LobeChatPluginsMarketIndex>('fetchPluginList', getPluginList, {
|
|
117
|
+
onSuccess: (pluginMarketIndex) => {
|
|
118
|
+
set({ pluginList: pluginMarketIndex.plugins }, false, t('useFetchPluginList'));
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { usePluginStore } from '../store';
|
|
4
|
+
|
|
5
|
+
export const usePluginHydrated = () => {
|
|
6
|
+
// 根据 sessions 是否有值来判断是否已经初始化
|
|
7
|
+
|
|
8
|
+
// 并且插件的 manifest 也准备完毕
|
|
9
|
+
const manifestPrepared = usePluginStore((s) => s.manifestPrepared);
|
|
10
|
+
const [isInit, setInit] = useState(false);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const hasRehydrated = usePluginStore.persist.hasHydrated();
|
|
14
|
+
|
|
15
|
+
if (hasRehydrated && !isInit) {
|
|
16
|
+
setInit(true);
|
|
17
|
+
}
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
return isInit && manifestPrepared;
|
|
21
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LobeChatPluginMeta } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
|
|
3
|
+
import { PluginManifestMap } from '@/types/plugin';
|
|
4
|
+
|
|
5
|
+
export type PluginManifestLoadingState = Record<string, boolean>;
|
|
6
|
+
|
|
7
|
+
export interface PluginStoreState {
|
|
8
|
+
manifestPrepared: boolean;
|
|
9
|
+
pluginList: LobeChatPluginMeta[];
|
|
10
|
+
pluginManifestLoading: PluginManifestLoadingState;
|
|
11
|
+
pluginManifestMap: PluginManifestMap;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const initialState: PluginStoreState = {
|
|
15
|
+
manifestPrepared: false,
|
|
16
|
+
pluginList: [],
|
|
17
|
+
pluginManifestLoading: {},
|
|
18
|
+
pluginManifestMap: {},
|
|
19
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { LobeChatPlugin } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
import { produce } from 'immer';
|
|
3
|
+
|
|
4
|
+
import { PluginManifestMap } from '@/types/plugin';
|
|
5
|
+
|
|
6
|
+
type AddManifestDispatch = { id: string; plugin: LobeChatPlugin; type: 'addManifest' };
|
|
7
|
+
type DeleteManifestDispatch = { id: string; type: 'deleteManifest' };
|
|
8
|
+
// type UpdateManifestDispatch = {
|
|
9
|
+
// id: string;
|
|
10
|
+
// plugin: LobeChatPlugin;
|
|
11
|
+
// type: 'updateManifest';
|
|
12
|
+
// version: string;
|
|
13
|
+
// };
|
|
14
|
+
|
|
15
|
+
export type PluginDispatch = AddManifestDispatch | DeleteManifestDispatch;
|
|
16
|
+
// | UpdateManifestDispatch;
|
|
17
|
+
|
|
18
|
+
export const pluginManifestReducer = (
|
|
19
|
+
state: PluginManifestMap,
|
|
20
|
+
payload: PluginDispatch,
|
|
21
|
+
): PluginManifestMap => {
|
|
22
|
+
switch (payload.type) {
|
|
23
|
+
case 'addManifest': {
|
|
24
|
+
return produce(state, (draftState) => {
|
|
25
|
+
draftState[payload.id] = payload.plugin;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
case 'deleteManifest': {
|
|
30
|
+
return produce(state, (draftState) => {
|
|
31
|
+
delete draftState[payload.id];
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// case 'updateManifest'
|
|
35
|
+
}
|
|
36
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { PluginStoreState } from './initialState';
|
|
2
|
+
|
|
3
|
+
const enabledSchema =
|
|
4
|
+
(enabledPlugins: string[] = []) =>
|
|
5
|
+
(s: PluginStoreState) => {
|
|
6
|
+
return Object.values(s.pluginManifestMap)
|
|
7
|
+
.filter((p) => {
|
|
8
|
+
// 如果不存在 enabledPlugins,那么全部不启用
|
|
9
|
+
if (!enabledPlugins) return false;
|
|
10
|
+
|
|
11
|
+
// 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
|
|
12
|
+
return enabledPlugins.includes(p.name);
|
|
13
|
+
})
|
|
14
|
+
.map((i) => i.schema);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const getPluginMetaByName = (name: string) => (s: PluginStoreState) =>
|
|
18
|
+
s.pluginList?.find((p) => p.name === name);
|
|
19
|
+
|
|
20
|
+
export const pluginSelectors = {
|
|
21
|
+
enabledSchema,
|
|
22
|
+
getPluginMetaByName,
|
|
23
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { PersistOptions, devtools, persist } from 'zustand/middleware';
|
|
2
|
+
import { shallow } from 'zustand/shallow';
|
|
3
|
+
import { createWithEqualityFn } from 'zustand/traditional';
|
|
4
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
5
|
+
|
|
6
|
+
import { isDev } from '@/utils/env';
|
|
7
|
+
|
|
8
|
+
import { PluginAction, createPluginSlice } from './action';
|
|
9
|
+
import { PluginStoreState, initialState } from './initialState';
|
|
10
|
+
|
|
11
|
+
// =============== 聚合 createStoreFn ============ //
|
|
12
|
+
|
|
13
|
+
export type PluginStore = PluginStoreState & PluginAction;
|
|
14
|
+
|
|
15
|
+
const createStore: StateCreator<PluginStore, [['zustand/devtools', never]]> = (...parameters) => ({
|
|
16
|
+
...initialState,
|
|
17
|
+
...createPluginSlice(...parameters),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// =============== persist 本地缓存中间件配置 ============ //
|
|
21
|
+
|
|
22
|
+
type SessionPersist = Pick<PluginStore, 'pluginList'>;
|
|
23
|
+
|
|
24
|
+
const storeName = 'LOBE_PLUGIN';
|
|
25
|
+
|
|
26
|
+
const persistOptions: PersistOptions<PluginStore, SessionPersist> = {
|
|
27
|
+
name: storeName,
|
|
28
|
+
|
|
29
|
+
partialize: (s) => ({
|
|
30
|
+
pluginList: s.pluginList,
|
|
31
|
+
}),
|
|
32
|
+
|
|
33
|
+
// 手动控制 Hydration ,避免 ssr 报错
|
|
34
|
+
skipHydration: true,
|
|
35
|
+
version: 0,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// =============== 实装 useStore ============ //
|
|
39
|
+
|
|
40
|
+
export const usePluginStore = createWithEqualityFn<PluginStore>()(
|
|
41
|
+
persist(
|
|
42
|
+
devtools(createStore, {
|
|
43
|
+
name: storeName + (isDev ? '_DEV' : ''),
|
|
44
|
+
}),
|
|
45
|
+
persistOptions,
|
|
46
|
+
),
|
|
47
|
+
shallow,
|
|
48
|
+
);
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
|
|
3
|
-
import { useSessionStore } from '../store';
|
|
3
|
+
import { SessionStore, useSessionStore } from '../store';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* 当 Session 水合完毕后才会执行的 useEffect
|
|
7
7
|
* @param fn
|
|
8
8
|
* @param deps
|
|
9
9
|
*/
|
|
10
|
-
export const useOnFinishHydrationSession = (
|
|
10
|
+
export const useOnFinishHydrationSession = (
|
|
11
|
+
fn: (state: SessionStore) => void,
|
|
12
|
+
deps: any[] = [],
|
|
13
|
+
) => {
|
|
11
14
|
useEffect(() => {
|
|
12
15
|
const hasRehydrated = useSessionStore.persist.hasHydrated();
|
|
13
16
|
// 只有当水合完毕后再开始做操作
|
|
14
17
|
if (!hasRehydrated) return;
|
|
15
18
|
|
|
16
|
-
fn();
|
|
19
|
+
fn(useSessionStore.getState());
|
|
17
20
|
}, deps);
|
|
18
21
|
};
|
|
@@ -5,7 +5,6 @@ import { useSessionStore } from '../store';
|
|
|
5
5
|
export const useSessionHydrated = () => {
|
|
6
6
|
// 根据 sessions 是否有值来判断是否已经初始化
|
|
7
7
|
const hasInited = !!Object.values(useSessionStore.getState().sessions).length;
|
|
8
|
-
|
|
9
8
|
const [isInit, setInit] = useState(hasInited);
|
|
10
9
|
|
|
11
10
|
useEffect(() => {
|
|
@@ -34,22 +34,21 @@ export interface AgentAction {
|
|
|
34
34
|
* @returns 一个 Promise,用于异步操作完成后的处理
|
|
35
35
|
*/
|
|
36
36
|
autocompleteAgentTitle: (id: string) => Promise<void>;
|
|
37
|
-
|
|
38
37
|
autocompleteMeta: (key: keyof MetaData) => void;
|
|
38
|
+
|
|
39
39
|
/**
|
|
40
40
|
* 自动完成会话代理元数据
|
|
41
41
|
* @param id - 代理的 ID
|
|
42
42
|
*/
|
|
43
43
|
autocompleteSessionAgentMeta: (id: string, replace?: boolean) => void;
|
|
44
|
-
|
|
45
44
|
/**
|
|
46
45
|
* 内部更新代理元数据
|
|
47
46
|
* @param id - 代理的 ID
|
|
48
47
|
* @returns 任意类型的返回值
|
|
49
48
|
*/
|
|
50
49
|
internalUpdateAgentMeta: (id: string) => any;
|
|
51
|
-
toggleAgentPlugin: (pluginId: string) => void;
|
|
52
50
|
|
|
51
|
+
toggleAgentPlugin: (pluginId: string, checked: boolean) => void;
|
|
53
52
|
/**
|
|
54
53
|
* 更新代理配置
|
|
55
54
|
* @param config - 部分 LobeAgentConfig 的配置
|
|
@@ -191,7 +190,6 @@ export const createAgentSlice: StateCreator<
|
|
|
191
190
|
get().dispatchSession({ id, key, type: 'updateSessionMeta', value });
|
|
192
191
|
};
|
|
193
192
|
},
|
|
194
|
-
|
|
195
193
|
toggleAgentPlugin: (id: string) => {
|
|
196
194
|
const { activeId } = get();
|
|
197
195
|
const session = sessionSelectors.currentSession(get());
|
|
@@ -212,7 +210,6 @@ export const createAgentSlice: StateCreator<
|
|
|
212
210
|
|
|
213
211
|
get().dispatchSession({ config, id: activeId, type: 'updateSessionConfig' });
|
|
214
212
|
},
|
|
215
|
-
|
|
216
213
|
updateAgentConfig: (config) => {
|
|
217
214
|
const { activeId } = get();
|
|
218
215
|
const session = sessionSelectors.currentSession(get());
|
|
@@ -220,7 +217,6 @@ export const createAgentSlice: StateCreator<
|
|
|
220
217
|
|
|
221
218
|
get().dispatchSession({ config, id: activeId, type: 'updateSessionConfig' });
|
|
222
219
|
},
|
|
223
|
-
|
|
224
220
|
updateAgentMeta: (meta) => {
|
|
225
221
|
const { activeId } = get();
|
|
226
222
|
const session = sessionSelectors.currentSession(get());
|
|
@@ -338,7 +338,21 @@ describe('organizeChats', () => {
|
|
|
338
338
|
|
|
339
339
|
session.chats[message.id] = message;
|
|
340
340
|
|
|
341
|
-
const result = organizeChats(session,
|
|
341
|
+
const result = organizeChats(session, {
|
|
342
|
+
...params,
|
|
343
|
+
pluginList: [
|
|
344
|
+
{
|
|
345
|
+
name: 'realtimeWeather',
|
|
346
|
+
meta: {
|
|
347
|
+
avatar: '🧩',
|
|
348
|
+
},
|
|
349
|
+
createAt: 'abc',
|
|
350
|
+
manifest: '',
|
|
351
|
+
homepage: '',
|
|
352
|
+
schemaVersion: 'v1',
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
});
|
|
342
356
|
const meta = result[3].meta;
|
|
343
357
|
|
|
344
358
|
expect(meta.avatar).toBe('🧩');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LobeChatPluginMeta } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
|
|
2
3
|
import { ChatMessage } from '@/types/chatMessage';
|
|
3
4
|
import { MetaData } from '@/types/meta';
|
|
4
5
|
import { LobeAgentSession } from '@/types/session';
|
|
@@ -8,12 +9,13 @@ interface OrganizeParams {
|
|
|
8
9
|
assistant?: MetaData;
|
|
9
10
|
user?: MetaData;
|
|
10
11
|
};
|
|
12
|
+
pluginList?: LobeChatPluginMeta[];
|
|
11
13
|
topicId?: string;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export const organizeChats = (
|
|
15
17
|
session: LobeAgentSession,
|
|
16
|
-
{ topicId, meta }: OrganizeParams = {},
|
|
18
|
+
{ topicId, meta, pluginList }: OrganizeParams = {},
|
|
17
19
|
) => {
|
|
18
20
|
const getMeta = (message: ChatMessage) => {
|
|
19
21
|
switch (message.role) {
|
|
@@ -36,7 +38,7 @@ export const organizeChats = (
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
case 'function': {
|
|
39
|
-
const plugin =
|
|
41
|
+
const plugin = (pluginList || []).find((m) => m.name === message.name);
|
|
40
42
|
|
|
41
43
|
return {
|
|
42
44
|
avatar: '🧩',
|
|
@@ -13,7 +13,6 @@ import { SessionAction, createSessionSlice } from './slices/session/action';
|
|
|
13
13
|
// =============== 聚合 createStoreFn ============ //
|
|
14
14
|
|
|
15
15
|
export type SessionStore = SessionAction & AgentAction & ChatAction & SessionStoreState;
|
|
16
|
-
|
|
17
16
|
const createStore: StateCreator<SessionStore, [['zustand/devtools', never]]> = (...parameters) => ({
|
|
18
17
|
...initialState,
|
|
19
18
|
...createAgentSlice(...parameters),
|
package/src/types/plugin.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
1
|
+
import { LobeChatPlugin } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
|
|
3
|
+
export type PluginManifestMap = Record<string, LobeChatPlugin>;
|