V4.6.5-alpha (#609)
This commit is contained in:
@@ -21,6 +21,7 @@ function Row({
|
||||
value?: string | number;
|
||||
rawDom?: React.ReactNode;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const val = value || rawDom;
|
||||
const strValue = `${value}`;
|
||||
@@ -29,7 +30,7 @@ function Row({
|
||||
return val !== undefined && val !== '' && val !== 'undefined' ? (
|
||||
<Box mb={3}>
|
||||
<Box fontSize={['sm', 'md']} mb={isCodeBlock ? 0 : 1} flex={'0 0 90px'}>
|
||||
{label}:
|
||||
{t(label)}:
|
||||
</Box>
|
||||
<Box
|
||||
borderRadius={'md'}
|
||||
@@ -69,12 +70,12 @@ const WholeResponseModal = ({
|
||||
alt={''}
|
||||
w={['14px', '16px']}
|
||||
/>
|
||||
{item.moduleName}
|
||||
{t(item.moduleName)}
|
||||
</Flex>
|
||||
),
|
||||
id: `${i}`
|
||||
})),
|
||||
[response]
|
||||
[response, t]
|
||||
);
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(`0`);
|
||||
@@ -103,26 +104,33 @@ const WholeResponseModal = ({
|
||||
<Tabs list={list} activeId={currentTab} onChange={setCurrentTab} />
|
||||
</Box>
|
||||
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>
|
||||
<Row label={t('chat.response.module name')} value={activeModule?.moduleName} />
|
||||
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
|
||||
{activeModule?.price !== undefined && (
|
||||
<Row
|
||||
label={t('chat.response.module price')}
|
||||
label={t('core.chat.response.module price')}
|
||||
value={`¥${formatPrice(activeModule?.price)}`}
|
||||
/>
|
||||
)}
|
||||
<Row
|
||||
label={t('chat.response.module time')}
|
||||
label={t('core.chat.response.module time')}
|
||||
value={`${activeModule?.runningTime || 0}s`}
|
||||
/>
|
||||
<Row label={t('chat.response.module tokens')} value={`${activeModule?.tokens}`} />
|
||||
<Row label={t('chat.response.module model')} value={activeModule?.model} />
|
||||
<Row label={t('chat.response.module query')} value={activeModule?.query} />
|
||||
<Row label={t('core.chat.response.module tokens')} value={`${activeModule?.tokens}`} />
|
||||
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
|
||||
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
|
||||
<Row
|
||||
label={t('core.chat.response.context total length')}
|
||||
value={activeModule?.contextTotalLen}
|
||||
/>
|
||||
|
||||
{/* ai chat */}
|
||||
<Row label={t('chat.response.module temperature')} value={activeModule?.temperature} />
|
||||
<Row label={t('chat.response.module maxToken')} value={activeModule?.maxToken} />
|
||||
<Row
|
||||
label={t('chat.response.module historyPreview')}
|
||||
label={t('core.chat.response.module temperature')}
|
||||
value={activeModule?.temperature}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module maxToken')} value={activeModule?.maxToken} />
|
||||
<Row
|
||||
label={t('core.chat.response.module historyPreview')}
|
||||
rawDom={
|
||||
activeModule.historyPreview ? (
|
||||
<>
|
||||
@@ -148,7 +156,7 @@ const WholeResponseModal = ({
|
||||
/>
|
||||
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
||||
<Row
|
||||
label={t('chat.response.module quoteList')}
|
||||
label={t('core.chat.response.module quoteList')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule.quoteList, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
@@ -161,27 +169,27 @@ const WholeResponseModal = ({
|
||||
value={t(DatasetSearchModeMap[activeModule.searchMode]?.title)}
|
||||
/>
|
||||
)}
|
||||
<Row label={t('chat.response.module similarity')} value={activeModule?.similarity} />
|
||||
<Row label={t('chat.response.module limit')} value={activeModule?.limit} />
|
||||
<Row label={t('core.chat.response.module similarity')} value={activeModule?.similarity} />
|
||||
<Row label={t('core.chat.response.module limit')} value={activeModule?.limit} />
|
||||
|
||||
{/* classify question */}
|
||||
<Row
|
||||
label={t('chat.response.module cq')}
|
||||
label={t('core.chat.response.module cq')}
|
||||
value={(() => {
|
||||
if (!activeModule?.cqList) return '';
|
||||
return activeModule.cqList.map((item) => `* ${item.value}`).join('\n');
|
||||
})()}
|
||||
/>
|
||||
<Row label={t('chat.response.module cq result')} value={activeModule?.cqResult} />
|
||||
<Row label={t('core.chat.response.module cq result')} value={activeModule?.cqResult} />
|
||||
|
||||
{/* extract */}
|
||||
<Row
|
||||
label={t('chat.response.module extract description')}
|
||||
label={t('core.chat.response.module extract description')}
|
||||
value={activeModule?.extractDescription}
|
||||
/>
|
||||
{activeModule?.extractResult && (
|
||||
<Row
|
||||
label={t('chat.response.module extract result')}
|
||||
label={t('core.chat.response.module extract result')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.extractResult, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
@@ -189,13 +197,13 @@ const WholeResponseModal = ({
|
||||
{/* http */}
|
||||
{activeModule?.body && (
|
||||
<Row
|
||||
label={t('chat.response.module http body')}
|
||||
label={t('core.chat.response.module http body')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.body, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
{activeModule?.httpResult && (
|
||||
<Row
|
||||
label={t('chat.response.module http result')}
|
||||
label={t('core.chat.response.module http result')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.httpResult, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
@@ -203,10 +211,13 @@ const WholeResponseModal = ({
|
||||
{/* plugin */}
|
||||
{activeModule?.pluginOutput && (
|
||||
<Row
|
||||
label={t('chat.response.plugin output')}
|
||||
label={t('core.chat.response.plugin output')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.pluginOutput, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* text editor */}
|
||||
<Row label={t('core.chat.response.text output')} value={activeModule?.textOutput} />
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyModal>
|
||||
|
||||
@@ -500,7 +500,7 @@ const ChatBox = (
|
||||
|
||||
return {
|
||||
bg: colorMap[chatContent.status] || colorMap.loading,
|
||||
name: chatContent.moduleName || t('common.Loading')
|
||||
name: t(chatContent.moduleName || '') || t('common.Loading')
|
||||
};
|
||||
}, [chatHistory, isChatting, t]);
|
||||
/* style end */
|
||||
@@ -517,7 +517,7 @@ const ChatBox = (
|
||||
};
|
||||
}, [router.query]);
|
||||
|
||||
// add guide text listener
|
||||
// add listener
|
||||
useEffect(() => {
|
||||
const windowMessage = ({ data }: MessageEvent<{ type: 'sendPrompt'; text: string }>) => {
|
||||
if (data?.type === 'sendPrompt' && data?.text) {
|
||||
@@ -536,9 +536,9 @@ const ChatBox = (
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('message', windowMessage);
|
||||
eventBus.off(EventNameEnum.sendQuestion);
|
||||
eventBus.off(EventNameEnum.editQuestion);
|
||||
window.removeEventListener('message', windowMessage);
|
||||
};
|
||||
}, [handleSubmit, resetInputVal, sendPrompt]);
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Textarea,
|
||||
TextareaProps,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyModal from '@/components/MyModal';
|
||||
|
||||
type Props = TextareaProps & {
|
||||
title?: string;
|
||||
showSetModalModeIcon?: boolean;
|
||||
// variables: string[];
|
||||
};
|
||||
|
||||
const PromptTextarea = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { title = t('core.app.edit.Prompt Editor'), value, ...childProps } = props;
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Editor {...childProps} value={value} showSetModalModeIcon onSetModalMode={onOpen} />
|
||||
{isOpen && (
|
||||
<MyModal iconSrc="/imgs/modal/edit.svg" title={title} isOpen onClose={onClose}>
|
||||
<ModalBody>
|
||||
<Editor
|
||||
{...childProps}
|
||||
value={value}
|
||||
minH={'300px'}
|
||||
maxH={'auto'}
|
||||
minW={['100%', '512px']}
|
||||
showSetModalModeIcon={false}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button onClick={onClose}>{t('common.Confirm')}</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PromptTextarea;
|
||||
|
||||
const Editor = React.memo(function Editor({
|
||||
showSetModalModeIcon = true,
|
||||
onSetModalMode,
|
||||
...props
|
||||
}: Props & { onSetModalMode?: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box h={'100%'} w={'100%'} position={'relative'}>
|
||||
<Textarea wordBreak={'break-all'} maxW={'100%'} {...props} />
|
||||
{showSetModalModeIcon && (
|
||||
<Box
|
||||
zIndex={1}
|
||||
position={'absolute'}
|
||||
bottom={1}
|
||||
right={2}
|
||||
cursor={'pointer'}
|
||||
onClick={onSetModalMode}
|
||||
>
|
||||
<MyTooltip label={t('common.ui.textarea.Magnifying')}>
|
||||
<MyIcon name={'fullScreenLight'} w={'14px'} color={'myGray.600'} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
const VariableSelectBlock = React.memo(function VariableSelectBlock({
|
||||
variables
|
||||
}: {
|
||||
variables: string[];
|
||||
}) {
|
||||
return <></>;
|
||||
});
|
||||
|
||||
const Placeholder = React.memo(function Placeholder({
|
||||
placeholder = ''
|
||||
}: {
|
||||
placeholder?: string;
|
||||
}) {
|
||||
return (
|
||||
<Box
|
||||
zIndex={0}
|
||||
userSelect={'none'}
|
||||
color={'myGray.400'}
|
||||
px={3}
|
||||
py={2}
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
{placeholder}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
@@ -26,6 +26,7 @@ import type { AIChatModuleProps } from '@fastgpt/global/core/module/node/type.d'
|
||||
import type { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type.d';
|
||||
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import PromptTextarea from '@/components/common/Textarea/PromptTextarea';
|
||||
|
||||
const PromptTemplate = dynamic(() => import('@/components/PromptTemplate'));
|
||||
|
||||
@@ -187,14 +188,18 @@ const AIChatSettingsModal = ({
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Textarea
|
||||
rows={6}
|
||||
placeholder={
|
||||
t('template.Quote Content Tip', { default: Prompt_QuoteTemplateList[0].value }) ||
|
||||
''
|
||||
}
|
||||
borderColor={'myGray.100'}
|
||||
{...register(ModuleInputKeyEnum.aiChatQuoteTemplate)}
|
||||
<PromptTextarea
|
||||
bg={'myWhite.400'}
|
||||
rows={8}
|
||||
placeholder={t('template.Quote Content Tip', {
|
||||
default: Prompt_QuoteTemplateList[0].value
|
||||
})}
|
||||
showSetModalModeIcon
|
||||
value={getValues(ModuleInputKeyEnum.aiChatQuoteTemplate)}
|
||||
onChange={(e) => {
|
||||
setValue(ModuleInputKeyEnum.aiChatQuoteTemplate, e.target.value);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
@@ -209,13 +214,18 @@ const AIChatSettingsModal = ({
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Textarea
|
||||
<PromptTextarea
|
||||
bg={'myWhite.400'}
|
||||
rows={11}
|
||||
placeholder={
|
||||
t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value }) || ''
|
||||
}
|
||||
borderColor={'myGray.100'}
|
||||
{...register(ModuleInputKeyEnum.aiChatQuotePrompt)}
|
||||
placeholder={t('template.Quote Prompt Tip', {
|
||||
default: Prompt_QuotePromptList[0].value
|
||||
})}
|
||||
showSetModalModeIcon
|
||||
value={getValues(ModuleInputKeyEnum.aiChatQuotePrompt)}
|
||||
onChange={(e) => {
|
||||
setValue(ModuleInputKeyEnum.aiChatQuotePrompt, e.target.value);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -28,8 +28,8 @@ import React, {
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { appModule2FlowEdge, appModule2FlowNode } from '@/utils/adapt';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
@@ -174,7 +174,7 @@ export const FlowProvider = ({
|
||||
const source = nodes.find((node) => node.id === connect.source)?.data;
|
||||
const sourceType = (() => {
|
||||
if (source?.flowType === FlowNodeTypeEnum.classifyQuestion) {
|
||||
return ModuleDataTypeEnum.boolean;
|
||||
return ModuleIOValueTypeEnum.string;
|
||||
}
|
||||
if (source?.flowType === FlowNodeTypeEnum.pluginInput) {
|
||||
return source?.inputs.find((input) => input.key === connect.sourceHandle)?.valueType;
|
||||
@@ -193,8 +193,8 @@ export const FlowProvider = ({
|
||||
});
|
||||
}
|
||||
if (
|
||||
sourceType !== ModuleDataTypeEnum.any &&
|
||||
targetType !== ModuleDataTypeEnum.any &&
|
||||
sourceType !== ModuleIOValueTypeEnum.any &&
|
||||
targetType !== ModuleIOValueTypeEnum.any &&
|
||||
sourceType !== targetType
|
||||
) {
|
||||
return toast({
|
||||
@@ -207,8 +207,7 @@ export const FlowProvider = ({
|
||||
addEdge(
|
||||
{
|
||||
...connect,
|
||||
type: 'buttonedge',
|
||||
animated: true,
|
||||
type: EDGE_TYPE,
|
||||
data: {
|
||||
onDelete: onDelConnect
|
||||
}
|
||||
@@ -228,6 +227,7 @@ export const FlowProvider = ({
|
||||
[setEdges, setNodes]
|
||||
);
|
||||
|
||||
/* change */
|
||||
const onChangeNode = useCallback(
|
||||
({ moduleId, type, key, value, index }: FlowNodeChangeProps) => {
|
||||
setNodes((nodes) =>
|
||||
@@ -434,51 +434,3 @@ export default React.memo(FlowProvider);
|
||||
export const onChangeNode = (e: FlowNodeChangeProps) => {
|
||||
eventBus.emit(EventNameEnum.updaterNode, e);
|
||||
};
|
||||
|
||||
export function flowNode2Modules({
|
||||
nodes,
|
||||
edges
|
||||
}: {
|
||||
nodes: Node<FlowModuleItemType, string | undefined>[];
|
||||
edges: Edge<any>[];
|
||||
}) {
|
||||
const modules: ModuleItemType[] = nodes.map((item) => ({
|
||||
moduleId: item.data.moduleId,
|
||||
name: item.data.name,
|
||||
avatar: item.data.avatar,
|
||||
flowType: item.data.flowType,
|
||||
showStatus: item.data.showStatus,
|
||||
position: item.position,
|
||||
inputs: item.data.inputs.map((input) => ({
|
||||
...input,
|
||||
connected: false
|
||||
})),
|
||||
outputs: item.data.outputs.map((item) => ({
|
||||
...item,
|
||||
targets: [] as FlowNodeOutputTargetItemType[]
|
||||
}))
|
||||
}));
|
||||
|
||||
// update inputs and outputs
|
||||
modules.forEach((module) => {
|
||||
module.inputs.forEach((input) => {
|
||||
input.connected = !!edges.find(
|
||||
(edge) => edge.target === module.moduleId && edge.targetHandle === input.key
|
||||
);
|
||||
});
|
||||
|
||||
module.outputs.forEach((output) => {
|
||||
output.targets = edges
|
||||
.filter(
|
||||
(edge) =>
|
||||
edge.source === module.moduleId && edge.sourceHandle === output.key && edge.targetHandle
|
||||
)
|
||||
.map((edge) => ({
|
||||
moduleId: edge.target,
|
||||
key: edge.targetHandle || ''
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,25 @@ import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { useFlowProviderStore } from './FlowProvider';
|
||||
import { useFlowProviderStore, type useFlowProviderStoreType } from './FlowProvider';
|
||||
|
||||
const ImportSettings = ({ onClose }: { onClose: () => void }) => {
|
||||
type Props = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const ImportSettings = ({
|
||||
onClose,
|
||||
setNodes,
|
||||
setEdges,
|
||||
initData
|
||||
}: Props & {
|
||||
setNodes: useFlowProviderStoreType['setNodes'];
|
||||
setEdges: useFlowProviderStoreType['setEdges'];
|
||||
initData: useFlowProviderStoreType['initData'];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [value, setValue] = useState('');
|
||||
const { setNodes, setEdges, initData } = useFlowProviderStore();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
@@ -56,4 +68,8 @@ const ImportSettings = ({ onClose }: { onClose: () => void }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ImportSettings);
|
||||
export default React.memo(function (props: Props) {
|
||||
const { setNodes, setEdges, initData } = useFlowProviderStore();
|
||||
|
||||
return <ImportSettings {...props} setNodes={setNodes} setEdges={setEdges} initData={initData} />;
|
||||
});
|
||||
|
||||
@@ -7,13 +7,12 @@ import type {
|
||||
import { useViewport, XYPosition } from 'reactflow';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useFlowProviderStore } from './FlowProvider';
|
||||
import { useFlowProviderStore, type useFlowProviderStoreType } from './FlowProvider';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { appModule2FlowNode } from '@/utils/adapt';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import MyIcon from '@/components/Icon';
|
||||
import EmptyTip from '@/components/EmptyTip';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { getPreviewPluginModule } from '@/web/core/plugin/api';
|
||||
@@ -22,47 +21,32 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { moduleTemplatesList } from '@/web/core/modules/template/system';
|
||||
import { ModuleTemplateTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
enum TemplateTypeEnum {
|
||||
system = 'system',
|
||||
plugin = 'plugin'
|
||||
}
|
||||
|
||||
export type ModuleTemplateProps = {
|
||||
systemTemplates: FlowModuleTemplateType[];
|
||||
pluginTemplates: FlowModuleTemplateType[];
|
||||
templates: FlowModuleTemplateType[];
|
||||
};
|
||||
|
||||
type ModuleTemplateListProps = ModuleTemplateProps & {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
type RenderListProps = {
|
||||
templates: FlowModuleTemplateType[];
|
||||
onClose: () => void;
|
||||
setNodes: useFlowProviderStoreType['setNodes'];
|
||||
reactFlowWrapper: useFlowProviderStoreType['reactFlowWrapper'];
|
||||
};
|
||||
|
||||
const ModuleTemplateList = ({
|
||||
systemTemplates,
|
||||
pluginTemplates,
|
||||
templates,
|
||||
isOpen,
|
||||
onClose
|
||||
}: ModuleTemplateProps & {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onClose,
|
||||
setNodes,
|
||||
reactFlowWrapper
|
||||
}: ModuleTemplateListProps & {
|
||||
setNodes: useFlowProviderStoreType['setNodes'];
|
||||
reactFlowWrapper: useFlowProviderStoreType['reactFlowWrapper'];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [templateType, setTemplateType] = React.useState(TemplateTypeEnum.system);
|
||||
|
||||
const typeList = useMemo(
|
||||
() => [
|
||||
{
|
||||
type: TemplateTypeEnum.system,
|
||||
label: t('app.module.System Module'),
|
||||
child: <RenderList templates={systemTemplates} onClose={onClose} />
|
||||
},
|
||||
{
|
||||
type: TemplateTypeEnum.plugin,
|
||||
label: t('plugin.Plugin Module'),
|
||||
child: <RenderList templates={pluginTemplates} onClose={onClose} isPlugin />
|
||||
}
|
||||
],
|
||||
[pluginTemplates, onClose, systemTemplates, t]
|
||||
);
|
||||
const TemplateItem = useMemo(
|
||||
() => typeList.find((item) => item.type === templateType)?.child,
|
||||
[templateType, typeList]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -82,6 +66,7 @@ const ModuleTemplateList = ({
|
||||
position={'absolute'}
|
||||
top={'65px'}
|
||||
left={0}
|
||||
pt={2}
|
||||
pb={4}
|
||||
h={isOpen ? 'calc(100% - 100px)' : '0'}
|
||||
w={isOpen ? ['100%', '360px'] : '0'}
|
||||
@@ -92,47 +77,32 @@ const ModuleTemplateList = ({
|
||||
transition={'.2s ease'}
|
||||
userSelect={'none'}
|
||||
>
|
||||
<Flex pt={4} pb={1} px={5} gap={4} alignItems={'center'} fontSize={['md', 'xl']}>
|
||||
{typeList.map((item) => (
|
||||
<Box
|
||||
key={item.label}
|
||||
borderBottom={'2px solid transparent'}
|
||||
{...(item.type === templateType
|
||||
? {
|
||||
color: 'myBlue.700',
|
||||
borderBottomColor: 'myBlue.700',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
: {
|
||||
cursor: 'pointer',
|
||||
onClick: () => setTemplateType(item.type)
|
||||
})}
|
||||
>
|
||||
{item.label}
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
{TemplateItem}
|
||||
<RenderList
|
||||
templates={templates}
|
||||
onClose={onClose}
|
||||
setNodes={setNodes}
|
||||
reactFlowWrapper={reactFlowWrapper}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ModuleTemplateList);
|
||||
export default React.memo(function (props: ModuleTemplateListProps) {
|
||||
const { setNodes, reactFlowWrapper } = useFlowProviderStore();
|
||||
|
||||
return <ModuleTemplateList {...props} setNodes={setNodes} reactFlowWrapper={reactFlowWrapper} />;
|
||||
});
|
||||
|
||||
const RenderList = React.memo(function RenderList({
|
||||
templates,
|
||||
isPlugin = false,
|
||||
onClose
|
||||
}: {
|
||||
templates: FlowModuleTemplateType[];
|
||||
isPlugin?: boolean;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
onClose,
|
||||
setNodes,
|
||||
reactFlowWrapper
|
||||
}: RenderListProps) {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { isPc } = useSystemStore();
|
||||
const { setNodes, reactFlowWrapper } = useFlowProviderStore();
|
||||
const { x, y, zoom } = useViewport();
|
||||
const { setLoading } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
@@ -199,9 +169,9 @@ const RenderList = React.memo(function RenderList({
|
||||
<Box key={item.type}>
|
||||
<Flex>
|
||||
<Box fontWeight={'bold'} flex={1}>
|
||||
{item.label}
|
||||
{t(item.label)}
|
||||
</Box>
|
||||
{isPlugin && item.type === ModuleTemplateTypeEnum.personalPlugin && (
|
||||
{/* {isPlugin && item.type === ModuleTemplateTypeEnum.personalPlugin && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
_hover={{ textDecoration: 'underline' }}
|
||||
@@ -213,7 +183,7 @@ const RenderList = React.memo(function RenderList({
|
||||
</Box>
|
||||
<MyIcon name={'common/rightArrowLight'} w={'12px'} />
|
||||
</Flex>
|
||||
)}
|
||||
)} */}
|
||||
</Flex>
|
||||
<>
|
||||
{item.list.map((template) => (
|
||||
@@ -248,9 +218,9 @@ const RenderList = React.memo(function RenderList({
|
||||
borderRadius={'0'}
|
||||
/>
|
||||
<Box ml={5} flex={'1 0 0'}>
|
||||
<Box color={'black'}>{template.name}</Box>
|
||||
<Box color={'black'}>{t(template.name)}</Box>
|
||||
<Box className="textEllipsis3" color={'myGray.500'} fontSize={'sm'}>
|
||||
{template.intro}
|
||||
{t(template.intro)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { useStoreApi, type ConnectionLineComponentProps } from 'reactflow';
|
||||
|
||||
const CustomConnection = ({ fromX, fromY, toX, toY }: ConnectionLineComponentProps) => {
|
||||
const store = useStoreApi();
|
||||
|
||||
const { connectionHandleId } = store.getState();
|
||||
console.log(fromX, fromY, toX, toY, connectionHandleId);
|
||||
|
||||
return (
|
||||
<g>
|
||||
<path
|
||||
fill="none"
|
||||
stroke={connectionHandleId || ''}
|
||||
strokeWidth={1.5}
|
||||
d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
|
||||
/>
|
||||
<circle
|
||||
cx={toX}
|
||||
cy={toY}
|
||||
fill="#fff"
|
||||
r={3}
|
||||
stroke={connectionHandleId || ''}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomConnection;
|
||||
@@ -1,23 +1,33 @@
|
||||
import React from 'react';
|
||||
import { BaseEdge, EdgeLabelRenderer, EdgeProps, getBezierPath } from 'reactflow';
|
||||
import {
|
||||
SmoothStepEdge,
|
||||
EdgeLabelRenderer,
|
||||
EdgeProps,
|
||||
getSmoothStepPath,
|
||||
MarkerType
|
||||
} from 'reactflow';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
|
||||
const ButtonEdge = ({
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
style = {},
|
||||
markerEnd,
|
||||
data
|
||||
}: EdgeProps<{
|
||||
onDelete: (id: string) => void;
|
||||
}>) => {
|
||||
const [edgePath, labelX, labelY] = getBezierPath({
|
||||
const ButtonEdge = (
|
||||
props: EdgeProps<{
|
||||
onDelete: (id: string) => void;
|
||||
}>
|
||||
) => {
|
||||
const {
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
data,
|
||||
selected,
|
||||
style = {}
|
||||
} = props;
|
||||
|
||||
const [edgePath, labelX, labelY] = getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
@@ -26,9 +36,19 @@ const ButtonEdge = ({
|
||||
targetPosition
|
||||
});
|
||||
|
||||
const edgeStyle = {
|
||||
...style,
|
||||
...(selected
|
||||
? {
|
||||
strokeWidth: 4,
|
||||
stroke: '#3370ff'
|
||||
}
|
||||
: { strokeWidth: 2, stroke: '#BDC1C5' })
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge path={edgePath} markerEnd={markerEnd} style={style} />
|
||||
<SmoothStepEdge {...props} style={edgeStyle} />
|
||||
<EdgeLabelRenderer>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
@@ -48,7 +68,11 @@ const ButtonEdge = ({
|
||||
}}
|
||||
onClick={() => data?.onDelete(id)}
|
||||
>
|
||||
<MyIcon name="closeSolid" w={'100%'} color={'myGray.600'}></MyIcon>
|
||||
<MyIcon
|
||||
name="closeSolid"
|
||||
w={'100%'}
|
||||
color={selected ? 'myBlue.800' : 'myGray.500'}
|
||||
></MyIcon>
|
||||
</Flex>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input
|
||||
} from '@chakra-ui/react';
|
||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import MyModal from '@/components/MyModal';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
|
||||
const ExtractFieldModal = ({
|
||||
defaultField = {
|
||||
desc: '',
|
||||
key: '',
|
||||
required: true
|
||||
},
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
defaultField?: ContextExtractAgentItemType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: ContextExtractAgentItemType) => void;
|
||||
}) => {
|
||||
const { register, handleSubmit } = useForm<ContextExtractAgentItemType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="/imgs/module/extract.png"
|
||||
title={'提取字段配置'}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>必填</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段描述</Box>
|
||||
<Input
|
||||
placeholder="姓名/年龄/sql语句……"
|
||||
{...register('desc', { required: '字段描述不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段 key</Box>
|
||||
<Input
|
||||
placeholder="name/age/sql"
|
||||
{...register('key', { required: '字段 key 不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>确认</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ExtractFieldModal);
|
||||
@@ -1,195 +0,0 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
export type EditFieldModeType = 'input' | 'output' | 'pluginInput';
|
||||
export type EditFieldType = {
|
||||
type?: `${FlowNodeInputTypeEnum}`; // input type
|
||||
key: string;
|
||||
label?: string;
|
||||
valueType?: `${ModuleDataTypeEnum}`;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
createSign?: boolean;
|
||||
};
|
||||
|
||||
const FieldEditModal = ({
|
||||
mode,
|
||||
defaultField,
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
mode: EditFieldModeType;
|
||||
defaultField: EditFieldType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: EditFieldType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const inputTypeList = [
|
||||
{
|
||||
label: t('core.module.inputType.target'),
|
||||
value: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleDataTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.input'),
|
||||
value: FlowNodeInputTypeEnum.input,
|
||||
valueType: ModuleDataTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.textarea'),
|
||||
value: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: ModuleDataTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.switch'),
|
||||
value: FlowNodeInputTypeEnum.switch,
|
||||
valueType: ModuleDataTypeEnum.boolean
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.selectDataset'),
|
||||
value: FlowNodeInputTypeEnum.selectDataset,
|
||||
valueType: ModuleDataTypeEnum.selectDataset
|
||||
}
|
||||
];
|
||||
const dataTypeSelectList = Object.values(FlowValueTypeMap)
|
||||
.slice(0, -2)
|
||||
.map((item) => ({
|
||||
label: t(item.label),
|
||||
value: item.value
|
||||
}));
|
||||
|
||||
const { register, getValues, setValue, handleSubmit } = useForm<EditFieldType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const title = ['input', 'pluginInput'].includes(mode)
|
||||
? t('app.Input Field Settings')
|
||||
: t('app.Output Field Settings');
|
||||
|
||||
const showValueTypeSelect = useMemo(() => {
|
||||
return getValues('type') === FlowNodeInputTypeEnum.target || mode === 'output';
|
||||
}, [getValues, mode, refresh]);
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} iconSrc="/imgs/module/extract.png" title={title} onClose={onClose}>
|
||||
<ModalBody minH={'260px'} overflow={'visible'}>
|
||||
{/* input type select: target, input, textarea.... */}
|
||||
{mode === 'pluginInput' && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Input Type')}</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={inputTypeList}
|
||||
value={getValues('type')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${FlowNodeInputTypeEnum}`;
|
||||
const selectedItem = inputTypeList.find((item) => item.value === type);
|
||||
setValue('type', type);
|
||||
setValue('valueType', selectedItem?.valueType);
|
||||
|
||||
if (type === FlowNodeInputTypeEnum.selectDataset) {
|
||||
setValue('label', selectedItem?.label);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{['input', 'pluginInput'].includes(mode) && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>{t('common.Require Input')}</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
)}
|
||||
{showValueTypeSelect && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Data Type')}</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={dataTypeSelectList}
|
||||
value={getValues('valueType')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${ModuleDataTypeEnum}`;
|
||||
setValue('valueType', type);
|
||||
|
||||
if (
|
||||
type === ModuleDataTypeEnum.chatHistory ||
|
||||
type === ModuleDataTypeEnum.datasetQuote
|
||||
) {
|
||||
const label = dataTypeSelectList.find((item) => item.value === type)?.label;
|
||||
setValue('label', label);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Name')}</Box>
|
||||
<Input
|
||||
placeholder="预约字段/sql语句……"
|
||||
{...register('label', { required: '字段名不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field key')}</Box>
|
||||
<Input
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', { required: '字段 key 不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mb={5} alignItems={'flex-start'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Description')}</Box>
|
||||
<Textarea placeholder="可选" rows={3} {...register('description')} />
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>{t('common.Confirm')}</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(FieldEditModal);
|
||||
|
||||
export const defaultInputField: EditFieldType = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
required: true,
|
||||
createSign: true
|
||||
};
|
||||
export const defaultOutputField: EditFieldType = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
required: true,
|
||||
createSign: true
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { Box, Button, Flex, Textarea } from '@chakra-ui/react';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
@@ -11,7 +11,7 @@ import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4);
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleIOValueTypeEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
@@ -19,7 +19,7 @@ import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { moduleId, inputs } = data;
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} {...data}>
|
||||
@@ -29,28 +29,57 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
moduleId={moduleId}
|
||||
flowInputList={inputs}
|
||||
CustomComponent={{
|
||||
[ModuleInputKeyEnum.agents]: ({
|
||||
key: agentKey,
|
||||
value: agents = [],
|
||||
...props
|
||||
}: {
|
||||
key: string;
|
||||
value?: ClassifyQuestionAgentItemType[];
|
||||
}) => (
|
||||
<Box>
|
||||
{agents.map((item, i) => (
|
||||
<Box key={item.key} mb={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyTooltip label={t('common.Delete')}>
|
||||
<MyIcon
|
||||
[ModuleInputKeyEnum.agents]: ({ key: agentKey, value = [], ...props }) => {
|
||||
const agents = value as ClassifyQuestionAgentItemType[];
|
||||
return (
|
||||
<Box>
|
||||
{agents.map((item, i) => (
|
||||
<Box key={item.key} mb={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyTooltip label={t('common.Delete')}>
|
||||
<MyIcon
|
||||
mt={1}
|
||||
mr={2}
|
||||
name={'minus'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
color={'myGray.600'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: agentKey,
|
||||
value: {
|
||||
...props,
|
||||
key: agentKey,
|
||||
value: agents.filter((input) => input.key !== item.key)
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<Box flex={1}>分类{i + 1}</Box>
|
||||
</Flex>
|
||||
<Box position={'relative'}>
|
||||
<Textarea
|
||||
rows={2}
|
||||
mt={1}
|
||||
mr={2}
|
||||
name={'minus'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
color={'myGray.600'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={() => {
|
||||
defaultValue={item.value}
|
||||
onChange={(e) => {
|
||||
const newVal = agents.map((val) =>
|
||||
val.key === item.key
|
||||
? {
|
||||
...val,
|
||||
value: e.target.value
|
||||
}
|
||||
: val
|
||||
);
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
@@ -58,80 +87,50 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
value: {
|
||||
...props,
|
||||
key: agentKey,
|
||||
value: agents.filter((input) => input.key !== item.key)
|
||||
value: newVal
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<Box flex={1}>分类{i + 1}</Box>
|
||||
</Flex>
|
||||
<Box position={'relative'}>
|
||||
<Textarea
|
||||
rows={2}
|
||||
mt={1}
|
||||
defaultValue={item.value}
|
||||
onChange={(e) => {
|
||||
const newVal = agents.map((val) =>
|
||||
val.key === item.key
|
||||
? {
|
||||
...val,
|
||||
value: e.target.value
|
||||
}
|
||||
: val
|
||||
);
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: agentKey,
|
||||
value: {
|
||||
...props,
|
||||
key: agentKey,
|
||||
value: newVal
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<SourceHandle handleKey={item.key} valueType={ModuleDataTypeEnum.string} />
|
||||
<SourceHandle
|
||||
handleKey={item.key}
|
||||
valueType={ModuleIOValueTypeEnum.string}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
<Button
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
))}
|
||||
<Button
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: agentKey,
|
||||
value: {
|
||||
...props,
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: agentKey,
|
||||
value: agents.concat({ value: '', key })
|
||||
}
|
||||
});
|
||||
value: {
|
||||
...props,
|
||||
key: agentKey,
|
||||
value: agents.concat({ value: '', key })
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key,
|
||||
label: '',
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加问题类型
|
||||
</Button>
|
||||
</Box>
|
||||
)
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key,
|
||||
label: '',
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加问题类型
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
|
||||
const NodeAnswer = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
|
||||
export const defaultField = {
|
||||
desc: '',
|
||||
key: '',
|
||||
required: true,
|
||||
enum: ''
|
||||
};
|
||||
|
||||
const ExtractFieldModal = ({
|
||||
defaultField,
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
defaultField: ContextExtractAgentItemType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: ContextExtractAgentItemType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, handleSubmit } = useForm<ContextExtractAgentItemType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="/imgs/module/extract.png"
|
||||
title={t('core.module.extract.Field Setting Title')}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('common.Require Input')}</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field key')}</Box>
|
||||
<Input placeholder="name/age/sql" {...register('key', { required: true })} />
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Description')}</Box>
|
||||
<Input
|
||||
placeholder={t('core.module.extract.Field Description Placeholder')}
|
||||
{...register('desc', { required: true })}
|
||||
/>
|
||||
</Flex>
|
||||
<Box mt={5}>
|
||||
<Flex alignItems={'center'}>
|
||||
{t('core.module.extract.Enum Value')}({t('common.choosable')})
|
||||
<MyTooltip label={t('core.module.extract.Enum Description')} forceShow>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
|
||||
<Textarea rows={5} placeholder={'apple\npeach\nwatermelon'} {...register('enum')} />
|
||||
</Box>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>{t('common.Confirm')}</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ExtractFieldModal);
|
||||
@@ -3,25 +3,24 @@ import { Box, Button, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@c
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import Container from '../modules/Container';
|
||||
import NodeCard from '../../render/NodeCard';
|
||||
import Container from '../../modules/Container';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import Divider from '../modules/Divider';
|
||||
import RenderInput from '../../render/RenderInput';
|
||||
import Divider from '../../modules/Divider';
|
||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import RenderOutput from '../../render/RenderOutput';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import ExtractFieldModal from '../modules/ExtractFieldModal';
|
||||
import ExtractFieldModal, { defaultField } from './ExtractFieldModal';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useFlowProviderStore, onChangeNode } from '../../FlowProvider';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { onChangeNode } from '../../../FlowProvider';
|
||||
|
||||
const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { inputs, outputs, moduleId } = data;
|
||||
const { t } = useTranslation();
|
||||
const [editExtractFiled, setEditExtractField] = useState<ContextExtractAgentItemType>();
|
||||
const { onDelEdge } = useFlowProviderStore();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} {...data}>
|
||||
@@ -42,13 +41,7 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<AddIcon fontSize={'10px'} />}
|
||||
onClick={() =>
|
||||
setEditExtractField({
|
||||
desc: '',
|
||||
key: '',
|
||||
required: true
|
||||
})
|
||||
}
|
||||
onClick={() => setEditExtractField(defaultField)}
|
||||
>
|
||||
新增字段
|
||||
</Button>
|
||||
@@ -150,7 +143,7 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
key: data.key,
|
||||
label: `提取结果-${data.desc}`,
|
||||
description: '无法提取时不会返回',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
@@ -13,10 +13,9 @@ import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
@@ -25,54 +24,10 @@ const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
<NodeCard minW={'350px'} {...data}>
|
||||
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
<Button
|
||||
variant={'base'}
|
||||
mt={5}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
key,
|
||||
value: {
|
||||
key,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: `入参${inputs.length - 1}`,
|
||||
edit: true
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加入参
|
||||
</Button>
|
||||
</Container>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
<Box textAlign={'right'} mt={5}>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: nanoid(),
|
||||
label: `出参${outputs.length}`,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加出参
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
|
||||
@@ -1,25 +1,51 @@
|
||||
import React, { useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import Container from '../modules/Container';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
import { defaultInputField, type EditFieldType } from '../modules/FieldEditModal';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import type {
|
||||
EditNodeFieldType,
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
} from '@fastgpt/global/core/module/node/type.d';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const FieldEditModal = dynamic(() => import('../render/FieldEditModal'));
|
||||
|
||||
const defaultCreateField: EditNodeFieldType = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
inputType: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
required: true
|
||||
};
|
||||
const createEditField = {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: true,
|
||||
dataType: true,
|
||||
inputType: true
|
||||
};
|
||||
|
||||
const NodePluginInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { toast } = useToast();
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
const [createField, setCreateField] = useState<EditNodeFieldType>();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'300px'} {...data}>
|
||||
@@ -42,10 +68,10 @@ const NodePluginInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
type: item.type,
|
||||
inputType: item.type,
|
||||
valueType: item.valueType,
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
description: item.description,
|
||||
required: item.required
|
||||
})
|
||||
@@ -71,14 +97,13 @@ const NodePluginInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{item.description && (
|
||||
<MyTooltip label={item.description} forceShow>
|
||||
<MyTooltip label={t(item.description)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box position={'relative'}>
|
||||
{item.label}
|
||||
{t(item.label)}
|
||||
{item.required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
@@ -99,95 +124,126 @@ const NodePluginInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
setEditField(defaultInputField);
|
||||
setCreateField(defaultCreateField);
|
||||
}}
|
||||
>
|
||||
添加入参
|
||||
{t('core.module.input.Add Input')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
{!!editField && (
|
||||
{!!createField && (
|
||||
<FieldEditModal
|
||||
mode={'pluginInput'}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
// create field
|
||||
if (e.createSign) {
|
||||
// check key repeat
|
||||
const memInput = inputs.find((item) => item.key === e.key);
|
||||
if (memInput) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: '字段key已存在'
|
||||
});
|
||||
editField={createEditField}
|
||||
defaultField={createField}
|
||||
keys={inputs.map((input) => input.key)}
|
||||
onClose={() => setCreateField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: data.inputType,
|
||||
required: data.required,
|
||||
description: data.description,
|
||||
edit: true,
|
||||
editField: createEditField
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
setCreateField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!!editField?.key && (
|
||||
<FieldEditModal
|
||||
editField={createEditField}
|
||||
defaultField={editField}
|
||||
keys={[editField.key]}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data, changeKey }) => {
|
||||
if (!data.inputType || !data.key || !data.label) return;
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key: e.key,
|
||||
valueType: e.valueType,
|
||||
type: e.type,
|
||||
label: e.label,
|
||||
required: e.required,
|
||||
edit: true
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: e.key,
|
||||
valueType: e.valueType,
|
||||
label: e.label,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
return setEditField(undefined);
|
||||
}
|
||||
// check key valid
|
||||
const memInput = inputs.find((item) => item.key === editField.key);
|
||||
const memOutput = outputs.find((item) => item.key === editField.key);
|
||||
|
||||
if (!memInput || !memOutput) return setEditField(undefined);
|
||||
const input = {
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
...memInput,
|
||||
...e
|
||||
type: data.inputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
required: data.required,
|
||||
label: data.label,
|
||||
description: data.description,
|
||||
...(data.inputType === FlowNodeInputTypeEnum.addInputParam
|
||||
? {
|
||||
editField: {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: true,
|
||||
dataType: true,
|
||||
inputType: false
|
||||
},
|
||||
defaultEditField: {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
inputType: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
: {})
|
||||
};
|
||||
const output = {
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
...memOutput,
|
||||
...e
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label
|
||||
};
|
||||
// not update key
|
||||
if (editField.key === e.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
});
|
||||
} else {
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
value: newOutput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: newInput.key,
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: newOutput.key,
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,52 @@
|
||||
import React, { useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import Container from '../modules/Container';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { EditFieldType, defaultOutputField } from '../modules/FieldEditModal';
|
||||
import TargetHandle from '../render/TargetHandle';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import {
|
||||
EditNodeFieldType,
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
} from '@fastgpt/global/core/module/node/type';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const FieldEditModal = dynamic(() => import('../render/FieldEditModal'));
|
||||
|
||||
const defaultCreateField: EditNodeFieldType = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
inputType: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
required: true
|
||||
};
|
||||
const createEditField = {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: false,
|
||||
dataType: true,
|
||||
inputType: false
|
||||
};
|
||||
|
||||
const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { toast } = useToast();
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
const [createField, setCreateField] = useState<EditNodeFieldType>();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'300px'} {...data}>
|
||||
@@ -36,7 +63,7 @@ const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
>
|
||||
<TargetHandle handleKey={item.key} valueType={item.valueType} />
|
||||
<Box position={'relative'}>
|
||||
{item.label}
|
||||
{t(item.label)}
|
||||
<Box
|
||||
position={'absolute'}
|
||||
right={'-6px'}
|
||||
@@ -47,7 +74,11 @@ const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
*
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{item.description && (
|
||||
<MyTooltip label={t(item.description)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={2} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
<MyIcon
|
||||
name={'settingLight'}
|
||||
w={'14px'}
|
||||
@@ -56,10 +87,12 @@ const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
inputType: item.type,
|
||||
valueType: item.valueType,
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
description: item.description
|
||||
description: item.description,
|
||||
required: item.required
|
||||
})
|
||||
}
|
||||
/>
|
||||
@@ -68,7 +101,7 @@ const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
@@ -84,12 +117,6 @@ const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{item.description && (
|
||||
<MyTooltip label={item.description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
<Box textAlign={'left'} mt={5}>
|
||||
@@ -97,92 +124,106 @@ const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
setEditField(defaultOutputField);
|
||||
setCreateField(defaultCreateField);
|
||||
}}
|
||||
>
|
||||
添加出参
|
||||
{t('core.module.output.Add Output')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
{!!editField && (
|
||||
{!!createField && (
|
||||
<FieldEditModal
|
||||
mode={'output'}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
if (e.createSign) {
|
||||
// check key repeat
|
||||
const memInput = inputs.find((item) => item.key === e.key);
|
||||
if (memInput) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: '字段key已存在'
|
||||
});
|
||||
editField={createEditField}
|
||||
defaultField={createField}
|
||||
keys={inputs.map((input) => input.key)}
|
||||
onClose={() => setCreateField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: data.inputType,
|
||||
required: data.required,
|
||||
description: data.description,
|
||||
edit: true,
|
||||
editField: createEditField
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
setCreateField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!!editField?.key && (
|
||||
<FieldEditModal
|
||||
editField={createEditField}
|
||||
defaultField={editField}
|
||||
keys={[editField.key]}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data, changeKey }) => {
|
||||
if (!data.inputType || !data.key || !data.label) return;
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key: e.key,
|
||||
valueType: e.valueType,
|
||||
type: e.type,
|
||||
label: e.label,
|
||||
required: e.required,
|
||||
edit: true
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: e.key,
|
||||
valueType: e.valueType,
|
||||
label: e.label,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
return setEditField(undefined);
|
||||
}
|
||||
// check key valid
|
||||
const memInput = inputs.find((item) => item.key === editField.key);
|
||||
const memOutput = outputs.find((item) => item.key === editField.key);
|
||||
if (!memInput || !memOutput) return;
|
||||
const input = {
|
||||
|
||||
if (!memInput || !memOutput) return setEditField(undefined);
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
...memInput,
|
||||
...e
|
||||
type: data.inputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
required: data.required,
|
||||
label: data.label,
|
||||
description: data.description
|
||||
};
|
||||
const output = {
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
...memOutput,
|
||||
...e
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label
|
||||
};
|
||||
// not update key
|
||||
if (editField.key === e.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
});
|
||||
} else {
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
value: newOutput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: newInput.key,
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: newOutput.key,
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
const QuestionInputNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { moduleId, outputs } = data;
|
||||
|
||||
return (
|
||||
<NodeCard minW={'240px'} {...data}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
|
||||
@@ -24,7 +24,7 @@ import VariableEdit from '../modules/VariableEdit';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import Container from '../modules/Container';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import type { VariableItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import QGSwitch from '@/components/core/module/Flow/components/modules/QGSwitch';
|
||||
import TTSSelect from '@/components/core/module/Flow/components/modules/TTSSelect';
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { Box, Button, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import NodeCard from '../../modules/NodeCard';
|
||||
import NodeCard from '../../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../../modules/Container';
|
||||
import { VariableInputEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { DYNAMIC_INPUT_KEY, ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { EditInputFieldMap, EditNodeFieldType } from '@fastgpt/global/core/module/node/type.d';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
|
||||
const FieldEditModal = ({
|
||||
editField = {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
dataType: true
|
||||
},
|
||||
defaultField,
|
||||
keys = [],
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
editField?: EditInputFieldMap;
|
||||
defaultField: EditNodeFieldType;
|
||||
keys: string[];
|
||||
onClose: () => void;
|
||||
onSubmit: (e: { data: EditNodeFieldType; changeKey: boolean }) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const isCreate = useMemo(() => !defaultField.key, [defaultField.key]);
|
||||
const showDynamicInputSelect =
|
||||
!keys.includes(DYNAMIC_INPUT_KEY) || defaultField.key === DYNAMIC_INPUT_KEY;
|
||||
|
||||
const inputTypeList = [
|
||||
{
|
||||
label: t('core.module.inputType.target'),
|
||||
value: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.input'),
|
||||
value: FlowNodeInputTypeEnum.input,
|
||||
valueType: ModuleIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.textarea'),
|
||||
value: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: ModuleIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.switch'),
|
||||
value: FlowNodeInputTypeEnum.switch,
|
||||
valueType: ModuleIOValueTypeEnum.boolean
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.selectDataset'),
|
||||
value: FlowNodeInputTypeEnum.selectDataset,
|
||||
valueType: ModuleIOValueTypeEnum.selectDataset
|
||||
},
|
||||
...(showDynamicInputSelect
|
||||
? [
|
||||
{
|
||||
label: t('core.module.inputType.dynamicTargetInput'),
|
||||
value: FlowNodeInputTypeEnum.addInputParam,
|
||||
valueType: ModuleIOValueTypeEnum.any
|
||||
}
|
||||
]
|
||||
: [])
|
||||
];
|
||||
|
||||
const dataTypeSelectList = Object.values(FlowValueTypeMap)
|
||||
.slice(0, -2)
|
||||
.map((item) => ({
|
||||
label: t(item.label),
|
||||
value: item.value
|
||||
}));
|
||||
|
||||
const { register, getValues, setValue, handleSubmit, watch } = useForm<EditNodeFieldType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const showDataTypeSelect = useMemo(() => {
|
||||
if (!editField.dataType) return false;
|
||||
const inputType = getValues('inputType');
|
||||
const outputType = getValues('outputType');
|
||||
|
||||
if (inputType === FlowNodeInputTypeEnum.target) return true;
|
||||
|
||||
if (outputType === FlowNodeOutputTypeEnum.source) return true;
|
||||
|
||||
return false;
|
||||
}, [editField.dataType, getValues, refresh]);
|
||||
|
||||
const showRequired = useMemo(() => {
|
||||
const inputType = getValues('inputType');
|
||||
const valueType = getValues('valueType');
|
||||
if (inputType === FlowNodeInputTypeEnum.addInputParam) return false;
|
||||
|
||||
return editField.required;
|
||||
}, [editField.required, getValues, refresh]);
|
||||
|
||||
const showNameInput = useMemo(() => {
|
||||
const inputType = getValues('inputType');
|
||||
|
||||
return editField.name;
|
||||
}, [editField.name, getValues, refresh]);
|
||||
|
||||
const showKeyInput = useMemo(() => {
|
||||
const inputType = getValues('inputType');
|
||||
const valueType = getValues('valueType');
|
||||
if (inputType === FlowNodeInputTypeEnum.addInputParam) return false;
|
||||
|
||||
return editField.key;
|
||||
}, [editField.key, getValues, refresh]);
|
||||
|
||||
const showDescriptionInput = useMemo(() => {
|
||||
const inputType = getValues('inputType');
|
||||
|
||||
return editField.description;
|
||||
}, [editField.description, getValues, refresh]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="/imgs/module/extract.png"
|
||||
title={t('core.module.edit.Field Edit')}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody overflow={'visible'}>
|
||||
{/* input type select: target, input, textarea.... */}
|
||||
{editField.inputType && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Input Type')}</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={inputTypeList}
|
||||
value={getValues('inputType')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${FlowNodeInputTypeEnum}`;
|
||||
const selectedItem = inputTypeList.find((item) => item.value === type);
|
||||
setValue('inputType', type);
|
||||
setValue('valueType', selectedItem?.valueType);
|
||||
|
||||
if (type === FlowNodeInputTypeEnum.selectDataset) {
|
||||
setValue('label', selectedItem?.label);
|
||||
} else if (type === FlowNodeInputTypeEnum.addInputParam) {
|
||||
setValue('label', t('core.module.valueType.dynamicTargetInput'));
|
||||
setValue('key', DYNAMIC_INPUT_KEY);
|
||||
setValue('required', false);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{showRequired && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>{t('common.Require Input')}</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
)}
|
||||
{showDataTypeSelect && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Data Type')}</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={dataTypeSelectList}
|
||||
value={getValues('valueType')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${ModuleIOValueTypeEnum}`;
|
||||
setValue('valueType', type);
|
||||
|
||||
if (
|
||||
type === ModuleIOValueTypeEnum.chatHistory ||
|
||||
type === ModuleIOValueTypeEnum.datasetQuote
|
||||
) {
|
||||
const label = dataTypeSelectList.find((item) => item.value === type)?.label;
|
||||
setValue('label', label);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{showNameInput && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Name')}</Box>
|
||||
<Input placeholder="预约字段/sql语句……" {...register('label', { required: true })} />
|
||||
</Flex>
|
||||
)}
|
||||
{showKeyInput && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field key')}</Box>
|
||||
<Input placeholder="appointment/sql" {...register('key', { required: true })} />
|
||||
</Flex>
|
||||
)}
|
||||
{showDescriptionInput && (
|
||||
<Flex mb={5} alignItems={'flex-start'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Description')}</Box>
|
||||
<Textarea placeholder={t('common.choosable')} rows={3} {...register('description')} />
|
||||
</Flex>
|
||||
)}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit((data) => {
|
||||
if (!data.key) return;
|
||||
if (isCreate && keys.includes(data.key)) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.edit.Field Already Exist')
|
||||
});
|
||||
}
|
||||
onSubmit({
|
||||
data,
|
||||
changeKey: !keys.includes(data.key)
|
||||
});
|
||||
})}
|
||||
>
|
||||
{t('common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(FieldEditModal);
|
||||
@@ -8,7 +8,11 @@ import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { useFlowProviderStore, onChangeNode } from '../../FlowProvider';
|
||||
import {
|
||||
useFlowProviderStore,
|
||||
onChangeNode,
|
||||
type useFlowProviderStoreType
|
||||
} from '../../FlowProvider';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
@@ -23,20 +27,28 @@ type Props = FlowModuleItemType & {
|
||||
isPreview?: boolean;
|
||||
};
|
||||
|
||||
const NodeCard = (props: Props) => {
|
||||
const NodeCard = (
|
||||
props: Props & {
|
||||
onCopyNode: useFlowProviderStoreType['onCopyNode'];
|
||||
onResetNode: useFlowProviderStoreType['onResetNode'];
|
||||
onDelNode: useFlowProviderStoreType['onDelNode'];
|
||||
}
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
children,
|
||||
avatar = LOGO_ICON,
|
||||
name = '未知模块',
|
||||
name = t('core.module.template.UnKnow Module'),
|
||||
intro,
|
||||
minW = '300px',
|
||||
moduleId,
|
||||
flowType,
|
||||
inputs,
|
||||
isPreview
|
||||
isPreview,
|
||||
onCopyNode,
|
||||
onResetNode,
|
||||
onDelNode
|
||||
} = props;
|
||||
const { onCopyNode, onResetNode, onDelNode } = useFlowProviderStore();
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { setLoading } = useSystemStore();
|
||||
@@ -147,10 +159,10 @@ const NodeCard = (props: Props) => {
|
||||
<Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}>
|
||||
<Avatar src={avatar} borderRadius={'md'} objectFit={'contain'} w={'30px'} h={'30px'} />
|
||||
<Box ml={3} fontSize={'lg'} color={'myGray.600'}>
|
||||
{name}
|
||||
{t(name)}
|
||||
</Box>
|
||||
{intro && (
|
||||
<MyTooltip label={intro} forceShow>
|
||||
<MyTooltip label={t(intro)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mb={'1px'} ml={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
@@ -186,4 +198,10 @@ const NodeCard = (props: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NodeCard);
|
||||
export default React.memo(function (props: Props) {
|
||||
const { onCopyNode, onResetNode, onDelNode } = useFlowProviderStore();
|
||||
|
||||
return (
|
||||
<NodeCard {...props} onCopyNode={onCopyNode} onResetNode={onResetNode} onDelNode={onDelNode} />
|
||||
);
|
||||
});
|
||||
@@ -1,729 +0,0 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { SelectAppItemType } from '@fastgpt/global/core/module/type';
|
||||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import {
|
||||
Box,
|
||||
Textarea,
|
||||
Input,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberDecrementStepper,
|
||||
Flex,
|
||||
useDisclosure,
|
||||
Button,
|
||||
useTheme,
|
||||
Grid,
|
||||
Switch
|
||||
} from '@chakra-ui/react';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { onChangeNode, useFlowProviderStore } from '../../FlowProvider';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MySelect from '@/components/Select';
|
||||
import MySlider from '@/components/Slider';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import TargetHandle from './TargetHandle';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type { AIChatModuleProps } from '@fastgpt/global/core/module/node/type.d';
|
||||
import { chatModelList, cqModelList } from '@/web/common/system/staticData';
|
||||
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { EditFieldModeType, EditFieldType } from '../modules/FieldEditModal';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const SelectAppModal = dynamic(() => import('../../SelectAppModal'));
|
||||
const AIChatSettingsModal = dynamic(() => import('../../../AIChatSettingsModal'));
|
||||
const DatasetSelectModal = dynamic(() => import('../../../DatasetSelectModal'));
|
||||
const DatasetParamsModal = dynamic(() => import('../../../DatasetParamsModal'));
|
||||
|
||||
export const Label = React.memo(function Label({
|
||||
moduleId,
|
||||
inputKey,
|
||||
editFiledType = 'input',
|
||||
...item
|
||||
}: FlowNodeInputItemType & {
|
||||
moduleId: string;
|
||||
inputKey: string;
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { mode } = useFlowProviderStore();
|
||||
const {
|
||||
required = false,
|
||||
description,
|
||||
edit,
|
||||
label,
|
||||
type,
|
||||
valueType,
|
||||
showTargetInApp,
|
||||
showTargetInPlugin
|
||||
} = item;
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
const targetHandle = useMemo(() => {
|
||||
if (type === FlowNodeInputTypeEnum.target) return true;
|
||||
if (mode === 'app' && showTargetInApp) return true;
|
||||
if (mode === 'plugin' && showTargetInPlugin) return true;
|
||||
return false;
|
||||
}, [mode, showTargetInApp, showTargetInPlugin, type]);
|
||||
|
||||
return (
|
||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
||||
<Box position={'relative'}>
|
||||
{t(label)}
|
||||
{description && (
|
||||
<MyTooltip label={description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
{required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'-2px'}
|
||||
right={'-8px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{targetHandle && <TargetHandle handleKey={inputKey} valueType={valueType} />}
|
||||
|
||||
{edit && (
|
||||
<>
|
||||
<MyIcon
|
||||
name={'settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
label: item.label,
|
||||
type: item.type,
|
||||
valueType: item.valueType,
|
||||
required: item.required,
|
||||
key: inputKey,
|
||||
description: item.description
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: inputKey,
|
||||
value: ''
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
mode={editFiledType}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
const data = {
|
||||
...item,
|
||||
...e
|
||||
};
|
||||
// same key
|
||||
if (editField.key === data.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: data.key,
|
||||
value: data
|
||||
});
|
||||
} else {
|
||||
// diff key. del and add
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: data
|
||||
});
|
||||
}
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
const RenderInput = ({
|
||||
flowInputList,
|
||||
moduleId,
|
||||
CustomComponent = {},
|
||||
editFiledType
|
||||
}: {
|
||||
flowInputList: FlowNodeInputItemType[];
|
||||
moduleId: string;
|
||||
CustomComponent?: Record<string, (e: FlowNodeInputItemType) => React.ReactNode>;
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) => {
|
||||
const sortInputs = useMemo(
|
||||
() =>
|
||||
flowInputList
|
||||
.filter((item) => !item.plusField || feConfigs.isPlus)
|
||||
.sort((a, b) => (a.key === FlowNodeInputTypeEnum.switch ? -1 : 1)),
|
||||
[flowInputList]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{sortInputs.map(
|
||||
(item) =>
|
||||
item.type !== FlowNodeInputTypeEnum.hidden && (
|
||||
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
{!!item.label && (
|
||||
<Label
|
||||
editFiledType={editFiledType}
|
||||
moduleId={moduleId}
|
||||
inputKey={item.key}
|
||||
{...item}
|
||||
/>
|
||||
)}
|
||||
<Box mt={2} className={'nodrag'}>
|
||||
{item.type === FlowNodeInputTypeEnum.numberInput && (
|
||||
<NumberInputRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.input && (
|
||||
<TextInputRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.switch && (
|
||||
<SwitchRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.textarea && (
|
||||
<TextareaRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.select && (
|
||||
<SelectRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.slider && (
|
||||
<SliderRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.selectApp && (
|
||||
<SelectAppRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.aiSettings && (
|
||||
<AISetting inputs={sortInputs} item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{[
|
||||
FlowNodeInputTypeEnum.selectChatModel,
|
||||
FlowNodeInputTypeEnum.selectCQModel
|
||||
].includes(item.type as any) && (
|
||||
<SelectAIModelRender inputs={sortInputs} item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.selectDataset && (
|
||||
<SelectDatasetRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.selectDatasetParamsModal && (
|
||||
<SelectDatasetParamsRender item={item} inputs={sortInputs} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.custom && CustomComponent[item.key] && (
|
||||
<>{CustomComponent[item.key]({ ...item })}</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(RenderInput);
|
||||
|
||||
type RenderProps = {
|
||||
inputs?: FlowNodeInputItemType[];
|
||||
item: FlowNodeInputItemType;
|
||||
moduleId: string;
|
||||
};
|
||||
|
||||
const NumberInputRender = React.memo(function NumberInputRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<NumberInput
|
||||
defaultValue={item.value}
|
||||
min={item.min}
|
||||
max={item.max}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: Number(e)
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<NumberInputField />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
);
|
||||
});
|
||||
|
||||
const TextInputRender = React.memo(function TextInputRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<Input
|
||||
placeholder={item.placeholder}
|
||||
defaultValue={item.value}
|
||||
onBlur={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const SwitchRender = React.memo(function SwitchRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<Switch
|
||||
size={'lg'}
|
||||
isChecked={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.checked
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const TextareaRender = React.memo(function TextareaRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<Textarea
|
||||
rows={5}
|
||||
placeholder={item.placeholder}
|
||||
resize={'both'}
|
||||
defaultValue={item.value}
|
||||
onBlur={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const SelectRender = React.memo(function SelectRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<MySelect
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={item.list || []}
|
||||
onchange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const SliderRender = React.memo(function SliderRender({ item, moduleId }: RenderProps) {
|
||||
return (
|
||||
<Box pt={5} pb={4} px={2}>
|
||||
<MySlider
|
||||
markList={item.markList}
|
||||
width={'100%'}
|
||||
min={item.min || 0}
|
||||
max={item.max}
|
||||
step={item.step || 1}
|
||||
value={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
const AISetting = React.memo(function AISetting({ inputs = [], moduleId }: RenderProps) {
|
||||
const { t } = useTranslation();
|
||||
const chatModulesData = useMemo(() => {
|
||||
const obj: Record<string, any> = {};
|
||||
inputs.forEach((item) => {
|
||||
obj[item.key] = item.value;
|
||||
});
|
||||
return obj as AIChatModuleProps;
|
||||
}, [inputs]);
|
||||
|
||||
const {
|
||||
isOpen: isOpenAIChatSetting,
|
||||
onOpen: onOpenAIChatSetting,
|
||||
onClose: onCloseAIChatSetting
|
||||
} = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<MyIcon name={'settingLight'} w={'14px'} />}
|
||||
onClick={onOpenAIChatSetting}
|
||||
>
|
||||
{t('app.AI Settings')}
|
||||
</Button>
|
||||
{isOpenAIChatSetting && (
|
||||
<AIChatSettingsModal
|
||||
isAdEdit
|
||||
onClose={onCloseAIChatSetting}
|
||||
onSuccess={(e) => {
|
||||
for (let key in e) {
|
||||
const item = inputs.find((input) => input.key === key);
|
||||
if (!item) continue;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key,
|
||||
value: {
|
||||
...item,
|
||||
//@ts-ignore
|
||||
value: e[key]
|
||||
}
|
||||
});
|
||||
}
|
||||
onCloseAIChatSetting();
|
||||
}}
|
||||
defaultData={chatModulesData}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const SelectAIModelRender = React.memo(function SelectAIModelRender({
|
||||
inputs = [],
|
||||
item,
|
||||
moduleId
|
||||
}: RenderProps) {
|
||||
const modelList = (() => {
|
||||
if (item.type === FlowNodeInputTypeEnum.selectChatModel) return chatModelList;
|
||||
if (item.type === FlowNodeInputTypeEnum.selectCQModel) return cqModelList;
|
||||
return [];
|
||||
})().map((item) => ({
|
||||
model: item.model,
|
||||
name: item.name,
|
||||
maxResponse: item.maxResponse,
|
||||
price: item.price
|
||||
}));
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(e: string) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
|
||||
// update max tokens
|
||||
const model = modelList.find((item) => item.model === e) || modelList[0];
|
||||
if (!model) return;
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: 'maxToken',
|
||||
value: {
|
||||
...inputs.find((input) => input.key === 'maxToken'),
|
||||
markList: [
|
||||
{ label: '100', value: 100 },
|
||||
{ label: `${model.maxResponse}`, value: model.maxResponse }
|
||||
],
|
||||
max: model.maxResponse,
|
||||
value: model.maxResponse / 2
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputs, item, modelList, moduleId]
|
||||
);
|
||||
|
||||
const list = modelList.map((item) => {
|
||||
const priceStr = `(${formatPrice(item.price, 1000)}元/1k Tokens)`;
|
||||
|
||||
return {
|
||||
value: item.model,
|
||||
label: `${item.name}${priceStr}`
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!item.value && list.length > 0) {
|
||||
onChangeModel(list[0].value);
|
||||
}
|
||||
}, [item.value, list, onChangeModel]);
|
||||
|
||||
return (
|
||||
<MySelect
|
||||
minW={'350px'}
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={list}
|
||||
onchange={onChangeModel}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const SelectDatasetRender = React.memo(function SelectDatasetRender({
|
||||
item,
|
||||
moduleId
|
||||
}: RenderProps) {
|
||||
const theme = useTheme();
|
||||
const { mode } = useFlowProviderStore();
|
||||
const { allDatasets, loadAllDatasets } = useDatasetStore();
|
||||
const {
|
||||
isOpen: isOpenKbSelect,
|
||||
onOpen: onOpenKbSelect,
|
||||
onClose: onCloseKbSelect
|
||||
} = useDisclosure();
|
||||
|
||||
const selectedDatasets = useMemo(() => {
|
||||
const value = item.value as SelectedDatasetType;
|
||||
return allDatasets.filter((dataset) => value?.find((item) => item.datasetId === dataset._id));
|
||||
}, [allDatasets, item.value]);
|
||||
|
||||
useQuery(['loadAllDatasets'], loadAllDatasets);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={4} minW={'350px'} w={'100%'}>
|
||||
<Button h={'36px'} onClick={onOpenKbSelect}>
|
||||
选择知识库
|
||||
</Button>
|
||||
{selectedDatasets.map((item) => (
|
||||
<Flex
|
||||
key={item._id}
|
||||
alignItems={'center'}
|
||||
h={'36px'}
|
||||
border={theme.borders.base}
|
||||
px={2}
|
||||
borderRadius={'md'}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'24px'}></Avatar>
|
||||
<Box
|
||||
ml={3}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className="textEllipsis"
|
||||
fontWeight={'bold'}
|
||||
fontSize={['md', 'lg', 'xl']}
|
||||
>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
{isOpenKbSelect && (
|
||||
<DatasetSelectModal
|
||||
isOpen={isOpenKbSelect}
|
||||
defaultSelectedDatasets={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: item.key,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
onClose={onCloseKbSelect}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const SelectAppRender = React.memo(function SelectAppRender({ item, moduleId }: RenderProps) {
|
||||
const { filterAppIds } = useFlowProviderStore();
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
isOpen: isOpenSelectApp,
|
||||
onOpen: onOpenSelectApp,
|
||||
onClose: onCloseSelectApp
|
||||
} = useDisclosure();
|
||||
|
||||
const value = item.value as SelectAppItemType | undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box onClick={onOpenSelectApp}>
|
||||
{!value ? (
|
||||
<Button variant={'base'} w={'100%'}>
|
||||
选择应用
|
||||
</Button>
|
||||
) : (
|
||||
<Flex alignItems={'center'} border={theme.borders.base} borderRadius={'md'} px={3} py={2}>
|
||||
<Avatar src={value?.logo} />
|
||||
<Box fontWeight={'bold'} ml={1}>
|
||||
{value?.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{isOpenSelectApp && (
|
||||
<SelectAppModal
|
||||
defaultApps={item.value?.id ? [item.value.id] : []}
|
||||
filterAppIds={filterAppIds}
|
||||
onClose={onCloseSelectApp}
|
||||
onSuccess={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: 'app',
|
||||
value: {
|
||||
...item,
|
||||
value: e[0]
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const SelectDatasetParamsRender = React.memo(function SelectDatasetParamsRender({
|
||||
item,
|
||||
inputs = [],
|
||||
moduleId
|
||||
}: RenderProps) {
|
||||
const { nodes } = useFlowProviderStore();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [data, setData] = useState({
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
limit: 5,
|
||||
similarity: 0.5
|
||||
});
|
||||
|
||||
const tokenLimit = useMemo(() => {
|
||||
let maxTokens = 3000;
|
||||
|
||||
nodes.forEach((item) => {
|
||||
if (item.type === FlowNodeTypeEnum.chatNode) {
|
||||
const model =
|
||||
item.data.inputs.find((item) => item.key === ModuleInputKeyEnum.aiModel)?.value || '';
|
||||
const quoteMaxToken =
|
||||
chatModelList.find((item) => item.model === model)?.quoteMaxToken || 3000;
|
||||
|
||||
maxTokens = Math.max(maxTokens, quoteMaxToken);
|
||||
}
|
||||
});
|
||||
|
||||
return maxTokens;
|
||||
}, [nodes]);
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
useEffect(() => {
|
||||
inputs.forEach((input) => {
|
||||
// @ts-ignore
|
||||
if (data[input.key] !== undefined) {
|
||||
setData((state) => ({
|
||||
...state,
|
||||
[input.key]: input.value
|
||||
}));
|
||||
}
|
||||
});
|
||||
}, [inputs]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<MyIcon name={'settingLight'} w={'14px'} />}
|
||||
onClick={onOpen}
|
||||
>
|
||||
{t('core.dataset.search.Params Setting')}
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<DatasetParamsModal
|
||||
{...data}
|
||||
maxTokens={tokenLimit}
|
||||
onClose={onClose}
|
||||
onSuccess={(e) => {
|
||||
for (let key in e) {
|
||||
const item = inputs.find((input) => input.key === key);
|
||||
if (!item) continue;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key,
|
||||
value: {
|
||||
...item,
|
||||
//@ts-ignore
|
||||
value: e[key]
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,160 @@
|
||||
import { EditNodeFieldType, FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
onChangeNode,
|
||||
useFlowProviderStore,
|
||||
useFlowProviderStoreType
|
||||
} from '../../../FlowProvider';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import TargetHandle from '../TargetHandle';
|
||||
import MyIcon from '@/components/Icon';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../FieldEditModal'));
|
||||
|
||||
type Props = FlowNodeInputItemType & {
|
||||
moduleId: string;
|
||||
inputKey: string;
|
||||
};
|
||||
|
||||
const InputLabel = ({
|
||||
moduleId,
|
||||
inputKey,
|
||||
mode,
|
||||
...item
|
||||
}: Props & {
|
||||
mode: useFlowProviderStoreType['mode'];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
required = false,
|
||||
description,
|
||||
edit,
|
||||
label,
|
||||
type,
|
||||
valueType,
|
||||
showTargetInApp,
|
||||
showTargetInPlugin
|
||||
} = item;
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
const targetHandle = useMemo(() => {
|
||||
if (type === FlowNodeInputTypeEnum.target) return true;
|
||||
if (mode === 'app' && showTargetInApp) return true;
|
||||
if (mode === 'plugin' && showTargetInPlugin) return true;
|
||||
return false;
|
||||
}, [mode, showTargetInApp, showTargetInPlugin, type]);
|
||||
|
||||
return (
|
||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
||||
<Box position={'relative'}>
|
||||
{t(label)}
|
||||
{description && (
|
||||
<MyTooltip label={t(description)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
{required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'-2px'}
|
||||
right={'-8px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{targetHandle && <TargetHandle handleKey={inputKey} valueType={valueType} />}
|
||||
|
||||
{edit && (
|
||||
<>
|
||||
<MyIcon
|
||||
name={'settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
inputType: type,
|
||||
valueType: valueType,
|
||||
key: inputKey,
|
||||
required,
|
||||
label,
|
||||
description
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: inputKey,
|
||||
value: ''
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!!editField?.key && (
|
||||
<FieldEditModal
|
||||
editField={item.editField}
|
||||
keys={[editField.key]}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data, changeKey }) => {
|
||||
if (!data.inputType || !data.key || !data.label) return;
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
...item,
|
||||
type: data.inputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
required: data.required,
|
||||
label: data.label,
|
||||
description: data.description
|
||||
};
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: newInput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: newInput.key,
|
||||
value: newInput
|
||||
});
|
||||
}
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(function (props: Props) {
|
||||
const { mode } = useFlowProviderStore();
|
||||
|
||||
return <InputLabel {...props} mode={mode} />;
|
||||
});
|
||||
@@ -0,0 +1,144 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import InputLabel from './Label';
|
||||
import type { RenderInputProps } from './type.d';
|
||||
import { useFlowProviderStore, type useFlowProviderStoreType } from '../../../FlowProvider';
|
||||
|
||||
const RenderList: {
|
||||
types: `${FlowNodeInputTypeEnum}`[];
|
||||
Component: React.ComponentType<RenderInputProps>;
|
||||
}[] = [
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.input],
|
||||
Component: dynamic(() => import('./templates/TextInput'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.numberInput],
|
||||
Component: dynamic(() => import('./templates/NumberInput'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.switch],
|
||||
Component: dynamic(() => import('./templates/Switch'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.textarea],
|
||||
Component: dynamic(() => import('./templates/Textarea'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.select],
|
||||
Component: dynamic(() => import('./templates/Select'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.slider],
|
||||
Component: dynamic(() => import('./templates/Slider'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.selectApp],
|
||||
Component: dynamic(() => import('./templates/SelectApp'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.aiSettings],
|
||||
Component: dynamic(() => import('./templates/AiSetting'))
|
||||
},
|
||||
{
|
||||
types: [
|
||||
FlowNodeInputTypeEnum.selectChatModel,
|
||||
FlowNodeInputTypeEnum.selectCQModel,
|
||||
FlowNodeInputTypeEnum.selectExtractModel
|
||||
],
|
||||
Component: dynamic(() => import('./templates/SelectAiModel'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.selectDataset],
|
||||
Component: dynamic(() => import('./templates/SelectDataset'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.selectDatasetParamsModal],
|
||||
Component: dynamic(() => import('./templates/SelectDatasetParams'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.addInputParam],
|
||||
Component: dynamic(() => import('./templates/AddInputParam'))
|
||||
}
|
||||
];
|
||||
|
||||
type Props = {
|
||||
flowInputList: FlowNodeInputItemType[];
|
||||
moduleId: string;
|
||||
CustomComponent?: Record<string, (e: FlowNodeInputItemType) => React.ReactNode>;
|
||||
};
|
||||
const RenderInput = ({
|
||||
flowInputList,
|
||||
moduleId,
|
||||
CustomComponent = {},
|
||||
mode
|
||||
}: Props & {
|
||||
mode: useFlowProviderStoreType['mode'];
|
||||
}) => {
|
||||
const sortInputs = useMemo(
|
||||
() =>
|
||||
flowInputList.sort((a, b) => {
|
||||
if (a.type === FlowNodeInputTypeEnum.addInputParam) {
|
||||
return 1;
|
||||
}
|
||||
if (b.type === FlowNodeInputTypeEnum.addInputParam) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.type === FlowNodeInputTypeEnum.switch) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}),
|
||||
[flowInputList]
|
||||
);
|
||||
const filterInputs = useMemo(
|
||||
() =>
|
||||
sortInputs.filter((input) => {
|
||||
if (mode === 'app' && input.hideInApp) return false;
|
||||
if (mode === 'plugin' && input.hideInPlugin) return false;
|
||||
|
||||
return true;
|
||||
}),
|
||||
[mode, sortInputs]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{filterInputs.map((input) => {
|
||||
const RenderComponent = (() => {
|
||||
if (input.type === FlowNodeInputTypeEnum.custom && CustomComponent[input.key]) {
|
||||
return <>{CustomComponent[input.key]({ ...input })}</>;
|
||||
}
|
||||
const Component = RenderList.find((item) => item.types.includes(input.type))?.Component;
|
||||
|
||||
if (!Component) return null;
|
||||
return <Component inputs={filterInputs} item={input} moduleId={moduleId} />;
|
||||
})();
|
||||
|
||||
return (
|
||||
input.type !== FlowNodeInputTypeEnum.hidden && (
|
||||
<Box key={input.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
{!!input.label && <InputLabel moduleId={moduleId} inputKey={input.key} {...input} />}
|
||||
{!!RenderComponent && (
|
||||
<Box mt={2} className={'nodrag'}>
|
||||
{RenderComponent}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(function (props: Props) {
|
||||
const { mode } = useFlowProviderStore();
|
||||
return <RenderInput {...props} mode={mode} />;
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { EditNodeFieldType } from '@fastgpt/global/core/module/node/type';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../../FieldEditModal'));
|
||||
|
||||
const AddInputParam = ({ inputs = [], item, moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
setEditField(item.defaultEditField || {});
|
||||
}}
|
||||
>
|
||||
{t('core.module.input.Add Input')}
|
||||
</Button>
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
editField={item.editField}
|
||||
defaultField={editField}
|
||||
keys={inputs.map((input) => input.key)}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
key: data.key,
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: data.inputType,
|
||||
required: data.required,
|
||||
description: data.description,
|
||||
edit: true,
|
||||
editField: item.editField
|
||||
}
|
||||
});
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AddInputParam);
|
||||
@@ -0,0 +1,63 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Button, useDisclosure } from '@chakra-ui/react';
|
||||
import { AIChatModuleProps } from '@fastgpt/global/core/module/node/type';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import AIChatSettingsModal from '@/components/core/module/AIChatSettingsModal';
|
||||
|
||||
const AiSettingRender = ({ inputs = [], moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const chatModulesData = useMemo(() => {
|
||||
const obj: Record<string, any> = {};
|
||||
inputs.forEach((item) => {
|
||||
obj[item.key] = item.value;
|
||||
});
|
||||
return obj as AIChatModuleProps;
|
||||
}, [inputs]);
|
||||
|
||||
const {
|
||||
isOpen: isOpenAIChatSetting,
|
||||
onOpen: onOpenAIChatSetting,
|
||||
onClose: onCloseAIChatSetting
|
||||
} = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<MyIcon name={'settingLight'} w={'14px'} />}
|
||||
onClick={onOpenAIChatSetting}
|
||||
>
|
||||
{t('app.AI Settings')}
|
||||
</Button>
|
||||
{isOpenAIChatSetting && (
|
||||
<AIChatSettingsModal
|
||||
isAdEdit
|
||||
onClose={onCloseAIChatSetting}
|
||||
onSuccess={(e) => {
|
||||
for (let key in e) {
|
||||
const item = inputs.find((input) => input.key === key);
|
||||
if (!item) continue;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key,
|
||||
value: {
|
||||
...item,
|
||||
//@ts-ignore
|
||||
value: e[key]
|
||||
}
|
||||
});
|
||||
}
|
||||
onCloseAIChatSetting();
|
||||
}}
|
||||
defaultData={chatModulesData}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AiSettingRender);
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import {
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper
|
||||
} from '@chakra-ui/react';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
|
||||
const NumberInputRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
return (
|
||||
<NumberInput
|
||||
defaultValue={item.value}
|
||||
min={item.min}
|
||||
max={item.max}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: Number(e)
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<NumberInputField />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NumberInputRender);
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import MySelect from '@/components/Select';
|
||||
|
||||
const SelectRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
return (
|
||||
<MySelect
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={item.list || []}
|
||||
onchange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SelectRender);
|
||||
@@ -0,0 +1,83 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import MySelect from '@/components/Select';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { chatModelList, cqModelList, extractModelList } from '@/web/common/system/staticData';
|
||||
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
|
||||
const SelectAiModelRender = ({ item, inputs = [], moduleId }: RenderInputProps) => {
|
||||
const modelList = (() => {
|
||||
if (item.type === FlowNodeInputTypeEnum.selectChatModel) return chatModelList;
|
||||
if (item.type === FlowNodeInputTypeEnum.selectCQModel) return cqModelList;
|
||||
if (item.type === FlowNodeInputTypeEnum.selectExtractModel) return extractModelList;
|
||||
|
||||
return [];
|
||||
})().map((item) => ({
|
||||
model: item.model,
|
||||
name: item.name,
|
||||
maxResponse: item.maxResponse,
|
||||
price: item.price
|
||||
}));
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(e: string) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
|
||||
// update max tokens
|
||||
const model = modelList.find((item) => item.model === e) || modelList[0];
|
||||
if (!model) return;
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: 'maxToken',
|
||||
value: {
|
||||
...inputs.find((input) => input.key === 'maxToken'),
|
||||
markList: [
|
||||
{ label: '100', value: 100 },
|
||||
{ label: `${model.maxResponse}`, value: model.maxResponse }
|
||||
],
|
||||
max: model.maxResponse,
|
||||
value: model.maxResponse / 2
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputs, item, modelList, moduleId]
|
||||
);
|
||||
|
||||
const list = modelList.map((item) => {
|
||||
const priceStr = `(${formatPrice(item.price, 1000)}元/1k Tokens)`;
|
||||
|
||||
return {
|
||||
value: item.model,
|
||||
label: `${item.name}${priceStr}`
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!item.value && list.length > 0) {
|
||||
onChangeModel(list[0].value);
|
||||
}
|
||||
}, [item.value, list, onChangeModel]);
|
||||
|
||||
return (
|
||||
<MySelect
|
||||
minW={'350px'}
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={list}
|
||||
onchange={onChangeModel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SelectAiModelRender);
|
||||
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import {
|
||||
onChangeNode,
|
||||
useFlowProviderStore,
|
||||
type useFlowProviderStoreType
|
||||
} from '../../../../FlowProvider';
|
||||
import { Box, Button, Flex, useDisclosure, useTheme } from '@chakra-ui/react';
|
||||
import { SelectAppItemType } from '@fastgpt/global/core/module/type';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import SelectAppModal from '../../../../SelectAppModal';
|
||||
|
||||
const SelectAppRender = ({
|
||||
item,
|
||||
moduleId,
|
||||
filterAppIds
|
||||
}: RenderInputProps & {
|
||||
filterAppIds: useFlowProviderStoreType['filterAppIds'];
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
isOpen: isOpenSelectApp,
|
||||
onOpen: onOpenSelectApp,
|
||||
onClose: onCloseSelectApp
|
||||
} = useDisclosure();
|
||||
|
||||
const value = item.value as SelectAppItemType | undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box onClick={onOpenSelectApp}>
|
||||
{!value ? (
|
||||
<Button variant={'base'} w={'100%'}>
|
||||
选择应用
|
||||
</Button>
|
||||
) : (
|
||||
<Flex alignItems={'center'} border={theme.borders.base} borderRadius={'md'} px={3} py={2}>
|
||||
<Avatar src={value?.logo} />
|
||||
<Box fontWeight={'bold'} ml={1}>
|
||||
{value?.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{isOpenSelectApp && (
|
||||
<SelectAppModal
|
||||
defaultApps={item.value?.id ? [item.value.id] : []}
|
||||
filterAppIds={filterAppIds}
|
||||
onClose={onCloseSelectApp}
|
||||
onSuccess={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: 'app',
|
||||
value: {
|
||||
...item,
|
||||
value: e[0]
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(function (props: RenderInputProps) {
|
||||
const { filterAppIds } = useFlowProviderStore();
|
||||
return <SelectAppRender {...props} filterAppIds={filterAppIds} />;
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { Box, Button, Flex, Grid, useDisclosure, useTheme } from '@chakra-ui/react';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { SelectedDatasetType } from '@fastgpt/global/core/module/api';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import DatasetSelectModal from '@/components/core/module/DatasetSelectModal';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
const SelectDatasetRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
const theme = useTheme();
|
||||
const { allDatasets, loadAllDatasets } = useDatasetStore();
|
||||
const {
|
||||
isOpen: isOpenKbSelect,
|
||||
onOpen: onOpenKbSelect,
|
||||
onClose: onCloseKbSelect
|
||||
} = useDisclosure();
|
||||
|
||||
const selectedDatasets = useMemo(() => {
|
||||
const value = item.value as SelectedDatasetType;
|
||||
return allDatasets.filter((dataset) => value?.find((item) => item.datasetId === dataset._id));
|
||||
}, [allDatasets, item.value]);
|
||||
|
||||
useQuery(['loadAllDatasets'], loadAllDatasets);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={4} minW={'350px'} w={'100%'}>
|
||||
<Button h={'36px'} onClick={onOpenKbSelect}>
|
||||
选择知识库
|
||||
</Button>
|
||||
{selectedDatasets.map((item) => (
|
||||
<Flex
|
||||
key={item._id}
|
||||
alignItems={'center'}
|
||||
h={'36px'}
|
||||
border={theme.borders.base}
|
||||
px={2}
|
||||
borderRadius={'md'}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'24px'}></Avatar>
|
||||
<Box
|
||||
ml={3}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className="textEllipsis"
|
||||
fontWeight={'bold'}
|
||||
fontSize={['md', 'lg', 'xl']}
|
||||
>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
{isOpenKbSelect && (
|
||||
<DatasetSelectModal
|
||||
isOpen={isOpenKbSelect}
|
||||
defaultSelectedDatasets={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: item.key,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
onClose={onCloseKbSelect}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SelectDatasetRender);
|
||||
@@ -0,0 +1,90 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode, useFlowProviderStore } from '../../../../FlowProvider';
|
||||
import { Button, useDisclosure } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { chatModelList } from '@/web/common/system/staticData';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import DatasetParamsModal from '@/components/core/module/DatasetParamsModal';
|
||||
|
||||
const SelectDatasetParam = ({ inputs = [], moduleId }: RenderInputProps) => {
|
||||
const { nodes } = useFlowProviderStore();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [data, setData] = useState({
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
limit: 5,
|
||||
similarity: 0.5
|
||||
});
|
||||
|
||||
const tokenLimit = useMemo(() => {
|
||||
let maxTokens = 3000;
|
||||
|
||||
nodes.forEach((item) => {
|
||||
if (item.type === FlowNodeTypeEnum.chatNode) {
|
||||
const model =
|
||||
item.data.inputs.find((item) => item.key === ModuleInputKeyEnum.aiModel)?.value || '';
|
||||
const quoteMaxToken =
|
||||
chatModelList.find((item) => item.model === model)?.quoteMaxToken || 3000;
|
||||
|
||||
maxTokens = Math.max(maxTokens, quoteMaxToken);
|
||||
}
|
||||
});
|
||||
|
||||
return maxTokens;
|
||||
}, [nodes]);
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
useEffect(() => {
|
||||
inputs.forEach((input) => {
|
||||
// @ts-ignore
|
||||
if (data[input.key] !== undefined) {
|
||||
setData((state) => ({
|
||||
...state,
|
||||
[input.key]: input.value
|
||||
}));
|
||||
}
|
||||
});
|
||||
}, [inputs]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<MyIcon name={'settingLight'} w={'14px'} />}
|
||||
onClick={onOpen}
|
||||
>
|
||||
{t('core.dataset.search.Params Setting')}
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<DatasetParamsModal
|
||||
{...data}
|
||||
maxTokens={tokenLimit}
|
||||
onClose={onClose}
|
||||
onSuccess={(e) => {
|
||||
for (let key in e) {
|
||||
const item = inputs.find((input) => input.key === key);
|
||||
if (!item) continue;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key,
|
||||
value: {
|
||||
...item,
|
||||
//@ts-ignore
|
||||
value: e[key]
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SelectDatasetParam);
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import MySlider from '@/components/Slider';
|
||||
|
||||
const SliderRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box pt={5} pb={4} px={2}>
|
||||
<MySlider
|
||||
markList={item.markList}
|
||||
width={'100%'}
|
||||
min={item.min || 0}
|
||||
max={item.max}
|
||||
step={item.step || 1}
|
||||
value={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SliderRender);
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Switch } from '@chakra-ui/react';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
|
||||
const SwitchRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
return (
|
||||
<Switch
|
||||
size={'lg'}
|
||||
isChecked={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.checked
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SwitchRender);
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Input } from '@chakra-ui/react';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
|
||||
const TextInput = ({ item, moduleId }: RenderInputProps) => {
|
||||
return (
|
||||
<Input
|
||||
placeholder={item.placeholder}
|
||||
defaultValue={item.value}
|
||||
onBlur={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TextInput);
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import PromptTextarea from '@/components/common/Textarea/PromptTextarea';
|
||||
|
||||
const TextareaRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<PromptTextarea
|
||||
title={t(item.label)}
|
||||
rows={5}
|
||||
bg={'myWhite.400'}
|
||||
placeholder={t(item.placeholder || '')}
|
||||
resize={'both'}
|
||||
defaultValue={item.value}
|
||||
onBlur={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TextareaRender);
|
||||
7
projects/app/src/components/core/module/Flow/components/render/RenderInput/type.d.ts
vendored
Normal file
7
projects/app/src/components/core/module/Flow/components/render/RenderInput/type.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
|
||||
export type RenderInputProps = {
|
||||
inputs?: FlowNodeInputItemType[];
|
||||
item: FlowNodeInputItemType;
|
||||
moduleId: string;
|
||||
};
|
||||
@@ -1,34 +1,30 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import type { FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import SourceHandle from './SourceHandle';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { EditNodeFieldType, FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { onChangeNode } from '../../../FlowProvider';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import SourceHandle from '../SourceHandle';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import type { EditFieldType, EditFieldModeType } from '../modules/FieldEditModal';
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const FieldEditModal = dynamic(() => import('../FieldEditModal'));
|
||||
|
||||
export const Label = ({
|
||||
const OutputLabel = ({
|
||||
moduleId,
|
||||
outputKey,
|
||||
outputs,
|
||||
editFiledType = 'output',
|
||||
...item
|
||||
}: FlowNodeOutputItemType & {
|
||||
outputKey: string;
|
||||
moduleId: string;
|
||||
outputs: FlowNodeOutputItemType[];
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { label = '', description, edit } = item;
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -48,10 +44,11 @@ export const Label = ({
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
key: outputKey,
|
||||
description: item.description
|
||||
label: item.label,
|
||||
description: item.description,
|
||||
valueType: item.valueType,
|
||||
outputType: item.type
|
||||
})
|
||||
}
|
||||
/>
|
||||
@@ -79,29 +76,41 @@ export const Label = ({
|
||||
)}
|
||||
<Box>{t(label)}</Box>
|
||||
|
||||
{item.type === FlowNodeOutputTypeEnum.source && (
|
||||
<SourceHandle handleKey={outputKey} valueType={item.valueType} />
|
||||
)}
|
||||
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
mode={editFiledType}
|
||||
editField={item.editField}
|
||||
defaultField={editField}
|
||||
keys={[outputKey]}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
const data = {
|
||||
onSubmit={({ data, changeKey }) => {
|
||||
if (!data.outputType || !data.key) return;
|
||||
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
...item,
|
||||
...e
|
||||
type: data.outputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label,
|
||||
description: data.description
|
||||
};
|
||||
if (editField.key === data.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: data.key,
|
||||
value: data
|
||||
});
|
||||
} else {
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: data
|
||||
value: newOutput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: newOutput.key,
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
|
||||
@@ -113,48 +122,4 @@ export const Label = ({
|
||||
);
|
||||
};
|
||||
|
||||
const RenderOutput = ({
|
||||
moduleId,
|
||||
flowOutputList,
|
||||
editFiledType
|
||||
}: {
|
||||
moduleId: string;
|
||||
flowOutputList: FlowNodeOutputItemType[];
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) => {
|
||||
const sortOutput = useMemo(
|
||||
() =>
|
||||
[...flowOutputList].sort((a, b) => {
|
||||
if (a.key === ModuleOutputKeyEnum.finish) return -1;
|
||||
if (b.key === ModuleOutputKeyEnum.finish) return 1;
|
||||
return 0;
|
||||
}),
|
||||
[flowOutputList]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{sortOutput.map(
|
||||
(item) =>
|
||||
item.type !== FlowNodeOutputTypeEnum.hidden && (
|
||||
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
<Label
|
||||
editFiledType={editFiledType}
|
||||
moduleId={moduleId}
|
||||
outputKey={item.key}
|
||||
outputs={sortOutput}
|
||||
{...item}
|
||||
/>
|
||||
<Box mt={FlowNodeOutputTypeEnum.answer ? 0 : 2} className={'nodrag'}>
|
||||
{item.type === FlowNodeOutputTypeEnum.source && (
|
||||
<SourceHandle handleKey={item.key} valueType={item.valueType} />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(RenderOutput);
|
||||
export default React.memo(OutputLabel);
|
||||
@@ -0,0 +1,80 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import OutputLabel from './Label';
|
||||
import { RenderOutputProps } from './type';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const RenderList: {
|
||||
types: `${FlowNodeOutputTypeEnum}`[];
|
||||
Component: React.ComponentType<RenderOutputProps>;
|
||||
}[] = [
|
||||
{
|
||||
types: [FlowNodeOutputTypeEnum.addOutputParam],
|
||||
Component: dynamic(() => import('./templates/AddOutputParam'))
|
||||
}
|
||||
];
|
||||
|
||||
const RenderOutput = ({
|
||||
moduleId,
|
||||
flowOutputList
|
||||
}: {
|
||||
moduleId: string;
|
||||
flowOutputList: FlowNodeOutputItemType[];
|
||||
}) => {
|
||||
const sortOutputs = useMemo(
|
||||
() =>
|
||||
[...flowOutputList].sort((a, b) => {
|
||||
if (a.type === FlowNodeOutputTypeEnum.addOutputParam) {
|
||||
return 1;
|
||||
}
|
||||
if (b.type === FlowNodeOutputTypeEnum.addOutputParam) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.key === ModuleOutputKeyEnum.finish) return -1;
|
||||
if (b.key === ModuleOutputKeyEnum.finish) return 1;
|
||||
return 0;
|
||||
}),
|
||||
[flowOutputList]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{sortOutputs.map((output) => {
|
||||
const RenderComponent = (() => {
|
||||
const Component = RenderList.find(
|
||||
(item) => output.type && item.types.includes(output.type)
|
||||
)?.Component;
|
||||
|
||||
if (!Component) return null;
|
||||
return <Component outputs={sortOutputs} item={output} moduleId={moduleId} />;
|
||||
})();
|
||||
|
||||
return (
|
||||
output.type !== FlowNodeOutputTypeEnum.hidden && (
|
||||
<Box key={output.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
{output.label && (
|
||||
<OutputLabel
|
||||
moduleId={moduleId}
|
||||
outputKey={output.key}
|
||||
outputs={sortOutputs}
|
||||
{...output}
|
||||
/>
|
||||
)}
|
||||
{!!RenderComponent && (
|
||||
<Box mt={2} className={'nodrag'}>
|
||||
{RenderComponent}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(RenderOutput);
|
||||
@@ -0,0 +1,59 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { RenderOutputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { Box, Button } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { EditNodeFieldType } from '@fastgpt/global/core/module/node/type';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../../FieldEditModal'));
|
||||
|
||||
const AddOutputParam = ({ outputs = [], item, moduleId }: RenderOutputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<Box textAlign={'right'}>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
setEditField(item.defaultEditField || {});
|
||||
}}
|
||||
>
|
||||
{t('core.module.output.Add Output')}
|
||||
</Button>
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
editField={item.editField}
|
||||
defaultField={editField}
|
||||
keys={outputs.map((output) => output.key)}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
key: data.key,
|
||||
value: {
|
||||
type: data.outputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label,
|
||||
description: data.description,
|
||||
required: data.required,
|
||||
edit: true,
|
||||
editField: item.editField,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AddOutputParam);
|
||||
7
projects/app/src/components/core/module/Flow/components/render/RenderOutput/type.d.ts
vendored
Normal file
7
projects/app/src/components/core/module/Flow/components/render/RenderOutput/type.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
|
||||
export type RenderOutputProps = {
|
||||
outputs?: FlowNodeOutputItemType[];
|
||||
item: FlowNodeOutputItemType;
|
||||
moduleId: string;
|
||||
};
|
||||
@@ -1,26 +1,26 @@
|
||||
import React, { useMemo, useTransition } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import { FlowValueTypeStyle, FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
interface Props extends BoxProps {
|
||||
handleKey: string;
|
||||
valueType?: `${ModuleDataTypeEnum}`;
|
||||
valueType?: `${ModuleIOValueTypeEnum}`;
|
||||
}
|
||||
|
||||
const SourceHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const valType = valueType ?? ModuleDataTypeEnum.any;
|
||||
const valType = valueType ?? ModuleIOValueTypeEnum.any;
|
||||
|
||||
const valueStyle = useMemo(
|
||||
() =>
|
||||
valueType
|
||||
? FlowValueTypeStyle[valueType]
|
||||
: (FlowValueTypeStyle[ModuleDataTypeEnum.any] as any),
|
||||
valueType && FlowValueTypeMap[valueType]
|
||||
? FlowValueTypeMap[valueType]?.handlerStyle
|
||||
: FlowValueTypeMap[ModuleIOValueTypeEnum.any]?.handlerStyle,
|
||||
[valueType]
|
||||
);
|
||||
|
||||
@@ -34,8 +34,8 @@ const SourceHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
>
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(FlowValueTypeMap[valType].label),
|
||||
example: FlowValueTypeMap[valType].example
|
||||
type: t(FlowValueTypeMap[valType]?.label),
|
||||
description: FlowValueTypeMap[valType]?.description
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
|
||||
@@ -1,32 +1,31 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { Handle, OnConnect, Position } from 'reactflow';
|
||||
import { FlowValueTypeStyle, FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
interface Props extends BoxProps {
|
||||
handleKey: string;
|
||||
valueType?: `${ModuleDataTypeEnum}`;
|
||||
valueType?: `${ModuleIOValueTypeEnum}`;
|
||||
onConnect?: OnConnect;
|
||||
}
|
||||
|
||||
const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const valType = valueType ?? ModuleDataTypeEnum.any;
|
||||
const valType = valueType ?? ModuleIOValueTypeEnum.any;
|
||||
const valueStyle = useMemo(
|
||||
() =>
|
||||
valueType
|
||||
? FlowValueTypeStyle[valueType]
|
||||
: (FlowValueTypeStyle[ModuleDataTypeEnum.any] as any),
|
||||
valueType && FlowValueTypeMap[valueType]
|
||||
? FlowValueTypeMap[valueType]?.handlerStyle
|
||||
: FlowValueTypeMap[ModuleIOValueTypeEnum.any]?.handlerStyle,
|
||||
[valueType]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={handleKey}
|
||||
position={'absolute'}
|
||||
top={'50%'}
|
||||
left={'-16px'}
|
||||
@@ -35,8 +34,8 @@ const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
|
||||
>
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(FlowValueTypeMap[valType].label),
|
||||
example: FlowValueTypeMap[valType].example
|
||||
type: t(FlowValueTypeMap[valType]?.label),
|
||||
description: FlowValueTypeMap[valType]?.description
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
|
||||
@@ -2,8 +2,7 @@ import React, { useEffect } from 'react';
|
||||
import ReactFlow, { Background, Controls, ReactFlowProvider } from 'reactflow';
|
||||
import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { edgeOptions, connectionLineStyle } from '@/web/core/modules/constants/flowUi';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
@@ -25,14 +24,14 @@ const nodeTypes = {
|
||||
[FlowNodeTypeEnum.answerNode]: dynamic(() => import('./components/nodes/NodeAnswer')),
|
||||
[FlowNodeTypeEnum.classifyQuestion]: dynamic(() => import('./components/nodes/NodeCQNode')),
|
||||
[FlowNodeTypeEnum.contentExtract]: dynamic(() => import('./components/nodes/NodeExtract')),
|
||||
[FlowNodeTypeEnum.httpRequest]: dynamic(() => import('./components/nodes/NodeHttp')),
|
||||
[FlowNodeTypeEnum.httpRequest]: NodeSimple,
|
||||
[FlowNodeTypeEnum.runApp]: NodeSimple,
|
||||
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./components/nodes/NodePluginInput')),
|
||||
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodePluginOutput')),
|
||||
[FlowNodeTypeEnum.pluginModule]: NodeSimple
|
||||
};
|
||||
const edgeTypes = {
|
||||
buttonedge: ButtonEdge
|
||||
[EDGE_TYPE]: ButtonEdge
|
||||
};
|
||||
type Props = {
|
||||
modules: ModuleItemType[];
|
||||
@@ -40,7 +39,7 @@ type Props = {
|
||||
} & ModuleTemplateProps;
|
||||
|
||||
const Container = React.memo(function Container(props: Props) {
|
||||
const { modules = [], Header, systemTemplates, pluginTemplates } = props;
|
||||
const { modules = [], Header, templates } = props;
|
||||
|
||||
const {
|
||||
isOpen: isOpenTemplate,
|
||||
@@ -96,8 +95,10 @@ const Container = React.memo(function Container(props: Props) {
|
||||
edges={edges}
|
||||
minZoom={0.1}
|
||||
maxZoom={1.5}
|
||||
defaultEdgeOptions={edgeOptions}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
defaultEdgeOptions={{
|
||||
animated: true
|
||||
}}
|
||||
connectionLineStyle={{ strokeWidth: 2, stroke: '#5A646Es' }}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onNodesChange={onNodesChange}
|
||||
@@ -115,8 +116,7 @@ const Container = React.memo(function Container(props: Props) {
|
||||
</ReactFlow>
|
||||
|
||||
<ModuleTemplateList
|
||||
systemTemplates={systemTemplates}
|
||||
pluginTemplates={pluginTemplates}
|
||||
templates={templates}
|
||||
isOpen={isOpenTemplate}
|
||||
onClose={onCloseTemplate}
|
||||
/>
|
||||
|
||||
51
projects/app/src/components/core/module/utils.ts
Normal file
51
projects/app/src/components/core/module/utils.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { FlowNodeOutputTargetItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { type Node, type Edge } from 'reactflow';
|
||||
|
||||
export function flowNode2Modules({
|
||||
nodes,
|
||||
edges
|
||||
}: {
|
||||
nodes: Node<FlowModuleItemType, string | undefined>[];
|
||||
edges: Edge<any>[];
|
||||
}) {
|
||||
const modules: ModuleItemType[] = nodes.map((item) => ({
|
||||
moduleId: item.data.moduleId,
|
||||
name: item.data.name,
|
||||
avatar: item.data.avatar,
|
||||
flowType: item.data.flowType,
|
||||
showStatus: item.data.showStatus,
|
||||
position: item.position,
|
||||
inputs: item.data.inputs.map((input) => ({
|
||||
...input,
|
||||
connected: false
|
||||
})),
|
||||
outputs: item.data.outputs.map((item) => ({
|
||||
...item,
|
||||
targets: [] as FlowNodeOutputTargetItemType[]
|
||||
}))
|
||||
}));
|
||||
|
||||
// update inputs and outputs
|
||||
modules.forEach((module) => {
|
||||
module.inputs.forEach((input) => {
|
||||
input.connected = !!edges.find(
|
||||
(edge) => edge.target === module.moduleId && edge.targetHandle === input.key
|
||||
);
|
||||
});
|
||||
|
||||
module.outputs.forEach((output) => {
|
||||
output.targets = edges
|
||||
.filter(
|
||||
(edge) =>
|
||||
edge.source === module.moduleId && edge.sourceHandle === output.key && edge.targetHandle
|
||||
)
|
||||
.map((edge) => ({
|
||||
moduleId: edge.target,
|
||||
key: edge.targetHandle || ''
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
return modules;
|
||||
}
|
||||
Reference in New Issue
Block a user