diff --git a/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts index 0c253118a..3348022e3 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts @@ -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 { 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: [] diff --git a/projects/app/src/pageComponents/app/detail/Edit/Agent/hooks/useAppManager.ts b/projects/app/src/pageComponents/app/detail/Edit/Agent/hooks/useAppManager.ts deleted file mode 100644 index 24cf2b9e5..000000000 --- a/projects/app/src/pageComponents/app/detail/Edit/Agent/hooks/useAppManager.ts +++ /dev/null @@ -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; - removeFolderContent: (folderId: string) => void; - loadedFolders: Set; -}; - -export const useAppManager = ({ - selectedSkillKey, - currentAppId -}: UseAppManagerProps): UseAppManagerReturn => { - const { t, i18n } = useTranslation(); - const lang = i18n?.language as localeType; - const [appSkillOptions, setAppSkillOptions] = useState([]); - const [queryString, setQueryString] = useState(null); - const [loadedFolders, setLoadedFolders] = useState>(new Set()); - const appSkillOptionsRef = useRef([]); - 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 - }; -}; diff --git a/projects/app/src/pageComponents/app/detail/Edit/Agent/hooks/useToolManager.ts b/projects/app/src/pageComponents/app/detail/Edit/Agent/hooks/useToolManager.ts deleted file mode 100644 index 20bbe6bb5..000000000 --- a/projects/app/src/pageComponents/app/detail/Edit/Agent/hooks/useToolManager.ts +++ /dev/null @@ -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>; - setConfigTool: (tool: ExtendedToolType | undefined) => void; - selectedSkillKey?: string; -}; - -type UseToolManagerReturn = { - toolSkillOptions: SkillOptionType[]; - queryString: string | null; - setQueryString: (value: string | null) => void; - - handleAddToolFromEditor: (toolKey: string) => Promise; - 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([]); - const [queryString, setQueryString] = useState(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 - >((acc, item) => { - acc[item.typeId] = { - label: t(parseI18nString(item.typeName, lang)), - type: item.typeId - }; - return acc; - }, {}); - - const pluginsByCategory = new Map(); - 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 => { - 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 - }; -}; diff --git a/projects/app/src/pageComponents/app/detail/Edit/SimpleApp/components/ToolSelectModal.tsx b/projects/app/src/pageComponents/app/detail/Edit/SimpleApp/components/ToolSelectModal.tsx deleted file mode 100644 index baaa63535..000000000 --- a/projects/app/src/pageComponents/app/detail/Edit/SimpleApp/components/ToolSelectModal.tsx +++ /dev/null @@ -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(''); - 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 ( - - {/* Header: row and search */} - - - loadTemplates({ - type: e as TemplateTypeEnum, - parentId: null - }) - } - /> - - setSearchKey(e.target.value)} - placeholder={ - templateType === TemplateTypeEnum.systemPlugin - ? t('common:search_tool') - : t('app:search_app') - } - /> - - - {/* route components */} - {!searchKey && parentId && ( - - - - )} - - - - - - - ); -}; - -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(); - 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 ( - - - - {t(item.label as any)} - - - - {item.list.map((template) => { - const selected = selectedTools.some((tool) => tool.pluginId === template.id); - - return ( - - - - - {t(template.name as any)} - - - - {t(template.intro as any) || t('common:core.workflow.Not intro')} - - {/* {type === TemplateTypeEnum.systemPlugin && ( - - )} */} - - } - > - - - - {t(template.name as any)} - - - {selected ? ( - - ) : template.flowNodeType === 'toolSet' ? ( - - - - - ) : template.isFolder ? ( - - ) : ( - - )} - - - ); - })} - - - ); - })} - - ); - }); - - return templates.length === 0 ? ( - - ) : ( - <> - - {formatTemplatesArray.length > 1 ? ( - <> - {formatTemplatesArray.map(({ list, label }, index) => ( - - - {t(label as any)} - - - - - - - ))} - - ) : ( - - )} - - - {!!configTool && ( - - )} - - ); -}); diff --git a/projects/app/src/pages/api/core/app/create.ts b/projects/app/src/pages/api/core/app/create.ts index dd91cd508..66aa5bd9b 100644 --- a/projects/app/src/pages/api/core/app/create.ts +++ b/projects/app/src/pages/api/core/app/create.ts @@ -71,7 +71,7 @@ async function handler(req: ApiRequestProps) { name, avatar, intro, - type: 'agent', + type, modules: await (async () => { if (modules) { const myModels = new Set(