mirror of
https://github.com/labring/FastGPT.git
synced 2025-12-26 04:32:50 +00:00
fix: ts
This commit is contained in:
parent
f5a636e706
commit
3b9f6a3ee4
|
|
@ -16,10 +16,10 @@ import {
|
|||
storeNodes2RuntimeNodes
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
||||
import { getChildAppRuntimeById } from '../../../../../../app/plugin/controller';
|
||||
import { getChildAppRuntimeById } from '../../../../../../app/tool/controller';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { getPluginRunUserQuery } from '@fastgpt/global/core/workflow/utils';
|
||||
import { getPluginInputsFromStoreNodes } from '@fastgpt/global/core/app/plugin/utils';
|
||||
import { serverGetWorkflowToolRunUserQuery } from '../../../../../../app/tool/workflowTool/utils';
|
||||
import { getWorkflowToolInputsFromStoreNodes } from '@fastgpt/global/core/app/tool/workflowTool/utils';
|
||||
|
||||
type Props = ModuleDispatchProps<{}> & {
|
||||
callParams: {
|
||||
|
|
@ -176,8 +176,8 @@ export const dispatchPlugin = async (props: Props): Promise<DispatchSubAppRespon
|
|||
isChildApp: true
|
||||
},
|
||||
variables: runtimeVariables,
|
||||
query: getPluginRunUserQuery({
|
||||
pluginInputs: getPluginInputsFromStoreNodes(plugin.nodes),
|
||||
query: serverGetWorkflowToolRunUserQuery({
|
||||
pluginInputs: getWorkflowToolInputsFromStoreNodes(plugin.nodes),
|
||||
variables: runtimeVariables
|
||||
}).value,
|
||||
chatConfig: {},
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import type { StoreSecretValueType } from '@fastgpt/global/common/secret/type';
|
||||
import { SystemToolInputTypeEnum } from '@fastgpt/global/core/app/systemTool/constants';
|
||||
import { SystemToolSecretInputTypeEnum } from '@fastgpt/global/core/app/tool/systemTool/constants';
|
||||
import type { DispatchSubAppResponse } from '../../type';
|
||||
import { splitCombinePluginId } from '@fastgpt/global/core/app/plugin/utils';
|
||||
import { getSystemToolById } from '../../../../../../app/plugin/controller';
|
||||
import { splitCombineToolId } from '@fastgpt/global/core/app/tool/utils';
|
||||
import { getSystemToolById } from '../../../../../../app/tool/controller';
|
||||
import { getSecretValue } from '../../../../../../../common/secret/utils';
|
||||
import { MongoSystemPlugin } from '../../../../../../app/plugin/systemPluginSchema';
|
||||
import { MongoSystemTool } from '../../../../../../plugin/tool/systemToolSchema';
|
||||
import { APIRunSystemTool } from '../../../../../../app/tool/api';
|
||||
import type {
|
||||
ChatDispatchProps,
|
||||
|
|
@ -18,10 +18,10 @@ import { pushTrack } from '../../../../../../../common/middle/tracks/utils';
|
|||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { getAppVersionById } from '../../../../../../app/version/controller';
|
||||
import { MCPClient } from '../../../../../../app/mcp';
|
||||
import type { McpToolDataType } from '@fastgpt/global/core/app/mcpTools/type';
|
||||
import type { McpToolDataType } from '@fastgpt/global/core/app/tool/mcpTool/type';
|
||||
|
||||
type SystemInputConfigType = {
|
||||
type: SystemToolInputTypeEnum;
|
||||
type: SystemToolSecretInputTypeEnum;
|
||||
value: StoreSecretValueType;
|
||||
};
|
||||
type Props = {
|
||||
|
|
@ -50,16 +50,16 @@ export const dispatchTool = async ({
|
|||
const tool = await getSystemToolById(toolConfig?.systemTool.toolId);
|
||||
const inputConfigParams = await (async () => {
|
||||
switch (system_input_config?.type) {
|
||||
case SystemToolInputTypeEnum.team:
|
||||
case SystemToolSecretInputTypeEnum.team:
|
||||
return Promise.reject(new Error('This is not supported yet'));
|
||||
case SystemToolInputTypeEnum.manual:
|
||||
case SystemToolSecretInputTypeEnum.manual:
|
||||
return getSecretValue({
|
||||
storeSecret: system_input_config.value || {}
|
||||
});
|
||||
case SystemToolInputTypeEnum.system:
|
||||
case SystemToolSecretInputTypeEnum.system:
|
||||
default:
|
||||
// read from mongo
|
||||
const dbPlugin = await MongoSystemPlugin.findOne({
|
||||
const dbPlugin = await MongoSystemTool.findOne({
|
||||
pluginId: tool.id
|
||||
}).lean();
|
||||
return dbPlugin?.inputListVal || {};
|
||||
|
|
@ -122,7 +122,7 @@ export const dispatchTool = async ({
|
|||
}
|
||||
|
||||
const usagePoints = (() => {
|
||||
if (params.system_input_config?.type !== SystemToolInputTypeEnum.system) {
|
||||
if (params.system_input_config?.type !== SystemToolSecretInputTypeEnum.system) {
|
||||
return 0;
|
||||
}
|
||||
return (tool.systemKeyCost ?? 0) + (tool.currentCost ?? 0);
|
||||
|
|
@ -147,7 +147,7 @@ export const dispatchTool = async ({
|
|||
]
|
||||
};
|
||||
} else if (toolConfig?.mcpTool?.toolId) {
|
||||
const { pluginId } = splitCombinePluginId(toolConfig.mcpTool.toolId);
|
||||
const { pluginId } = splitCombineToolId(toolConfig.mcpTool.toolId);
|
||||
const [parentId, toolName] = pluginId.split('/');
|
||||
const tool = await getAppVersionById({
|
||||
appId: parentId,
|
||||
|
|
@ -163,7 +163,10 @@ export const dispatchTool = async ({
|
|||
})
|
||||
});
|
||||
|
||||
const result = await mcpClient.toolCall(toolName, params);
|
||||
const result = await mcpClient.toolCall({
|
||||
toolName,
|
||||
params
|
||||
});
|
||||
return {
|
||||
response: JSON.stringify(result),
|
||||
usages: []
|
||||
|
|
|
|||
|
|
@ -1,184 +0,0 @@
|
|||
import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type { SkillOptionType } from '@fastgpt/web/components/common/Textarea/PromptEditor/plugins/SkillPickerPlugin';
|
||||
import type { NodeTemplateListItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import type { localeType } from '@fastgpt/global/common/i18n/type';
|
||||
import { parseI18nString } from '@fastgpt/global/common/i18n/utils';
|
||||
import { getTeamPlugTemplates } from '@/web/core/app/api/plugin';
|
||||
|
||||
type UseAppManagerProps = {
|
||||
selectedSkillKey?: string;
|
||||
currentAppId?: string;
|
||||
};
|
||||
|
||||
type UseAppManagerReturn = {
|
||||
appSkillOptions: SkillOptionType[];
|
||||
queryString: string | null;
|
||||
setQueryString: (value: string | null) => void;
|
||||
loadFolderContent: (folderId: string) => Promise<void>;
|
||||
removeFolderContent: (folderId: string) => void;
|
||||
loadedFolders: Set<string>;
|
||||
};
|
||||
|
||||
export const useAppManager = ({
|
||||
selectedSkillKey,
|
||||
currentAppId
|
||||
}: UseAppManagerProps): UseAppManagerReturn => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const lang = i18n?.language as localeType;
|
||||
const [appSkillOptions, setAppSkillOptions] = useState<SkillOptionType[]>([]);
|
||||
const [queryString, setQueryString] = useState<string | null>(null);
|
||||
const [loadedFolders, setLoadedFolders] = useState<Set<string>>(new Set());
|
||||
const appSkillOptionsRef = useRef<SkillOptionType[]>([]);
|
||||
const defaultAppSkillOption = useMemo(() => {
|
||||
return {
|
||||
key: 'app',
|
||||
label: t('common:App'),
|
||||
icon: 'core/workflow/template/runApp'
|
||||
};
|
||||
}, [t]);
|
||||
|
||||
const buildAppSkillOptions = useCallback(
|
||||
(teamApps: NodeTemplateListItemType[], parentKey: string = 'app') => {
|
||||
return teamApps
|
||||
.filter((app) => app.id !== currentAppId) // 过滤掉当前应用
|
||||
.map((app) => ({
|
||||
key: app.id,
|
||||
label: t(parseI18nString(app.name, lang)),
|
||||
icon: app.isFolder ? 'common/folderFill' : app.avatar || 'core/workflow/template/runApp',
|
||||
parentKey,
|
||||
canOpen: app.isFolder
|
||||
}));
|
||||
},
|
||||
[t, lang, currentAppId]
|
||||
);
|
||||
|
||||
const loadFolderContent = useCallback(
|
||||
async (folderId: string) => {
|
||||
if (loadedFolders.has(folderId)) return;
|
||||
|
||||
try {
|
||||
// 先添加 loading 占位符
|
||||
setAppSkillOptions((prev) => {
|
||||
const newOptions = [
|
||||
...prev,
|
||||
{
|
||||
key: 'loading',
|
||||
label: 'Loading...',
|
||||
icon: '',
|
||||
parentKey: folderId
|
||||
}
|
||||
];
|
||||
appSkillOptionsRef.current = newOptions;
|
||||
return newOptions;
|
||||
});
|
||||
|
||||
// 加载文件夹内容
|
||||
const children = await getTeamPlugTemplates({
|
||||
parentId: folderId,
|
||||
searchKey: ''
|
||||
});
|
||||
|
||||
// 构建子项选项
|
||||
const childOptions = buildAppSkillOptions(children, folderId);
|
||||
|
||||
// 替换 loading 占位符为实际内容
|
||||
setAppSkillOptions((prev) => {
|
||||
const filteredOptions = prev.filter(
|
||||
(opt) => !(opt.parentKey === folderId && opt.key === 'loading')
|
||||
);
|
||||
const newOptions = [...filteredOptions, ...childOptions];
|
||||
appSkillOptionsRef.current = newOptions;
|
||||
return newOptions;
|
||||
});
|
||||
|
||||
// 标记已加载
|
||||
setLoadedFolders((prev) => new Set([...prev, folderId]));
|
||||
} catch (error) {
|
||||
console.error('Failed to load folder content:', error);
|
||||
// 移除 loading 占位符
|
||||
setAppSkillOptions((prev) => {
|
||||
const newOptions = prev.filter(
|
||||
(opt) => !(opt.parentKey === folderId && opt.key === 'loading')
|
||||
);
|
||||
appSkillOptionsRef.current = newOptions;
|
||||
return newOptions;
|
||||
});
|
||||
}
|
||||
},
|
||||
[loadedFolders, buildAppSkillOptions]
|
||||
);
|
||||
|
||||
const removeFolderContent = useCallback((folderId: string) => {
|
||||
// 递归移除文件夹及其所有子项的内容
|
||||
const removeRecursively = (parentId: string) => {
|
||||
const children = appSkillOptionsRef.current.filter((opt) => opt.parentKey === parentId);
|
||||
children.forEach((child) => {
|
||||
if (child.canOpen) {
|
||||
removeRecursively(child.key);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 移除文件夹的所有子项
|
||||
removeRecursively(folderId);
|
||||
|
||||
// 从数据中移除所有子项
|
||||
setAppSkillOptions((prev) => {
|
||||
const newOptions = prev.filter((opt) => {
|
||||
// 检查是否是要移除的文件夹的后代
|
||||
const isDescendant = (optionKey: string): boolean => {
|
||||
const option = prev.find((o) => o.key === optionKey);
|
||||
if (!option?.parentKey) return false;
|
||||
if (option.parentKey === folderId) return true;
|
||||
return isDescendant(option.parentKey);
|
||||
};
|
||||
|
||||
return !isDescendant(opt.key);
|
||||
});
|
||||
appSkillOptionsRef.current = newOptions;
|
||||
return newOptions;
|
||||
});
|
||||
|
||||
// 从已加载集合中移除
|
||||
setLoadedFolders((prevLoaded) => {
|
||||
const newLoaded = new Set(prevLoaded);
|
||||
newLoaded.delete(folderId);
|
||||
return newLoaded;
|
||||
});
|
||||
}, []);
|
||||
|
||||
useRequest2(
|
||||
async () => {
|
||||
try {
|
||||
return await getTeamPlugTemplates({
|
||||
parentId: '',
|
||||
searchKey: queryString?.trim() || ''
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load team plugin templates:', error);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [queryString],
|
||||
onSuccess(data) {
|
||||
const options = buildAppSkillOptions(data);
|
||||
const newOptions = [defaultAppSkillOption, ...options];
|
||||
setAppSkillOptions(newOptions);
|
||||
appSkillOptionsRef.current = newOptions;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
appSkillOptions,
|
||||
queryString,
|
||||
setQueryString,
|
||||
loadFolderContent,
|
||||
removeFolderContent,
|
||||
loadedFolders
|
||||
};
|
||||
};
|
||||
|
|
@ -1,359 +0,0 @@
|
|||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import {
|
||||
getSystemPlugTemplates,
|
||||
getPluginGroups,
|
||||
getPreviewPluginNode
|
||||
} from '@/web/core/app/api/plugin';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import type { SkillOptionType } from '@fastgpt/web/components/common/Textarea/PromptEditor/plugins/SkillPickerPlugin';
|
||||
import type {
|
||||
FlowNodeTemplateType,
|
||||
NodeTemplateListItemType
|
||||
} from '@fastgpt/global/core/workflow/type/node';
|
||||
import type { localeType } from '@fastgpt/global/common/i18n/type';
|
||||
import { parseI18nString } from '@fastgpt/global/common/i18n/utils';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { workflowStartNodeId } from '@/web/core/app/constants';
|
||||
import type { AppFormEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import type { SystemToolGroupSchemaType } from '@fastgpt/service/core/app/plugin/type';
|
||||
|
||||
export type ExtendedToolType = FlowNodeTemplateType & {
|
||||
isUnconfigured?: boolean;
|
||||
};
|
||||
|
||||
type UseToolManagerProps = {
|
||||
appForm: AppFormEditFormType;
|
||||
setAppForm: React.Dispatch<React.SetStateAction<AppFormEditFormType>>;
|
||||
setConfigTool: (tool: ExtendedToolType | undefined) => void;
|
||||
selectedSkillKey?: string;
|
||||
};
|
||||
|
||||
type UseToolManagerReturn = {
|
||||
toolSkillOptions: SkillOptionType[];
|
||||
queryString: string | null;
|
||||
setQueryString: (value: string | null) => void;
|
||||
|
||||
handleAddToolFromEditor: (toolKey: string) => Promise<string>;
|
||||
handleConfigureTool: (toolId: string) => void;
|
||||
handleRemoveToolFromEditor: (toolId: string) => void;
|
||||
};
|
||||
|
||||
export const useToolManager = ({
|
||||
appForm,
|
||||
setAppForm,
|
||||
setConfigTool,
|
||||
selectedSkillKey
|
||||
}: UseToolManagerProps): UseToolManagerReturn => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const lang = i18n?.language as localeType;
|
||||
const [toolSkillOptions, setToolSkillOptions] = useState<SkillOptionType[]>([]);
|
||||
const [queryString, setQueryString] = useState<string | null>(null);
|
||||
|
||||
/* get tool skills */
|
||||
const { data: pluginGroups = [] } = useRequest2(
|
||||
async () => {
|
||||
try {
|
||||
return await getPluginGroups();
|
||||
} catch (error) {
|
||||
console.error('Failed to load plugin groups:', error);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
onSuccess(data) {
|
||||
const primaryOptions: SkillOptionType[] = data.map((item) => ({
|
||||
key: item.groupId,
|
||||
label: t(item.groupName),
|
||||
icon: 'core/workflow/template/toolCall'
|
||||
}));
|
||||
setToolSkillOptions(primaryOptions);
|
||||
}
|
||||
}
|
||||
);
|
||||
const requestParentId = useMemo(() => {
|
||||
if (queryString?.trim()) {
|
||||
return '';
|
||||
}
|
||||
const selectedOption = toolSkillOptions.find((option) => option.key === selectedSkillKey);
|
||||
if (!toolSkillOptions.some((option) => option.parentKey) && selectedOption) {
|
||||
return '';
|
||||
}
|
||||
if (selectedOption?.canOpen) {
|
||||
const hasLoadingPlaceholder = toolSkillOptions.some(
|
||||
(option) => option.parentKey === selectedSkillKey && option.key === 'loading'
|
||||
);
|
||||
if (hasLoadingPlaceholder) {
|
||||
return selectedSkillKey;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [toolSkillOptions, selectedSkillKey, queryString]);
|
||||
const buildToolSkillOptions = useCallback(
|
||||
(systemPlugins: NodeTemplateListItemType[], pluginGroups: SystemToolGroupSchemaType[]) => {
|
||||
const skillOptions: SkillOptionType[] = [];
|
||||
|
||||
pluginGroups.forEach((group) => {
|
||||
skillOptions.push({
|
||||
key: group.groupId,
|
||||
label: t(group.groupName as any),
|
||||
icon: 'core/workflow/template/toolCall'
|
||||
});
|
||||
});
|
||||
|
||||
pluginGroups.forEach((group) => {
|
||||
const categoryMap = group.groupTypes.reduce<
|
||||
Record<string, { label: string; type: string }>
|
||||
>((acc, item) => {
|
||||
acc[item.typeId] = {
|
||||
label: t(parseI18nString(item.typeName, lang)),
|
||||
type: item.typeId
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const pluginsByCategory = new Map<string, NodeTemplateListItemType[]>();
|
||||
systemPlugins.forEach((plugin) => {
|
||||
if (categoryMap[plugin.templateType]) {
|
||||
if (!pluginsByCategory.has(plugin.templateType)) {
|
||||
pluginsByCategory.set(plugin.templateType, []);
|
||||
}
|
||||
pluginsByCategory.get(plugin.templateType)!.push(plugin);
|
||||
}
|
||||
});
|
||||
|
||||
pluginsByCategory.forEach((plugins, categoryType) => {
|
||||
plugins.forEach((plugin) => {
|
||||
const canOpen = plugin.flowNodeType === 'toolSet' || plugin.isFolder;
|
||||
const category = categoryMap[categoryType];
|
||||
|
||||
skillOptions.push({
|
||||
key: plugin.id,
|
||||
label: t(parseI18nString(plugin.name, lang)),
|
||||
icon: plugin.avatar || 'core/workflow/template/toolCall',
|
||||
parentKey: group.groupId,
|
||||
canOpen,
|
||||
categoryType: category.type,
|
||||
categoryLabel: category.label
|
||||
});
|
||||
|
||||
if (canOpen) {
|
||||
skillOptions.push({
|
||||
key: 'loading',
|
||||
label: 'Loading...',
|
||||
icon: plugin.avatar || 'core/workflow/template/toolCall',
|
||||
parentKey: plugin.id
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return skillOptions;
|
||||
},
|
||||
[t, lang]
|
||||
);
|
||||
const buildSearchOptions = useCallback(
|
||||
(searchResults: NodeTemplateListItemType[]) => {
|
||||
return searchResults.map((plugin) => ({
|
||||
key: plugin.id,
|
||||
label: t(parseI18nString(plugin.name, lang)),
|
||||
icon: plugin.avatar || 'core/workflow/template/toolCall'
|
||||
}));
|
||||
},
|
||||
[t, lang]
|
||||
);
|
||||
const updateTertiaryOptions = useCallback(
|
||||
(
|
||||
currentOptions: SkillOptionType[],
|
||||
parentKey: string | undefined,
|
||||
subItems: NodeTemplateListItemType[]
|
||||
) => {
|
||||
const filteredOptions = currentOptions.filter((option) => !(option.parentKey === parentKey));
|
||||
|
||||
const newTertiaryOptions = subItems.map((plugin) => ({
|
||||
key: plugin.id,
|
||||
label: t(parseI18nString(plugin.name, lang)),
|
||||
icon: 'core/workflow/template/toolCall',
|
||||
parentKey
|
||||
}));
|
||||
|
||||
return [...filteredOptions, ...newTertiaryOptions];
|
||||
},
|
||||
[t, lang]
|
||||
);
|
||||
useRequest2(
|
||||
async () => {
|
||||
try {
|
||||
return await getSystemPlugTemplates({
|
||||
parentId: requestParentId || '',
|
||||
searchKey: queryString?.trim() || ''
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load system plugin templates:', error);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: requestParentId === null,
|
||||
refreshDeps: [requestParentId, queryString],
|
||||
onSuccess(data) {
|
||||
if (queryString?.trim()) {
|
||||
const searchOptions = buildSearchOptions(data);
|
||||
setToolSkillOptions(searchOptions);
|
||||
} else if (requestParentId === '') {
|
||||
const fullOptions = buildToolSkillOptions(data, pluginGroups);
|
||||
setToolSkillOptions(fullOptions);
|
||||
} else if (requestParentId === selectedSkillKey) {
|
||||
setToolSkillOptions((prevOptions) =>
|
||||
updateTertiaryOptions(prevOptions, requestParentId, data)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const validateToolConfiguration = useCallback(
|
||||
(toolTemplate: FlowNodeTemplateType): boolean => {
|
||||
// 检查文件上传配置
|
||||
const oneFileInput =
|
||||
toolTemplate.inputs.filter((input) =>
|
||||
input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)
|
||||
).length === 1;
|
||||
|
||||
const canUploadFile =
|
||||
appForm.chatConfig?.fileSelectConfig?.canSelectFile ||
|
||||
appForm.chatConfig?.fileSelectConfig?.canSelectImg;
|
||||
|
||||
const hasValidFileInput = oneFileInput && !!canUploadFile;
|
||||
|
||||
// 检查是否有无效的输入配置
|
||||
const hasInvalidInput = toolTemplate.inputs.some(
|
||||
(input) =>
|
||||
// 引用类型但没有工具描述
|
||||
(input.renderTypeList.length === 1 &&
|
||||
input.renderTypeList[0] === FlowNodeInputTypeEnum.reference &&
|
||||
!input.toolDescription) ||
|
||||
// 包含数据集选择
|
||||
input.renderTypeList.includes(FlowNodeInputTypeEnum.selectDataset) ||
|
||||
// 包含动态输入参数
|
||||
input.renderTypeList.includes(FlowNodeInputTypeEnum.addInputParam) ||
|
||||
// 文件选择但配置无效
|
||||
(input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect) && !hasValidFileInput)
|
||||
);
|
||||
|
||||
if (hasInvalidInput) {
|
||||
toast({
|
||||
title: t('app:simple_tool_tips'),
|
||||
status: 'warning'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[appForm.chatConfig, toast, t]
|
||||
);
|
||||
const checkNeedsUserConfiguration = useCallback((toolTemplate: FlowNodeTemplateType): boolean => {
|
||||
const formRenderTypes = [
|
||||
FlowNodeInputTypeEnum.input,
|
||||
FlowNodeInputTypeEnum.textarea,
|
||||
FlowNodeInputTypeEnum.numberInput,
|
||||
FlowNodeInputTypeEnum.switch,
|
||||
FlowNodeInputTypeEnum.select,
|
||||
FlowNodeInputTypeEnum.JSONEditor
|
||||
];
|
||||
|
||||
return (
|
||||
toolTemplate.inputs.length > 0 &&
|
||||
toolTemplate.inputs.some((input) => {
|
||||
// 有工具描述的不需要配置
|
||||
if (input.toolDescription) return false;
|
||||
// 禁用流的不需要配置
|
||||
if (input.key === NodeInputKeyEnum.forbidStream) return false;
|
||||
// 系统输入配置需要配置
|
||||
if (input.key === NodeInputKeyEnum.systemInputConfig) return true;
|
||||
|
||||
// 检查是否包含表单类型的输入
|
||||
return formRenderTypes.some((type) => input.renderTypeList.includes(type));
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
const handleAddToolFromEditor = useCallback(
|
||||
async (toolKey: string): Promise<string> => {
|
||||
try {
|
||||
const toolTemplate = await getPreviewPluginNode({ appId: toolKey });
|
||||
if (!validateToolConfiguration(toolTemplate)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const needsConfiguration = checkNeedsUserConfiguration(toolTemplate);
|
||||
const toolId = `tool_${getNanoid(6)}`;
|
||||
const toolInstance: ExtendedToolType = {
|
||||
...toolTemplate,
|
||||
id: toolId,
|
||||
inputs: toolTemplate.inputs.map((input) => {
|
||||
if (input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)) {
|
||||
return {
|
||||
...input,
|
||||
value: [[workflowStartNodeId, NodeOutputKeyEnum.userFiles]]
|
||||
};
|
||||
}
|
||||
return input;
|
||||
}),
|
||||
isUnconfigured: needsConfiguration
|
||||
};
|
||||
|
||||
setAppForm((state: any) => ({
|
||||
...state,
|
||||
selectedTools: [...state.selectedTools, toolInstance]
|
||||
}));
|
||||
|
||||
return toolId;
|
||||
} catch (error) {
|
||||
console.error('Failed to add tool from editor:', error);
|
||||
return '';
|
||||
}
|
||||
},
|
||||
[validateToolConfiguration, checkNeedsUserConfiguration, setAppForm]
|
||||
);
|
||||
|
||||
const handleRemoveToolFromEditor = useCallback(
|
||||
(toolId: string) => {
|
||||
setAppForm((state: any) => ({
|
||||
...state,
|
||||
selectedTools: state.selectedTools.filter((tool: ExtendedToolType) => tool.id !== toolId)
|
||||
}));
|
||||
},
|
||||
[setAppForm]
|
||||
);
|
||||
|
||||
const handleConfigureTool = useCallback(
|
||||
(toolId: string) => {
|
||||
const tool = appForm.selectedTools.find(
|
||||
(tool: ExtendedToolType) => tool.id === toolId
|
||||
) as ExtendedToolType;
|
||||
|
||||
if (tool?.isUnconfigured) {
|
||||
setConfigTool(tool);
|
||||
}
|
||||
},
|
||||
[appForm.selectedTools, setConfigTool]
|
||||
);
|
||||
|
||||
return {
|
||||
toolSkillOptions,
|
||||
queryString,
|
||||
setQueryString,
|
||||
|
||||
handleAddToolFromEditor,
|
||||
handleConfigureTool,
|
||||
handleRemoveToolFromEditor
|
||||
};
|
||||
};
|
||||
|
|
@ -1,592 +0,0 @@
|
|||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { parseI18nString } from '@fastgpt/global/common/i18n/utils';
|
||||
import type { localeType } from '@fastgpt/global/common/i18n/type';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Box,
|
||||
Button,
|
||||
css,
|
||||
Flex,
|
||||
Grid
|
||||
} from '@chakra-ui/react';
|
||||
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import {
|
||||
type FlowNodeTemplateType,
|
||||
type NodeTemplateListItemType,
|
||||
type NodeTemplateListType
|
||||
} from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import {
|
||||
getPluginGroups,
|
||||
getPreviewPluginNode,
|
||||
getSystemPlugTemplates,
|
||||
getSystemPluginPaths
|
||||
} from '@/web/core/app/api/plugin';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { getTeamPlugTemplates } from '@/web/core/app/api/plugin';
|
||||
import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { getAppFolderPath } from '@/web/core/app/api/app';
|
||||
import FolderPath from '@/components/common/folder/Path';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '../../../context';
|
||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import MyAvatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { type AppFormEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { workflowStartNodeId } from '@/web/core/app/constants';
|
||||
import ConfigToolModal from '../../component/ConfigToolModal';
|
||||
import CostTooltip from '@/components/core/app/plugin/CostTooltip';
|
||||
|
||||
type Props = {
|
||||
selectedTools: FlowNodeTemplateType[];
|
||||
chatConfig: AppFormEditFormType['chatConfig'];
|
||||
selectedModel: LLMModelItemType;
|
||||
onAddTool: (tool: FlowNodeTemplateType) => void;
|
||||
onRemoveTool: (tool: NodeTemplateListItemType) => void;
|
||||
};
|
||||
|
||||
export const childAppSystemKey: string[] = [
|
||||
NodeInputKeyEnum.forbidStream,
|
||||
NodeInputKeyEnum.history,
|
||||
NodeInputKeyEnum.historyMaxAmount,
|
||||
NodeInputKeyEnum.userChatInput
|
||||
];
|
||||
|
||||
enum TemplateTypeEnum {
|
||||
'systemPlugin' = 'systemPlugin',
|
||||
'teamPlugin' = 'teamPlugin'
|
||||
}
|
||||
|
||||
const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const [templateType, setTemplateType] = useState(TemplateTypeEnum.systemPlugin);
|
||||
const [parentId, setParentId] = useState<ParentIdType>('');
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
|
||||
const {
|
||||
data: templates = [],
|
||||
runAsync: loadTemplates,
|
||||
loading: isLoading
|
||||
} = useRequest2(
|
||||
async ({
|
||||
type = templateType,
|
||||
parentId = '',
|
||||
searchVal = searchKey
|
||||
}: {
|
||||
type?: TemplateTypeEnum;
|
||||
parentId?: ParentIdType;
|
||||
searchVal?: string;
|
||||
}) => {
|
||||
if (type === TemplateTypeEnum.systemPlugin) {
|
||||
return getSystemPlugTemplates({ parentId, searchKey: searchVal });
|
||||
} else if (type === TemplateTypeEnum.teamPlugin) {
|
||||
return getTeamPlugTemplates({
|
||||
parentId,
|
||||
searchKey: searchVal
|
||||
}).then((res) => res.filter((app) => app.id !== appDetail._id));
|
||||
}
|
||||
},
|
||||
{
|
||||
onSuccess(_, [{ type = templateType, parentId = '' }]) {
|
||||
setTemplateType(type);
|
||||
setParentId(parentId);
|
||||
},
|
||||
refreshDeps: [templateType, searchKey, parentId],
|
||||
errorToast: t('common:core.module.templates.Load plugin error')
|
||||
}
|
||||
);
|
||||
|
||||
const { data: paths = [] } = useRequest2(
|
||||
() => {
|
||||
if (templateType === TemplateTypeEnum.teamPlugin)
|
||||
return getAppFolderPath({ sourceId: parentId, type: 'current' });
|
||||
return getSystemPluginPaths({ sourceId: parentId, type: 'current' });
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [parentId]
|
||||
}
|
||||
);
|
||||
|
||||
const onUpdateParentId = useCallback(
|
||||
(parentId: ParentIdType) => {
|
||||
loadTemplates({
|
||||
parentId
|
||||
});
|
||||
},
|
||||
[loadTemplates]
|
||||
);
|
||||
|
||||
useRequest2(() => loadTemplates({ searchVal: searchKey }), {
|
||||
manual: false,
|
||||
throttleWait: 300,
|
||||
refreshDeps: [searchKey]
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
title={t('common:core.app.Tool call')}
|
||||
iconSrc="core/app/toolCall"
|
||||
onClose={onClose}
|
||||
maxW={['90vw', '700px']}
|
||||
w={'700px'}
|
||||
h={['90vh', '80vh']}
|
||||
>
|
||||
{/* Header: row and search */}
|
||||
<Box px={[3, 6]} pt={4} display={'flex'} justifyContent={'space-between'} w={'full'}>
|
||||
<FillRowTabs
|
||||
list={[
|
||||
{
|
||||
icon: 'phoneTabbar/tool',
|
||||
label: t('common:navbar.Toolkit'),
|
||||
value: TemplateTypeEnum.systemPlugin
|
||||
},
|
||||
{
|
||||
icon: 'core/modules/teamPlugin',
|
||||
label: t('common:core.module.template.Team app'),
|
||||
value: TemplateTypeEnum.teamPlugin
|
||||
}
|
||||
]}
|
||||
py={'5px'}
|
||||
px={'15px'}
|
||||
value={templateType}
|
||||
onChange={(e) =>
|
||||
loadTemplates({
|
||||
type: e as TemplateTypeEnum,
|
||||
parentId: null
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Box w={300}>
|
||||
<SearchInput
|
||||
value={searchKey}
|
||||
onChange={(e) => setSearchKey(e.target.value)}
|
||||
placeholder={
|
||||
templateType === TemplateTypeEnum.systemPlugin
|
||||
? t('common:search_tool')
|
||||
: t('app:search_app')
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* route components */}
|
||||
{!searchKey && parentId && (
|
||||
<Flex mt={2} px={[3, 6]}>
|
||||
<FolderPath paths={paths} FirstPathDom={null} onClick={onUpdateParentId} />
|
||||
</Flex>
|
||||
)}
|
||||
<MyBox isLoading={isLoading} mt={2} pb={3} flex={'1 0 0'} h={0}>
|
||||
<Box px={[3, 6]} overflow={'overlay'} height={'100%'}>
|
||||
<RenderList
|
||||
templates={templates}
|
||||
type={templateType}
|
||||
setParentId={onUpdateParentId}
|
||||
{...props}
|
||||
/>
|
||||
</Box>
|
||||
</MyBox>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ToolSelectModal);
|
||||
|
||||
const RenderList = React.memo(function RenderList({
|
||||
templates,
|
||||
type,
|
||||
onAddTool,
|
||||
onRemoveTool,
|
||||
setParentId,
|
||||
selectedTools,
|
||||
chatConfig,
|
||||
selectedModel
|
||||
}: Props & {
|
||||
templates: NodeTemplateListItemType[];
|
||||
type: TemplateTypeEnum;
|
||||
setParentId: (parentId: ParentIdType) => any;
|
||||
}) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const lang = i18n.language as localeType;
|
||||
const [configTool, setConfigTool] = useState<FlowNodeTemplateType>();
|
||||
const onCloseConfigTool = useCallback(() => setConfigTool(undefined), []);
|
||||
const { toast } = useToast();
|
||||
|
||||
const { runAsync: onClickAdd, loading: isLoading } = useRequest2(
|
||||
async (template: NodeTemplateListItemType) => {
|
||||
const res = await getPreviewPluginNode({ appId: template.id });
|
||||
|
||||
/* Invalid plugin check
|
||||
1. Reference type. but not tool description;
|
||||
2. Has dataset select
|
||||
3. Has dynamic external data
|
||||
*/
|
||||
const oneFileInput =
|
||||
res.inputs.filter((input) =>
|
||||
input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)
|
||||
).length === 1;
|
||||
const canUploadFile =
|
||||
chatConfig?.fileSelectConfig?.canSelectFile || chatConfig?.fileSelectConfig?.canSelectImg;
|
||||
const invalidFileInput = oneFileInput && !!canUploadFile;
|
||||
if (
|
||||
res.inputs.some(
|
||||
(input) =>
|
||||
(input.renderTypeList.length === 1 &&
|
||||
input.renderTypeList[0] === FlowNodeInputTypeEnum.reference &&
|
||||
!input.toolDescription) ||
|
||||
input.renderTypeList.includes(FlowNodeInputTypeEnum.selectDataset) ||
|
||||
input.renderTypeList.includes(FlowNodeInputTypeEnum.addInputParam) ||
|
||||
(input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect) && !invalidFileInput)
|
||||
)
|
||||
) {
|
||||
return toast({
|
||||
title: t('app:simple_tool_tips'),
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
|
||||
// 判断是否可以直接添加工具,满足以下任一条件:
|
||||
// 1. 有工具描述
|
||||
// 2. 是模型选择类型
|
||||
// 3. 是文件上传类型且:已开启文件上传、非必填、只有一个文件上传输入
|
||||
const hasInputForm =
|
||||
res.inputs.length > 0 &&
|
||||
res.inputs.some((input) => {
|
||||
if (input.toolDescription) {
|
||||
return false;
|
||||
}
|
||||
if (input.key === NodeInputKeyEnum.forbidStream) {
|
||||
return false;
|
||||
}
|
||||
if (input.key === NodeInputKeyEnum.systemInputConfig) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if input has any of the form render types
|
||||
const formRenderTypes = [
|
||||
FlowNodeInputTypeEnum.input,
|
||||
FlowNodeInputTypeEnum.textarea,
|
||||
FlowNodeInputTypeEnum.numberInput,
|
||||
FlowNodeInputTypeEnum.switch,
|
||||
FlowNodeInputTypeEnum.select,
|
||||
FlowNodeInputTypeEnum.JSONEditor
|
||||
];
|
||||
|
||||
return formRenderTypes.some((type) => input.renderTypeList.includes(type));
|
||||
});
|
||||
|
||||
// 构建默认表单数据
|
||||
const defaultForm = {
|
||||
...res,
|
||||
inputs: res.inputs.map((input) => {
|
||||
// 如果是文件上传类型,设置为从工作流开始节点获取用户文件
|
||||
if (input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)) {
|
||||
return {
|
||||
...input,
|
||||
value: [[workflowStartNodeId, NodeOutputKeyEnum.userFiles]]
|
||||
};
|
||||
}
|
||||
return input;
|
||||
})
|
||||
};
|
||||
|
||||
if (hasInputForm) {
|
||||
setConfigTool(defaultForm);
|
||||
} else {
|
||||
onAddTool(defaultForm);
|
||||
}
|
||||
},
|
||||
{
|
||||
errorToast: t('common:core.module.templates.Load plugin error')
|
||||
}
|
||||
);
|
||||
|
||||
const { data: pluginGroups = [] } = useRequest2(getPluginGroups, {
|
||||
manual: false
|
||||
});
|
||||
|
||||
const formatTemplatesArray = useMemo(() => {
|
||||
const data = (() => {
|
||||
if (type === TemplateTypeEnum.systemPlugin) {
|
||||
return pluginGroups.map((group) => {
|
||||
const map = group.groupTypes.reduce<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
list: NodeTemplateListItemType[];
|
||||
label: string;
|
||||
}
|
||||
>
|
||||
>((acc, item) => {
|
||||
acc[item.typeId] = {
|
||||
list: [],
|
||||
label: t(parseI18nString(item.typeName, i18n.language))
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
templates.forEach((item) => {
|
||||
if (map[item.templateType]) {
|
||||
map[item.templateType].list.push({
|
||||
...item,
|
||||
name: t(parseI18nString(item.name, i18n.language)),
|
||||
intro: t(parseI18nString(item.intro, i18n.language))
|
||||
});
|
||||
}
|
||||
});
|
||||
return {
|
||||
label: group.groupName,
|
||||
list: Object.entries(map)
|
||||
.map(([type, { list, label }]) => ({
|
||||
type,
|
||||
label,
|
||||
list
|
||||
}))
|
||||
.filter((item) => item.list.length > 0)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Team apps
|
||||
return [
|
||||
{
|
||||
list: [
|
||||
{
|
||||
list: templates,
|
||||
type: '',
|
||||
label: ''
|
||||
}
|
||||
],
|
||||
label: ''
|
||||
}
|
||||
];
|
||||
})();
|
||||
|
||||
return data.filter(({ list }) => list.length > 0);
|
||||
}, [i18n.language, pluginGroups, t, templates, type]);
|
||||
|
||||
const gridStyle = useMemo(() => {
|
||||
if (type === TemplateTypeEnum.teamPlugin) {
|
||||
return {
|
||||
gridTemplateColumns: ['1fr', '1fr'],
|
||||
py: 2,
|
||||
avatarSize: '2rem'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
gridTemplateColumns: ['1fr', '1fr 1fr'],
|
||||
py: 3,
|
||||
avatarSize: '1.75rem'
|
||||
};
|
||||
}, [type]);
|
||||
|
||||
const PluginListRender = useMemoizedFn(({ list = [] }: { list: NodeTemplateListType }) => {
|
||||
return (
|
||||
<>
|
||||
{list.map((item, i) => {
|
||||
return (
|
||||
<Box
|
||||
key={item.type}
|
||||
css={css({
|
||||
span: {
|
||||
display: 'block'
|
||||
}
|
||||
})}
|
||||
>
|
||||
<Flex>
|
||||
<Box fontSize={'sm'} my={2} fontWeight={'500'} flex={1} color={'myGray.900'}>
|
||||
{t(item.label as any)}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Grid gridTemplateColumns={gridStyle.gridTemplateColumns} rowGap={2} columnGap={3}>
|
||||
{item.list.map((template) => {
|
||||
const selected = selectedTools.some((tool) => tool.pluginId === template.id);
|
||||
|
||||
return (
|
||||
<MyTooltip
|
||||
key={template.id}
|
||||
placement={'right'}
|
||||
label={
|
||||
<Box py={2}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyAvatar
|
||||
src={template.avatar}
|
||||
w={'1.75rem'}
|
||||
objectFit={'contain'}
|
||||
borderRadius={'sm'}
|
||||
/>
|
||||
<Box fontWeight={'bold'} ml={3} color={'myGray.900'}>
|
||||
{t(template.name as any)}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box mt={2} color={'myGray.500'} maxH={'100px'} overflow={'hidden'}>
|
||||
{t(template.intro as any) || t('common:core.workflow.Not intro')}
|
||||
</Box>
|
||||
{/* {type === TemplateTypeEnum.systemPlugin && (
|
||||
<CostTooltip
|
||||
cost={template.currentCost}
|
||||
hasTokenFee={template.hasTokenFee}
|
||||
/>
|
||||
)} */}
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
py={gridStyle.py}
|
||||
px={3}
|
||||
_hover={{ bg: 'myWhite.600' }}
|
||||
borderRadius={'sm'}
|
||||
whiteSpace={'nowrap'}
|
||||
overflow={'hidden'}
|
||||
textOverflow={'ellipsis'}
|
||||
>
|
||||
<MyAvatar
|
||||
src={template.avatar}
|
||||
w={gridStyle.avatarSize}
|
||||
objectFit={'contain'}
|
||||
borderRadius={'sm'}
|
||||
flexShrink={0}
|
||||
/>
|
||||
<Box
|
||||
color={'myGray.900'}
|
||||
fontWeight={'500'}
|
||||
fontSize={'sm'}
|
||||
flex={'1 0 0'}
|
||||
ml={3}
|
||||
className="textEllipsis"
|
||||
>
|
||||
{t(template.name as any)}
|
||||
</Box>
|
||||
|
||||
{selected ? (
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'grayDanger'}
|
||||
leftIcon={<MyIcon name={'delete'} w={'16px'} mr={-1} />}
|
||||
onClick={() => onRemoveTool(template)}
|
||||
px={2}
|
||||
fontSize={'mini'}
|
||||
>
|
||||
{t('common:Remove')}
|
||||
</Button>
|
||||
) : template.flowNodeType === 'toolSet' ? (
|
||||
<Flex gap={2}>
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'whiteBase'}
|
||||
isLoading={isLoading}
|
||||
leftIcon={<MyIcon name={'common/arrowRight'} w={'16px'} mr={-1.5} />}
|
||||
onClick={() => setParentId(template.id)}
|
||||
px={2}
|
||||
fontSize={'mini'}
|
||||
>
|
||||
{t('common:Open')}
|
||||
</Button>
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'primaryOutline'}
|
||||
leftIcon={<MyIcon name={'common/addLight'} w={'16px'} mr={-1.5} />}
|
||||
isLoading={isLoading}
|
||||
onClick={() => onClickAdd(template)}
|
||||
px={2}
|
||||
fontSize={'mini'}
|
||||
>
|
||||
{t('common:Add')}
|
||||
</Button>
|
||||
</Flex>
|
||||
) : template.isFolder ? (
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'whiteBase'}
|
||||
leftIcon={<MyIcon name={'common/arrowRight'} w={'16px'} mr={-1.5} />}
|
||||
onClick={() => setParentId(template.id)}
|
||||
px={2}
|
||||
fontSize={'mini'}
|
||||
>
|
||||
{t('common:Open')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'primaryOutline'}
|
||||
leftIcon={<MyIcon name={'common/addLight'} w={'16px'} mr={-1.5} />}
|
||||
isLoading={isLoading}
|
||||
onClick={() => onClickAdd(template)}
|
||||
px={2}
|
||||
fontSize={'mini'}
|
||||
>
|
||||
{t('common:Add')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
return templates.length === 0 ? (
|
||||
<EmptyTip text={t('app:module.No Modules')} />
|
||||
) : (
|
||||
<>
|
||||
<Accordion defaultIndex={[0]} allowMultiple reduceMotion>
|
||||
{formatTemplatesArray.length > 1 ? (
|
||||
<>
|
||||
{formatTemplatesArray.map(({ list, label }, index) => (
|
||||
<AccordionItem key={index} border={'none'}>
|
||||
<AccordionButton
|
||||
fontSize={'sm'}
|
||||
fontWeight={'500'}
|
||||
color={'myGray.900'}
|
||||
justifyContent={'space-between'}
|
||||
alignItems={'center'}
|
||||
borderRadius={'md'}
|
||||
px={3}
|
||||
>
|
||||
{t(label as any)}
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel py={0}>
|
||||
<PluginListRender list={list} />
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<PluginListRender list={formatTemplatesArray?.[0]?.list} />
|
||||
)}
|
||||
</Accordion>
|
||||
|
||||
{!!configTool && (
|
||||
<ConfigToolModal
|
||||
configTool={configTool}
|
||||
onCloseConfigTool={onCloseConfigTool}
|
||||
onAddTool={onAddTool}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -71,7 +71,7 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
|
|||
name,
|
||||
avatar,
|
||||
intro,
|
||||
type: 'agent',
|
||||
type,
|
||||
modules: await (async () => {
|
||||
if (modules) {
|
||||
const myModels = new Set(
|
||||
|
|
|
|||
Loading…
Reference in New Issue