@lobehub/chat 0.114.9 → 0.115.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/CHANGELOG.md +25 -0
- package/locales/de-DE/tool.json +8 -0
- package/locales/en-US/tool.json +8 -0
- package/locales/es-ES/tool.json +8 -0
- package/locales/fr-FR/tool.json +8 -0
- package/locales/it-IT/tool.json +8 -0
- package/locales/ja-JP/tool.json +8 -0
- package/locales/ko-KR/tool.json +8 -0
- package/locales/nl-NL/tool.json +8 -0
- package/locales/pl-PL/tool.json +8 -0
- package/locales/pt-BR/tool.json +8 -0
- package/locales/ru-RU/tool.json +8 -0
- package/locales/tr-TR/tool.json +8 -0
- package/locales/zh-CN/plugin.json +2 -2
- package/locales/zh-CN/setting.json +1 -1
- package/locales/zh-CN/tool.json +8 -0
- package/locales/zh-TW/tool.json +8 -0
- package/package.json +1 -1
- package/src/app/api/openai/images/createImageGeneration.ts +26 -0
- package/src/app/api/openai/images/route.ts +16 -0
- package/src/app/api/proxy/route.ts +3 -1
- package/src/app/chat/(desktop)/features/ChatHeader.tsx +56 -56
- package/src/app/chat/features/ChatHeader/PluginTag/PluginStatus.tsx +7 -4
- package/src/app/chat/features/ChatHeader/PluginTag/index.tsx +3 -3
- package/src/app/chat/features/SystemRole/index.tsx +0 -1
- package/src/components/GalleyGrid/Grid.tsx +36 -0
- package/src/components/GalleyGrid/index.tsx +58 -0
- package/src/components/GalleyGrid/style.ts +24 -0
- package/src/const/settings.ts +7 -0
- package/src/database/schemas/files.ts +1 -0
- package/src/database/schemas/message.ts +1 -1
- package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
- package/src/features/ChatInput/ActionBar/ModelSwitch.tsx +2 -1
- package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +3 -3
- package/src/features/ChatInput/ActionBar/Tools/index.tsx +26 -14
- package/src/features/Conversation/ChatList/Messages/Function.tsx +1 -1
- package/src/features/Conversation/ChatList/Plugins/Inspector/index.tsx +4 -2
- package/src/features/Conversation/ChatList/Plugins/Render/BuiltinType/index.tsx +36 -0
- package/src/features/Conversation/ChatList/Plugins/Render/DefaultType/index.tsx +6 -66
- package/src/features/Conversation/ChatList/Plugins/Render/Loading.tsx +61 -0
- package/src/features/Conversation/ChatList/Plugins/Render/index.tsx +21 -12
- package/src/features/Conversation/ChatList/Plugins/Render/useParseContent.ts +15 -0
- package/src/locales/default/index.ts +2 -0
- package/src/locales/default/plugin.ts +2 -2
- package/src/locales/default/setting.ts +1 -1
- package/src/locales/default/tool.ts +9 -0
- package/src/services/__tests__/chat.test.ts +132 -2
- package/src/services/_url.ts +1 -0
- package/src/services/chat.ts +3 -5
- package/src/services/file.ts +20 -5
- package/src/services/imageGeneration.ts +26 -0
- package/src/store/chat/actions/enhance.ts +65 -1
- package/src/store/chat/actions/plugin.test.ts +9 -9
- package/src/store/chat/actions/plugin.ts +38 -6
- package/src/store/chat/initialState.ts +3 -0
- package/src/store/chat/selectors/enchance.ts +11 -0
- package/src/store/chat/selectors/index.ts +1 -0
- package/src/store/global/selectors/__snapshots__/settings.test.ts.snap +5 -0
- package/src/store/global/selectors/settings.ts +5 -0
- package/src/store/tool/helpers.ts +6 -5
- package/src/store/tool/initialState.ts +3 -1
- package/src/store/tool/selectors/index.ts +5 -0
- package/src/store/tool/selectors/tool.test.ts +128 -0
- package/src/store/tool/selectors/tool.ts +125 -0
- package/src/store/tool/slices/builtin/action.ts +54 -0
- package/src/store/tool/slices/builtin/index.ts +2 -0
- package/src/store/tool/slices/builtin/initialState.ts +13 -0
- package/src/store/tool/slices/builtin/selectors.test.ts +70 -0
- package/src/store/tool/slices/builtin/selectors.ts +20 -0
- package/src/store/tool/slices/customPlugin/action.ts +0 -3
- package/src/store/tool/slices/plugin/selectors.test.ts +0 -69
- package/src/store/tool/slices/plugin/selectors.ts +1 -84
- package/src/store/tool/store.ts +8 -2
- package/src/tools/dalle/Render/EditMode.tsx +66 -0
- package/src/tools/dalle/Render/Item.tsx +97 -0
- package/src/tools/dalle/Render/ToolBar.tsx +54 -0
- package/src/tools/dalle/Render/index.tsx +21 -0
- package/src/tools/dalle/index.ts +81 -0
- package/src/tools/index.ts +11 -0
- package/src/tools/renders.ts +8 -0
- package/src/types/chatMessage.ts +1 -1
- package/src/types/openai/image.ts +34 -0
- package/src/types/settings.ts +6 -0
- package/src/types/tool/builtin.ts +36 -0
- package/src/types/tool/dalle.ts +12 -0
- package/src/types/tool/index.ts +5 -1
- package/src/types/tool/tool.ts +9 -0
- package/src/store/tool/selectors.ts +0 -3
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
import { uniqBy } from 'lodash-es';
|
|
3
|
+
import { Md5 } from 'ts-md5';
|
|
4
|
+
|
|
5
|
+
import { PLUGIN_SCHEMA_API_MD5_PREFIX, PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
|
|
6
|
+
import { MetaData } from '@/types/meta';
|
|
7
|
+
import { ChatCompletionFunctions } from '@/types/openai/chat';
|
|
8
|
+
import { LobeToolMeta } from '@/types/tool/tool';
|
|
9
|
+
|
|
10
|
+
import { pluginHelpers } from '../helpers';
|
|
11
|
+
import { ToolStoreState } from '../initialState';
|
|
12
|
+
import { builtinToolSelectors } from '../slices/builtin/selectors';
|
|
13
|
+
import { pluginSelectors } from '../slices/plugin/selectors';
|
|
14
|
+
|
|
15
|
+
const getAPIName = (identifier: string, name: string, type?: string) => {
|
|
16
|
+
const pluginType = type && type !== 'default' ? `${PLUGIN_SCHEMA_SEPARATOR + type}` : '';
|
|
17
|
+
|
|
18
|
+
// 将插件的 identifier 作为前缀,避免重复
|
|
19
|
+
let apiName = identifier + PLUGIN_SCHEMA_SEPARATOR + name + pluginType;
|
|
20
|
+
|
|
21
|
+
// OpenAI GPT function_call name can't be longer than 64 characters
|
|
22
|
+
// So we need to use md5 to shorten the name
|
|
23
|
+
// and then find the correct apiName in response by md5
|
|
24
|
+
if (apiName.length >= 64) {
|
|
25
|
+
const md5Content = PLUGIN_SCHEMA_API_MD5_PREFIX + Md5.hashStr(name).toString();
|
|
26
|
+
|
|
27
|
+
apiName = identifier + PLUGIN_SCHEMA_SEPARATOR + md5Content + pluginType;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return apiName;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const enabledSchema =
|
|
34
|
+
(tools: string[] = []) =>
|
|
35
|
+
(s: ToolStoreState): ChatCompletionFunctions[] => {
|
|
36
|
+
const list = pluginSelectors
|
|
37
|
+
.installedPluginManifestList(s)
|
|
38
|
+
.concat(s.builtinTools.map((b) => b.manifest as LobeChatPluginManifest))
|
|
39
|
+
// 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
|
|
40
|
+
.filter((m) => tools.includes(m?.identifier))
|
|
41
|
+
.flatMap((manifest) =>
|
|
42
|
+
manifest.api.map((m) => ({
|
|
43
|
+
...m,
|
|
44
|
+
name: getAPIName(manifest.identifier, m.name, manifest.type),
|
|
45
|
+
})),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return uniqBy(list, 'name');
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const enabledSystemRoles =
|
|
52
|
+
(tools: string[] = []) =>
|
|
53
|
+
(s: ToolStoreState) => {
|
|
54
|
+
const toolsSystemRole = pluginSelectors
|
|
55
|
+
.installedPluginManifestList(s)
|
|
56
|
+
.concat(s.builtinTools.map((b) => b.manifest as LobeChatPluginManifest))
|
|
57
|
+
// 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
|
|
58
|
+
.filter((m) => tools.includes(m?.identifier))
|
|
59
|
+
.map((manifest) => {
|
|
60
|
+
if (!manifest) return '';
|
|
61
|
+
|
|
62
|
+
const meta = manifest.meta || {};
|
|
63
|
+
|
|
64
|
+
const title = pluginHelpers.getPluginTitle(meta) || manifest.identifier;
|
|
65
|
+
const systemRole = manifest.systemRole || pluginHelpers.getPluginDesc(meta);
|
|
66
|
+
|
|
67
|
+
const methods = manifest.api
|
|
68
|
+
.map((m) =>
|
|
69
|
+
[`#### ${getAPIName(manifest.identifier, m.name, manifest.type)}`, m.description].join(
|
|
70
|
+
'\n\n',
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
.join('\n\n');
|
|
74
|
+
|
|
75
|
+
return [`### ${title}`, systemRole, 'The APIs you can use:', methods].join('\n\n');
|
|
76
|
+
})
|
|
77
|
+
.filter(Boolean);
|
|
78
|
+
|
|
79
|
+
if (toolsSystemRole.length > 0) {
|
|
80
|
+
return ['## Tools', 'You can use these tools below:', ...toolsSystemRole]
|
|
81
|
+
.filter(Boolean)
|
|
82
|
+
.join('\n\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return '';
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const metaList = (s: ToolStoreState): LobeToolMeta[] => {
|
|
89
|
+
const pluginList = pluginSelectors.installedPluginMetaList(s) as LobeToolMeta[];
|
|
90
|
+
|
|
91
|
+
return builtinToolSelectors.metaList(s).concat(pluginList);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const getMetaById =
|
|
95
|
+
(id: string) =>
|
|
96
|
+
(s: ToolStoreState): MetaData | undefined =>
|
|
97
|
+
metaList(s).find((m) => m.identifier === id)?.meta;
|
|
98
|
+
|
|
99
|
+
const getManifestById =
|
|
100
|
+
(id: string) =>
|
|
101
|
+
(s: ToolStoreState): LobeChatPluginManifest | undefined =>
|
|
102
|
+
pluginSelectors
|
|
103
|
+
.installedPluginManifestList(s)
|
|
104
|
+
.concat(s.builtinTools.map((b) => b.manifest as LobeChatPluginManifest))
|
|
105
|
+
.find((i) => i.identifier === id);
|
|
106
|
+
|
|
107
|
+
// 获取插件 manifest 加载状态
|
|
108
|
+
const getManifestLoadingStatus = (id: string) => (s: ToolStoreState) => {
|
|
109
|
+
const manifest = getManifestById(id)(s);
|
|
110
|
+
|
|
111
|
+
if (s.pluginInstallLoading[id]) return 'loading';
|
|
112
|
+
|
|
113
|
+
if (!manifest) return 'error';
|
|
114
|
+
|
|
115
|
+
if (!!manifest) return 'success';
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const toolSelectors = {
|
|
119
|
+
enabledSchema,
|
|
120
|
+
enabledSystemRoles,
|
|
121
|
+
getManifestById,
|
|
122
|
+
getManifestLoadingStatus,
|
|
123
|
+
getMetaById,
|
|
124
|
+
metaList,
|
|
125
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
2
|
+
|
|
3
|
+
import { OpenAIImagePayload } from '@/types/openai/image';
|
|
4
|
+
import { DallEImageItem } from '@/types/tool/dalle';
|
|
5
|
+
import { setNamespace } from '@/utils/storeDebug';
|
|
6
|
+
|
|
7
|
+
import { ToolStore } from '../../store';
|
|
8
|
+
|
|
9
|
+
const n = setNamespace('builtinTool');
|
|
10
|
+
|
|
11
|
+
interface Text2ImageParams extends Pick<OpenAIImagePayload, 'quality' | 'style' | 'size'> {
|
|
12
|
+
prompts: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 代理行为接口
|
|
17
|
+
*/
|
|
18
|
+
export interface BuiltinToolAction {
|
|
19
|
+
invokeBuiltinTool: (key: string, params: any) => Promise<string | undefined>;
|
|
20
|
+
text2image: (params: Text2ImageParams, messageId: string) => DallEImageItem[];
|
|
21
|
+
toggleBuiltinToolLoading: (key: string, value: boolean) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const createBuiltinToolSlice: StateCreator<
|
|
25
|
+
ToolStore,
|
|
26
|
+
[['zustand/devtools', never]],
|
|
27
|
+
[],
|
|
28
|
+
BuiltinToolAction
|
|
29
|
+
> = (set, get) => ({
|
|
30
|
+
invokeBuiltinTool: async (key, params) => {
|
|
31
|
+
const { builtinToolLoading, toggleBuiltinToolLoading } = get();
|
|
32
|
+
|
|
33
|
+
if (builtinToolLoading[key]) return;
|
|
34
|
+
|
|
35
|
+
toggleBuiltinToolLoading(key, true);
|
|
36
|
+
|
|
37
|
+
const { [key as keyof BuiltinToolAction]: action } = get();
|
|
38
|
+
|
|
39
|
+
if (!action) return;
|
|
40
|
+
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
const result = await action(params);
|
|
43
|
+
|
|
44
|
+
toggleBuiltinToolLoading(key, false);
|
|
45
|
+
|
|
46
|
+
return JSON.stringify(result);
|
|
47
|
+
},
|
|
48
|
+
text2image: ({ prompts, size = '1024x1024' as const, quality = 'standard', style = 'vivid' }) =>
|
|
49
|
+
prompts.map((p) => ({ prompt: p, quality, size, style })),
|
|
50
|
+
|
|
51
|
+
toggleBuiltinToolLoading: (key, value) => {
|
|
52
|
+
set({ builtinToolLoading: { [key]: value } }, false, n('toggleBuiltinToolLoading'));
|
|
53
|
+
},
|
|
54
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LobeBuiltinTool } from '@/types/tool';
|
|
2
|
+
|
|
3
|
+
import { builtinTools } from '../../../../tools';
|
|
4
|
+
|
|
5
|
+
export interface BuiltinToolState {
|
|
6
|
+
builtinToolLoading: Record<string, boolean>;
|
|
7
|
+
builtinTools: LobeBuiltinTool[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const initialBuiltinToolState: BuiltinToolState = {
|
|
11
|
+
builtinToolLoading: {},
|
|
12
|
+
builtinTools,
|
|
13
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { LobeChatPluginManifest, LobeChatPluginMeta } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { ToolStoreState, initialState } from '../../initialState';
|
|
5
|
+
import { builtinToolSelectors } from './selectors';
|
|
6
|
+
|
|
7
|
+
const mockState = {
|
|
8
|
+
...initialState,
|
|
9
|
+
pluginManifestLoading: {
|
|
10
|
+
'plugin-1': false,
|
|
11
|
+
'plugin-2': true,
|
|
12
|
+
},
|
|
13
|
+
installedPlugins: [
|
|
14
|
+
{
|
|
15
|
+
identifier: 'plugin-1',
|
|
16
|
+
type: 'plugin',
|
|
17
|
+
manifest: {
|
|
18
|
+
identifier: 'plugin-1',
|
|
19
|
+
api: [{ name: 'api-1' }],
|
|
20
|
+
type: 'default',
|
|
21
|
+
} as LobeChatPluginManifest,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
identifier: 'plugin-2',
|
|
25
|
+
manifest: {
|
|
26
|
+
identifier: 'plugin-2',
|
|
27
|
+
api: [{ name: 'api-2' }],
|
|
28
|
+
type: 'default',
|
|
29
|
+
},
|
|
30
|
+
type: 'plugin',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
pluginStoreList: [
|
|
34
|
+
{
|
|
35
|
+
identifier: 'plugin-1',
|
|
36
|
+
author: 'Author 1',
|
|
37
|
+
createdAt: '2021-01-01',
|
|
38
|
+
meta: { avatar: 'avatar-url-1', title: 'Plugin 1' },
|
|
39
|
+
homepage: 'http://homepage-1.com',
|
|
40
|
+
} as LobeChatPluginMeta,
|
|
41
|
+
{
|
|
42
|
+
identifier: 'plugin-2',
|
|
43
|
+
author: 'Author 2',
|
|
44
|
+
createdAt: '2022-02-02',
|
|
45
|
+
meta: { avatar: 'avatar-url-2', title: 'Plugin 2' },
|
|
46
|
+
homepage: 'http://homepage-2.com',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
} as ToolStoreState;
|
|
50
|
+
|
|
51
|
+
describe('pluginSelectors', () => {
|
|
52
|
+
describe('isCustomPlugin', () => {
|
|
53
|
+
it('should return false for a non-custom plugin', () => {
|
|
54
|
+
const result = builtinToolSelectors.isBuiltinTool('plugin-1')(mockState);
|
|
55
|
+
expect(result).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should return true for a custom plugin', () => {
|
|
59
|
+
const stateWithCustomPlugin = {
|
|
60
|
+
...mockState,
|
|
61
|
+
installedPlugins: [
|
|
62
|
+
...mockState.installedPlugins,
|
|
63
|
+
{ identifier: 'custom-plugin', type: 'customPlugin' },
|
|
64
|
+
],
|
|
65
|
+
} as ToolStoreState;
|
|
66
|
+
const result = builtinToolSelectors.isBuiltinTool('custom-plugin')(stateWithCustomPlugin);
|
|
67
|
+
expect(result).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LobeToolMeta } from '@/types/tool/tool';
|
|
2
|
+
|
|
3
|
+
import { pluginHelpers } from '../../helpers';
|
|
4
|
+
import type { ToolStoreState } from '../../initialState';
|
|
5
|
+
|
|
6
|
+
const isBuiltinTool = (id: string) => (s: ToolStoreState) =>
|
|
7
|
+
pluginHelpers.isCustomPlugin(id, s.installedPlugins);
|
|
8
|
+
|
|
9
|
+
const metaList = (s: ToolStoreState): LobeToolMeta[] =>
|
|
10
|
+
s.builtinTools.map((t) => ({
|
|
11
|
+
author: 'LobeHub',
|
|
12
|
+
identifier: t.identifier,
|
|
13
|
+
meta: t.manifest.meta,
|
|
14
|
+
type: 'builtin',
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
export const builtinToolSelectors = {
|
|
18
|
+
isBuiltinTool,
|
|
19
|
+
metaList,
|
|
20
|
+
};
|
|
@@ -14,9 +14,6 @@ import { defaultCustomPlugin } from './initialState';
|
|
|
14
14
|
|
|
15
15
|
const n = setNamespace('customPlugin');
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
* 代理行为接口
|
|
19
|
-
*/
|
|
20
17
|
export interface CustomPluginAction {
|
|
21
18
|
installCustomPlugin: (value: LobeToolCustomPlugin) => Promise<void>;
|
|
22
19
|
reinstallCustomPlugin: (id: string) => Promise<void>;
|
|
@@ -58,57 +58,6 @@ const mockState = {
|
|
|
58
58
|
} as ToolStoreState;
|
|
59
59
|
|
|
60
60
|
describe('pluginSelectors', () => {
|
|
61
|
-
describe('enabledSchema', () => {
|
|
62
|
-
it('enabledSchema should return correct ChatCompletionFunctions array', () => {
|
|
63
|
-
const result = pluginSelectors.enabledSchema(['plugin-1', 'plugin-2'])(mockState);
|
|
64
|
-
expect(result).toEqual([{ name: 'plugin-1____api-1' }, { name: 'plugin-2____api-2' }]);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('enabledSchema should return with standalone plugin', () => {
|
|
68
|
-
const result = pluginSelectors.enabledSchema(['plugin-4'])({
|
|
69
|
-
...mockState,
|
|
70
|
-
installedPlugins: [
|
|
71
|
-
...mockState.installedPlugins,
|
|
72
|
-
{
|
|
73
|
-
identifier: 'plugin-4',
|
|
74
|
-
manifest: {
|
|
75
|
-
identifier: 'plugin-4',
|
|
76
|
-
api: [{ name: 'api-4' }],
|
|
77
|
-
type: 'standalone',
|
|
78
|
-
},
|
|
79
|
-
type: 'plugin',
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
} as ToolStoreState);
|
|
83
|
-
expect(result).toEqual([{ name: 'plugin-4____api-4____standalone' }]);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('enabledSchema should return md5 hash apiName', () => {
|
|
87
|
-
const result = pluginSelectors.enabledSchema(['long-long-plugin-with-id'])({
|
|
88
|
-
...mockState,
|
|
89
|
-
installedPlugins: [
|
|
90
|
-
...mockState.installedPlugins,
|
|
91
|
-
{
|
|
92
|
-
identifier: 'long-long-plugin-with-id',
|
|
93
|
-
manifest: {
|
|
94
|
-
identifier: 'long-long-plugin-with-id',
|
|
95
|
-
api: [{ name: 'long-long-manifest-long-long-apiName' }],
|
|
96
|
-
},
|
|
97
|
-
type: 'plugin',
|
|
98
|
-
},
|
|
99
|
-
],
|
|
100
|
-
} as ToolStoreState);
|
|
101
|
-
expect(result).toEqual([
|
|
102
|
-
{ name: 'long-long-plugin-with-id____MD5HASH_396eae4c671da3fb642c49ad2b9e8790' },
|
|
103
|
-
]);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('enabledSchema should return empty', () => {
|
|
107
|
-
const result = pluginSelectors.enabledSchema([])(mockState);
|
|
108
|
-
expect(result).toEqual([]);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
61
|
describe('getPluginManifestById', () => {
|
|
113
62
|
it('getPluginManifestById should return the correct manifest', () => {
|
|
114
63
|
const result = pluginSelectors.getPluginManifestById('plugin-1')(mockState);
|
|
@@ -211,24 +160,6 @@ describe('pluginSelectors', () => {
|
|
|
211
160
|
});
|
|
212
161
|
});
|
|
213
162
|
|
|
214
|
-
describe('getPluginManifestLoadingStatus', () => {
|
|
215
|
-
it('should return "loading" if the plugin manifest is being loaded', () => {
|
|
216
|
-
const result = pluginSelectors.getPluginManifestLoadingStatus('plugin-2')(mockState);
|
|
217
|
-
expect(result).toBe('loading');
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it('should return "error" if the plugin manifest is not found', () => {
|
|
221
|
-
const result =
|
|
222
|
-
pluginSelectors.getPluginManifestLoadingStatus('non-existing-plugin')(mockState);
|
|
223
|
-
expect(result).toBe('error');
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('should return "success" if the plugin manifest is loaded', () => {
|
|
227
|
-
const result = pluginSelectors.getPluginManifestLoadingStatus('plugin-1')(mockState);
|
|
228
|
-
expect(result).toBe('success');
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
163
|
describe('storeAndInstallPluginsIdList', () => {
|
|
233
164
|
it('should return a list of unique plugin identifiers from both installed and store lists', () => {
|
|
234
165
|
const result = pluginSelectors.storeAndInstallPluginsIdList(mockState);
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
|
2
|
-
import { uniq
|
|
3
|
-
import { Md5 } from 'ts-md5';
|
|
2
|
+
import { uniq } from 'lodash-es';
|
|
4
3
|
|
|
5
|
-
import { PLUGIN_SCHEMA_API_MD5_PREFIX, PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
|
|
6
|
-
import { ChatCompletionFunctions } from '@/types/openai/chat';
|
|
7
4
|
import { InstallPluginMeta, LobeToolCustomPlugin } from '@/types/tool/plugin';
|
|
8
5
|
|
|
9
|
-
import { pluginHelpers } from '../../helpers';
|
|
10
6
|
import type { ToolStoreState } from '../../initialState';
|
|
11
7
|
|
|
12
8
|
const installedPlugins = (s: ToolStoreState) => s.installedPlugins;
|
|
@@ -37,17 +33,6 @@ const getPluginManifestById = (id: string) => (s: ToolStoreState) =>
|
|
|
37
33
|
const getPluginSettingsById = (id: string) => (s: ToolStoreState) =>
|
|
38
34
|
getInstalledPluginById(id)(s)?.settings || {};
|
|
39
35
|
|
|
40
|
-
// 获取插件 manifest 加载状态
|
|
41
|
-
const getPluginManifestLoadingStatus = (id: string) => (s: ToolStoreState) => {
|
|
42
|
-
const manifest = getPluginManifestById(id)(s);
|
|
43
|
-
|
|
44
|
-
if (s.pluginInstallLoading[id]) return 'loading';
|
|
45
|
-
|
|
46
|
-
if (!manifest) return 'error';
|
|
47
|
-
|
|
48
|
-
if (!!manifest) return 'success';
|
|
49
|
-
};
|
|
50
|
-
|
|
51
36
|
const storeAndInstallPluginsIdList = (s: ToolStoreState) =>
|
|
52
37
|
uniq(
|
|
53
38
|
[
|
|
@@ -79,78 +64,10 @@ const isPluginHasUI = (id: string) => (s: ToolStoreState) => {
|
|
|
79
64
|
return !!plugin?.ui;
|
|
80
65
|
};
|
|
81
66
|
|
|
82
|
-
const enabledSchema =
|
|
83
|
-
(enabledPlugins: string[] = []) =>
|
|
84
|
-
(s: ToolStoreState): ChatCompletionFunctions[] => {
|
|
85
|
-
// 如果不存在 enabledPlugins,那么全部不启用
|
|
86
|
-
if (!enabledPlugins) return [];
|
|
87
|
-
|
|
88
|
-
const list = installedPluginManifestList(s)
|
|
89
|
-
.filter((m) =>
|
|
90
|
-
// 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
|
|
91
|
-
enabledPlugins.includes(m?.identifier),
|
|
92
|
-
)
|
|
93
|
-
.flatMap((manifest) =>
|
|
94
|
-
manifest.api.map((m) => {
|
|
95
|
-
const pluginType =
|
|
96
|
-
manifest.type && manifest.type !== 'default'
|
|
97
|
-
? `${PLUGIN_SCHEMA_SEPARATOR + manifest.type}`
|
|
98
|
-
: '';
|
|
99
|
-
|
|
100
|
-
// 将插件的 identifier 作为前缀,避免重复
|
|
101
|
-
let apiName = manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + m.name + pluginType;
|
|
102
|
-
|
|
103
|
-
// OpenAI GPT function_call name can't be longer than 64 characters
|
|
104
|
-
// So we need to use md5 to shorten the name
|
|
105
|
-
// and then find the correct apiName in response by md5
|
|
106
|
-
if (apiName.length >= 64) {
|
|
107
|
-
const md5Content = PLUGIN_SCHEMA_API_MD5_PREFIX + Md5.hashStr(m.name).toString();
|
|
108
|
-
|
|
109
|
-
apiName = manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + md5Content + pluginType;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
...m,
|
|
114
|
-
name: apiName,
|
|
115
|
-
};
|
|
116
|
-
}),
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
return uniqBy(list, 'name');
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const enabledPluginsSystemRoles =
|
|
123
|
-
(enabledPlugins: string[] = []) =>
|
|
124
|
-
(s: ToolStoreState) => {
|
|
125
|
-
const toolsSystemRole = enabledPlugins
|
|
126
|
-
.map((id) => {
|
|
127
|
-
const manifest = getPluginManifestById(id)(s);
|
|
128
|
-
if (!manifest) return '';
|
|
129
|
-
const meta = getPluginMetaById(id)(s);
|
|
130
|
-
|
|
131
|
-
const title = pluginHelpers.getPluginTitle(meta);
|
|
132
|
-
const desc = pluginHelpers.getPluginDesc(meta);
|
|
133
|
-
|
|
134
|
-
return [`### ${title}`, manifest.systemRole || desc].join('\n\n');
|
|
135
|
-
})
|
|
136
|
-
.filter(Boolean);
|
|
137
|
-
|
|
138
|
-
if (toolsSystemRole.length > 0) {
|
|
139
|
-
return ['## Tools', 'You can use these tools below:', ...toolsSystemRole]
|
|
140
|
-
.filter(Boolean)
|
|
141
|
-
.join('\n\n');
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return '';
|
|
145
|
-
};
|
|
146
|
-
|
|
147
67
|
export const pluginSelectors = {
|
|
148
|
-
enabledPluginsSystemRoles,
|
|
149
|
-
enabledSchema,
|
|
150
68
|
getCustomPluginById,
|
|
151
69
|
getInstalledPluginById,
|
|
152
70
|
getPluginManifestById,
|
|
153
|
-
getPluginManifestLoadingStatus,
|
|
154
71
|
getPluginMetaById,
|
|
155
72
|
getPluginSettingsById,
|
|
156
73
|
installedCustomPluginMetaList,
|
package/src/store/tool/store.ts
CHANGED
|
@@ -6,26 +6,32 @@ import { StateCreator } from 'zustand/vanilla';
|
|
|
6
6
|
import { isDev } from '@/utils/env';
|
|
7
7
|
|
|
8
8
|
import { ToolStoreState, initialState } from './initialState';
|
|
9
|
+
import { BuiltinToolAction, createBuiltinToolSlice } from './slices/builtin';
|
|
9
10
|
import { CustomPluginAction, createCustomPluginSlice } from './slices/customPlugin';
|
|
10
11
|
import { PluginAction, createPluginSlice } from './slices/plugin';
|
|
11
12
|
import { PluginStoreAction, createPluginStoreSlice } from './slices/store';
|
|
12
13
|
|
|
13
14
|
// =============== 聚合 createStoreFn ============ //
|
|
14
15
|
|
|
15
|
-
export type ToolStore = ToolStoreState &
|
|
16
|
+
export type ToolStore = ToolStoreState &
|
|
17
|
+
CustomPluginAction &
|
|
18
|
+
PluginAction &
|
|
19
|
+
PluginStoreAction &
|
|
20
|
+
BuiltinToolAction;
|
|
16
21
|
|
|
17
22
|
const createStore: StateCreator<ToolStore, [['zustand/devtools', never]]> = (...parameters) => ({
|
|
18
23
|
...initialState,
|
|
19
24
|
...createPluginSlice(...parameters),
|
|
20
25
|
...createCustomPluginSlice(...parameters),
|
|
21
26
|
...createPluginStoreSlice(...parameters),
|
|
27
|
+
...createBuiltinToolSlice(...parameters),
|
|
22
28
|
});
|
|
23
29
|
|
|
24
30
|
// =============== 实装 useStore ============ //
|
|
25
31
|
|
|
26
32
|
export const useToolStore = createWithEqualityFn<ToolStore>()(
|
|
27
33
|
devtools(createStore, {
|
|
28
|
-
name: '
|
|
34
|
+
name: 'LobeChat_Tool' + (isDev ? '_DEV' : ''),
|
|
29
35
|
}),
|
|
30
36
|
shallow,
|
|
31
37
|
);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { TextArea } from '@lobehub/ui';
|
|
2
|
+
import { Button, Radio, Select } from 'antd';
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { Flexbox } from 'react-layout-kit';
|
|
6
|
+
|
|
7
|
+
import { DallEImageItem } from '@/types/tool/dalle';
|
|
8
|
+
|
|
9
|
+
interface EditModeProps extends DallEImageItem {
|
|
10
|
+
setEdit: (edit: boolean) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const EditMode = memo<EditModeProps>(({ prompt, setEdit, style, size, quality }) => {
|
|
14
|
+
const { t } = useTranslation('tool');
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Flexbox gap={16}>
|
|
18
|
+
<TextArea style={{ minHeight: 120 }} type={'block'} value={prompt} />
|
|
19
|
+
<Flexbox horizontal justify={'space-between'}>
|
|
20
|
+
风格
|
|
21
|
+
<Radio.Group
|
|
22
|
+
defaultValue={style}
|
|
23
|
+
options={[
|
|
24
|
+
{ label: 'vivid', value: 'vivid' },
|
|
25
|
+
{ label: 'natural', value: 'natural' },
|
|
26
|
+
]}
|
|
27
|
+
/>
|
|
28
|
+
</Flexbox>
|
|
29
|
+
<Flexbox horizontal justify={'space-between'}>
|
|
30
|
+
质量
|
|
31
|
+
<Radio.Group
|
|
32
|
+
defaultValue={quality}
|
|
33
|
+
options={[
|
|
34
|
+
{ label: 'standard', value: 'standard' },
|
|
35
|
+
{ label: 'hd', value: 'hd' },
|
|
36
|
+
]}
|
|
37
|
+
/>
|
|
38
|
+
</Flexbox>
|
|
39
|
+
<Flexbox horizontal justify={'space-between'}>
|
|
40
|
+
尺寸
|
|
41
|
+
<Select
|
|
42
|
+
defaultValue={size}
|
|
43
|
+
options={[
|
|
44
|
+
{ label: '1792x1024', value: '1792x1024' },
|
|
45
|
+
{ label: '1024x1024', value: '1024x1024' },
|
|
46
|
+
{ label: '1024x1792', value: '1024x1792' },
|
|
47
|
+
]}
|
|
48
|
+
size={'small'}
|
|
49
|
+
/>
|
|
50
|
+
</Flexbox>
|
|
51
|
+
|
|
52
|
+
<Flexbox direction={'horizontal-reverse'} gap={12}>
|
|
53
|
+
<Button type={'primary'}>{t('dalle.generate')}</Button>
|
|
54
|
+
<Button
|
|
55
|
+
onClick={() => {
|
|
56
|
+
setEdit(false);
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
{t('cancel', { ns: 'common' })}
|
|
60
|
+
</Button>
|
|
61
|
+
</Flexbox>
|
|
62
|
+
</Flexbox>
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export default EditMode;
|