perf: agent form editor

This commit is contained in:
archer 2025-09-08 20:07:56 +08:00
parent b0f9dd0f69
commit 3e0eea7cfb
No known key found for this signature in database
GPG Key ID: 4446499B846D4A9E
38 changed files with 896 additions and 2075 deletions

View File

@ -97,7 +97,7 @@ export type AppDatasetSearchParamsType = {
datasetSearchExtensionBg?: string;
};
export type AppSimpleEditFormType = {
export type AppFormEditFormType = {
// templateId: string;
aiSettings: {
[NodeInputKeyEnum.aiModel]: string;

View File

@ -1,16 +1,11 @@
import type { AppChatConfigType, AppSimpleEditFormType } from '../app/type';
import { FlowNodeTypeEnum } from '../workflow/node/constant';
import { FlowNodeTemplateTypeEnum, NodeInputKeyEnum } from '../workflow/constants';
import type { FlowNodeInputItemType } from '../workflow/type/io.d';
import { getAppChatConfig } from '../workflow/utils';
import { type StoreNodeItemType } from '../workflow/type/node';
import type { AppFormEditFormType } from '../app/type';
import { DatasetSearchModeEnum } from '../dataset/constants';
import { type WorkflowTemplateBasicType } from '../workflow/type';
import { AppTypeEnum } from './constants';
import appErrList from '../../common/error/code/app';
import pluginErrList from '../../common/error/code/plugin';
export const getDefaultAppForm = (): AppSimpleEditFormType => {
export const getDefaultAppForm = (): AppFormEditFormType => {
return {
aiSettings: {
model: '',
@ -37,143 +32,7 @@ export const getDefaultAppForm = (): AppSimpleEditFormType => {
};
};
/* format app nodes to edit form */
export const appWorkflow2Form = ({
nodes,
chatConfig
}: {
nodes: StoreNodeItemType[];
chatConfig: AppChatConfigType;
}) => {
const defaultAppForm = getDefaultAppForm();
const findInputValueByKey = (inputs: FlowNodeInputItemType[], key: string) => {
return inputs.find((item) => item.key === key)?.value;
};
nodes.forEach((node) => {
if (
node.flowNodeType === FlowNodeTypeEnum.chatNode ||
node.flowNodeType === FlowNodeTypeEnum.toolCall
) {
defaultAppForm.aiSettings.model = findInputValueByKey(node.inputs, NodeInputKeyEnum.aiModel);
defaultAppForm.aiSettings.systemPrompt = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiSystemPrompt
);
defaultAppForm.aiSettings.temperature = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatTemperature
);
defaultAppForm.aiSettings.maxToken = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatMaxToken
);
defaultAppForm.aiSettings.maxHistories = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.history
);
defaultAppForm.aiSettings.aiChatReasoning = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatReasoning
);
defaultAppForm.aiSettings.aiChatTopP = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatTopP
);
defaultAppForm.aiSettings.aiChatStopSign = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatStopSign
);
defaultAppForm.aiSettings.aiChatResponseFormat = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatResponseFormat
);
defaultAppForm.aiSettings.aiChatJsonSchema = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatJsonSchema
);
} else if (node.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) {
defaultAppForm.dataset.datasets = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSelectList
);
defaultAppForm.dataset.similarity = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSimilarity
);
defaultAppForm.dataset.limit = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetMaxTokens
);
defaultAppForm.dataset.searchMode =
findInputValueByKey(node.inputs, NodeInputKeyEnum.datasetSearchMode) ||
DatasetSearchModeEnum.embedding;
defaultAppForm.dataset.embeddingWeight = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchEmbeddingWeight
);
// Rerank
defaultAppForm.dataset.usingReRank = !!findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchUsingReRank
);
defaultAppForm.dataset.rerankModel = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchRerankModel
);
defaultAppForm.dataset.rerankWeight = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchRerankWeight
);
// Query extension
defaultAppForm.dataset.datasetSearchUsingExtensionQuery = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchUsingExtensionQuery
);
defaultAppForm.dataset.datasetSearchExtensionModel = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchExtensionModel
);
defaultAppForm.dataset.datasetSearchExtensionBg = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchExtensionBg
);
} else if (
node.flowNodeType === FlowNodeTypeEnum.pluginModule ||
node.flowNodeType === FlowNodeTypeEnum.appModule ||
node.flowNodeType === FlowNodeTypeEnum.tool ||
node.flowNodeType === FlowNodeTypeEnum.toolSet
) {
if (!node.pluginId) return;
defaultAppForm.selectedTools.push({
id: node.nodeId,
pluginId: node.pluginId,
name: node.name,
avatar: node.avatar,
intro: node.intro || '',
flowNodeType: node.flowNodeType,
showStatus: node.showStatus,
version: node.version,
inputs: node.inputs,
outputs: node.outputs,
templateType: FlowNodeTemplateTypeEnum.other,
pluginData: node.pluginData,
toolConfig: node.toolConfig
});
} else if (node.flowNodeType === FlowNodeTypeEnum.systemConfig) {
defaultAppForm.chatConfig = getAppChatConfig({
chatConfig,
systemConfigNode: node,
isPublicFetch: true
});
}
});
return defaultAppForm;
};
export const getAppType = (config?: WorkflowTemplateBasicType | AppSimpleEditFormType) => {
export const getAppType = (config?: WorkflowTemplateBasicType | AppFormEditFormType) => {
if (!config) return '';
if ('aiSettings' in config) {

View File

@ -43,12 +43,6 @@ export const AgentNode: FlowNodeTemplateType = {
},
Input_Template_System_Prompt,
Input_Template_History,
{
key: NodeInputKeyEnum.modelConfig,
renderTypeList: [FlowNodeInputTypeEnum.hidden], // Set in the pop-up window
label: '',
valueType: WorkflowIOValueTypeEnum.object
},
{
key: NodeInputKeyEnum.subApps,
renderTypeList: [FlowNodeInputTypeEnum.hidden], // Set in the pop-up window

View File

@ -1,284 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '../context';
import FolderPath from '@/components/common/folder/Path';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { getAppFolderPath } from '@/web/core/app/api/app';
import { Box, Flex, IconButton } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import RouteTab from '../RouteTab';
import { useTranslation } from 'next-i18next';
import { type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { form2AppWorkflow } from '@/web/core/app/utils';
import { TabEnum } from '../context';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTag from '@fastgpt/web/components/common/Tag/index';
import { publishStatusStyle } from '../constants';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SaveButton from '../Workflow/components/SaveButton';
import { useBoolean, useDebounceEffect, useLockFn } from 'ahooks';
import { appWorkflow2Form } from '@fastgpt/global/core/app/utils';
import {
compareSimpleAppSnapshot,
type onSaveSnapshotFnType,
type SimpleAppSnapshotType
} from './useSnapshots';
import PublishHistories from '../PublishHistoriesSlider';
import { type AppVersionSchemaType } from '@fastgpt/global/core/app/version';
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
import { isProduction } from '@fastgpt/global/common/system/constants';
import { useToast } from '@fastgpt/web/hooks/useToast';
import {
checkWorkflowNodeAndConnection,
storeEdge2RenderEdge,
storeNode2FlowNode
} from '@/web/core/workflow/utils';
const Header = ({
forbiddenSaveSnapshot,
appForm,
setAppForm,
past,
setPast,
saveSnapshot
}: {
forbiddenSaveSnapshot: React.MutableRefObject<boolean>;
appForm: AppSimpleEditFormType;
setAppForm: (form: AppSimpleEditFormType) => void;
past: SimpleAppSnapshotType[];
setPast: (value: React.SetStateAction<SimpleAppSnapshotType[]>) => void;
saveSnapshot: onSaveSnapshotFnType;
}) => {
const { t } = useTranslation();
const { isPc } = useSystem();
const { toast } = useToast();
const router = useRouter();
const appId = useContextSelector(AppContext, (v) => v.appId);
const onSaveApp = useContextSelector(AppContext, (v) => v.onSaveApp);
const currentTab = useContextSelector(AppContext, (v) => v.currentTab);
const { lastAppListRouteType } = useSystemStore();
const { data: paths = [] } = useRequest2(
() => getAppFolderPath({ sourceId: appId, type: 'parent' }),
{
manual: false,
refreshDeps: [appId]
}
);
const onClickRoute = useCallback(
(parentId: string) => {
router.push({
pathname: '/dashboard/apps',
query: {
parentId,
type: lastAppListRouteType
}
});
},
[router, lastAppListRouteType]
);
const { runAsync: onClickSave, loading } = useRequest2(
async ({
isPublish,
versionName = formatTime2YMDHMS(new Date()),
autoSave
}: {
isPublish?: boolean;
versionName?: string;
autoSave?: boolean;
}) => {
const { nodes, edges } = form2AppWorkflow(appForm, t);
await onSaveApp({
nodes,
edges,
chatConfig: appForm.chatConfig,
isPublish,
versionName,
autoSave
});
setPast((prevPast) =>
prevPast.map((item, index) =>
index === 0
? {
...item,
isSaved: true
}
: item
)
);
}
);
const [isShowHistories, { setTrue: setIsShowHistories, setFalse: closeHistories }] =
useBoolean(false);
const onSwitchTmpVersion = useCallback(
(data: SimpleAppSnapshotType, customTitle: string) => {
setAppForm(data.appForm);
// Remove multiple "copy-"
const copyText = t('app:version_copy');
const regex = new RegExp(`(${copyText}-)\\1+`, 'g');
const title = customTitle.replace(regex, `$1`);
return saveSnapshot({
appForm: data.appForm,
title
});
},
[saveSnapshot, setAppForm, t]
);
const onSwitchCloudVersion = useCallback(
(appVersion: AppVersionSchemaType) => {
const appForm = appWorkflow2Form({
nodes: appVersion.nodes,
chatConfig: appVersion.chatConfig
});
const res = saveSnapshot({
appForm,
title: `${t('app:version_copy')}-${appVersion.versionName}`
});
forbiddenSaveSnapshot.current = true;
setAppForm(appForm);
return res;
},
[forbiddenSaveSnapshot, saveSnapshot, setAppForm, t]
);
// Check if the workflow is published
const [isSaved, setIsSaved] = useState(false);
useDebounceEffect(
() => {
const savedSnapshot = past.find((snapshot) => snapshot.isSaved);
const val = compareSimpleAppSnapshot(savedSnapshot?.appForm, appForm);
setIsSaved(val);
},
[past],
{ wait: 500 }
);
const onLeaveAutoSave = useLockFn(async () => {
if (isSaved) return;
try {
console.log('Leave auto save');
return onClickSave({ isPublish: false, autoSave: true });
} catch (error) {
console.error(error);
}
});
useEffect(() => {
return () => {
if (isProduction) {
onLeaveAutoSave();
}
};
}, []);
useBeforeunload({
tip: t('common:core.tip.leave page'),
callback: onLeaveAutoSave
});
return (
<Box h={14}>
{!isPc && (
<Flex justifyContent={'center'}>
<RouteTab />
</Flex>
)}
<Flex w={'full'} alignItems={'center'} position={'relative'} h={'full'}>
<Box flex={'1'}>
<FolderPath
rootName={t('app:all_apps')}
paths={paths}
hoverStyle={{ color: 'primary.600' }}
onClick={onClickRoute}
fontSize={'14px'}
/>
</Box>
{isPc && (
<Box position={'absolute'} left={'50%'} transform={'translateX(-50%)'}>
<RouteTab />
</Box>
)}
{currentTab === TabEnum.appEdit && (
<Flex alignItems={'center'}>
{!isShowHistories && (
<>
{isPc && (
<MyTag
mr={3}
type={'borderFill'}
showDot
colorSchema={
isSaved
? publishStatusStyle.published.colorSchema
: publishStatusStyle.unPublish.colorSchema
}
>
{t(
isSaved
? publishStatusStyle.published.text
: publishStatusStyle.unPublish.text
)}
</MyTag>
)}
<IconButton
mr={[2, 4]}
icon={<MyIcon name={'history'} w={'18px'} />}
aria-label={''}
size={'sm'}
w={'30px'}
variant={'whitePrimary'}
onClick={setIsShowHistories}
/>
<SaveButton
isLoading={loading}
onClickSave={onClickSave}
checkData={() => {
const { nodes: storeNodes, edges: storeEdges } = form2AppWorkflow(appForm, t);
const nodes = storeNodes.map((item) => storeNode2FlowNode({ item, t }));
const edges = storeEdges.map((item) => storeEdge2RenderEdge({ edge: item }));
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
if (checkResults) {
toast({
title: t('app:app.error.publish_unExist_app'),
status: 'warning'
});
}
return !checkResults;
}}
/>
</>
)}
</Flex>
)}
</Flex>
{isShowHistories && currentTab === TabEnum.appEdit && (
<PublishHistories<SimpleAppSnapshotType>
onClose={closeHistories}
past={past}
onSwitchTmpVersion={onSwitchTmpVersion}
onSwitchCloudVersion={onSwitchCloudVersion}
positionStyles={{
top: 14,
bottom: 3
}}
/>
)}
</Box>
);
};
export default Header;

View File

@ -1,198 +0,0 @@
import { Button, Flex, HStack, ModalBody, ModalFooter } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import React from 'react';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { Box } from '@chakra-ui/react';
import { type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { childAppSystemKey } from './ToolSelectModal';
import { Controller, useForm } from 'react-hook-form';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import UseGuideModal from '@/components/common/Modal/UseGuideModal';
import InputRender from '@/components/core/app/formRender';
import { nodeInputTypeToInputType } from '@/components/core/app/formRender/utils';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import SecretInputModal, {
type ToolParamsFormType
} from '@/pageComponents/app/plugin/SecretInputModal';
import { SystemToolInputTypeMap } from '@fastgpt/global/core/app/systemTool/constants';
import { useBoolean } from 'ahooks';
const ConfigToolModal = ({
configTool,
onCloseConfigTool,
onAddTool
}: {
configTool: AppSimpleEditFormType['selectedTools'][number];
onCloseConfigTool: () => void;
onAddTool: (tool: AppSimpleEditFormType['selectedTools'][number]) => void;
}) => {
const { t } = useTranslation();
const [isOpenSecretModal, { setTrue: setTrueSecretModal, setFalse: setFalseSecretModal }] =
useBoolean(false);
const { handleSubmit, control } = useForm({
defaultValues: configTool
? configTool.inputs.reduce(
(acc, input) => {
acc[input.key] = input.value || input.defaultValue;
return acc;
},
{} as Record<string, any>
)
: {}
});
return (
<MyModal
isOpen
isCentered
title={t('common:core.app.ToolCall.Parameter setting')}
iconSrc="core/app/toolCall"
overflow={'auto'}
>
<ModalBody>
<HStack mb={4} spacing={1} fontSize={'sm'}>
<MyIcon name={'common/info'} color={'primary.600'} w={'1.25rem'} />
<Box flex={1}>{t('app:tool_input_param_tip')}</Box>
{!!(configTool?.courseUrl || configTool?.userGuide) && (
<UseGuideModal
title={configTool?.name}
iconSrc={configTool?.avatar}
text={configTool?.userGuide}
link={configTool?.courseUrl}
>
{({ onClick }) => (
<Box cursor={'pointer'} color={'primary.500'} onClick={onClick}>
{t('app:workflow.Input guide')}
</Box>
)}
</UseGuideModal>
)}
</HStack>
{configTool.inputs
.filter(
(input) =>
!input.toolDescription &&
!childAppSystemKey.includes(input.key) &&
!input.renderTypeList.includes(FlowNodeInputTypeEnum.selectLLMModel) &&
!input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)
)
.map((input) => {
return (
<Box key={input.key} _notLast={{ mb: 4 }}>
<Flex alignItems={'center'} mb={1}>
{input.required && <Box color={'red.500'}>*</Box>}
<FormLabel>{input.label}</FormLabel>
{input.description && <QuestionTip ml={1} label={input.description} />}
</Flex>
{input.key === NodeInputKeyEnum.systemInputConfig && input.inputList ? (
<Controller
control={control}
name={input.key}
render={({ field: { onChange, value } }) => (
<Box>
<FormLabel mb={1}>{t('common:secret_key')}</FormLabel>
<Button
variant={'whiteBase'}
border={'base'}
borderRadius={'md'}
leftIcon={
<Box w={'6px'} h={'6px'} bg={'primary.600'} borderRadius={'md'} />
}
onClick={setTrueSecretModal}
>
{(() => {
const val = value as ToolParamsFormType;
if (!val) {
return t('workflow:tool_active_config');
}
return t('workflow:tool_active_config_type', {
type: t(SystemToolInputTypeMap[val.type]?.text as any)
});
})()}
</Button>
{isOpenSecretModal && (
<SecretInputModal
isFolder={configTool?.isFolder}
inputConfig={{
...input,
value: value as ToolParamsFormType
}}
hasSystemSecret={configTool?.hasSystemSecret}
secretCost={configTool?.systemKeyCost}
courseUrl={configTool?.courseUrl}
parentId={configTool?.pluginId}
onClose={setFalseSecretModal}
onSubmit={(data) => {
onChange(data);
setFalseSecretModal();
}}
/>
)}
</Box>
)}
/>
) : (
<Controller
control={control}
name={input.key}
rules={{
validate: (value) => {
if (input.valueType === WorkflowIOValueTypeEnum.boolean) {
return value !== undefined;
}
if (input.required) {
return !!value;
}
return true;
}
}}
render={({ field: { onChange, value }, fieldState: { error } }) => {
return (
<InputRender
{...input}
isInvalid={!!error}
inputType={nodeInputTypeToInputType(input.renderTypeList)}
value={value}
onChange={onChange}
/>
);
}}
/>
)}
</Box>
);
})}
</ModalBody>
<ModalFooter gap={3}>
<Button onClick={onCloseConfigTool} variant={'whiteBase'}>
{t('common:Cancel')}
</Button>
<Button
variant={'primary'}
onClick={handleSubmit((data) => {
onAddTool({
...configTool,
inputs: configTool.inputs.map((input) => ({
...input,
value: data[input.key] ?? input.value
}))
});
onCloseConfigTool();
})}
>
{t('common:Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default React.memo(ConfigToolModal);

View File

@ -1,184 +0,0 @@
import { Box, Button, Flex, Grid, useDisclosure } from '@chakra-ui/react';
import React, { useState } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { SmallAddIcon } from '@chakra-ui/icons';
import { type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { theme } from '@fastgpt/web/styles/theme';
import DeleteIcon, { hoverDeleteStyles } from '@fastgpt/web/components/common/Icon/delete';
import ToolSelectModal, { childAppSystemKey } from './ToolSelectModal';
import {
FlowNodeInputTypeEnum,
FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import Avatar from '@fastgpt/web/components/common/Avatar';
import ConfigToolModal from './ConfigToolModal';
import { getWebLLMModel } from '@/web/common/system/utils';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { formatToolError } from '@fastgpt/global/core/app/utils';
const ToolSelect = ({
appForm,
setAppForm
}: {
appForm: AppSimpleEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
}) => {
const { t } = useTranslation();
const [configTool, setConfigTool] = useState<
AppSimpleEditFormType['selectedTools'][number] | null
>(null);
const {
isOpen: isOpenToolsSelect,
onOpen: onOpenToolsSelect,
onClose: onCloseToolsSelect
} = useDisclosure();
const selectedModel = getWebLLMModel(appForm.aiSettings.model);
return (
<>
<Flex alignItems={'center'}>
<Flex alignItems={'center'} flex={1}>
<MyIcon name={'core/app/toolCall'} w={'20px'} />
<FormLabel ml={2}>{t('common:core.app.Tool call')}</FormLabel>
<QuestionTip ml={1} label={t('app:plugin_dispatch_tip')} />
</Flex>
<Button
variant={'transparentBase'}
leftIcon={<SmallAddIcon />}
iconSpacing={1}
mr={'-5px'}
size={'sm'}
fontSize={'sm'}
onClick={onOpenToolsSelect}
>
{t('common:Choose')}
</Button>
</Flex>
<Grid
mt={appForm.selectedTools.length > 0 ? 2 : 0}
gridTemplateColumns={'repeat(2, minmax(0, 1fr))'}
gridGap={[2, 4]}
>
{appForm.selectedTools.map((item) => {
const toolError = formatToolError(item.pluginData?.error);
return (
<MyTooltip key={item.id} label={item.intro}>
<Flex
overflow={'hidden'}
alignItems={'center'}
p={2.5}
bg={'white'}
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
borderRadius={'md'}
border={theme.borders.base}
borderColor={toolError ? 'red.600' : ''}
_hover={{
...hoverDeleteStyles,
borderColor: toolError ? 'red.600' : 'primary.300'
}}
cursor={'pointer'}
onClick={() => {
if (
item.inputs
.filter((input) => !childAppSystemKey.includes(input.key))
.every(
(input) =>
input.toolDescription ||
input.renderTypeList.includes(FlowNodeInputTypeEnum.selectLLMModel) ||
input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)
) ||
toolError ||
item.flowNodeType === FlowNodeTypeEnum.tool ||
item.flowNodeType === FlowNodeTypeEnum.toolSet
) {
return;
}
setConfigTool(item);
}}
>
<Avatar src={item.avatar} w={'1.5rem'} h={'1.5rem'} borderRadius={'sm'} />
<Box
flex={'1 0 0'}
ml={2}
gap={2}
className={'textEllipsis'}
fontSize={'sm'}
color={'myGray.900'}
>
{item.name}
</Box>
{toolError && (
<Flex
bg={'red.50'}
alignItems={'center'}
h={6}
px={2}
rounded={'6px'}
fontSize={'xs'}
fontWeight={'medium'}
>
<MyIcon name={'common/errorFill'} w={'14px'} mr={1} />
<Box color={'red.600'}>{t(toolError as any)}</Box>
</Flex>
)}
<DeleteIcon
ml={2}
onClick={(e) => {
e.stopPropagation();
setAppForm((state: AppSimpleEditFormType) => ({
...state,
selectedTools: state.selectedTools.filter((tool) => tool.id !== item.id)
}));
}}
/>
</Flex>
</MyTooltip>
);
})}
</Grid>
{isOpenToolsSelect && (
<ToolSelectModal
selectedTools={appForm.selectedTools}
chatConfig={appForm.chatConfig}
selectedModel={selectedModel}
onAddTool={(e) => {
setAppForm((state) => ({
...state,
selectedTools: [...state.selectedTools, e]
}));
}}
onRemoveTool={(e) => {
setAppForm((state) => ({
...state,
selectedTools: state.selectedTools.filter((item) => item.pluginId !== e.id)
}));
}}
onClose={onCloseToolsSelect}
/>
)}
{configTool && (
<ConfigToolModal
configTool={configTool}
onCloseConfigTool={() => setConfigTool(null)}
onAddTool={(e) => {
setAppForm((state) => ({
...state,
selectedTools: state.selectedTools.map((item) =>
item.pluginId === configTool.pluginId ? e : item
)
}));
}}
/>
)}
</>
);
};
export default React.memo(ToolSelect);

View File

@ -1,99 +0,0 @@
import { useMemoizedFn } from 'ahooks';
import { useRef, useState } from 'react';
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
import { type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { isEqual } from 'lodash';
export type SimpleAppSnapshotType = {
title: string;
isSaved?: boolean;
appForm: AppSimpleEditFormType;
// abandon
state?: AppSimpleEditFormType;
diff?: Record<string, any>;
};
export type onSaveSnapshotFnType = (props: {
appForm: AppSimpleEditFormType; // Current edited app form data
title?: string;
isSaved?: boolean;
}) => Promise<boolean>;
export const compareSimpleAppSnapshot = (
appForm1?: AppSimpleEditFormType,
appForm2?: AppSimpleEditFormType
) => {
if (
appForm1?.chatConfig &&
appForm2?.chatConfig &&
!isEqual(
{
welcomeText: appForm1.chatConfig?.welcomeText || '',
variables: appForm1.chatConfig?.variables || [],
questionGuide: appForm1.chatConfig?.questionGuide || undefined,
ttsConfig: appForm1.chatConfig?.ttsConfig || undefined,
whisperConfig: appForm1.chatConfig?.whisperConfig || undefined,
chatInputGuide: appForm1.chatConfig?.chatInputGuide || undefined,
fileSelectConfig: appForm1.chatConfig?.fileSelectConfig || undefined
},
{
welcomeText: appForm2.chatConfig?.welcomeText || '',
variables: appForm2.chatConfig?.variables || [],
questionGuide: appForm2.chatConfig?.questionGuide || undefined,
ttsConfig: appForm2.chatConfig?.ttsConfig || undefined,
whisperConfig: appForm2.chatConfig?.whisperConfig || undefined,
chatInputGuide: appForm2.chatConfig?.chatInputGuide || undefined,
fileSelectConfig: appForm2.chatConfig?.fileSelectConfig || undefined
}
)
) {
console.log('chatConfig not equal');
return false;
}
return isEqual({ ...appForm1, chatConfig: undefined }, { ...appForm2, chatConfig: undefined });
};
export const useSimpleAppSnapshots = (appId: string) => {
const forbiddenSaveSnapshot = useRef(false);
const [past, setPast] = useState<SimpleAppSnapshotType[]>([]);
const saveSnapshot: onSaveSnapshotFnType = useMemoizedFn(async ({ appForm, title, isSaved }) => {
if (forbiddenSaveSnapshot.current) {
forbiddenSaveSnapshot.current = false;
return false;
}
if (past.length === 0) {
setPast([
{
title: title || formatTime2YMDHMS(new Date()),
isSaved,
appForm
}
]);
return true;
}
const pastState = past[0];
const isPastEqual = compareSimpleAppSnapshot(pastState?.appForm, appForm);
if (isPastEqual) return false;
setPast((past) => [
{
appForm,
title: title || formatTime2YMDHMS(new Date()),
isSaved
},
...past.slice(0, 99)
]);
return true;
});
return { forbiddenSaveSnapshot, past, setPast, saveSnapshot };
};
export default function Snapshots() {
return <></>;
}

View File

@ -1,23 +1,24 @@
import React, { useState } from 'react';
import { Box } from '@chakra-ui/react';
import ChatTest from './ChatTest';
import AppCard from './AppCard';
import ChatTest from '../FormComponent/ChatTest';
import AppCard from '../FormComponent/AppCard';
import EditForm from './EditForm';
import { type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { cardStyles } from '../constants';
import { type AppFormEditFormType } from '@fastgpt/global/core/app/type';
import { cardStyles } from '../../constants';
import styles from './styles.module.scss';
import styles from '../FormComponent/styles.module.scss';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { type SimpleAppSnapshotType } from './useSnapshots';
import { type SimpleAppSnapshotType } from '../FormComponent/useSnapshots';
import { agentForm2AppWorkflow } from './utils';
const Edit = ({
appForm,
setAppForm,
setPast
}: {
appForm: AppSimpleEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
appForm: AppFormEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppFormEditFormType>>;
setPast: (value: React.SetStateAction<SimpleAppSnapshotType[]>) => void;
}) => {
const { isPc } = useSystem();
@ -42,7 +43,7 @@ const Edit = ({
flex={'1'}
>
<Box {...cardStyles} boxShadow={'2'}>
<AppCard appForm={appForm} setPast={setPast} />
<AppCard appForm={appForm} setPast={setPast} form2WorkflowFn={agentForm2AppWorkflow} />
</Box>
<Box mt={4} {...cardStyles} boxShadow={'3.5'}>
@ -52,7 +53,11 @@ const Edit = ({
)}
{isPc && (
<Box flex={'2 0 0'} w={0} mb={3}>
<ChatTest appForm={appForm} setRenderEdit={setRenderEdit} />
<ChatTest
appForm={appForm}
setRenderEdit={setRenderEdit}
form2WorkflowFn={agentForm2AppWorkflow}
/>
</Box>
)}
</Box>

View File

@ -9,7 +9,7 @@ import {
Button,
HStack
} from '@chakra-ui/react';
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import type { AppFormEditFormType } from '@fastgpt/global/core/app/type.d';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
@ -30,7 +30,7 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import VariableTip from '@/components/common/Textarea/MyTextarea/VariableTip';
import { getWebLLMModel } from '@/web/common/system/utils';
import ToolSelect from './components/ToolSelect';
import ToolSelect from '../FormComponent/ToolSelector/ToolSelect';
import OptimizerPopover from '@/components/common/PromptEditor/OptimizerPopover';
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
@ -60,8 +60,8 @@ const EditForm = ({
appForm,
setAppForm
}: {
appForm: AppSimpleEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
appForm: AppFormEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppFormEditFormType>>;
}) => {
const theme = useTheme();
const router = useRouter();

View File

@ -1,21 +1,20 @@
import React, { useState } from 'react';
import { getDefaultAppForm } from '@fastgpt/global/core/app/utils';
import { appWorkflow2AgentForm } from './utils';
import { agentForm2AppWorkflow, appWorkflow2AgentForm } from './utils';
import Header from './Header';
import Header from '../FormComponent/Header';
import { useContextSelector } from 'use-context-selector';
import { AppContext, TabEnum } from '../context';
import { AppContext, TabEnum } from '../../context';
import dynamic from 'next/dynamic';
import { Box, Flex } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { useSimpleAppSnapshots } from './useSnapshots';
import { useSimpleAppSnapshots } from '../FormComponent/useSnapshots';
import { useDebounceEffect, useMount } from 'ahooks';
import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
import { defaultAppSelectFileConfig } from '@fastgpt/global/core/app/constants';
const Edit = dynamic(() => import('./Edit'));
const Logs = dynamic(() => import('../Logs/index'));
const PublishChannel = dynamic(() => import('../Publish'));
const Logs = dynamic(() => import('../../Logs/index'));
const PublishChannel = dynamic(() => import('../../Publish'));
const AgentEdit = () => {
const { t } = useTranslation();
@ -71,6 +70,8 @@ const AgentEdit = () => {
past={past}
setPast={setPast}
saveSnapshot={saveSnapshot}
form2WorkflowFn={agentForm2AppWorkflow}
form2AppWorkflowFn={appWorkflow2AgentForm}
/>
{currentTab === TabEnum.appEdit ? (
<Edit appForm={appForm} setAppForm={setAppForm} setPast={setPast} />

View File

@ -1,14 +1,13 @@
import { type AppChatConfigType, type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { type StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
import type { AppChatConfigType, AppFormEditFormType } from '@fastgpt/global/core/app/type';
import type {
FlowNodeTemplateType,
StoreNodeItemType
} from '@fastgpt/global/core/workflow/type/node.d';
import {
FlowNodeInputTypeEnum,
FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import {
FlowNodeTemplateTypeEnum,
NodeInputKeyEnum,
WorkflowIOValueTypeEnum
} from '@fastgpt/global/core/workflow/constants';
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { type StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import {
@ -21,7 +20,6 @@ import { workflowStartNodeId } from '@/web/core/app/constants';
import { AgentNode } from '@fastgpt/global/core/workflow/template/system/agent/index';
import { getDefaultAppForm } from '@fastgpt/global/core/app/utils';
import type { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
/* format app nodes to edit form */
@ -38,116 +36,21 @@ export const appWorkflow2AgentForm = ({
};
nodes.forEach((node) => {
if (
node.flowNodeType === FlowNodeTypeEnum.chatNode ||
node.flowNodeType === FlowNodeTypeEnum.toolCall
) {
const inputMap = new Map(node.inputs.map((input) => [input.key, input.value]));
if (node.flowNodeType === FlowNodeTypeEnum.agent) {
defaultAppForm.aiSettings.model = findInputValueByKey(node.inputs, NodeInputKeyEnum.aiModel);
defaultAppForm.aiSettings.systemPrompt = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiSystemPrompt
);
defaultAppForm.aiSettings.temperature = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatTemperature
);
defaultAppForm.aiSettings.maxToken = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatMaxToken
);
defaultAppForm.aiSettings.maxHistories = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.history
);
defaultAppForm.aiSettings.aiChatReasoning = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatReasoning
);
defaultAppForm.aiSettings.aiChatTopP = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatTopP
);
defaultAppForm.aiSettings.aiChatStopSign = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatStopSign
);
defaultAppForm.aiSettings.aiChatResponseFormat = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatResponseFormat
);
defaultAppForm.aiSettings.aiChatJsonSchema = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatJsonSchema
);
} else if (node.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) {
defaultAppForm.dataset.datasets = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSelectList
);
defaultAppForm.dataset.similarity = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSimilarity
);
defaultAppForm.dataset.limit = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetMaxTokens
);
defaultAppForm.dataset.searchMode =
findInputValueByKey(node.inputs, NodeInputKeyEnum.datasetSearchMode) ||
DatasetSearchModeEnum.embedding;
defaultAppForm.dataset.embeddingWeight = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchEmbeddingWeight
);
// Rerank
defaultAppForm.dataset.usingReRank = !!findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchUsingReRank
);
defaultAppForm.dataset.rerankModel = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchRerankModel
);
defaultAppForm.dataset.rerankWeight = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchRerankWeight
);
// Query extension
defaultAppForm.dataset.datasetSearchUsingExtensionQuery = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchUsingExtensionQuery
);
defaultAppForm.dataset.datasetSearchExtensionModel = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchExtensionModel
);
defaultAppForm.dataset.datasetSearchExtensionBg = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchExtensionBg
);
} else if (
node.flowNodeType === FlowNodeTypeEnum.pluginModule ||
node.flowNodeType === FlowNodeTypeEnum.appModule ||
node.flowNodeType === FlowNodeTypeEnum.tool ||
node.flowNodeType === FlowNodeTypeEnum.toolSet
) {
if (!node.pluginId) return;
defaultAppForm.aiSettings.systemPrompt = inputMap.get(NodeInputKeyEnum.aiSystemPrompt);
defaultAppForm.aiSettings.temperature = inputMap.get(NodeInputKeyEnum.aiChatTemperature);
defaultAppForm.aiSettings.maxHistories = inputMap.get(NodeInputKeyEnum.history);
defaultAppForm.aiSettings.aiChatTopP = inputMap.get(NodeInputKeyEnum.aiChatTopP);
defaultAppForm.selectedTools.push({
id: node.nodeId,
pluginId: node.pluginId,
name: node.name,
avatar: node.avatar,
intro: node.intro || '',
flowNodeType: node.flowNodeType,
showStatus: node.showStatus,
version: node.version,
inputs: node.inputs,
outputs: node.outputs,
templateType: FlowNodeTemplateTypeEnum.other,
pluginData: node.pluginData,
toolConfig: node.toolConfig
});
const subApps = inputMap.get(NodeInputKeyEnum.subApps) as FlowNodeTemplateType[];
console.log(subApps);
if (subApps) {
subApps.forEach((subApp) => {
defaultAppForm.selectedTools.push(subApp);
});
}
} else if (node.flowNodeType === FlowNodeTypeEnum.systemConfig) {
defaultAppForm.chatConfig = getAppChatConfig({
chatConfig,
@ -160,12 +63,12 @@ export const appWorkflow2AgentForm = ({
return defaultAppForm;
};
type WorkflowType = {
export type WorkflowType = {
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
};
export function agentForm2AppWorkflow(
data: AppSimpleEditFormType,
data: AppFormEditFormType,
t: any // i18nT
): WorkflowType & {
chatConfig: AppChatConfigType;

View File

@ -10,11 +10,11 @@ import {
ModalFooter
} from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { type AppSchema, type AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import { type AppSchema, type AppFormEditFormType } from '@fastgpt/global/core/app/type.d';
import { useTranslation } from 'next-i18next';
import Avatar from '@fastgpt/web/components/common/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import TagsEditModal from '../TagsEditModal';
import TagsEditModal from '../../TagsEditModal';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { AppContext } from '@/pageComponents/app/detail/context';
import { useContextSelector } from 'use-context-selector';
@ -22,17 +22,19 @@ import MyMenu from '@fastgpt/web/components/common/MyMenu';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { postTransition2Workflow } from '@/web/core/app/api/app';
import { form2AppWorkflow } from '@/web/core/app/utils';
import { type SimpleAppSnapshotType } from './useSnapshots';
import type { SimpleAppSnapshotType } from './useSnapshots';
import ExportConfigPopover from '@/pageComponents/app/detail/ExportConfigPopover';
import { ChatSidebarPaneEnum } from '@/pageComponents/chat/constants';
import type { Form2WorkflowFnType } from './type';
const AppCard = ({
appForm,
setPast
setPast,
form2WorkflowFn
}: {
appForm: AppSimpleEditFormType;
appForm: AppFormEditFormType;
setPast: (value: React.SetStateAction<SimpleAppSnapshotType[]>) => void;
form2WorkflowFn: Form2WorkflowFnType;
}) => {
const router = useRouter();
const { t } = useTranslation();
@ -49,7 +51,7 @@ const AppCard = ({
const [transitionCreateNew, setTransitionCreateNew] = useState<boolean>();
const { runAsync: onTransition, loading: transiting } = useRequest2(
async () => {
const { nodes, edges } = form2AppWorkflow(appForm, t);
const { nodes, edges } = form2WorkflowFn(appForm, t);
await onSaveApp({
nodes,
edges,

View File

@ -5,25 +5,26 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useSafeState } from 'ahooks';
import { type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { agentForm2AppWorkflow } from './utils';
import type { AppFormEditFormType } from '@fastgpt/global/core/app/type';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '../context';
import { useChatTest } from '../useChatTest';
import { AppContext } from '../../context';
import { useChatTest } from '../../useChatTest';
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import ChatRecordContextProvider from '@/web/core/chat/context/chatRecordContext';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { cardStyles } from '../constants';
import { cardStyles } from '../../constants';
import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList';
import VariablePopover from '@/components/core/chat/ChatContainer/components/VariablePopover';
import { ChatTypeEnum } from '@/components/core/chat/ChatContainer/ChatBox/constants';
import type { Form2WorkflowFnType } from './type';
type Props = {
appForm: AppSimpleEditFormType;
appForm: AppFormEditFormType;
setRenderEdit: React.Dispatch<React.SetStateAction<boolean>>;
form2WorkflowFn: Form2WorkflowFnType;
};
const ChatTest = ({ appForm, setRenderEdit }: Props) => {
const ChatTest = ({ appForm, setRenderEdit, form2WorkflowFn }: Props) => {
const { t } = useTranslation();
const { appDetail } = useContextSelector(AppContext, (v) => v);
@ -38,7 +39,7 @@ const ChatTest = ({ appForm, setRenderEdit }: Props) => {
});
useEffect(() => {
const { nodes, edges } = agentForm2AppWorkflow(appForm, t);
const { nodes, edges } = form2WorkflowFn(appForm, t);
setWorkflowData({ nodes, edges });
}, [appForm, setWorkflowData, t]);
@ -103,7 +104,7 @@ const ChatTest = ({ appForm, setRenderEdit }: Props) => {
);
};
const Render = ({ appForm, setRenderEdit }: Props) => {
const Render = ({ appForm, setRenderEdit, form2WorkflowFn }: Props) => {
const { chatId } = useChatStore();
const { appDetail } = useContextSelector(AppContext, (v) => v);
@ -124,7 +125,11 @@ const Render = ({ appForm, setRenderEdit }: Props) => {
showNodeStatus
>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<ChatTest appForm={appForm} setRenderEdit={setRenderEdit} />
<ChatTest
appForm={appForm}
setRenderEdit={setRenderEdit}
form2WorkflowFn={form2WorkflowFn}
/>
</ChatRecordContextProvider>
</ChatItemContextProvider>
);

View File

@ -1,32 +1,30 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '../context';
import { AppContext } from '../../context';
import FolderPath from '@/components/common/folder/Path';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { getAppFolderPath } from '@/web/core/app/api/app';
import { Box, Flex, IconButton } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import RouteTab from '../RouteTab';
import RouteTab from '../../RouteTab';
import { useTranslation } from 'next-i18next';
import { type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { form2AppWorkflow } from '@/web/core/app/utils';
import { TabEnum } from '../context';
import type { AppFormEditFormType } from '@fastgpt/global/core/app/type';
import { TabEnum } from '../../context';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTag from '@fastgpt/web/components/common/Tag/index';
import { publishStatusStyle } from '../constants';
import { publishStatusStyle } from '../../constants';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SaveButton from '../Workflow/components/SaveButton';
import SaveButton from '../../Workflow/components/SaveButton';
import { useBoolean, useDebounceEffect, useLockFn } from 'ahooks';
import { appWorkflow2Form } from '@fastgpt/global/core/app/utils';
import {
compareSimpleAppSnapshot,
type onSaveSnapshotFnType,
type SimpleAppSnapshotType
} from './useSnapshots';
import PublishHistories from '../PublishHistoriesSlider';
import { type AppVersionSchemaType } from '@fastgpt/global/core/app/version';
import PublishHistories from '../../PublishHistoriesSlider';
import type { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
import { isProduction } from '@fastgpt/global/common/system/constants';
import { useToast } from '@fastgpt/web/hooks/useToast';
@ -35,6 +33,7 @@ import {
storeEdge2RenderEdge,
storeNode2FlowNode
} from '@/web/core/workflow/utils';
import type { AppForm2WorkflowFnType, Form2WorkflowFnType } from './type.d';
const Header = ({
forbiddenSaveSnapshot,
@ -42,14 +41,18 @@ const Header = ({
setAppForm,
past,
setPast,
saveSnapshot
saveSnapshot,
form2WorkflowFn,
form2AppWorkflowFn
}: {
forbiddenSaveSnapshot: React.MutableRefObject<boolean>;
appForm: AppSimpleEditFormType;
setAppForm: (form: AppSimpleEditFormType) => void;
appForm: AppFormEditFormType;
setAppForm: (form: AppFormEditFormType) => void;
past: SimpleAppSnapshotType[];
setPast: (value: React.SetStateAction<SimpleAppSnapshotType[]>) => void;
saveSnapshot: onSaveSnapshotFnType;
form2AppWorkflowFn: AppForm2WorkflowFnType;
form2WorkflowFn: Form2WorkflowFnType;
}) => {
const { t } = useTranslation();
const { isPc } = useSystem();
@ -91,7 +94,7 @@ const Header = ({
versionName?: string;
autoSave?: boolean;
}) => {
const { nodes, edges } = form2AppWorkflow(appForm, t);
const { nodes, edges } = form2WorkflowFn(appForm, t);
await onSaveApp({
nodes,
edges,
@ -134,7 +137,7 @@ const Header = ({
);
const onSwitchCloudVersion = useCallback(
(appVersion: AppVersionSchemaType) => {
const appForm = appWorkflow2Form({
const appForm = form2AppWorkflowFn({
nodes: appVersion.nodes,
chatConfig: appVersion.chatConfig
});
@ -243,7 +246,7 @@ const Header = ({
isLoading={loading}
onClickSave={onClickSave}
checkData={() => {
const { nodes: storeNodes, edges: storeEdges } = form2AppWorkflow(appForm, t);
const { nodes: storeNodes, edges: storeEdges } = form2WorkflowFn(appForm, t);
const nodes = storeNodes.map((item) => storeNode2FlowNode({ item, t }));
const edges = storeEdges.map((item) => storeEdge2RenderEdge({ edge: item }));

View File

@ -4,7 +4,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { SmallAddIcon } from '@chakra-ui/icons';
import { type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { type AppFormEditFormType } from '@fastgpt/global/core/app/type';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { theme } from '@fastgpt/web/styles/theme';
import DeleteIcon, { hoverDeleteStyles } from '@fastgpt/web/components/common/Icon/delete';
@ -14,7 +14,7 @@ import {
FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import Avatar from '@fastgpt/web/components/common/Avatar';
import ConfigToolModal from './ConfigToolModal';
import ConfigToolModal from '../../component/ConfigToolModal';
import { getWebLLMModel } from '@/web/common/system/utils';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { formatToolError } from '@fastgpt/global/core/app/utils';
@ -25,14 +25,14 @@ const ToolSelect = ({
appForm,
setAppForm
}: {
appForm: AppSimpleEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
appForm: AppFormEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppFormEditFormType>>;
}) => {
const { t } = useTranslation();
const [configTool, setConfigTool] = useState<
AppSimpleEditFormType['selectedTools'][number] | null
>(null);
const [configTool, setConfigTool] = useState<AppFormEditFormType['selectedTools'][number] | null>(
null
);
const {
isOpen: isOpenToolsSelect,
@ -141,7 +141,7 @@ const ToolSelect = ({
ml={2}
onClick={(e) => {
e.stopPropagation();
setAppForm((state: AppSimpleEditFormType) => ({
setAppForm((state: AppFormEditFormType) => ({
...state,
selectedTools: state.selectedTools.filter((tool) => tool.id !== item.id)
}));

View File

@ -21,16 +21,16 @@ 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 { 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 AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
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 './ConfigToolModal';
import ConfigToolModal from '../../component/ConfigToolModal';
import CostTooltip from '@/components/core/app/tool/CostTooltip';
import { useSafeTranslation } from '@fastgpt/web/hooks/useSafeTranslation';
import { useSystemStore } from '@/web/common/system/useSystemStore';
@ -42,7 +42,7 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
type Props = {
selectedTools: FlowNodeTemplateType[];
chatConfig: AppSimpleEditFormType['chatConfig'];
chatConfig: AppFormEditFormType['chatConfig'];
selectedModel: LLMModelItemType;
onAddTool: (tool: FlowNodeTemplateType) => void;
onRemoveTool: (tool: NodeTemplateListItemType) => void;

View File

@ -0,0 +1,18 @@
import type { AppChatConfigType, AppFormEditFormType } from '@fastgpt/global/core/app/type';
import type { WorkflowType } from '../Agent/utils';
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
export type AppForm2WorkflowFnType = ({
nodes,
chatConfig
}: {
nodes: StoreNodeItemType[];
chatConfig: AppChatConfigType;
}) => AppFormEditFormType;
export type Form2WorkflowFnType = (
data: AppFormEditFormType,
t: any
) => WorkflowType & {
chatConfig: AppChatConfigType;
};

View File

@ -1,27 +1,27 @@
import { useMemoizedFn } from 'ahooks';
import { useRef, useState } from 'react';
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
import { type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { type AppFormEditFormType } from '@fastgpt/global/core/app/type';
import { isEqual } from 'lodash';
export type SimpleAppSnapshotType = {
title: string;
isSaved?: boolean;
appForm: AppSimpleEditFormType;
appForm: AppFormEditFormType;
// abandon
state?: AppSimpleEditFormType;
state?: AppFormEditFormType;
diff?: Record<string, any>;
};
export type onSaveSnapshotFnType = (props: {
appForm: AppSimpleEditFormType; // Current edited app form data
appForm: AppFormEditFormType; // Current edited app form data
title?: string;
isSaved?: boolean;
}) => Promise<boolean>;
export const compareSimpleAppSnapshot = (
appForm1?: AppSimpleEditFormType,
appForm2?: AppSimpleEditFormType
appForm1?: AppFormEditFormType,
appForm2?: AppFormEditFormType
) => {
if (
appForm1?.chatConfig &&

View File

@ -1,13 +1,13 @@
import { Box, Button, Flex, HStack, IconButton } from '@chakra-ui/react';
import React, { useState } from 'react';
import { AppContext } from '../context';
import { AppContext } from '../../context';
import { useContextSelector } from 'use-context-selector';
import Avatar from '@fastgpt/web/components/common/Avatar';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { type AppSchema } from '@fastgpt/global/core/app/type';
import TagsEditModal from '../TagsEditModal';
import TagsEditModal from '../../TagsEditModal';
const AppCard = () => {
const { t } = useTranslation();

View File

@ -1,11 +1,11 @@
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import React, { useEffect, useMemo, useState } from 'react';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '../context';
import { AppContext } from '../../context';
import ChatItemContextProvider from '@/web/core/chat/context/chatItemContext';
import ChatRecordContextProvider from '@/web/core/chat/context/chatRecordContext';
import { Box, Button, Flex, HStack } from '@chakra-ui/react';
import { cardStyles } from '../constants';
import { cardStyles } from '../../constants';
import { useTranslation } from 'next-i18next';
import { type McpToolConfigType } from '@fastgpt/global/core/app/tool/mcpTool/type';
import { useForm } from 'react-hook-form';
@ -17,7 +17,7 @@ import { valueTypeToInputType } from '@/components/core/app/formRender/utils';
import { getNodeInputTypeFromSchemaInputType } from '@fastgpt/global/core/app/jsonschema';
import LabelAndFormRender from '@/components/core/app/formRender/LabelAndForm';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import ValueTypeLabel from '../WorkflowComponents/Flow/nodes/render/ValueTypeLabel';
import ValueTypeLabel from '../../WorkflowComponents/Flow/nodes/render/ValueTypeLabel';
import { valueTypeFormat } from '@fastgpt/global/core/workflow/runtime/utils';
const ChatTest = ({

View File

@ -1,8 +1,8 @@
import { Box, Flex } from '@chakra-ui/react';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import React from 'react';
import styles from '../SimpleApp/styles.module.scss';
import { cardStyles } from '../constants';
import styles from '../FormComponent/styles.module.scss';
import { cardStyles } from '../../constants';
import AppCard from './AppCard';
import ChatTest from './ChatTest';
import MyBox from '@fastgpt/web/components/common/MyBox';

View File

@ -4,7 +4,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { useTranslation } from 'next-i18next';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { AppContext } from '../context';
import { AppContext } from '../../context';
import { useContextSelector } from 'use-context-selector';
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
import { type McpToolConfigType } from '@fastgpt/global/core/app/tool/mcpTool/type';

View File

@ -3,7 +3,7 @@ import FolderPath from '@/components/common/folder/Path';
import { useTranslation } from 'next-i18next';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '../context';
import { AppContext } from '../../context';
import { getAppFolderPath } from '@/web/core/app/api/app';
import { useCallback } from 'react';
import { useRouter } from 'next/router';

View File

@ -3,7 +3,7 @@ import React, { useMemo, useState } from 'react';
import Header from './Header';
import Edit from './Edit';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '../context';
import { AppContext } from '../../context';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { type McpToolConfigType } from '@fastgpt/global/core/app/tool/mcpTool/type';
import { type StoreSecretValueType } from '@fastgpt/global/common/secret/type';

View File

@ -1,23 +1,24 @@
import React, { useState } from 'react';
import { Box } from '@chakra-ui/react';
import ChatTest from './ChatTest';
import AppCard from './AppCard';
import AppCard from '../FormComponent/AppCard';
import EditForm from './EditForm';
import { type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { cardStyles } from '../constants';
import { type AppFormEditFormType } from '@fastgpt/global/core/app/type';
import { cardStyles } from '../../constants';
import styles from './styles.module.scss';
import styles from '../FormComponent/styles.module.scss';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { type SimpleAppSnapshotType } from './useSnapshots';
import { type SimpleAppSnapshotType } from '../FormComponent/useSnapshots';
import ChatTest from '../FormComponent/ChatTest';
import { form2AppWorkflow } from './utils';
const Edit = ({
appForm,
setAppForm,
setPast
}: {
appForm: AppSimpleEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
appForm: AppFormEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppFormEditFormType>>;
setPast: (value: React.SetStateAction<SimpleAppSnapshotType[]>) => void;
}) => {
const { isPc } = useSystem();
@ -42,7 +43,7 @@ const Edit = ({
flex={'1'}
>
<Box {...cardStyles} boxShadow={'2'}>
<AppCard appForm={appForm} setPast={setPast} />
<AppCard appForm={appForm} setPast={setPast} form2WorkflowFn={form2AppWorkflow} />
</Box>
<Box mt={4} {...cardStyles} boxShadow={'3.5'}>
@ -52,7 +53,11 @@ const Edit = ({
)}
{isPc && (
<Box flex={'2 0 0'} w={0} mb={3}>
<ChatTest appForm={appForm} setRenderEdit={setRenderEdit} />
<ChatTest
appForm={appForm}
setRenderEdit={setRenderEdit}
form2WorkflowFn={form2AppWorkflow}
/>
</Box>
)}
</Box>

View File

@ -9,7 +9,7 @@ import {
Button,
HStack
} from '@chakra-ui/react';
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import type { AppFormEditFormType } from '@fastgpt/global/core/app/type.d';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
@ -30,7 +30,7 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import VariableTip from '@/components/common/Textarea/MyTextarea/VariableTip';
import { getWebLLMModel } from '@/web/common/system/utils';
import ToolSelect from './components/ToolSelect';
import ToolSelect from '../FormComponent/ToolSelector/ToolSelect';
import OptimizerPopover from '@/components/common/PromptEditor/OptimizerPopover';
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
@ -60,8 +60,8 @@ const EditForm = ({
appForm,
setAppForm
}: {
appForm: AppSimpleEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
appForm: AppFormEditFormType;
setAppForm: React.Dispatch<React.SetStateAction<AppFormEditFormType>>;
}) => {
const theme = useTheme();
const router = useRouter();

View File

@ -39,21 +39,21 @@ 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 { 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 AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
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 './ConfigToolModal';
import ConfigToolModal from '../../component/ConfigToolModal';
import CostTooltip from '@/components/core/app/plugin/CostTooltip';
type Props = {
selectedTools: FlowNodeTemplateType[];
chatConfig: AppSimpleEditFormType['chatConfig'];
chatConfig: AppFormEditFormType['chatConfig'];
selectedModel: LLMModelItemType;
onAddTool: (tool: FlowNodeTemplateType) => void;
onRemoveTool: (tool: NodeTemplateListItemType) => void;

View File

@ -1,21 +1,21 @@
import React, { useState } from 'react';
import { appWorkflow2Form, getDefaultAppForm } from '@fastgpt/global/core/app/utils';
import { getDefaultAppForm } from '@fastgpt/global/core/app/utils';
import Header from './Header';
import Header from '../FormComponent/Header';
import { useContextSelector } from 'use-context-selector';
import { AppContext, TabEnum } from '../context';
import { AppContext, TabEnum } from '../../context';
import dynamic from 'next/dynamic';
import { Box, Flex } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { type SimpleAppSnapshotType, useSimpleAppSnapshots } from './useSnapshots';
import { useSimpleAppSnapshots } from '../FormComponent/useSnapshots';
import { useDebounceEffect, useMount } from 'ahooks';
import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
import { getAppConfigByDiff } from '@/web/core/app/diff';
import { defaultAppSelectFileConfig } from '@fastgpt/global/core/app/constants';
import { form2AppWorkflow, appWorkflow2Form } from './utils';
const Edit = dynamic(() => import('./Edit'));
const Logs = dynamic(() => import('../Logs/index'));
const PublishChannel = dynamic(() => import('../Publish'));
const Logs = dynamic(() => import('../../Logs/index'));
const PublishChannel = dynamic(() => import('../../Publish'));
const SimpleEdit = () => {
const { t } = useTranslation();
@ -81,6 +81,8 @@ const SimpleEdit = () => {
past={past}
setPast={setPast}
saveSnapshot={saveSnapshot}
form2WorkflowFn={form2AppWorkflow}
form2AppWorkflowFn={appWorkflow2Form}
/>
{currentTab === TabEnum.appEdit ? (
<Edit appForm={appForm} setAppForm={setAppForm} setPast={setPast} />

View File

@ -0,0 +1,708 @@
import { type AppChatConfigType, type AppFormEditFormType } from '@fastgpt/global/core/app/type';
import { type StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
import {
FlowNodeInputTypeEnum,
FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import {
FlowNodeTemplateTypeEnum,
NodeInputKeyEnum,
NodeOutputKeyEnum,
WorkflowIOValueTypeEnum
} from '@fastgpt/global/core/workflow/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { type StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { ToolCallNode } from '@fastgpt/global/core/workflow/template/system/toolCall';
import {
WorkflowStart,
userFilesInput
} from '@fastgpt/global/core/workflow/template/system/workflowStart';
import { SystemConfigNode } from '@fastgpt/global/core/workflow/template/system/systemConfig';
import {
AiChatModule,
AiChatQuotePrompt,
AiChatQuoteRole,
AiChatQuoteTemplate
} from '@fastgpt/global/core/workflow/template/system/aiChat/index';
import { DatasetSearchModule } from '@fastgpt/global/core/workflow/template/system/datasetSearch';
import { i18nT } from '@fastgpt/web/i18n/utils';
import {
Input_Template_File_Link,
Input_Template_UserChatInput
} from '@fastgpt/global/core/workflow/template/input';
import { workflowStartNodeId } from '@/web/core/app/constants';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
import { getDefaultAppForm } from '@fastgpt/global/core/app/utils';
import type { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
/* format app nodes to edit form */
export const appWorkflow2Form = ({
nodes,
chatConfig
}: {
nodes: StoreNodeItemType[];
chatConfig: AppChatConfigType;
}) => {
const defaultAppForm = getDefaultAppForm();
const findInputValueByKey = (inputs: FlowNodeInputItemType[], key: string) => {
return inputs.find((item) => item.key === key)?.value;
};
nodes.forEach((node) => {
if (
node.flowNodeType === FlowNodeTypeEnum.chatNode ||
node.flowNodeType === FlowNodeTypeEnum.toolCall
) {
defaultAppForm.aiSettings.model = findInputValueByKey(node.inputs, NodeInputKeyEnum.aiModel);
defaultAppForm.aiSettings.systemPrompt = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiSystemPrompt
);
defaultAppForm.aiSettings.temperature = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatTemperature
);
defaultAppForm.aiSettings.maxToken = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatMaxToken
);
defaultAppForm.aiSettings.maxHistories = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.history
);
defaultAppForm.aiSettings.aiChatReasoning = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatReasoning
);
defaultAppForm.aiSettings.aiChatTopP = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatTopP
);
defaultAppForm.aiSettings.aiChatStopSign = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatStopSign
);
defaultAppForm.aiSettings.aiChatResponseFormat = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatResponseFormat
);
defaultAppForm.aiSettings.aiChatJsonSchema = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.aiChatJsonSchema
);
} else if (node.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) {
defaultAppForm.dataset.datasets = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSelectList
);
defaultAppForm.dataset.similarity = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSimilarity
);
defaultAppForm.dataset.limit = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetMaxTokens
);
defaultAppForm.dataset.searchMode =
findInputValueByKey(node.inputs, NodeInputKeyEnum.datasetSearchMode) ||
DatasetSearchModeEnum.embedding;
defaultAppForm.dataset.embeddingWeight = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchEmbeddingWeight
);
// Rerank
defaultAppForm.dataset.usingReRank = !!findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchUsingReRank
);
defaultAppForm.dataset.rerankModel = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchRerankModel
);
defaultAppForm.dataset.rerankWeight = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchRerankWeight
);
// Query extension
defaultAppForm.dataset.datasetSearchUsingExtensionQuery = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchUsingExtensionQuery
);
defaultAppForm.dataset.datasetSearchExtensionModel = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchExtensionModel
);
defaultAppForm.dataset.datasetSearchExtensionBg = findInputValueByKey(
node.inputs,
NodeInputKeyEnum.datasetSearchExtensionBg
);
} else if (
node.flowNodeType === FlowNodeTypeEnum.pluginModule ||
node.flowNodeType === FlowNodeTypeEnum.appModule ||
node.flowNodeType === FlowNodeTypeEnum.tool ||
node.flowNodeType === FlowNodeTypeEnum.toolSet
) {
if (!node.pluginId) return;
defaultAppForm.selectedTools.push({
id: node.nodeId,
pluginId: node.pluginId,
name: node.name,
avatar: node.avatar,
intro: node.intro || '',
flowNodeType: node.flowNodeType,
showStatus: node.showStatus,
version: node.version,
inputs: node.inputs,
outputs: node.outputs,
templateType: FlowNodeTemplateTypeEnum.other,
pluginData: node.pluginData,
toolConfig: node.toolConfig
});
} else if (node.flowNodeType === FlowNodeTypeEnum.systemConfig) {
defaultAppForm.chatConfig = getAppChatConfig({
chatConfig,
systemConfigNode: node,
isPublicFetch: true
});
}
});
return defaultAppForm;
};
export type WorkflowType = {
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
};
export function form2AppWorkflow(
data: AppFormEditFormType,
t: any // i18nT
): WorkflowType & {
chatConfig: AppChatConfigType;
} {
const datasetNodeId = 'iKBoX2vIzETU';
const aiChatNodeId = '7BdojPlukIQw';
const selectedDatasets = data.dataset.datasets;
function systemConfigTemplate(): StoreNodeItemType {
return {
nodeId: SystemConfigNode.id,
name: t(SystemConfigNode.name),
intro: '',
flowNodeType: SystemConfigNode.flowNodeType,
position: {
x: 531.2422736065552,
y: -486.7611729549753
},
version: SystemConfigNode.version,
inputs: [],
outputs: []
};
}
function workflowStartTemplate(): StoreNodeItemType {
return {
nodeId: workflowStartNodeId,
name: t(WorkflowStart.name),
intro: '',
avatar: WorkflowStart.avatar,
flowNodeType: WorkflowStart.flowNodeType,
position: {
x: 558.4082376415505,
y: 123.72387429194112
},
version: WorkflowStart.version,
inputs: WorkflowStart.inputs,
outputs: [...WorkflowStart.outputs, userFilesInput]
};
}
function aiChatTemplate(formData: AppFormEditFormType): StoreNodeItemType {
return {
nodeId: aiChatNodeId,
name: t(AiChatModule.name),
intro: t(AiChatModule.intro),
avatar: AiChatModule.avatar,
flowNodeType: AiChatModule.flowNodeType,
showStatus: true,
position: {
x: 1106.3238387960757,
y: -350.6030674683474
},
version: AiChatModule.version,
inputs: [
{
key: NodeInputKeyEnum.aiModel,
renderTypeList: [FlowNodeInputTypeEnum.settingLLMModel, FlowNodeInputTypeEnum.reference],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.aiSettings.model
},
{
key: NodeInputKeyEnum.aiChatTemperature,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: formData.aiSettings.temperature,
valueType: WorkflowIOValueTypeEnum.number,
min: 0,
max: 10,
step: 1
},
{
key: NodeInputKeyEnum.aiChatMaxToken,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: formData.aiSettings.maxToken,
valueType: WorkflowIOValueTypeEnum.number,
min: 100,
max: 4000,
step: 50
},
{
key: NodeInputKeyEnum.aiChatIsResponseText,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: true,
valueType: WorkflowIOValueTypeEnum.boolean
},
AiChatQuoteRole,
AiChatQuoteTemplate,
AiChatQuotePrompt,
{
key: NodeInputKeyEnum.aiSystemPrompt,
renderTypeList: [FlowNodeInputTypeEnum.textarea, FlowNodeInputTypeEnum.reference],
max: 3000,
valueType: WorkflowIOValueTypeEnum.string,
label: 'core.ai.Prompt',
description: 'core.app.tip.systemPromptTip',
placeholder: 'core.app.tip.chatNodeSystemPromptTip',
value: formData.aiSettings.systemPrompt
},
{
key: NodeInputKeyEnum.history,
renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.chatHistory,
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
value: formData.aiSettings.maxHistories
},
{
key: NodeInputKeyEnum.userChatInput,
renderTypeList: [FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.textarea],
valueType: WorkflowIOValueTypeEnum.string,
label: i18nT('common:core.module.input.label.user question'),
required: true,
toolDescription: i18nT('common:core.module.input.label.user question'),
value: [workflowStartNodeId, NodeInputKeyEnum.userChatInput]
},
{
key: NodeInputKeyEnum.aiChatDatasetQuote,
renderTypeList: [FlowNodeInputTypeEnum.settingDatasetQuotePrompt],
label: '',
debugLabel: i18nT('common:core.module.Dataset quote.label'),
description: '',
valueType: WorkflowIOValueTypeEnum.datasetQuote,
value: selectedDatasets?.length > 0 ? [datasetNodeId, 'quoteQA'] : undefined
},
{
...Input_Template_File_Link,
value: [[workflowStartNodeId, NodeOutputKeyEnum.userFiles]]
},
{
key: NodeInputKeyEnum.aiChatVision,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.boolean,
value: true
},
{
key: NodeInputKeyEnum.aiChatReasoning,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.boolean,
value: formData.aiSettings.aiChatReasoning
},
{
key: NodeInputKeyEnum.aiChatTopP,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.number,
value: formData.aiSettings.aiChatTopP
},
{
key: NodeInputKeyEnum.aiChatStopSign,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.aiSettings.aiChatStopSign
},
{
key: NodeInputKeyEnum.aiChatResponseFormat,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.aiSettings.aiChatResponseFormat
},
{
key: NodeInputKeyEnum.aiChatJsonSchema,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.aiSettings.aiChatJsonSchema
}
],
outputs: AiChatModule.outputs
};
}
function datasetNodeTemplate(formData: AppFormEditFormType, question: any): StoreNodeItemType {
return {
nodeId: datasetNodeId,
name: t(DatasetSearchModule.name),
intro: t('app:dataset_search_tool_description'),
avatar: DatasetSearchModule.avatar,
flowNodeType: DatasetSearchModule.flowNodeType,
showStatus: true,
position: {
x: 918.5901682164496,
y: -227.11542247619582
},
version: DatasetSearchModule.version,
inputs: [
{
key: NodeInputKeyEnum.datasetSelectList,
renderTypeList: [FlowNodeInputTypeEnum.selectDataset, FlowNodeInputTypeEnum.reference],
label: i18nT('common:core.module.input.label.Select dataset'),
value: selectedDatasets,
valueType: WorkflowIOValueTypeEnum.selectDataset,
list: [],
required: true
},
{
key: NodeInputKeyEnum.datasetSimilarity,
renderTypeList: [FlowNodeInputTypeEnum.selectDatasetParamsModal],
label: '',
value: formData.dataset.similarity,
valueType: WorkflowIOValueTypeEnum.number
},
{
key: NodeInputKeyEnum.datasetMaxTokens,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: formData.dataset.limit,
valueType: WorkflowIOValueTypeEnum.number
},
{
key: NodeInputKeyEnum.datasetSearchMode,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.dataset.searchMode
},
{
key: NodeInputKeyEnum.datasetSearchEmbeddingWeight,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.number,
value: formData.dataset.embeddingWeight
},
{
key: NodeInputKeyEnum.datasetSearchUsingReRank,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.boolean,
value: formData.dataset.usingReRank
},
{
key: NodeInputKeyEnum.datasetSearchRerankModel,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.dataset.rerankModel
},
{
key: NodeInputKeyEnum.datasetSearchRerankWeight,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.number,
value: formData.dataset.rerankWeight
},
{
key: NodeInputKeyEnum.datasetSearchUsingExtensionQuery,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.boolean,
value: formData.dataset.datasetSearchUsingExtensionQuery
},
{
key: NodeInputKeyEnum.datasetSearchExtensionModel,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.dataset.datasetSearchExtensionModel
},
{
key: NodeInputKeyEnum.datasetSearchExtensionBg,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.dataset.datasetSearchExtensionBg
},
{
...Input_Template_UserChatInput,
toolDescription: i18nT('workflow:content_to_search'),
value: question
}
],
outputs: DatasetSearchModule.outputs
};
}
// Start, AiChat
function simpleChatTemplate(formData: AppFormEditFormType): WorkflowType {
return {
nodes: [aiChatTemplate(formData)],
edges: [
{
source: workflowStartNodeId,
target: aiChatNodeId,
sourceHandle: `${workflowStartNodeId}-source-right`,
targetHandle: `${aiChatNodeId}-target-left`
}
]
};
}
// Start, Dataset search, AiChat
function datasetTemplate(formData: AppFormEditFormType): WorkflowType {
return {
nodes: [
aiChatTemplate(formData),
datasetNodeTemplate(formData, [workflowStartNodeId, 'userChatInput'])
],
edges: [
{
source: workflowStartNodeId,
target: datasetNodeId,
sourceHandle: `${workflowStartNodeId}-source-right`,
targetHandle: `${datasetNodeId}-target-left`
},
{
source: datasetNodeId,
target: aiChatNodeId,
sourceHandle: `${datasetNodeId}-source-right`,
targetHandle: `${aiChatNodeId}-target-left`
}
]
};
}
function toolTemplates(formData: AppFormEditFormType): WorkflowType {
const toolNodeId = getNanoid(6);
// Dataset tool config
const datasetTool: WorkflowType | null =
selectedDatasets.length > 0
? {
nodes: [datasetNodeTemplate(formData, '')],
edges: [
{
source: toolNodeId,
target: datasetNodeId,
sourceHandle: 'selectedTools',
targetHandle: 'selectedTools'
}
]
}
: null;
// Computed tools config
const pluginTool: WorkflowType[] = formData.selectedTools.map((tool, i) => {
const nodeId = getNanoid(6);
return {
nodes: [
{
nodeId,
id: tool.id,
pluginId: tool.pluginId,
name: tool.name,
intro: tool.intro,
toolDescription: tool.toolDescription,
avatar: tool.avatar,
flowNodeType: tool.flowNodeType,
showStatus: tool.showStatus,
position: {
x: 500 + 500 * (i + 1),
y: 545
},
toolConfig: tool.toolConfig,
pluginData: tool.pluginData,
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
}
],
edges: [
{
source: toolNodeId,
target: nodeId,
sourceHandle: 'selectedTools',
targetHandle: 'selectedTools'
}
]
};
});
const config: WorkflowType = {
nodes: [
{
nodeId: toolNodeId,
name: ToolCallNode.name,
intro: ToolCallNode.intro,
avatar: ToolCallNode.avatar,
flowNodeType: ToolCallNode.flowNodeType,
showStatus: true,
position: {
x: 1062.1738942532802,
y: -223.65033022650476
},
version: ToolCallNode.version,
inputs: [
{
key: NodeInputKeyEnum.aiModel,
renderTypeList: [
FlowNodeInputTypeEnum.settingLLMModel,
FlowNodeInputTypeEnum.reference
],
label: 'core.module.input.label.aiModel',
valueType: WorkflowIOValueTypeEnum.string,
llmModelType: 'all',
value: formData.aiSettings.model
},
{
key: 'temperature',
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: formData.aiSettings.temperature,
valueType: WorkflowIOValueTypeEnum.number,
min: 0,
max: 10,
step: 1
},
{
key: 'maxToken',
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: formData.aiSettings.maxToken,
valueType: WorkflowIOValueTypeEnum.number,
min: 100,
max: 4000,
step: 50
},
{
key: 'systemPrompt',
renderTypeList: [FlowNodeInputTypeEnum.textarea, FlowNodeInputTypeEnum.reference],
max: 3000,
valueType: WorkflowIOValueTypeEnum.string,
label: 'core.ai.Prompt',
description: 'core.app.tip.systemPromptTip',
placeholder: 'core.app.tip.chatNodeSystemPromptTip',
value: formData.aiSettings.systemPrompt
},
{
key: 'history',
renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.chatHistory,
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
value: formData.aiSettings.maxHistories
},
{
...Input_Template_File_Link,
value: [[workflowStartNodeId, NodeOutputKeyEnum.userFiles]]
},
{
key: 'userChatInput',
renderTypeList: [FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.textarea],
valueType: WorkflowIOValueTypeEnum.string,
label: i18nT('common:core.module.input.label.user question'),
required: true,
value: [workflowStartNodeId, 'userChatInput']
},
{
key: NodeInputKeyEnum.aiChatVision,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.boolean,
value: true
},
{
key: NodeInputKeyEnum.aiChatReasoning,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.boolean,
value: formData.aiSettings.aiChatReasoning
}
],
outputs: ToolCallNode.outputs
},
// tool nodes
...(datasetTool ? datasetTool.nodes : []),
...pluginTool.map((tool) => tool.nodes).flat()
],
edges: [
{
source: workflowStartNodeId,
target: toolNodeId,
sourceHandle: `${workflowStartNodeId}-source-right`,
targetHandle: `${toolNodeId}-target-left`
},
// tool edges
...(datasetTool ? datasetTool.edges : []),
...pluginTool.map((tool) => tool.edges).flat()
]
};
// Add t
config.nodes.forEach((node) => {
node.name = t(node.name);
node.intro = t(node.intro);
node.inputs.forEach((input) => {
input.label = t(input.label);
input.description = t(input.description);
input.toolDescription = t(input.toolDescription);
});
});
return config;
}
const workflow = (() => {
if (data.selectedTools.length > 0) return toolTemplates(data);
if (selectedDatasets.length > 0) return datasetTemplate(data);
return simpleChatTemplate(data);
})();
return {
nodes: [systemConfigTemplate(), workflowStartTemplate(), ...workflow.nodes],
edges: workflow.edges,
chatConfig: data.chatConfig
};
}

View File

@ -4,8 +4,8 @@ import React from 'react';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { Box } from '@chakra-ui/react';
import { type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { childAppSystemKey } from './ToolSelectModal';
import { type AppFormEditFormType } from '@fastgpt/global/core/app/type';
import { childAppSystemKey } from '../FormComponent/ToolSelector/ToolSelectModal';
import { Controller, useForm } from 'react-hook-form';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
@ -26,9 +26,9 @@ const ConfigToolModal = ({
onCloseConfigTool,
onAddTool
}: {
configTool: AppSimpleEditFormType['selectedTools'][number];
configTool: AppFormEditFormType['selectedTools'][number];
onCloseConfigTool: () => void;
onAddTool: (tool: AppSimpleEditFormType['selectedTools'][number]) => void;
onAddTool: (tool: AppFormEditFormType['selectedTools'][number]) => void;
}) => {
const { t } = useTranslation();
const [isOpenSecretModal, { setTrue: setTrueSecretModal, setFalse: setFalseSecretModal }] =

View File

@ -6,7 +6,7 @@ import { filterSensitiveNodesData } from '@/web/core/workflow/utils';
import { useCopyData } from '@fastgpt/web/hooks/useCopyData';
import MyPopover from '@fastgpt/web/components/common/MyPopover';
import { fileDownload } from '@/web/common/file/utils';
import { type AppChatConfigType, type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { type AppChatConfigType, type AppFormEditFormType } from '@fastgpt/global/core/app/type';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { filterSensitiveFormData } from '@/web/core/app/utils';
import { type RequireOnlyOne } from '@fastgpt/global/common/type/utils';
@ -29,7 +29,7 @@ const ExportConfigPopover = ({
edges: StoreEdgeItemType[];
}
| undefined;
appForm: AppSimpleEditFormType;
appForm: AppFormEditFormType;
}>) => {
const { t } = useTranslation();
const { copyData } = useCopyData();

View File

@ -1,216 +0,0 @@
import React, { useState } from 'react';
import {
Box,
Flex,
Button,
IconButton,
HStack,
ModalBody,
Checkbox,
ModalFooter
} from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { type AppSchema, type AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import { useTranslation } from 'next-i18next';
import Avatar from '@fastgpt/web/components/common/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import TagsEditModal from '../TagsEditModal';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { AppContext } from '@/pageComponents/app/detail/context';
import { useContextSelector } from 'use-context-selector';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { postTransition2Workflow } from '@/web/core/app/api/app';
import { form2AppWorkflow } from '@/web/core/app/utils';
import { type SimpleAppSnapshotType } from './useSnapshots';
import ExportConfigPopover from '@/pageComponents/app/detail/ExportConfigPopover';
import { ChatSidebarPaneEnum } from '@/pageComponents/chat/constants';
const AppCard = ({
appForm,
setPast
}: {
appForm: AppSimpleEditFormType;
setPast: (value: React.SetStateAction<SimpleAppSnapshotType[]>) => void;
}) => {
const router = useRouter();
const { t } = useTranslation();
const onSaveApp = useContextSelector(AppContext, (v) => v.onSaveApp);
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
const onOpenInfoEdit = useContextSelector(AppContext, (v) => v.onOpenInfoEdit);
const onDelApp = useContextSelector(AppContext, (v) => v.onDelApp);
const appId = appDetail._id;
const { feConfigs } = useSystemStore();
const [TeamTagsSet, setTeamTagsSet] = useState<AppSchema>();
// transition to workflow
const [transitionCreateNew, setTransitionCreateNew] = useState<boolean>();
const { runAsync: onTransition, loading: transiting } = useRequest2(
async () => {
const { nodes, edges } = form2AppWorkflow(appForm, t);
await onSaveApp({
nodes,
edges,
chatConfig: appForm.chatConfig,
isPublish: false,
versionName: t('app:transition_to_workflow')
});
return postTransition2Workflow({ appId, createNew: transitionCreateNew });
},
{
onSuccess: ({ id }) => {
if (id) {
router.replace({
query: {
appId: id
}
});
} else {
setPast([]);
router.reload();
}
},
successToast: t('common:Success')
}
);
return (
<>
{/* basic info */}
<Box px={[4, 6]} py={4} position={'relative'}>
<Flex alignItems={'center'}>
<Avatar src={appDetail.avatar} borderRadius={'md'} w={'28px'} />
<Box ml={3} fontWeight={'bold'} fontSize={'md'} flex={'1 0 0'} color={'myGray.900'}>
{appDetail.name}
</Box>
</Flex>
<Box
flex={1}
mt={3}
mb={4}
className={'textEllipsis3'}
wordBreak={'break-all'}
color={'myGray.600'}
fontSize={'xs'}
minH={'46px'}
>
{appDetail.intro || t('common:core.app.tip.Add a intro to app')}
</Box>
<HStack alignItems={'center'}>
<Button
size={['sm', 'md']}
variant={'whitePrimary'}
leftIcon={<MyIcon name={'core/chat/chatLight'} w={'16px'} />}
onClick={() =>
router.push(`/chat?appId=${appId}&pane=${ChatSidebarPaneEnum.RECENTLY_USED_APPS}`)
}
>
{t('common:core.Chat')}
</Button>
{appDetail.permission.hasManagePer && (
<Button
size={['sm', 'md']}
variant={'whitePrimary'}
leftIcon={<MyIcon name={'common/settingLight'} w={'16px'} />}
onClick={onOpenInfoEdit}
>
{t('common:Setting')}
</Button>
)}
{appDetail.permission.isOwner && (
<MyMenu
size={'xs'}
Button={
<IconButton
variant={'whitePrimary'}
size={['smSquare', 'mdSquare']}
icon={<MyIcon name={'more'} w={'1rem'} />}
aria-label={''}
/>
}
menuList={[
{
children: [
{
label: (
<Flex>
<ExportConfigPopover
appName={appDetail.name}
appForm={appForm}
chatConfig={appDetail.chatConfig}
/>
</Flex>
)
},
{
icon: 'core/app/type/workflow',
label: t('app:transition_to_workflow'),
onClick: () => setTransitionCreateNew(true)
},
...(appDetail.permission.hasWritePer && feConfigs?.show_team_chat
? [
{
icon: 'core/chat/fileSelect',
label: t('app:team_tags_set'),
onClick: () => setTeamTagsSet(appDetail)
}
]
: [])
]
},
{
children: [
{
icon: 'delete',
type: 'danger',
label: t('common:Delete'),
onClick: onDelApp
}
]
}
]}
/>
)}
<Box flex={1} />
{/* {isPc && ( */}
{/* <MyTag */}
{/* type="borderFill" */}
{/* colorSchema="gray" */}
{/* onClick={() => (appDetail.permission.hasManagePer ? onOpenInfoEdit() : undefined)} */}
{/* > */}
{/* <PermissionIconText defaultPermission={appDetail.defaultPermission} /> */}
{/* </MyTag> */}
{/* )} */}
</HStack>
</Box>
{TeamTagsSet && <TagsEditModal onClose={() => setTeamTagsSet(undefined)} />}
{transitionCreateNew !== undefined && (
<MyModal isOpen title={t('app:transition_to_workflow')} iconSrc="core/app/type/workflow">
<ModalBody>
<Box mb={3}>{t('app:transition_to_workflow_create_new_tip')}</Box>
<HStack cursor={'pointer'} onClick={() => setTransitionCreateNew((state) => !state)}>
<Checkbox
isChecked={transitionCreateNew}
icon={<MyIcon name={'common/check'} w={'12px'} />}
/>
<Box>{t('app:transition_to_workflow_create_new_placeholder')}</Box>
</HStack>
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} onClick={() => setTransitionCreateNew(undefined)} mr={3}>
{t('common:Close')}
</Button>
<Button variant={'dangerFill'} isLoading={transiting} onClick={() => onTransition()}>
{t('common:Confirm')}
</Button>
</ModalFooter>
</MyModal>
)}
</>
);
};
export default React.memo(AppCard);

View File

@ -1,133 +0,0 @@
import { Box, Flex, IconButton } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import React, { useEffect, useMemo } from 'react';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useSafeState } from 'ahooks';
import { type AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { form2AppWorkflow } from '@/web/core/app/utils';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '../context';
import { useChatTest } from '../useChatTest';
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import ChatRecordContextProvider from '@/web/core/chat/context/chatRecordContext';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { cardStyles } from '../constants';
import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList';
import VariablePopover from '@/components/core/chat/ChatContainer/components/VariablePopover';
import { ChatTypeEnum } from '@/components/core/chat/ChatContainer/ChatBox/constants';
type Props = {
appForm: AppSimpleEditFormType;
setRenderEdit: React.Dispatch<React.SetStateAction<boolean>>;
};
const ChatTest = ({ appForm, setRenderEdit }: Props) => {
const { t } = useTranslation();
const { appDetail } = useContextSelector(AppContext, (v) => v);
const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData);
const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData);
// form2AppWorkflow dependent allDatasets
const isVariableVisible = useContextSelector(ChatItemContext, (v) => v.isVariableVisible);
const [workflowData, setWorkflowData] = useSafeState({
nodes: appDetail.modules || [],
edges: appDetail.edges || []
});
useEffect(() => {
const { nodes, edges } = form2AppWorkflow(appForm, t);
setWorkflowData({ nodes, edges });
}, [appForm, setWorkflowData, t]);
useEffect(() => {
setRenderEdit(!datasetCiteData);
}, [datasetCiteData, setRenderEdit]);
const { ChatContainer, restartChat } = useChatTest({
...workflowData,
chatConfig: appForm.chatConfig,
isReady: true
});
return (
<Flex h={'full'} gap={2}>
<MyBox
flex={'1 0 0'}
w={0}
display={'flex'}
position={'relative'}
flexDirection={'column'}
h={'full'}
py={4}
{...cardStyles}
boxShadow={'3'}
>
<Flex px={[2, 5]} pb={2}>
<Box fontSize={['md', 'lg']} fontWeight={'bold'} color={'myGray.900'} mr={3}>
{t('app:chat_debug')}
</Box>
{!isVariableVisible && <VariablePopover chatType={ChatTypeEnum.test} />}
<Box flex={1} />
<MyTooltip label={t('common:core.chat.Restart')}>
<IconButton
className="chat"
size={'smSquare'}
icon={<MyIcon name={'common/clearLight'} w={'14px'} />}
variant={'whiteDanger'}
borderRadius={'md'}
aria-label={'delete'}
onClick={(e) => {
e.stopPropagation();
restartChat();
}}
/>
</MyTooltip>
</Flex>
<Box flex={1}>
<ChatContainer />
</Box>
</MyBox>
{datasetCiteData && (
<Box flex={'1 0 0'} w={0} maxW={'560px'} {...cardStyles} boxShadow={'3'}>
<ChatQuoteList
rawSearch={datasetCiteData.rawSearch}
metadata={datasetCiteData.metadata}
onClose={() => setCiteModalData(undefined)}
/>
</Box>
)}
</Flex>
);
};
const Render = ({ appForm, setRenderEdit }: Props) => {
const { chatId } = useChatStore();
const { appDetail } = useContextSelector(AppContext, (v) => v);
const chatRecordProviderParams = useMemo(
() => ({
chatId: chatId,
appId: appDetail._id
}),
[appDetail._id, chatId]
);
return (
<ChatItemContextProvider
showRouteToDatasetDetail={true}
isShowReadRawSource={true}
isResponseDetail={true}
// isShowFullText={true}
showNodeStatus
>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<ChatTest appForm={appForm} setRenderEdit={setRenderEdit} />
</ChatRecordContextProvider>
</ChatItemContextProvider>
);
};
export default React.memo(Render);

View File

@ -1,10 +0,0 @@
.EditAppBox {
&::-webkit-scrollbar-thumb {
background: #dfe2ea !important;
transition: background 1s;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--chakra-colors-gray-300) !important;
}
}

View File

@ -21,19 +21,19 @@ 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 AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { type AppFormEditFormType } from '@fastgpt/global/core/app/type';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { workflowStartNodeId } from '@/web/core/app/constants';
import ConfigToolModal from '@/pageComponents/app/detail/SimpleApp/components/ConfigToolModal';
import type { ChatSettingType } from '@fastgpt/global/core/chat/setting/type';
import CostTooltip from '@/components/core/app/tool/CostTooltip';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import ToolTagFilterBox from '@fastgpt/web/components/core/plugin/tool/TagFilterBox';
import { getPluginToolTags } from '@/web/core/plugin/toolTag/api';
import ConfigToolModal from '@/pageComponents/app/detail/Edit/component/ConfigToolModal';
type Props = {
selectedTools: ChatSettingType['selectedTools'];
chatConfig?: AppSimpleEditFormType['chatConfig'];
chatConfig?: AppFormEditFormType['chatConfig'];
onAddTool: (tool: FlowNodeTemplateType) => void;
onRemoveTool: (tool: NodeTemplateListItemType) => void;
};

View File

@ -11,11 +11,11 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { TabEnum } from '@/pageComponents/app/detail/context';
const SimpleEdit = dynamic(() => import('@/pageComponents/app/detail/SimpleApp'), {
const SimpleEdit = dynamic(() => import('@/pageComponents/app/detail/Edit/SimpleApp'), {
ssr: false,
loading: () => <Loading fixed={false} />
});
const AgentEdit = dynamic(() => import('@/pageComponents/app/detail/Agent'), {
const AgentEdit = dynamic(() => import('@/pageComponents/app/detail/Edit/Agent'), {
ssr: false,
loading: () => <Loading fixed={false} />
});
@ -27,7 +27,7 @@ const Plugin = dynamic(() => import('@/pageComponents/app/detail/Plugin'), {
ssr: false,
loading: () => <Loading fixed={false} />
});
const MCPTools = dynamic(() => import('@/pageComponents/app/detail/MCPTools'), {
const MCPTools = dynamic(() => import('@/pageComponents/app/detail/Edit/MCPTools'), {
ssr: false,
loading: () => <Loading fixed={false} />
});

View File

@ -1,579 +1,19 @@
import {
type AppChatConfigType,
type AppDetailType,
type AppSchema,
type AppSimpleEditFormType
type AppFormEditFormType
} from '@fastgpt/global/core/app/type';
import { type StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
import {
chatHistoryValueDesc,
FlowNodeInputTypeEnum,
FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import {
NodeInputKeyEnum,
NodeOutputKeyEnum,
WorkflowIOValueTypeEnum
} from '@fastgpt/global/core/workflow/constants';
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { type StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { type EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
import { ToolCallNode } from '@fastgpt/global/core/workflow/template/system/toolCall';
import {
WorkflowStart,
userFilesInput
} from '@fastgpt/global/core/workflow/template/system/workflowStart';
import { SystemConfigNode } from '@fastgpt/global/core/workflow/template/system/systemConfig';
import {
AiChatModule,
AiChatQuotePrompt,
AiChatQuoteRole,
AiChatQuoteTemplate
} from '@fastgpt/global/core/workflow/template/system/aiChat/index';
import { DatasetSearchModule } from '@fastgpt/global/core/workflow/template/system/datasetSearch';
import { i18nT } from '@fastgpt/web/i18n/utils';
import {
Input_Template_File_Link,
Input_Template_UserChatInput
} from '@fastgpt/global/core/workflow/template/input';
import { workflowStartNodeId } from './constants';
import { getDefaultAppForm } from '@fastgpt/global/core/app/utils';
type WorkflowType = {
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
};
export function form2AppWorkflow(
data: AppSimpleEditFormType,
t: any // i18nT
): WorkflowType & {
chatConfig: AppChatConfigType;
} {
const datasetNodeId = 'iKBoX2vIzETU';
const aiChatNodeId = '7BdojPlukIQw';
const selectedDatasets = data.dataset.datasets;
function systemConfigTemplate(): StoreNodeItemType {
return {
nodeId: SystemConfigNode.id,
name: t(SystemConfigNode.name),
intro: '',
flowNodeType: SystemConfigNode.flowNodeType,
position: {
x: 531.2422736065552,
y: -486.7611729549753
},
version: SystemConfigNode.version,
inputs: [],
outputs: []
};
}
function workflowStartTemplate(): StoreNodeItemType {
return {
nodeId: workflowStartNodeId,
name: t(WorkflowStart.name),
intro: '',
avatar: WorkflowStart.avatar,
flowNodeType: WorkflowStart.flowNodeType,
position: {
x: 558.4082376415505,
y: 123.72387429194112
},
version: WorkflowStart.version,
inputs: WorkflowStart.inputs,
outputs: [...WorkflowStart.outputs, userFilesInput]
};
}
function aiChatTemplate(formData: AppSimpleEditFormType): StoreNodeItemType {
return {
nodeId: aiChatNodeId,
name: t(AiChatModule.name),
intro: t(AiChatModule.intro),
avatar: AiChatModule.avatar,
flowNodeType: AiChatModule.flowNodeType,
showStatus: true,
position: {
x: 1106.3238387960757,
y: -350.6030674683474
},
version: AiChatModule.version,
inputs: [
{
key: NodeInputKeyEnum.aiModel,
renderTypeList: [FlowNodeInputTypeEnum.settingLLMModel, FlowNodeInputTypeEnum.reference],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.aiSettings.model
},
{
key: NodeInputKeyEnum.aiChatTemperature,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: formData.aiSettings.temperature,
valueType: WorkflowIOValueTypeEnum.number,
min: 0,
max: 10,
step: 1
},
{
key: NodeInputKeyEnum.aiChatMaxToken,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: formData.aiSettings.maxToken,
valueType: WorkflowIOValueTypeEnum.number,
min: 100,
max: 4000,
step: 50
},
{
key: NodeInputKeyEnum.aiChatIsResponseText,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: true,
valueType: WorkflowIOValueTypeEnum.boolean
},
AiChatQuoteRole,
AiChatQuoteTemplate,
AiChatQuotePrompt,
{
key: NodeInputKeyEnum.aiSystemPrompt,
renderTypeList: [FlowNodeInputTypeEnum.textarea, FlowNodeInputTypeEnum.reference],
max: 3000,
valueType: WorkflowIOValueTypeEnum.string,
label: 'core.ai.Prompt',
description: 'core.app.tip.systemPromptTip',
placeholder: 'core.app.tip.chatNodeSystemPromptTip',
value: formData.aiSettings.systemPrompt
},
{
key: NodeInputKeyEnum.history,
renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.chatHistory,
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
value: formData.aiSettings.maxHistories
},
{
key: NodeInputKeyEnum.userChatInput,
renderTypeList: [FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.textarea],
valueType: WorkflowIOValueTypeEnum.string,
label: i18nT('common:core.module.input.label.user question'),
required: true,
toolDescription: i18nT('common:core.module.input.label.user question'),
value: [workflowStartNodeId, NodeInputKeyEnum.userChatInput]
},
{
key: NodeInputKeyEnum.aiChatDatasetQuote,
renderTypeList: [FlowNodeInputTypeEnum.settingDatasetQuotePrompt],
label: '',
debugLabel: i18nT('common:core.module.Dataset quote.label'),
description: '',
valueType: WorkflowIOValueTypeEnum.datasetQuote,
value: selectedDatasets?.length > 0 ? [datasetNodeId, 'quoteQA'] : undefined
},
{
...Input_Template_File_Link,
value: [[workflowStartNodeId, NodeOutputKeyEnum.userFiles]]
},
{
key: NodeInputKeyEnum.aiChatVision,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.boolean,
value: true
},
{
key: NodeInputKeyEnum.aiChatReasoning,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.boolean,
value: formData.aiSettings.aiChatReasoning
},
{
key: NodeInputKeyEnum.aiChatTopP,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.number,
value: formData.aiSettings.aiChatTopP
},
{
key: NodeInputKeyEnum.aiChatStopSign,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.aiSettings.aiChatStopSign
},
{
key: NodeInputKeyEnum.aiChatResponseFormat,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.aiSettings.aiChatResponseFormat
},
{
key: NodeInputKeyEnum.aiChatJsonSchema,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.aiSettings.aiChatJsonSchema
}
],
outputs: AiChatModule.outputs
};
}
function datasetNodeTemplate(formData: AppSimpleEditFormType, question: any): StoreNodeItemType {
return {
nodeId: datasetNodeId,
name: t(DatasetSearchModule.name),
intro: t('app:dataset_search_tool_description'),
avatar: DatasetSearchModule.avatar,
flowNodeType: DatasetSearchModule.flowNodeType,
showStatus: true,
position: {
x: 918.5901682164496,
y: -227.11542247619582
},
version: DatasetSearchModule.version,
inputs: [
{
key: NodeInputKeyEnum.datasetSelectList,
renderTypeList: [FlowNodeInputTypeEnum.selectDataset, FlowNodeInputTypeEnum.reference],
label: i18nT('common:core.module.input.label.Select dataset'),
value: selectedDatasets,
valueType: WorkflowIOValueTypeEnum.selectDataset,
list: [],
required: true
},
{
key: NodeInputKeyEnum.datasetSimilarity,
renderTypeList: [FlowNodeInputTypeEnum.selectDatasetParamsModal],
label: '',
value: formData.dataset.similarity,
valueType: WorkflowIOValueTypeEnum.number
},
{
key: NodeInputKeyEnum.datasetMaxTokens,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: formData.dataset.limit,
valueType: WorkflowIOValueTypeEnum.number
},
{
key: NodeInputKeyEnum.datasetSearchMode,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.dataset.searchMode
},
{
key: NodeInputKeyEnum.datasetSearchEmbeddingWeight,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.number,
value: formData.dataset.embeddingWeight
},
{
key: NodeInputKeyEnum.datasetSearchUsingReRank,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.boolean,
value: formData.dataset.usingReRank
},
{
key: NodeInputKeyEnum.datasetSearchRerankModel,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.dataset.rerankModel
},
{
key: NodeInputKeyEnum.datasetSearchRerankWeight,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.number,
value: formData.dataset.rerankWeight
},
{
key: NodeInputKeyEnum.datasetSearchUsingExtensionQuery,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.boolean,
value: formData.dataset.datasetSearchUsingExtensionQuery
},
{
key: NodeInputKeyEnum.datasetSearchExtensionModel,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.dataset.datasetSearchExtensionModel
},
{
key: NodeInputKeyEnum.datasetSearchExtensionBg,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string,
value: formData.dataset.datasetSearchExtensionBg
},
{
...Input_Template_UserChatInput,
toolDescription: i18nT('workflow:content_to_search'),
value: question
}
],
outputs: DatasetSearchModule.outputs
};
}
// Start, AiChat
function simpleChatTemplate(formData: AppSimpleEditFormType): WorkflowType {
return {
nodes: [aiChatTemplate(formData)],
edges: [
{
source: workflowStartNodeId,
target: aiChatNodeId,
sourceHandle: `${workflowStartNodeId}-source-right`,
targetHandle: `${aiChatNodeId}-target-left`
}
]
};
}
// Start, Dataset search, AiChat
function datasetTemplate(formData: AppSimpleEditFormType): WorkflowType {
return {
nodes: [
aiChatTemplate(formData),
datasetNodeTemplate(formData, [workflowStartNodeId, 'userChatInput'])
],
edges: [
{
source: workflowStartNodeId,
target: datasetNodeId,
sourceHandle: `${workflowStartNodeId}-source-right`,
targetHandle: `${datasetNodeId}-target-left`
},
{
source: datasetNodeId,
target: aiChatNodeId,
sourceHandle: `${datasetNodeId}-source-right`,
targetHandle: `${aiChatNodeId}-target-left`
}
]
};
}
function toolTemplates(formData: AppSimpleEditFormType): WorkflowType {
const toolNodeId = getNanoid(6);
// Dataset tool config
const datasetTool: WorkflowType | null =
selectedDatasets.length > 0
? {
nodes: [datasetNodeTemplate(formData, '')],
edges: [
{
source: toolNodeId,
target: datasetNodeId,
sourceHandle: 'selectedTools',
targetHandle: 'selectedTools'
}
]
}
: null;
// Computed tools config
const pluginTool: WorkflowType[] = formData.selectedTools.map((tool, i) => {
const nodeId = getNanoid(6);
return {
nodes: [
{
nodeId,
id: tool.id,
pluginId: tool.pluginId,
name: tool.name,
intro: tool.intro,
toolDescription: tool.toolDescription,
avatar: tool.avatar,
flowNodeType: tool.flowNodeType,
showStatus: tool.showStatus,
position: {
x: 500 + 500 * (i + 1),
y: 545
},
toolConfig: tool.toolConfig,
pluginData: tool.pluginData,
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
}
],
edges: [
{
source: toolNodeId,
target: nodeId,
sourceHandle: 'selectedTools',
targetHandle: 'selectedTools'
}
]
};
});
const config: WorkflowType = {
nodes: [
{
nodeId: toolNodeId,
name: ToolCallNode.name,
intro: ToolCallNode.intro,
avatar: ToolCallNode.avatar,
flowNodeType: ToolCallNode.flowNodeType,
showStatus: true,
position: {
x: 1062.1738942532802,
y: -223.65033022650476
},
version: ToolCallNode.version,
inputs: [
{
key: NodeInputKeyEnum.aiModel,
renderTypeList: [
FlowNodeInputTypeEnum.settingLLMModel,
FlowNodeInputTypeEnum.reference
],
label: 'core.module.input.label.aiModel',
valueType: WorkflowIOValueTypeEnum.string,
llmModelType: 'all',
value: formData.aiSettings.model
},
{
key: 'temperature',
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: formData.aiSettings.temperature,
valueType: WorkflowIOValueTypeEnum.number,
min: 0,
max: 10,
step: 1
},
{
key: 'maxToken',
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: formData.aiSettings.maxToken,
valueType: WorkflowIOValueTypeEnum.number,
min: 100,
max: 4000,
step: 50
},
{
key: 'systemPrompt',
renderTypeList: [FlowNodeInputTypeEnum.textarea, FlowNodeInputTypeEnum.reference],
max: 3000,
valueType: WorkflowIOValueTypeEnum.string,
label: 'core.ai.Prompt',
description: 'core.app.tip.systemPromptTip',
placeholder: 'core.app.tip.chatNodeSystemPromptTip',
value: formData.aiSettings.systemPrompt
},
{
key: 'history',
renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.chatHistory,
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
value: formData.aiSettings.maxHistories
},
{
...Input_Template_File_Link,
value: [[workflowStartNodeId, NodeOutputKeyEnum.userFiles]]
},
{
key: 'userChatInput',
renderTypeList: [FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.textarea],
valueType: WorkflowIOValueTypeEnum.string,
label: i18nT('common:core.module.input.label.user question'),
required: true,
value: [workflowStartNodeId, 'userChatInput']
},
{
key: NodeInputKeyEnum.aiChatVision,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.boolean,
value: true
},
{
key: NodeInputKeyEnum.aiChatReasoning,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.boolean,
value: formData.aiSettings.aiChatReasoning
}
],
outputs: ToolCallNode.outputs
},
// tool nodes
...(datasetTool ? datasetTool.nodes : []),
...pluginTool.map((tool) => tool.nodes).flat()
],
edges: [
{
source: workflowStartNodeId,
target: toolNodeId,
sourceHandle: `${workflowStartNodeId}-source-right`,
targetHandle: `${toolNodeId}-target-left`
},
// tool edges
...(datasetTool ? datasetTool.edges : []),
...pluginTool.map((tool) => tool.edges).flat()
]
};
// Add t
config.nodes.forEach((node) => {
node.name = t(node.name);
node.intro = t(node.intro);
node.inputs.forEach((input) => {
input.label = t(input.label);
input.description = t(input.description);
input.toolDescription = t(input.toolDescription);
});
});
return config;
}
const workflow = (() => {
if (data.selectedTools.length > 0) return toolTemplates(data);
if (selectedDatasets.length > 0) return datasetTemplate(data);
return simpleChatTemplate(data);
})();
return {
nodes: [systemConfigTemplate(), workflowStartTemplate(), ...workflow.nodes],
edges: workflow.edges,
chatConfig: data.chatConfig
};
}
export function filterSensitiveFormData(appForm: AppSimpleEditFormType) {
export function filterSensitiveFormData(appForm: AppFormEditFormType) {
const defaultAppForm = getDefaultAppForm();
return {
...appForm,