diff --git a/packages/global/core/app/plugin/type.d.ts b/packages/global/core/app/plugin/type.d.ts index 5848c9ffcf45..807e15a4d22e 100644 --- a/packages/global/core/app/plugin/type.d.ts +++ b/packages/global/core/app/plugin/type.d.ts @@ -19,6 +19,7 @@ export type PluginRuntimeType = { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[]; currentCost?: number; + systemKeyCost?: number; hasTokenFee?: boolean; }; @@ -44,6 +45,7 @@ export type SystemPluginTemplateItemType = WorkflowTemplateType & { // commercial plugin config originCost?: number; // n points/one time currentCost?: number; + systemKeyCost?: number; hasTokenFee?: boolean; pluginOrder?: number; @@ -52,6 +54,7 @@ export type SystemPluginTemplateItemType = WorkflowTemplateType & { // Admin config inputList?: FlowNodeInputItemType['inputList']; + inputListVal?: Record; hasSystemSecret?: boolean; }; diff --git a/packages/global/core/workflow/type/node.d.ts b/packages/global/core/workflow/type/node.d.ts index 1da7ad002bf7..ee4d1ba1ccfd 100644 --- a/packages/global/core/workflow/type/node.d.ts +++ b/packages/global/core/workflow/type/node.d.ts @@ -77,6 +77,7 @@ export type FlowNodeCommonType = { // Not store, just computed currentCost?: number; + systemKeyCost?: number; hasTokenFee?: boolean; hasSystemSecret?: boolean; }; @@ -135,6 +136,7 @@ export type NodeTemplateListItemType = { author?: string; unique?: boolean; // 唯一的 currentCost?: number; // 当前积分消耗 + systemKeyCost?: number; // 系统密钥费用,统一为数字 hasTokenFee?: boolean; // 是否配置积分 instructions?: string; // 使用说明 courseUrl?: string; // 教程链接 diff --git a/packages/service/core/app/plugin/controller.ts b/packages/service/core/app/plugin/controller.ts index 5b1129f9cae7..5357e943845f 100644 --- a/packages/service/core/app/plugin/controller.ts +++ b/packages/service/core/app/plugin/controller.ts @@ -373,8 +373,10 @@ export async function getChildAppPreviewNode({ showTargetHandle: true, currentCost: app.currentCost, + systemKeyCost: app.systemKeyCost, hasTokenFee: app.hasTokenFee, hasSystemSecret: app.hasSystemSecret, + isFolder: app.isFolder, ...nodeIOConfig, outputs: nodeIOConfig.outputs.some((item) => item.type === FlowNodeOutputTypeEnum.error) @@ -427,6 +429,7 @@ export async function getChildAppRuntimeById({ originCost: 0, currentCost: 0, + systemKeyCost: 0, hasTokenFee: false, pluginOrder: 0 }; @@ -443,6 +446,7 @@ export async function getChildAppRuntimeById({ avatar: app.avatar || '', showStatus: true, currentCost: app.currentCost, + systemKeyCost: app.systemKeyCost, nodes: app.workflow.nodes, edges: app.workflow.edges, hasTokenFee: app.hasTokenFee @@ -469,6 +473,7 @@ const dbPluginFormat = (item: SystemPluginConfigSchemaType): SystemPluginTemplat currentCost: item.currentCost, hasTokenFee: item.hasTokenFee, pluginOrder: item.pluginOrder, + systemKeyCost: item.systemKeyCost, associatedPluginId, userGuide, workflow: { @@ -532,34 +537,32 @@ export const getSystemTools = async (): Promise const formatTools = tools.map((item) => { const dbPluginConfig = systemPlugins.get(item.id); + const isFolder = tools.some((tool) => tool.parentId === item.id); const versionList = (item.versionList as SystemPluginTemplateItemType['versionList']) || []; return { id: item.id, parentId: item.parentId, - isFolder: tools.some((tool) => tool.parentId === item.id), - + isFolder, name: item.name, avatar: item.avatar, intro: item.description, - author: item.author, courseUrl: item.courseUrl, weight: item.weight, - workflow: { nodes: [], edges: [] }, versionList, - templateType: item.templateType, showStatus: true, - isActive: item.isActive, inputList: item?.secretInputConfig, - hasSystemSecret: !!dbPluginConfig?.inputListVal + hasSystemSecret: !!dbPluginConfig?.inputListVal, + currentCost: dbPluginConfig?.currentCost ?? 0, + systemKeyCost: dbPluginConfig?.systemKeyCost ?? 0 }; }); diff --git a/packages/service/core/app/plugin/systemPluginSchema.ts b/packages/service/core/app/plugin/systemPluginSchema.ts index 76f4d91e382c..c83bdc521a55 100644 --- a/packages/service/core/app/plugin/systemPluginSchema.ts +++ b/packages/service/core/app/plugin/systemPluginSchema.ts @@ -27,6 +27,10 @@ const SystemPluginSchema = new Schema({ pluginOrder: { type: Number }, + systemKeyCost: { + type: Number, + default: 0 + }, customConfig: Object, inputListVal: Object, diff --git a/packages/service/core/app/plugin/type.d.ts b/packages/service/core/app/plugin/type.d.ts index 8628b3d2ec00..31848103a672 100644 --- a/packages/service/core/app/plugin/type.d.ts +++ b/packages/service/core/app/plugin/type.d.ts @@ -1,6 +1,7 @@ import { SystemPluginListItemType } from '@fastgpt/global/core/app/type'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import type { WorkflowTemplateBasicType } from '@fastgpt/global/core/workflow/type'; +import type { InputConfigType } from '@fastgpt/global/core/workflow/type/io'; export type SystemPluginConfigSchemaType = { pluginId: string; @@ -10,6 +11,7 @@ export type SystemPluginConfigSchemaType = { hasTokenFee: boolean; isActive: boolean; pluginOrder?: number; + systemKeyCost?: number; customConfig?: { name: string; diff --git a/packages/service/core/app/utils.ts b/packages/service/core/app/utils.ts index 37acfcbff0b3..391f04f4040e 100644 --- a/packages/service/core/app/utils.ts +++ b/packages/service/core/app/utils.ts @@ -82,8 +82,10 @@ export async function rewriteAppWorkflowToDetail({ node.version = preview.version; node.currentCost = preview.currentCost; + node.systemKeyCost = preview.systemKeyCost; node.hasTokenFee = preview.hasTokenFee; node.hasSystemSecret = preview.hasSystemSecret; + node.isFolder = preview.isFolder; node.toolConfig = preview.toolConfig; diff --git a/packages/service/core/workflow/dispatch/child/runTool.ts b/packages/service/core/workflow/dispatch/child/runTool.ts index 17e3875c9dc8..33d2abdb6cbc 100644 --- a/packages/service/core/workflow/dispatch/child/runTool.ts +++ b/packages/service/core/workflow/dispatch/child/runTool.ts @@ -150,7 +150,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise { +const CostTooltip = ({ + cost, + hasTokenFee, + isFolder +}: { + cost?: number; + hasTokenFee?: boolean; + isFolder?: boolean; +}) => { const { t } = useTranslation(); const getCostText = () => { + if (isFolder) { + return t('app:plugin_cost_folder_tip'); + } + if (hasTokenFee && cost && cost > 0) { return `${t('app:plugin_cost_per_times', { cost: cost @@ -25,8 +37,8 @@ const CostTooltip = ({ cost, hasTokenFee }: { cost?: number; hasTokenFee?: boole return ( <> - - {t('common:core.plugin.cost')} + + {t('common:core.plugin.cost')} {getCostText()} diff --git a/projects/app/src/pageComponents/app/detail/SimpleApp/components/ConfigToolModal.tsx b/projects/app/src/pageComponents/app/detail/SimpleApp/components/ConfigToolModal.tsx index f2cafa545120..8cc73d1ba1e2 100644 --- a/projects/app/src/pageComponents/app/detail/SimpleApp/components/ConfigToolModal.tsx +++ b/projects/app/src/pageComponents/app/detail/SimpleApp/components/ConfigToolModal.tsx @@ -120,13 +120,15 @@ const ConfigToolModal = ({ {isOpenSecretModal && ( { onChange(data); diff --git a/projects/app/src/pageComponents/app/detail/SimpleApp/components/ToolSelectModal.tsx b/projects/app/src/pageComponents/app/detail/SimpleApp/components/ToolSelectModal.tsx index ccda4ea8695f..39200b259a5e 100644 --- a/projects/app/src/pageComponents/app/detail/SimpleApp/components/ToolSelectModal.tsx +++ b/projects/app/src/pageComponents/app/detail/SimpleApp/components/ToolSelectModal.tsx @@ -47,6 +47,7 @@ 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 './ConfigToolModal'; +import CostTooltip from '@/components/core/app/plugin/CostTooltip'; type Props = { selectedTools: FlowNodeTemplateType[]; diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/NodeTemplates/list.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/NodeTemplates/list.tsx index 425563248a85..060de7f72c6b 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/NodeTemplates/list.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/NodeTemplates/list.tsx @@ -92,9 +92,11 @@ const NodeTemplateListItem = ({ {t(template.intro as any) || t('common:core.workflow.Not intro')} - {/* {templateType === TemplateTypeEnum.systemPlugin && ( - - )} */} + } shouldWrapChildren={false} diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/ToolParamConfig.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/ToolParamConfig.tsx index 0fbaef78e650..85199e848df4 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/ToolParamConfig.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/ToolParamConfig.tsx @@ -61,10 +61,12 @@ const ToolConfig = ({ nodeId, inputs }: { nodeId?: string; inputs?: FlowNodeInpu {isOpen && ( diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx index 5a92fd124024..eef2fee217a6 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx @@ -113,6 +113,7 @@ const NodeCard = (props: Props) => { return { node, parentNode }; }, [nodeList, nodeId]); + const isAppNode = node && AppNodeFlowNodeTypeMap[node?.flowNodeType]; const showVersion = useMemo(() => { // 1. MCP tool set do not have version @@ -409,6 +410,7 @@ const NodeCard = (props: Props) => { {inputConfig && isOpenToolParamConfigModal && ( { onChangeNode({ @@ -425,7 +427,8 @@ const NodeCard = (props: Props) => { courseUrl={node?.courseUrl} inputConfig={inputConfig} hasSystemSecret={node?.hasSystemSecret} - secretCost={node?.currentCost} + parentId={node?.pluginId} + secretCost={node?.systemKeyCost} /> )} diff --git a/projects/app/src/pageComponents/app/plugin/SecretInputModal.tsx b/projects/app/src/pageComponents/app/plugin/SecretInputModal.tsx index 922cb92d0e4e..7020ac9511f5 100644 --- a/projects/app/src/pageComponents/app/plugin/SecretInputModal.tsx +++ b/projects/app/src/pageComponents/app/plugin/SecretInputModal.tsx @@ -1,9 +1,18 @@ -import { Box, Button, Flex, HStack, Input, ModalBody, ModalFooter } from '@chakra-ui/react'; +import { + Box, + Button, + Flex, + HStack, + Input, + ModalBody, + ModalFooter, + useDisclosure +} from '@chakra-ui/react'; import { SystemToolInputTypeEnum } from '@fastgpt/global/core/app/systemTool/constants'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import LeftRadio from '@fastgpt/web/components/common/Radio/LeftRadio'; import { useTranslation } from 'next-i18next'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import MyIcon from '@fastgpt/web/components/common/Icon'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import type { FlowNodeInputItemType, InputConfigType } from '@fastgpt/global/core/workflow/type/io'; @@ -13,6 +22,9 @@ import IconButton from '@/pageComponents/account/team/OrgManage/IconButton'; import MyModal from '@fastgpt/web/components/common/MyModal'; import InputRender from '@/components/core/app/formRender'; import { secretInputTypeToInputType } from '@/components/core/app/formRender/utils'; +import { getSystemPlugTemplates } from '@/web/core/app/api/plugin'; +import type { NodeTemplateListItemType } from '@fastgpt/global/core/workflow/type/node'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; export type ToolParamsFormType = { type: SystemToolInputTypeEnum; @@ -22,20 +34,27 @@ export type ToolParamsFormType = { const SecretInputModal = ({ hasSystemSecret, secretCost = 0, + isFolder, inputConfig, courseUrl, onClose, - onSubmit + onSubmit, + parentId }: { + isFolder?: boolean; inputConfig: FlowNodeInputItemType; hasSystemSecret?: boolean; secretCost?: number; courseUrl?: string; onClose: () => void; onSubmit: (data: ToolParamsFormType) => void; + parentId?: string; }) => { const { t } = useTranslation(); const [editIndex, setEditIndex] = useState(); + const { isOpen: isSystemCostOpen, onToggle: onToggleSystemCost } = useDisclosure({ + defaultIsOpen: false + }); const inputList = inputConfig?.inputList || []; const { register, watch, setValue, getValues, handleSubmit, control } = @@ -59,6 +78,27 @@ const SecretInputModal = ({ }); const configType = watch('type'); + const { data: childTools = [], loading: isLoadingChildTools } = useRequest2< + NodeTemplateListItemType[], + [] + >( + async () => { + if (!isFolder) return []; + return getSystemPlugTemplates({ parentId }); + }, + { + manual: false, + refreshDeps: [isFolder, isSystemCostOpen, parentId] + } + ); + + const hasCost = useMemo(() => { + if (isFolder) { + return (childTools || [])?.some((item) => (item.systemKeyCost || 0) > 0); + } + return (secretCost || 0) > 0; + }, [isFolder, childTools, secretCost]); + return ( - - - {t('app:tool_active_system_config_price_desc', { - price: secretCost || 0 - })} - - - ) : null + configType === SystemToolInputTypeEnum.system + ? (() => { + if (!hasCost) return null; + + return ( + + {isFolder ? ( + <> + + + + {t('app:tool_active_system_config_price_desc_folder')} + + + + {isSystemCostOpen && ( + + {isLoadingChildTools ? ( + Loading... + ) : childTools.length > 0 ? ( + + {childTools.map((tool) => ( + + {t(tool.name as any)}: {tool.systemKeyCost || 0}{' '} + 积分/次 + + ))} + + ) : null} + + )} + + ) : ( + + + + {t('app:tool_active_system_config_price_desc', { + price: secretCost + })} + + + )} + + ); + })() + : null } ] : []),