Support simpleApp select workflow (#2772)

* fix: share page id error

* feat: simple workflow support childApp tool

* perf: aichat box animation
This commit is contained in:
Archer
2024-09-23 15:43:57 +08:00
committed by GitHub
parent b6833ca3ea
commit f4d4d6516c
14 changed files with 83 additions and 51 deletions

View File

@@ -92,7 +92,7 @@ weight: 813
6. 新增 - 支持 Openai o1 模型,需增加模型的 `defaultConfig` 配置,覆盖 `temperature``max_tokens``stream`配置o1 不支持 stream 模式, 详细可重新拉取 `config.json` 配置文件查看。 6. 新增 - 支持 Openai o1 模型,需增加模型的 `defaultConfig` 配置,覆盖 `temperature``max_tokens``stream`配置o1 不支持 stream 模式, 详细可重新拉取 `config.json` 配置文件查看。
7. 新增 - AI 对话节点知识库引用,支持配置 role=system 和 role=user已配置的过自定义提示词的节点将会保持 user 模式,其余用户将转成 system 模式。 7. 新增 - AI 对话节点知识库引用,支持配置 role=system 和 role=user已配置的过自定义提示词的节点将会保持 user 模式,其余用户将转成 system 模式。
8. 新增 - 插件支持上传系统文件。 8. 新增 - 插件支持上传系统文件。
9. 新增 - 支持工作流嵌套子应用时,可以设置`非流模式` 9. 新增 - 支持工作流嵌套子应用时,可以设置`非流模式`,同时简易模式也可以选择工作流作为插件了,简易模式调用子应用时,都将强制使用非流模式
10. 新增 - 调试模式下,子应用调用,支持返回详细运行数据。 10. 新增 - 调试模式下,子应用调用,支持返回详细运行数据。
11. 新增 - 保留所有模式下子应用嵌套调用的日志。 11. 新增 - 保留所有模式下子应用嵌套调用的日志。
12. 优化 - 工作流嵌套层级限制 20 层,避免因编排不合理导致的无限死循环。 12. 优化 - 工作流嵌套层级限制 20 层,避免因编排不合理导致的无限死循环。

View File

@@ -23,6 +23,7 @@ export const Input_Template_UserChatInput: FlowNodeInputItemType = {
renderTypeList: [FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.textarea], renderTypeList: [FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.textarea],
valueType: WorkflowIOValueTypeEnum.string, valueType: WorkflowIOValueTypeEnum.string,
label: i18nT('workflow:user_question'), label: i18nT('workflow:user_question'),
toolDescription: i18nT('workflow:user_question'),
required: true required: true
}; };

View File

@@ -89,7 +89,7 @@ export async function getChildAppPreviewNode({
intro: app.intro, intro: app.intro,
inputExplanationUrl: app.inputExplanationUrl, inputExplanationUrl: app.inputExplanationUrl,
showStatus: app.showStatus, showStatus: app.showStatus,
isTool: isPlugin, isTool: true,
version: app.version, version: app.version,
sourceHandle: getHandleConfig(true, true, true, true), sourceHandle: getHandleConfig(true, true, true, true),
targetHandle: getHandleConfig(true, true, true, true), targetHandle: getHandleConfig(true, true, true, true),

View File

@@ -81,7 +81,7 @@
"permission.des.write": "可查看和编辑应用", "permission.des.write": "可查看和编辑应用",
"plugin_cost_per_times": "{{cost}}/次", "plugin_cost_per_times": "{{cost}}/次",
"plugin_dispatch": "插件调用", "plugin_dispatch": "插件调用",
"plugin_dispatch_tip": "给模型附加额外的能力,具体调用哪些插件,将由模型自主决定。\n若选择了插件知识库调用将自动作为一个特殊的插件。", "plugin_dispatch_tip": "给模型附加获取外部数据的能力,具体调用哪些插件,将由模型自主决定,所有插件都将以非流模式运行。\n若选择了插件知识库调用将自动作为一个特殊的插件。",
"publish_channel": "发布渠道", "publish_channel": "发布渠道",
"publish_success": "发布成功", "publish_success": "发布成功",
"saved_success": "保存成功", "saved_success": "保存成功",
@@ -155,4 +155,4 @@
"workflow.user_file_input_desc": "用户上传的文档和图片链接", "workflow.user_file_input_desc": "用户上传的文档和图片链接",
"workflow.user_select": "用户选择", "workflow.user_select": "用户选择",
"workflow.user_select_tip": "该模块可配置多个选项,以供对话时选择。不同选项可导向不同工作流支线" "workflow.user_select_tip": "该模块可配置多个选项,以供对话时选择。不同选项可导向不同工作流支线"
} }

View File

@@ -95,7 +95,7 @@ const AIContentCard = React.memo(function AIContentCard({
<AIResponseBox <AIResponseBox
key={key} key={key}
value={value} value={value}
isLastChild={isLastChild && i === chatValue.length - 1} isLastResponseValue={isLastChild && i === chatValue.length - 1}
isChatting={isChatting} isChatting={isChatting}
/> />
); );

View File

@@ -33,7 +33,7 @@ const RenderOutput = () => {
<AIResponseBox <AIResponseBox
key={key} key={key}
value={value} value={value}
isLastChild={true} isLastResponseValue={true}
isChatting={isChatting} isChatting={isChatting}
/> />
); );

View File

@@ -110,9 +110,9 @@ const RenderPluginInput = ({
* *
</Box> </Box>
)} )}
{input.label} {t(input.label as any)}
</Box> </Box>
{input.description && <QuestionTip ml={2} label={input.description} />} {input.description && <QuestionTip ml={2} label={t(input.description as any)} />}
</Flex> </Flex>
{render} {render}
</Box> </Box>

View File

@@ -24,7 +24,7 @@ import { onSendPrompt } from '../ChatContainer/useChat';
type props = { type props = {
value: UserChatItemValueItemType | AIChatItemValueItemType; value: UserChatItemValueItemType | AIChatItemValueItemType;
isLastChild: boolean; isLastResponseValue: boolean;
isChatting: 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) if (value.type === ChatItemValueTypeEnum.text && value.text)
return <RenderText showAnimation={isChatting && isLastChild} text={value.text.content} />; return (
<RenderText showAnimation={isChatting && isLastResponseValue} text={value.text.content} />
);
if (value.type === ChatItemValueTypeEnum.tool && value.tools) if (value.type === ChatItemValueTypeEnum.tool && value.tools)
return <RenderTool showAnimation={isChatting && isLastChild} tools={value.tools} />; return <RenderTool showAnimation={isChatting} tools={value.tools} />;
if ( if (
value.type === ChatItemValueTypeEnum.interactive && value.type === ChatItemValueTypeEnum.interactive &&
value.interactive && value.interactive &&

View File

@@ -120,6 +120,7 @@ console.log("Chat box loaded")
return ( return (
<MyModal <MyModal
isOpen isOpen
isCentered
iconSrc="/imgs/modal/usingWay.svg" iconSrc="/imgs/modal/usingWay.svg"
title={t('common:core.app.outLink.Select Using Way')} title={t('common:core.app.outLink.Select Using Way')}
onClose={onClose} onClose={onClose}

View File

@@ -11,17 +11,10 @@ import {
InputGroup, InputGroup,
InputLeftElement, InputLeftElement,
ModalBody, ModalBody,
ModalFooter, ModalFooter
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
Switch,
Textarea
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs'; import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { import {
FlowNodeTemplateType, FlowNodeTemplateType,
@@ -39,14 +32,15 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { getTeamPlugTemplates } from '@/web/core/app/api/plugin'; import { getTeamPlugTemplates } from '@/web/core/app/api/plugin';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { getAppFolderPath } from '@/web/core/app/api/app'; import { getAppFolderPath } from '@/web/core/app/api/app';
import FolderPath from '@/components/common/folder/Path'; import FolderPath from '@/components/common/folder/Path';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import CostTooltip from '@/components/core/app/plugin/CostTooltip'; import CostTooltip from '@/components/core/app/plugin/CostTooltip';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import RenderPluginInput from '@/components/core/chat/ChatContainer/PluginRunBox/components/renderPluginInput'; import RenderPluginInput from '@/components/core/chat/ChatContainer/PluginRunBox/components/renderPluginInput';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '../../context';
type Props = { type Props = {
selectedTools: FlowNodeTemplateType[]; selectedTools: FlowNodeTemplateType[];
@@ -54,6 +48,13 @@ type Props = {
onRemoveTool: (tool: NodeTemplateListItemType) => void; onRemoveTool: (tool: NodeTemplateListItemType) => void;
}; };
const childAppSystemKey: string[] = [
NodeInputKeyEnum.forbidStream,
NodeInputKeyEnum.history,
NodeInputKeyEnum.historyMaxAmount,
NodeInputKeyEnum.userChatInput
];
enum TemplateTypeEnum { enum TemplateTypeEnum {
'systemPlugin' = 'systemPlugin', 'systemPlugin' = 'systemPlugin',
'teamPlugin' = 'teamPlugin' 'teamPlugin' = 'teamPlugin'
@@ -61,6 +62,7 @@ enum TemplateTypeEnum {
const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void }) => { const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { appDetail } = useContextSelector(AppContext, (v) => v);
const [templateType, setTemplateType] = useState(TemplateTypeEnum.teamPlugin); const [templateType, setTemplateType] = useState(TemplateTypeEnum.teamPlugin);
const [parentId, setParentId] = useState<ParentIdType>(''); const [parentId, setParentId] = useState<ParentIdType>('');
@@ -85,9 +87,8 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void })
} else if (type === TemplateTypeEnum.teamPlugin) { } else if (type === TemplateTypeEnum.teamPlugin) {
return getTeamPlugTemplates({ return getTeamPlugTemplates({
parentId, parentId,
searchKey: searchVal, searchKey: searchVal
type: [AppTypeEnum.folder, AppTypeEnum.httpPlugin, AppTypeEnum.plugin] }).then((res) => res.filter((app) => app.id !== appDetail._id));
});
} }
}, },
{ {
@@ -238,20 +239,24 @@ const RenderList = React.memo(function RenderList({
} }
}, [configTool, reset]); }, [configTool, reset]);
const { mutate: onClickAdd, isLoading } = useRequest({ const { runAsync: onClickAdd, loading: isLoading } = useRequest2(
mutationFn: async (template: FlowNodeTemplateType) => { async (template: NodeTemplateListItemType) => {
const res = await getPreviewPluginNode({ appId: template.id }); const res = await getPreviewPluginNode({ appId: template.id });
// All input is tool params // 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); onAddTool(res);
} else { } else {
reset(); reset();
setConfigTool(res); 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 ? ( return templates.length === 0 && !isLoadingData ? (
<EmptyTip text={t('common:core.app.ToolCall.No plugin')} /> <EmptyTip text={t('common:core.app.ToolCall.No plugin')} />
@@ -340,6 +345,7 @@ const RenderList = React.memo(function RenderList({
{!!configTool && ( {!!configTool && (
<MyModal <MyModal
isOpen isOpen
isCentered
title={t('common:core.app.ToolCall.Parameter setting')} title={t('common:core.app.ToolCall.Parameter setting')}
iconSrc="core/app/toolCall" iconSrc="core/app/toolCall"
overflow={'auto'} overflow={'auto'}
@@ -359,7 +365,7 @@ const RenderList = React.memo(function RenderList({
)} )}
</HStack> </HStack>
{configTool.inputs {configTool.inputs
.filter((item) => !item.toolDescription) .filter((item) => !item.toolDescription && !childAppSystemKey.includes(item.key))
.map((input) => { .map((input) => {
return ( return (
<Controller <Controller

View File

@@ -250,7 +250,8 @@ const FieldEditModal = ({
} }
} }
if (isToolInput) { // Focus remove toolDescription
if (isToolInput && data.renderTypeList.includes(FlowNodeInputTypeEnum.reference)) {
data.toolDescription = data.description; data.toolDescription = data.description;
} else { } else {
data.toolDescription = undefined; data.toolDescription = undefined;

View File

@@ -356,17 +356,13 @@ const OutLink = ({
const Render = (props: Props) => { const Render = (props: Props) => {
const { shareId, authToken } = props; const { shareId, authToken } = props;
const { localUId, setLocalUId } = useShareChatStore(); const { localUId, loaded } = useShareChatStore();
const contextParams = useMemo(() => { const contextParams = useMemo(() => {
if (!localUId) {
const localId = `shareChat-${Date.now()}-${nanoid()}`;
setLocalUId(localId);
return { shareId, outLinkUid: authToken || localId };
}
return { shareId, outLinkUid: authToken || localUId }; return { shareId, outLinkUid: authToken || localUId };
}, []); }, [authToken, localUId, shareId]);
if (!loaded || !contextParams.outLinkUid) return <></>;
return ( return (
<ChatContextProvider params={contextParams}> <ChatContextProvider params={contextParams}>
@@ -375,7 +371,7 @@ const Render = (props: Props) => {
); );
}; };
export default Render; export default React.memo(Render);
export async function getServerSideProps(context: any) { export async function getServerSideProps(context: any) {
const shareId = context?.query?.shareId || ''; const shareId = context?.query?.shareId || '';

View File

@@ -378,7 +378,23 @@ export function form2AppWorkflow(
y: 545 y: 545
}, },
version: tool.version, 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 outputs: tool.outputs
} }
], ],

View File

@@ -1,25 +1,34 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware'; import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWSYZ1234567890_',
24
);
type State = { type State = {
localUId: string; localUId: string;
setLocalUId: (id: string) => void; loaded: boolean;
}; };
export const useShareChatStore = create<State>()( export const useShareChatStore = create<State>()(
devtools( devtools(
persist( persist(
immer((set, get) => ({ immer((set, get) => ({
localUId: '', localUId: `shareChat-${Date.now()}-${nanoid()}`,
setLocalUId(id) { loaded: false
set((state) => {
state.localUId = id;
});
}
})), })),
{ {
name: 'shareChatStore' name: 'shareChatStore',
onRehydrateStorage: () => (state) => {
if (state) {
state.loaded = true;
}
},
partialize: (state) => ({
localUId: state.localUId
})
} }
) )
) )