From f4d4d6516c4adc34f2f9cac7a91abef5c6d7d671 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Mon, 23 Sep 2024 15:43:57 +0800 Subject: [PATCH] Support simpleApp select workflow (#2772) * fix: share page id error * feat: simple workflow support childApp tool * perf: aichat box animation --- .../zh-cn/docs/development/upgrading/4811.md | 2 +- .../global/core/workflow/template/input.ts | 1 + .../service/core/app/plugin/controller.ts | 2 +- packages/web/i18n/zh/app.json | 4 +- .../ChatBox/components/ChatItem.tsx | 2 +- .../PluginRunBox/components/RenderOutput.tsx | 2 +- .../components/renderPluginInput.tsx | 4 +- .../core/chat/components/AIResponseBox.tsx | 10 ++-- .../Publish/Link/SelectUsingWayModal.tsx | 1 + .../SimpleApp/components/ToolSelectModal.tsx | 46 +++++++++++-------- .../nodes/NodePluginIO/InputEditModal.tsx | 3 +- projects/app/src/pages/chat/share.tsx | 14 ++---- projects/app/src/web/core/app/utils.ts | 18 +++++++- .../app/src/web/core/chat/storeShareChat.ts | 25 ++++++---- 14 files changed, 83 insertions(+), 51 deletions(-) diff --git a/docSite/content/zh-cn/docs/development/upgrading/4811.md b/docSite/content/zh-cn/docs/development/upgrading/4811.md index 2103e32228f8..32737cd2393d 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/4811.md +++ b/docSite/content/zh-cn/docs/development/upgrading/4811.md @@ -92,7 +92,7 @@ weight: 813 6. 新增 - 支持 Openai o1 模型,需增加模型的 `defaultConfig` 配置,覆盖 `temperature`、`max_tokens` 和 `stream`配置,o1 不支持 stream 模式, 详细可重新拉取 `config.json` 配置文件查看。 7. 新增 - AI 对话节点知识库引用,支持配置 role=system 和 role=user,已配置的过自定义提示词的节点将会保持 user 模式,其余用户将转成 system 模式。 8. 新增 - 插件支持上传系统文件。 -9. 新增 - 支持工作流嵌套子应用时,可以设置`非流模式`。 +9. 新增 - 支持工作流嵌套子应用时,可以设置`非流模式`,同时简易模式也可以选择工作流作为插件了,简易模式调用子应用时,都将强制使用非流模式。 10. 新增 - 调试模式下,子应用调用,支持返回详细运行数据。 11. 新增 - 保留所有模式下子应用嵌套调用的日志。 12. 优化 - 工作流嵌套层级限制 20 层,避免因编排不合理导致的无限死循环。 diff --git a/packages/global/core/workflow/template/input.ts b/packages/global/core/workflow/template/input.ts index f2b392f7ad35..766a7881da9e 100644 --- a/packages/global/core/workflow/template/input.ts +++ b/packages/global/core/workflow/template/input.ts @@ -23,6 +23,7 @@ export const Input_Template_UserChatInput: FlowNodeInputItemType = { renderTypeList: [FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.textarea], valueType: WorkflowIOValueTypeEnum.string, label: i18nT('workflow:user_question'), + toolDescription: i18nT('workflow:user_question'), required: true }; diff --git a/packages/service/core/app/plugin/controller.ts b/packages/service/core/app/plugin/controller.ts index b64287646626..14a808ae7146 100644 --- a/packages/service/core/app/plugin/controller.ts +++ b/packages/service/core/app/plugin/controller.ts @@ -89,7 +89,7 @@ export async function getChildAppPreviewNode({ intro: app.intro, inputExplanationUrl: app.inputExplanationUrl, showStatus: app.showStatus, - isTool: isPlugin, + isTool: true, version: app.version, sourceHandle: getHandleConfig(true, true, true, true), targetHandle: getHandleConfig(true, true, true, true), diff --git a/packages/web/i18n/zh/app.json b/packages/web/i18n/zh/app.json index 5fd003b8c6ce..acf1de8a00ed 100644 --- a/packages/web/i18n/zh/app.json +++ b/packages/web/i18n/zh/app.json @@ -81,7 +81,7 @@ "permission.des.write": "可查看和编辑应用", "plugin_cost_per_times": "{{cost}}/次", "plugin_dispatch": "插件调用", - "plugin_dispatch_tip": "给模型附加额外的能力,具体调用哪些插件,将由模型自主决定。\n若选择了插件,知识库调用将自动作为一个特殊的插件。", + "plugin_dispatch_tip": "给模型附加获取外部数据的能力,具体调用哪些插件,将由模型自主决定,所有插件都将以非流模式运行。\n若选择了插件,知识库调用将自动作为一个特殊的插件。", "publish_channel": "发布渠道", "publish_success": "发布成功", "saved_success": "保存成功", @@ -155,4 +155,4 @@ "workflow.user_file_input_desc": "用户上传的文档和图片链接", "workflow.user_select": "用户选择", "workflow.user_select_tip": "该模块可配置多个选项,以供对话时选择。不同选项可导向不同工作流支线" -} \ No newline at end of file +} diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx index be1af1b858a2..91f89439ea7d 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx @@ -95,7 +95,7 @@ const AIContentCard = React.memo(function AIContentCard({ ); diff --git a/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/RenderOutput.tsx b/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/RenderOutput.tsx index 0a575cd45875..03429edf5198 100644 --- a/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/RenderOutput.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/RenderOutput.tsx @@ -33,7 +33,7 @@ const RenderOutput = () => { ); diff --git a/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/renderPluginInput.tsx b/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/renderPluginInput.tsx index fb96f19a3c22..b3a34b6829e9 100644 --- a/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/renderPluginInput.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/renderPluginInput.tsx @@ -110,9 +110,9 @@ const RenderPluginInput = ({ * )} - {input.label} + {t(input.label as any)} - {input.description && } + {input.description && } {render} diff --git a/projects/app/src/components/core/chat/components/AIResponseBox.tsx b/projects/app/src/components/core/chat/components/AIResponseBox.tsx index 60732856d55c..9efa62e9d7d6 100644 --- a/projects/app/src/components/core/chat/components/AIResponseBox.tsx +++ b/projects/app/src/components/core/chat/components/AIResponseBox.tsx @@ -24,7 +24,7 @@ import { onSendPrompt } from '../ChatContainer/useChat'; type props = { value: UserChatItemValueItemType | AIChatItemValueItemType; - isLastChild: boolean; + isLastResponseValue: boolean; isChatting: boolean; }; @@ -167,11 +167,13 @@ const RenderInteractive = React.memo(function RenderInteractive({ ); }); -const AIResponseBox = ({ value, isLastChild, isChatting }: props) => { +const AIResponseBox = ({ value, isLastResponseValue, isChatting }: props) => { if (value.type === ChatItemValueTypeEnum.text && value.text) - return ; + return ( + + ); if (value.type === ChatItemValueTypeEnum.tool && value.tools) - return ; + return ; if ( value.type === ChatItemValueTypeEnum.interactive && value.interactive && diff --git a/projects/app/src/pages/app/detail/components/Publish/Link/SelectUsingWayModal.tsx b/projects/app/src/pages/app/detail/components/Publish/Link/SelectUsingWayModal.tsx index 07f3d4d45f19..240443e07962 100644 --- a/projects/app/src/pages/app/detail/components/Publish/Link/SelectUsingWayModal.tsx +++ b/projects/app/src/pages/app/detail/components/Publish/Link/SelectUsingWayModal.tsx @@ -120,6 +120,7 @@ console.log("Chat box loaded") return ( void; }; +const childAppSystemKey: string[] = [ + NodeInputKeyEnum.forbidStream, + NodeInputKeyEnum.history, + NodeInputKeyEnum.historyMaxAmount, + NodeInputKeyEnum.userChatInput +]; + enum TemplateTypeEnum { 'systemPlugin' = 'systemPlugin', 'teamPlugin' = 'teamPlugin' @@ -61,6 +62,7 @@ enum TemplateTypeEnum { const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void }) => { const { t } = useTranslation(); + const { appDetail } = useContextSelector(AppContext, (v) => v); const [templateType, setTemplateType] = useState(TemplateTypeEnum.teamPlugin); const [parentId, setParentId] = useState(''); @@ -85,9 +87,8 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void }) } else if (type === TemplateTypeEnum.teamPlugin) { return getTeamPlugTemplates({ parentId, - searchKey: searchVal, - type: [AppTypeEnum.folder, AppTypeEnum.httpPlugin, AppTypeEnum.plugin] - }); + searchKey: searchVal + }).then((res) => res.filter((app) => app.id !== appDetail._id)); } }, { @@ -238,20 +239,24 @@ const RenderList = React.memo(function RenderList({ } }, [configTool, reset]); - const { mutate: onClickAdd, isLoading } = useRequest({ - mutationFn: async (template: FlowNodeTemplateType) => { + const { runAsync: onClickAdd, loading: isLoading } = useRequest2( + async (template: NodeTemplateListItemType) => { const res = await getPreviewPluginNode({ appId: template.id }); // All input is tool params - if (res.inputs.every((input) => input.toolDescription)) { + if ( + res.inputs.every((input) => childAppSystemKey.includes(input.key) || input.toolDescription) + ) { onAddTool(res); } else { reset(); setConfigTool(res); } }, - errorToast: t('common:core.module.templates.Load plugin error') - }); + { + errorToast: t('common:core.module.templates.Load plugin error') + } + ); return templates.length === 0 && !isLoadingData ? ( @@ -340,6 +345,7 @@ const RenderList = React.memo(function RenderList({ {!!configTool && ( {configTool.inputs - .filter((item) => !item.toolDescription) + .filter((item) => !item.toolDescription && !childAppSystemKey.includes(item.key)) .map((input) => { return ( { const { shareId, authToken } = props; - const { localUId, setLocalUId } = useShareChatStore(); + const { localUId, loaded } = useShareChatStore(); const contextParams = useMemo(() => { - if (!localUId) { - const localId = `shareChat-${Date.now()}-${nanoid()}`; - setLocalUId(localId); - return { shareId, outLinkUid: authToken || localId }; - } - return { shareId, outLinkUid: authToken || localUId }; - }, []); + }, [authToken, localUId, shareId]); + + if (!loaded || !contextParams.outLinkUid) return <>; return ( @@ -375,7 +371,7 @@ const Render = (props: Props) => { ); }; -export default Render; +export default React.memo(Render); export async function getServerSideProps(context: any) { const shareId = context?.query?.shareId || ''; diff --git a/projects/app/src/web/core/app/utils.ts b/projects/app/src/web/core/app/utils.ts index c7cf05e47766..35c7f01ff509 100644 --- a/projects/app/src/web/core/app/utils.ts +++ b/projects/app/src/web/core/app/utils.ts @@ -378,7 +378,23 @@ export function form2AppWorkflow( y: 545 }, version: tool.version, - inputs: tool.inputs, + inputs: tool.inputs.map((input) => { + // Special key value + if (input.key === NodeInputKeyEnum.forbidStream) { + input.value = true; + } + // Special tool + if ( + tool.flowNodeType === FlowNodeTypeEnum.appModule && + input.key === NodeInputKeyEnum.history + ) { + return { + ...input, + value: formData.aiSettings.maxHistories + }; + } + return input; + }), outputs: tool.outputs } ], diff --git a/projects/app/src/web/core/chat/storeShareChat.ts b/projects/app/src/web/core/chat/storeShareChat.ts index 3341097dc41a..6256f0e204eb 100644 --- a/projects/app/src/web/core/chat/storeShareChat.ts +++ b/projects/app/src/web/core/chat/storeShareChat.ts @@ -1,25 +1,34 @@ import { create } from 'zustand'; import { devtools, persist } from 'zustand/middleware'; import { immer } from 'zustand/middleware/immer'; +import { customAlphabet } from 'nanoid'; +const nanoid = customAlphabet( + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWSYZ1234567890_', + 24 +); type State = { localUId: string; - setLocalUId: (id: string) => void; + loaded: boolean; }; export const useShareChatStore = create()( devtools( persist( immer((set, get) => ({ - localUId: '', - setLocalUId(id) { - set((state) => { - state.localUId = id; - }); - } + localUId: `shareChat-${Date.now()}-${nanoid()}`, + loaded: false })), { - name: 'shareChatStore' + name: 'shareChatStore', + onRehydrateStorage: () => (state) => { + if (state) { + state.loaded = true; + } + }, + partialize: (state) => ({ + localUId: state.localUId + }) } ) )