From 903f39fe1784376d73c3937a6f281848af51f319 Mon Sep 17 00:00:00 2001 From: heheer Date: Fri, 30 Aug 2024 17:12:57 +0800 Subject: [PATCH] feat: add plugin instruction config (#2579) * feat: add plugin instruction config * fix build --- packages/global/core/app/type.d.ts | 3 + packages/global/core/workflow/constants.ts | 3 + .../global/core/workflow/node/constant.ts | 1 + .../core/workflow/template/constants.ts | 2 + .../workflow/template/system/pluginConfig.ts | 21 ++++ packages/global/core/workflow/utils.ts | 11 +- packages/service/core/app/schema.ts | 3 +- .../service/core/workflow/dispatch/index.ts | 1 + packages/web/i18n/en/workflow.json | 4 + packages/web/i18n/zh/workflow.json | 4 + projects/app/public/imgs/app/help.svg | 4 + projects/app/public/imgs/app/instruction.svg | 75 +++++++++++++ projects/app/src/components/core/app/Tip.tsx | 14 ++- .../PluginRunBox/components/RenderInput.tsx | 30 ++++- .../chat/ChatContainer/PluginRunBox/type.d.ts | 1 + .../WorkflowComponents/Flow/index.tsx | 1 + .../nodes/NodePluginIO/NodePluginConfig.tsx | 103 ++++++++++++++++++ .../app/detail/components/useChatTest.tsx | 1 + projects/app/src/pages/chat/index.tsx | 1 + projects/app/src/web/core/workflow/utils.ts | 7 +- 20 files changed, 278 insertions(+), 12 deletions(-) create mode 100644 packages/global/core/workflow/template/system/pluginConfig.ts create mode 100644 projects/app/public/imgs/app/help.svg create mode 100644 projects/app/public/imgs/app/instruction.svg create mode 100644 projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/NodePluginConfig.tsx diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index 38e7cb7c3..089c2c352 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -96,6 +96,9 @@ export type AppChatConfigType = { scheduledTriggerConfig?: AppScheduledTriggerConfigType; chatInputGuide?: ChatInputGuideConfigType; fileSelectConfig?: AppFileSelectConfigType; + + // plugin + instruction?: string; }; export type SettingAIDataType = { model: string; diff --git a/packages/global/core/workflow/constants.ts b/packages/global/core/workflow/constants.ts index 4877d734a..6f5df62e5 100644 --- a/packages/global/core/workflow/constants.ts +++ b/packages/global/core/workflow/constants.ts @@ -52,6 +52,9 @@ export enum NodeInputKeyEnum { scheduleTrigger = 'scheduleTrigger', chatInputGuide = 'chatInputGuide', + // plugin config + instruction = 'instruction', + // entry userChatInput = 'userChatInput', inputFiles = 'inputFiles', diff --git a/packages/global/core/workflow/node/constant.ts b/packages/global/core/workflow/node/constant.ts index cbcec8865..b7cad9839 100644 --- a/packages/global/core/workflow/node/constant.ts +++ b/packages/global/core/workflow/node/constant.ts @@ -94,6 +94,7 @@ export enum FlowNodeOutputTypeEnum { export enum FlowNodeTypeEnum { emptyNode = 'emptyNode', systemConfig = 'userGuide', + pluginConfig = 'pluginConfig', globalVariable = 'globalVariable', workflowStart = 'workflowStart', chatNode = 'chatNode', diff --git a/packages/global/core/workflow/template/constants.ts b/packages/global/core/workflow/template/constants.ts index e59c801d8..19843860e 100644 --- a/packages/global/core/workflow/template/constants.ts +++ b/packages/global/core/workflow/template/constants.ts @@ -1,4 +1,5 @@ import { SystemConfigNode } from './system/systemConfig'; +import { PluginConfigNode } from './system/pluginConfig'; import { EmptyNode } from './system/emptyNode'; import { WorkflowStart } from './system/workflowStart'; import { AiChatModule } from './system/aiChat'; @@ -57,6 +58,7 @@ export const appSystemModuleTemplates: FlowNodeTemplateType[] = [ ]; /* plugin flow module templates */ export const pluginSystemModuleTemplates: FlowNodeTemplateType[] = [ + PluginConfigNode, PluginInputModule, PluginOutputModule, ...systemNodes diff --git a/packages/global/core/workflow/template/system/pluginConfig.ts b/packages/global/core/workflow/template/system/pluginConfig.ts new file mode 100644 index 000000000..9fc8e3504 --- /dev/null +++ b/packages/global/core/workflow/template/system/pluginConfig.ts @@ -0,0 +1,21 @@ +import { FlowNodeTypeEnum } from '../../node/constant'; +import { FlowNodeTemplateType } from '../../type/node.d'; +import { FlowNodeTemplateTypeEnum } from '../../constants'; +import { getHandleConfig } from '../utils'; +import { i18nT } from '../../../../../web/i18n/utils'; + +export const PluginConfigNode: FlowNodeTemplateType = { + id: FlowNodeTypeEnum.pluginConfig, + templateType: FlowNodeTemplateTypeEnum.systemInput, + flowNodeType: FlowNodeTypeEnum.pluginConfig, + sourceHandle: getHandleConfig(false, false, false, false), + targetHandle: getHandleConfig(false, false, false, false), + avatar: 'core/workflow/template/systemConfig', + name: i18nT('workflow:template.system_config'), + intro: '', + unique: true, + forbidDelete: true, + version: '4811', + inputs: [], + outputs: [] +}; diff --git a/packages/global/core/workflow/utils.ts b/packages/global/core/workflow/utils.ts index 65cdd9d71..7a12c570a 100644 --- a/packages/global/core/workflow/utils.ts +++ b/packages/global/core/workflow/utils.ts @@ -79,6 +79,10 @@ export const splitGuideModule = (guideModules?: StoreNodeItemType) => { guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.chatInputGuide)?.value || defaultChatInputGuideConfig; + // plugin + const instruction: string = + guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.instruction)?.value || ''; + return { welcomeText, variables, @@ -86,7 +90,8 @@ export const splitGuideModule = (guideModules?: StoreNodeItemType) => { ttsConfig, whisperConfig, scheduledTriggerConfig, - chatInputGuide + chatInputGuide, + instruction }; }; @@ -111,7 +116,8 @@ export const getAppChatConfig = ({ ttsConfig, whisperConfig, scheduledTriggerConfig, - chatInputGuide + chatInputGuide, + instruction } = splitGuideModule(systemConfigNode); const config: AppChatConfigType = { @@ -120,6 +126,7 @@ export const getAppChatConfig = ({ whisperConfig, scheduledTriggerConfig, chatInputGuide, + instruction, ...chatConfig, variables: storeVariables ?? chatConfig?.variables ?? variables, welcomeText: storeWelcomeText ?? chatConfig?.welcomeText ?? welcomeText diff --git a/packages/service/core/app/schema.ts b/packages/service/core/app/schema.ts index 48bd1049c..0e2f117b0 100644 --- a/packages/service/core/app/schema.ts +++ b/packages/service/core/app/schema.ts @@ -18,7 +18,8 @@ export const chatConfigType = { whisperConfig: Object, scheduledTriggerConfig: Object, chatInputGuide: Object, - fileSelectConfig: Object + fileSelectConfig: Object, + instruction: String }; // schema diff --git a/packages/service/core/workflow/dispatch/index.ts b/packages/service/core/workflow/dispatch/index.ts index 57aceb5f8..3adf22b11 100644 --- a/packages/service/core/workflow/dispatch/index.ts +++ b/packages/service/core/workflow/dispatch/index.ts @@ -93,6 +93,7 @@ const callbackMap: Record = { // none [FlowNodeTypeEnum.systemConfig]: dispatchSystemConfig, + [FlowNodeTypeEnum.pluginConfig]: () => Promise.resolve(), [FlowNodeTypeEnum.emptyNode]: () => Promise.resolve(), [FlowNodeTypeEnum.globalVariable]: () => Promise.resolve() }; diff --git a/packages/web/i18n/en/workflow.json b/packages/web/i18n/en/workflow.json index ac500b849..46ae22d1d 100644 --- a/packages/web/i18n/en/workflow.json +++ b/packages/web/i18n/en/workflow.json @@ -97,6 +97,10 @@ "optional_value_type_tip": "One or more data types can be specified, and users can only select the configured type when adding fields in winter", "other_questions": "Other questions", "pass_returned_object_as_output_to_next_nodes": "Use the object returned in the code as output and pass it to subsequent nodes. \nThe variable name needs to correspond to the key of return", + "plugin": { + "Instruction_Tip": "You can configure a description to explain the purpose of this plugin. This description will be displayed each time before using the plugin. Standard Markdown syntax is supported.", + "Instructions": "Instructions" + }, "plugin_input": "Plug-in input", "question_classification": "Problem classification", "question_optimization": "Problem optimization", diff --git a/packages/web/i18n/zh/workflow.json b/packages/web/i18n/zh/workflow.json index 1e3ebe930..c848ad32f 100644 --- a/packages/web/i18n/zh/workflow.json +++ b/packages/web/i18n/zh/workflow.json @@ -97,6 +97,10 @@ "optional_value_type_tip": "可以指定 1 个或多个数据类型,用户在动态添加字段时,仅可选择配置的类型", "other_questions": "其他问题", "pass_returned_object_as_output_to_next_nodes": "将代码中 return 的对象作为输出,传递给后续的节点。变量名需要对应 return 的 key", + "plugin": { + "Instruction_Tip": "可以配置一段说明,以解释该插件的用途。每次使用插件前,会显示该段说明。支持标准 Markdown 语法。", + "Instructions": "使用说明" + }, "plugin_input": "插件输入", "question_classification": "问题分类", "question_optimization": "问题优化", diff --git a/projects/app/public/imgs/app/help.svg b/projects/app/public/imgs/app/help.svg new file mode 100644 index 000000000..1632ee0c6 --- /dev/null +++ b/projects/app/public/imgs/app/help.svg @@ -0,0 +1,4 @@ + + + + diff --git a/projects/app/public/imgs/app/instruction.svg b/projects/app/public/imgs/app/instruction.svg new file mode 100644 index 000000000..910ade6f1 --- /dev/null +++ b/projects/app/public/imgs/app/instruction.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/app/src/components/core/app/Tip.tsx b/projects/app/src/components/core/app/Tip.tsx index 6cc95fc40..7fa9edca7 100644 --- a/projects/app/src/components/core/app/Tip.tsx +++ b/projects/app/src/components/core/app/Tip.tsx @@ -11,18 +11,18 @@ enum FnTypeEnum { variable = 'variable', welcome = 'welcome', file = 'file', - visionModel = 'visionModel' + visionModel = 'visionModel', + instruction = 'instruction' } const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => { const { t } = useTranslation(); - const { chatT } = useI18n(); const map = useRef({ [FnTypeEnum.inputGuide]: { icon: '/imgs/app/inputGuide-icon.svg', - title: chatT('input_guide'), - desc: chatT('input_guide_tip'), + title: t('chat:input_guide'), + desc: t('chat:input_guide_tip'), imgUrl: '/imgs/app/inputGuide.svg' }, [FnTypeEnum.nextQuestion]: { @@ -60,6 +60,12 @@ const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => { title: t('app:vision_model_title'), desc: t('app:llm_use_vision_tip'), imgUrl: '/imgs/app/visionModel.png' + }, + [FnTypeEnum.instruction]: { + icon: '/imgs/app/help.svg', + title: t('workflow:plugin.Instructions'), + desc: t('workflow:plugin.Instruction_Tip'), + imgUrl: '/imgs/app/instruction.svg' } }); const data = map.current[type]; diff --git a/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/RenderInput.tsx b/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/RenderInput.tsx index 113b65bcb..1655e1ea0 100644 --- a/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/RenderInput.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/RenderInput.tsx @@ -1,16 +1,26 @@ import React, { useEffect, useMemo } from 'react'; import { Controller } from 'react-hook-form'; import RenderPluginInput from './renderPluginInput'; -import { Button, Flex } from '@chakra-ui/react'; +import { Box, Button, Flex } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import { useContextSelector } from 'use-context-selector'; import { PluginRunContext } from '../context'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { isEqual } from 'lodash'; +import { AppChatConfigType } from '@fastgpt/global/core/app/type'; +import Markdown from '@/components/Markdown'; const RenderInput = () => { - const { pluginInputs, variablesForm, histories, onStartChat, onNewChat, onSubmit, isChatting } = - useContextSelector(PluginRunContext, (v) => v); + const { + pluginInputs, + variablesForm, + histories, + onStartChat, + onNewChat, + onSubmit, + isChatting, + chatConfig + } = useContextSelector(PluginRunContext, (v) => v); const { t } = useTranslation(); const { @@ -64,6 +74,20 @@ const RenderInput = () => { return ( <> + {/* instruction */} + {chatConfig?.instruction && ( + + + + )} + {pluginInputs.map((input) => { return ( >; + chatConfig?: AppChatConfigType; }; diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx index 6127979f1..50f3df0bd 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx @@ -28,6 +28,7 @@ const nodeTypes: Record = { [FlowNodeTypeEnum.textEditor]: NodeSimple, [FlowNodeTypeEnum.customFeedback]: NodeSimple, [FlowNodeTypeEnum.systemConfig]: dynamic(() => import('./nodes/NodeSystemConfig')), + [FlowNodeTypeEnum.pluginConfig]: dynamic(() => import('./nodes/NodePluginIO/NodePluginConfig')), [FlowNodeTypeEnum.workflowStart]: dynamic(() => import('./nodes/NodeWorkflowStart')), [FlowNodeTypeEnum.chatNode]: NodeSimple, [FlowNodeTypeEnum.readFiles]: NodeSimple, diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/NodePluginConfig.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/NodePluginConfig.tsx new file mode 100644 index 000000000..e539467d4 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/NodePluginConfig.tsx @@ -0,0 +1,103 @@ +import React, { Dispatch, useMemo, useState } from 'react'; +import { NodeProps } from 'reactflow'; +import NodeCard from '../render/NodeCard'; +import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d'; +import { Box, Flex } from '@chakra-ui/react'; +import Container from '../../components/Container'; +import { useTranslation } from 'next-i18next'; +import { useContextSelector } from 'use-context-selector'; +import MyTextarea from '@/components/common/Textarea/MyTextarea'; +import { AppContext } from '../../../../context'; +import { AppChatConfigType, AppDetailType } from '@fastgpt/global/core/app/type'; +import { getAppChatConfig } from '@fastgpt/global/core/workflow/utils'; +import { useCreation } from 'ahooks'; +import ChatFunctionTip from '@/components/core/app/Tip'; +import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; + +type ComponentProps = { + chatConfig: AppChatConfigType; + setAppDetail: Dispatch>; +}; + +const NodePluginConfig = ({ data, selected }: NodeProps) => { + const { appDetail, setAppDetail } = useContextSelector(AppContext, (v) => v); + + const chatConfig = useMemo(() => { + return getAppChatConfig({ + chatConfig: appDetail.chatConfig, + systemConfigNode: data, + isPublicFetch: true + }); + }, [data, appDetail]); + + useCreation(() => { + setAppDetail((state) => ({ + ...state, + chatConfig: { + ...state.chatConfig, + ...chatConfig + } + })); + }, []); + + const componentsProps = useMemo( + () => ({ + chatConfig, + setAppDetail + }), + [chatConfig, setAppDetail] + ); + + return ( + + + + + + ); +}; +export default React.memo(NodePluginConfig); + +function Instruction({ chatConfig: { instruction }, setAppDetail }: ComponentProps) { + const { t } = useTranslation(); + + return ( + <> + + + {t('workflow:plugin.Instructions')} + + + + { + setAppDetail((state) => ({ + ...state, + chatConfig: { + ...state.chatConfig, + instruction: e.target.value + } + })); + }} + /> + + ); +} diff --git a/projects/app/src/pages/app/detail/components/useChatTest.tsx b/projects/app/src/pages/app/detail/components/useChatTest.tsx index a41b5f08b..7506ceeb2 100644 --- a/projects/app/src/pages/app/detail/components/useChatTest.tsx +++ b/projects/app/src/pages/app/detail/components/useChatTest.tsx @@ -77,6 +77,7 @@ export const useChatTest = ({ histories={chatRecords} setHistories={setChatRecords} appId={appDetail._id} + chatConfig={appDetail.chatConfig} tab={pluginRunTab} setTab={setPluginRunTab} onNewChat={clearChatRecords} diff --git a/projects/app/src/pages/chat/index.tsx b/projects/app/src/pages/chat/index.tsx index b391ba001..c43317d1a 100644 --- a/projects/app/src/pages/chat/index.tsx +++ b/projects/app/src/pages/chat/index.tsx @@ -240,6 +240,7 @@ const Chat = ({ histories={chatRecords} setHistories={setChatRecords} appId={chatData.appId} + chatConfig={chatData.app.chatConfig} tab={pluginRunTab} setTab={setPluginRunTab} onNewChat={() => onChangeChatId(getNanoid())} diff --git a/projects/app/src/web/core/workflow/utils.ts b/projects/app/src/web/core/workflow/utils.ts index 032489a4d..f376e9595 100644 --- a/projects/app/src/web/core/workflow/utils.ts +++ b/projects/app/src/web/core/workflow/utils.ts @@ -260,6 +260,7 @@ export const checkWorkflowNodeAndConnection = ({ if ( data.flowNodeType === FlowNodeTypeEnum.systemConfig || + data.flowNodeType === FlowNodeTypeEnum.pluginConfig || data.flowNodeType === FlowNodeTypeEnum.pluginInput || data.flowNodeType === FlowNodeTypeEnum.workflowStart ) { @@ -554,7 +555,8 @@ export const compareSnapshot = ( whisperConfig: clone1.chatConfig?.whisperConfig || undefined, scheduledTriggerConfig: clone1.chatConfig?.scheduledTriggerConfig || undefined, chatInputGuide: clone1.chatConfig?.chatInputGuide || undefined, - fileSelectConfig: clone1.chatConfig?.fileSelectConfig || undefined + fileSelectConfig: clone1.chatConfig?.fileSelectConfig || undefined, + instruction: clone1.chatConfig?.instruction || '' }, { welcomeText: clone2.chatConfig?.welcomeText || '', @@ -564,7 +566,8 @@ export const compareSnapshot = ( whisperConfig: clone2.chatConfig?.whisperConfig || undefined, scheduledTriggerConfig: clone2.chatConfig?.scheduledTriggerConfig || undefined, chatInputGuide: clone2.chatConfig?.chatInputGuide || undefined, - fileSelectConfig: clone2.chatConfig?.fileSelectConfig || undefined + fileSelectConfig: clone2.chatConfig?.fileSelectConfig || undefined, + instruction: clone2.chatConfig?.instruction || '' } ) ) {