mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-02 20:58:12 +00:00
V4.7-alpha (#985)
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
@@ -103,7 +103,7 @@ const QuoteItem = ({
|
||||
fontSize={'sm'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
_hover={{ '& .hover-data': { display: 'flex' } }}
|
||||
_hover={{ '& .hover-data': { visibility: 'visible' } }}
|
||||
h={'100%'}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
@@ -218,7 +218,8 @@ const QuoteItem = ({
|
||||
<MyTooltip label={t('core.dataset.data.Edit')}>
|
||||
<Box
|
||||
className="hover-data"
|
||||
display={['flex', 'none']}
|
||||
visibility={'hidden'}
|
||||
display={'flex'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
>
|
||||
@@ -245,7 +246,7 @@ const QuoteItem = ({
|
||||
<Link
|
||||
as={NextLink}
|
||||
className="hover-data"
|
||||
display={'none'}
|
||||
visibility={'hidden'}
|
||||
alignItems={'center'}
|
||||
color={'primary.500'}
|
||||
href={`/dataset/detail?datasetId=${quoteItem.datasetId}¤tTab=dataCard&collectionId=${quoteItem.collectionId}`}
|
||||
|
@@ -86,10 +86,12 @@ const DatasetParamsModal = ({
|
||||
const cfbBgDesc = watch('datasetSearchExtensionBg');
|
||||
|
||||
const chatModelSelectList = (() =>
|
||||
llmModelList.map((item) => ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
})))();
|
||||
llmModelList
|
||||
.filter((model) => model.usedInQueryExtension)
|
||||
.map((item) => ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
})))();
|
||||
|
||||
const searchModeList = useMemo(() => {
|
||||
const list = Object.values(DatasetSearchModeMap);
|
||||
|
@@ -13,7 +13,8 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { streamFetch } from '@/web/common/api/fetch';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
|
||||
import ChatBox from '@/components/ChatBox';
|
||||
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
|
||||
import { getGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
@@ -12,7 +12,10 @@ import type {
|
||||
FlowModuleItemType,
|
||||
FlowModuleTemplateType
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import type { FlowNodeChangeProps } from '@fastgpt/global/core/module/node/type';
|
||||
import type {
|
||||
FlowNodeChangeProps,
|
||||
FlowNodeInputItemType
|
||||
} from '@fastgpt/global/core/module/node/type';
|
||||
import React, {
|
||||
type SetStateAction,
|
||||
type Dispatch,
|
||||
@@ -20,13 +23,18 @@ import React, {
|
||||
useCallback,
|
||||
createContext,
|
||||
useRef,
|
||||
useEffect
|
||||
useEffect,
|
||||
useMemo
|
||||
} from 'react';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { appModule2FlowEdge, appModule2FlowNode } from '@/utils/adapt';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import {
|
||||
ModuleIOValueTypeEnum,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum
|
||||
} 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';
|
||||
@@ -34,6 +42,14 @@ import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
||||
type requestEventType =
|
||||
| 'onChangeNode'
|
||||
| 'onCopyNode'
|
||||
| 'onResetNode'
|
||||
| 'onDelNode'
|
||||
| 'onDelConnect'
|
||||
| 'setNodes';
|
||||
|
||||
export type useFlowProviderStoreType = {
|
||||
reactFlowWrapper: null | React.RefObject<HTMLDivElement>;
|
||||
mode: 'app' | 'plugin';
|
||||
@@ -57,14 +73,16 @@ export type useFlowProviderStoreType = {
|
||||
onDelConnect: (id: string) => void;
|
||||
onConnect: ({ connect }: { connect: Connection }) => any;
|
||||
initData: (modules: ModuleItemType[]) => void;
|
||||
splitToolInputs: (
|
||||
inputs: FlowNodeInputItemType[],
|
||||
moduleId: string
|
||||
) => {
|
||||
isTool: boolean;
|
||||
toolInputs: FlowNodeInputItemType[];
|
||||
commonInputs: FlowNodeInputItemType[];
|
||||
};
|
||||
hasToolNode: boolean;
|
||||
};
|
||||
type requestEventType =
|
||||
| 'onChangeNode'
|
||||
| 'onCopyNode'
|
||||
| 'onResetNode'
|
||||
| 'onDelNode'
|
||||
| 'onDelConnect'
|
||||
| 'setNodes';
|
||||
|
||||
const StateContext = createContext<useFlowProviderStoreType>({
|
||||
reactFlowWrapper: null,
|
||||
@@ -116,7 +134,18 @@ const StateContext = createContext<useFlowProviderStoreType>({
|
||||
},
|
||||
onResetNode: function (e): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
},
|
||||
splitToolInputs: function (
|
||||
inputs: FlowNodeInputItemType[],
|
||||
moduleId: string
|
||||
): {
|
||||
isTool: boolean;
|
||||
toolInputs: FlowNodeInputItemType[];
|
||||
commonInputs: FlowNodeInputItemType[];
|
||||
} {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
hasToolNode: false
|
||||
});
|
||||
export const useFlowProviderStore = () => useContext(StateContext);
|
||||
|
||||
@@ -135,6 +164,10 @@ export const FlowProvider = ({
|
||||
const [nodes = [], setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
const hasToolNode = useMemo(() => {
|
||||
return !!nodes.find((node) => node.data.flowType === FlowNodeTypeEnum.tools);
|
||||
}, [nodes]);
|
||||
|
||||
const onFixView = useCallback(() => {
|
||||
const btn = document.querySelector('.react-flow__controls-fitview') as HTMLButtonElement;
|
||||
|
||||
@@ -180,10 +213,13 @@ export const FlowProvider = ({
|
||||
const type = source?.outputs.find(
|
||||
(output) => output.key === connect.sourceHandle
|
||||
)?.valueType;
|
||||
console.log(type);
|
||||
|
||||
if (source?.flowType === FlowNodeTypeEnum.classifyQuestion && !type) {
|
||||
return ModuleIOValueTypeEnum.boolean;
|
||||
}
|
||||
if (source?.flowType === FlowNodeTypeEnum.tools) {
|
||||
return ModuleIOValueTypeEnum.tools;
|
||||
}
|
||||
if (source?.flowType === FlowNodeTypeEnum.pluginInput) {
|
||||
return source?.inputs.find((input) => input.key === connect.sourceHandle)?.valueType;
|
||||
}
|
||||
@@ -193,14 +229,17 @@ export const FlowProvider = ({
|
||||
const targetType = nodes
|
||||
.find((node) => node.id === connect.target)
|
||||
?.data?.inputs.find((input) => input.key === connect.targetHandle)?.valueType;
|
||||
console.log(source, targetType);
|
||||
if (!sourceType || !targetType) {
|
||||
|
||||
if (
|
||||
connect.sourceHandle === ModuleOutputKeyEnum.selectedTools &&
|
||||
connect.targetHandle === ModuleOutputKeyEnum.selectedTools
|
||||
) {
|
||||
} else if (!sourceType || !targetType) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('app.Connection is invalid')
|
||||
});
|
||||
}
|
||||
if (
|
||||
} else if (
|
||||
sourceType !== ModuleIOValueTypeEnum.any &&
|
||||
targetType !== ModuleIOValueTypeEnum.any &&
|
||||
sourceType !== targetType
|
||||
@@ -215,16 +254,13 @@ export const FlowProvider = ({
|
||||
addEdge(
|
||||
{
|
||||
...connect,
|
||||
type: EDGE_TYPE,
|
||||
data: {
|
||||
onDelete: onDelConnect
|
||||
}
|
||||
type: EDGE_TYPE
|
||||
},
|
||||
state
|
||||
)
|
||||
);
|
||||
},
|
||||
[nodes, onDelConnect, setEdges, t, toast]
|
||||
[nodes, setEdges, t, toast]
|
||||
);
|
||||
|
||||
const onDelNode = useCallback(
|
||||
@@ -359,6 +395,26 @@ export const FlowProvider = ({
|
||||
[setNodes]
|
||||
);
|
||||
|
||||
/* If the module is connected by a tool, the tool input and the normal input are separated */
|
||||
const splitToolInputs = useCallback(
|
||||
(inputs: FlowNodeInputItemType[], moduleId: string) => {
|
||||
const isTool = !!edges.find(
|
||||
(edge) =>
|
||||
edge.targetHandle === ModuleOutputKeyEnum.selectedTools && edge.target === moduleId
|
||||
);
|
||||
|
||||
return {
|
||||
isTool,
|
||||
toolInputs: inputs.filter((item) => isTool && item.toolDescription),
|
||||
commonInputs: inputs.filter((item) => {
|
||||
if (!isTool) return true;
|
||||
return !item.toolDescription && item.key !== ModuleInputKeyEnum.switch;
|
||||
})
|
||||
};
|
||||
},
|
||||
[edges]
|
||||
);
|
||||
|
||||
// reset a node data. delete edge and replace it
|
||||
const onResetNode = useCallback(
|
||||
({ id, module }: { id: string; module: FlowModuleTemplateType }) => {
|
||||
@@ -465,7 +521,9 @@ export const FlowProvider = ({
|
||||
onDelEdge,
|
||||
onDelConnect,
|
||||
onConnect,
|
||||
initData
|
||||
initData,
|
||||
splitToolInputs,
|
||||
hasToolNode
|
||||
};
|
||||
|
||||
return <StateContext.Provider value={value}>{children}</StateContext.Provider>;
|
||||
|
@@ -17,7 +17,7 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { getPreviewPluginModule } from '@/web/core/plugin/api';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { moduleTemplatesList } from '@/web/core/modules/template/system';
|
||||
import { moduleTemplatesList } from '@fastgpt/global/core/module/template/constants';
|
||||
|
||||
export type ModuleTemplateProps = {
|
||||
templates: FlowModuleTemplateType[];
|
||||
|
@@ -3,6 +3,7 @@ import { BezierEdge, getBezierPath, EdgeLabelRenderer, EdgeProps } from 'reactfl
|
||||
import { onDelConnect, useFlowProviderStore } from '../../FlowProvider';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
const ButtonEdge = (props: EdgeProps) => {
|
||||
const { nodes } = useFlowProviderStore();
|
||||
@@ -15,6 +16,8 @@ const ButtonEdge = (props: EdgeProps) => {
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
selected,
|
||||
sourceHandleId,
|
||||
animated,
|
||||
style = {}
|
||||
} = props;
|
||||
|
||||
@@ -34,6 +37,8 @@ const ButtonEdge = (props: EdgeProps) => {
|
||||
targetPosition
|
||||
});
|
||||
|
||||
const isToolEdge = sourceHandleId === ModuleOutputKeyEnum.selectedTools;
|
||||
|
||||
const memoEdgeLabel = useMemo(() => {
|
||||
return (
|
||||
<EdgeLabelRenderer>
|
||||
@@ -60,29 +65,31 @@ const ButtonEdge = (props: EdgeProps) => {
|
||||
<MyIcon
|
||||
name="closeSolid"
|
||||
w={'100%'}
|
||||
color={active ? 'primary.800' : 'myGray.400'}
|
||||
></MyIcon>
|
||||
</Flex>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
position={'absolute'}
|
||||
transform={`translate(-78%, -50%) translate(${targetX}px,${targetY}px)`}
|
||||
pointerEvents={'all'}
|
||||
w={'16px'}
|
||||
h={'16px'}
|
||||
bg={'white'}
|
||||
zIndex={active ? 1000 : 0}
|
||||
>
|
||||
<MyIcon
|
||||
name={'common/rightArrowLight'}
|
||||
w={'100%'}
|
||||
color={active ? 'primary.800' : 'myGray.400'}
|
||||
color={active ? 'primary.700' : 'myGray.400'}
|
||||
></MyIcon>
|
||||
</Flex>
|
||||
{!isToolEdge && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
position={'absolute'}
|
||||
transform={`translate(-78%, -50%) translate(${targetX}px,${targetY}px)`}
|
||||
pointerEvents={'all'}
|
||||
w={'16px'}
|
||||
h={'16px'}
|
||||
bg={'white'}
|
||||
zIndex={active ? 1000 : 0}
|
||||
>
|
||||
<MyIcon
|
||||
name={'common/rightArrowLight'}
|
||||
w={'100%'}
|
||||
color={active ? 'primary.700' : 'myGray.400'}
|
||||
></MyIcon>
|
||||
</Flex>
|
||||
)}
|
||||
</EdgeLabelRenderer>
|
||||
);
|
||||
}, [id, labelX, labelY, active, targetX, targetY]);
|
||||
}, [labelX, labelY, active, isToolEdge, targetX, targetY, id]);
|
||||
|
||||
const memoBezierEdge = useMemo(() => {
|
||||
const edgeStyle: React.CSSProperties = {
|
||||
@@ -96,7 +103,7 @@ const ButtonEdge = (props: EdgeProps) => {
|
||||
};
|
||||
|
||||
return <BezierEdge {...props} style={edgeStyle} />;
|
||||
}, [props, active, style]);
|
||||
}, [style, active, props]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@@ -4,7 +4,7 @@ import { BoxProps } from '@chakra-ui/react';
|
||||
|
||||
const Container = ({ children, ...props }: BoxProps) => {
|
||||
return (
|
||||
<Box px={4} py={3} position={'relative'} {...props}>
|
||||
<Box px={'16px'} py={'10px'} position={'relative'} {...props}>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
|
@@ -2,7 +2,13 @@ import React from 'react';
|
||||
import { Box, useTheme } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const Divider = ({ text }: { text?: 'Input' | 'Output' | string }) => {
|
||||
const Divider = ({
|
||||
text,
|
||||
showBorderBottom = true
|
||||
}: {
|
||||
text?: 'Input' | 'Output' | string;
|
||||
showBorderBottom?: boolean;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -14,10 +20,10 @@ const Divider = ({ text }: { text?: 'Input' | 'Output' | string }) => {
|
||||
bg={'#f8f8f8'}
|
||||
py={isDivider ? '0' : 2}
|
||||
borderTop={theme.borders.base}
|
||||
borderBottom={theme.borders.base}
|
||||
borderBottom={showBorderBottom ? theme.borders.base : 0}
|
||||
fontSize={'lg'}
|
||||
>
|
||||
{text ? t(`common.${text}`) : ''}
|
||||
{text}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@@ -5,14 +5,29 @@ import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import { useFlowProviderStore } from '../../FlowProvider';
|
||||
import Divider from '../modules/Divider';
|
||||
import RenderToolInput from '../render/RenderToolInput';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const NodeAnswer = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { splitToolInputs } = useFlowProviderStore();
|
||||
const { toolInputs, commonInputs } = splitToolInputs(inputs, moduleId);
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} selected={selected} {...data}>
|
||||
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
{toolInputs.length > 0 && (
|
||||
<>
|
||||
<Divider text={t('core.module.tool.Tool input')} />
|
||||
<Container>
|
||||
<RenderToolInput moduleId={moduleId} inputs={toolInputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
<RenderInput moduleId={moduleId} flowInputList={commonInputs} />
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</NodeCard>
|
||||
|
@@ -23,7 +23,7 @@ const NodeCQNode = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} selected={selected} {...data}>
|
||||
<Divider text="Input" />
|
||||
<Divider text={t('common.Input')} />
|
||||
<Container>
|
||||
<RenderInput
|
||||
moduleId={moduleId}
|
||||
|
@@ -26,117 +26,132 @@ import ExtractFieldModal, { defaultField } from './ExtractFieldModal';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { onChangeNode } from '../../../FlowProvider';
|
||||
import { onChangeNode, useFlowProviderStore } from '../../../FlowProvider';
|
||||
import RenderToolInput from '../../render/RenderToolInput';
|
||||
|
||||
const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { inputs, outputs, moduleId } = data;
|
||||
const { splitToolInputs } = useFlowProviderStore();
|
||||
const { toolInputs, commonInputs } = splitToolInputs(inputs, moduleId);
|
||||
const { t } = useTranslation();
|
||||
const [editExtractFiled, setEditExtractField] = useState<ContextExtractAgentItemType>();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} {...data}>
|
||||
<Divider text="Input" />
|
||||
<Container>
|
||||
<RenderInput
|
||||
moduleId={moduleId}
|
||||
flowInputList={inputs}
|
||||
CustomComponent={{
|
||||
[ModuleInputKeyEnum.extractKeys]: ({
|
||||
value: extractKeys = [],
|
||||
...props
|
||||
}: {
|
||||
value?: ContextExtractAgentItemType[];
|
||||
}) => (
|
||||
<Box>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'1 0 0'}>{t('core.module.extract.Target field')}</Box>
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<AddIcon fontSize={'10px'} />}
|
||||
onClick={() => setEditExtractField(defaultField)}
|
||||
{toolInputs.length > 0 && (
|
||||
<>
|
||||
<Divider text={t('core.module.tool.Tool input')} />
|
||||
<Container>
|
||||
<RenderToolInput moduleId={moduleId} inputs={toolInputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<Divider text={t('common.Input')} />
|
||||
<Container>
|
||||
<RenderInput
|
||||
moduleId={moduleId}
|
||||
flowInputList={commonInputs}
|
||||
CustomComponent={{
|
||||
[ModuleInputKeyEnum.extractKeys]: ({
|
||||
value: extractKeys = [],
|
||||
...props
|
||||
}: {
|
||||
value?: ContextExtractAgentItemType[];
|
||||
}) => (
|
||||
<Box>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'1 0 0'}>{t('core.module.extract.Target field')}</Box>
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<AddIcon fontSize={'10px'} />}
|
||||
onClick={() => setEditExtractField(defaultField)}
|
||||
>
|
||||
{t('core.module.extract.Add field')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box
|
||||
mt={2}
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
borderWidth={'1px'}
|
||||
borderBottom="none"
|
||||
>
|
||||
{t('core.module.extract.Add field')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box
|
||||
mt={2}
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
borderWidth={'1px'}
|
||||
borderBottom="none"
|
||||
>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th bg={'myGray.50'}>字段 key</Th>
|
||||
<Th bg={'myGray.50'}>字段描述</Th>
|
||||
<Th bg={'myGray.50'}>必须</Th>
|
||||
<Th bg={'myGray.50'}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{extractKeys.map((item, index) => (
|
||||
<Tr
|
||||
key={index}
|
||||
position={'relative'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.desc}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td whiteSpace={'nowrap'}>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
setEditExtractField(item);
|
||||
}}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: ModuleInputKeyEnum.extractKeys,
|
||||
value: {
|
||||
...props,
|
||||
value: extractKeys.filter(
|
||||
(extract) => item.key !== extract.key
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th bg={'myGray.50'}>字段 key</Th>
|
||||
<Th bg={'myGray.50'}>字段描述</Th>
|
||||
<Th bg={'myGray.50'}>必须</Th>
|
||||
<Th bg={'myGray.50'}></Th>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{extractKeys.map((item, index) => (
|
||||
<Tr
|
||||
key={index}
|
||||
position={'relative'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.desc}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td whiteSpace={'nowrap'}>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
setEditExtractField(item);
|
||||
}}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: ModuleInputKeyEnum.extractKeys,
|
||||
value: {
|
||||
...props,
|
||||
value: extractKeys.filter(
|
||||
(extract) => item.key !== extract.key
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
</>
|
||||
<>
|
||||
<Divider text={t('common.Output')} />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</>
|
||||
|
||||
{!!editExtractFiled && (
|
||||
<ExtractFieldModal
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { ModalBody, Button, ModalFooter, useDisclosure, Textarea, Box } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { onChangeNode } from '../../../FlowProvider';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
|
@@ -38,6 +38,7 @@ import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textare
|
||||
import HttpInput from '@fastgpt/web/components/common/Input/HttpInput';
|
||||
import dynamic from 'next/dynamic';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import RenderToolInput from '../../render/RenderToolInput';
|
||||
const OpenApiImportModal = dynamic(() => import('./OpenApiImportModal'));
|
||||
|
||||
enum TabEnum {
|
||||
@@ -137,12 +138,12 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={2} display={'flex'} justifyContent={'space-between'}>
|
||||
<span>{t('core.module.Http request settings')}</span>
|
||||
<span>
|
||||
<Box>{t('core.module.Http request settings')}</Box>
|
||||
<Box>
|
||||
<OpenApiImportModal moduleId={moduleId} inputs={inputs}>
|
||||
<Button variant={'link'}>{t('core.module.http.OpenAPI import')}</Button>
|
||||
</OpenApiImportModal>
|
||||
</span>
|
||||
</Box>
|
||||
</Box>
|
||||
<Flex alignItems={'center'} className="nodrag">
|
||||
<MySelect
|
||||
@@ -252,7 +253,7 @@ function RenderHttpProps({
|
||||
];
|
||||
const moduleVariables = formatEditorVariablePickerIcon(
|
||||
inputs
|
||||
.filter((input) => input.edit)
|
||||
.filter((input) => input.edit || input.toolDescription)
|
||||
.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.label
|
||||
@@ -593,6 +594,8 @@ const RenderPropsItem = ({ text, num }: { text: string; num: number }) => {
|
||||
const NodeHttp = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { splitToolInputs, hasToolNode } = useFlowProviderStore();
|
||||
const { toolInputs, commonInputs } = splitToolInputs(inputs, moduleId);
|
||||
|
||||
const CustomComponents = useMemo(
|
||||
() => ({
|
||||
@@ -613,18 +616,30 @@ const NodeHttp = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
|
||||
return (
|
||||
<NodeCard minW={'350px'} selected={selected} {...data}>
|
||||
<Divider text="Input" />
|
||||
<Container>
|
||||
<RenderInput
|
||||
moduleId={moduleId}
|
||||
flowInputList={inputs}
|
||||
CustomComponent={CustomComponents}
|
||||
/>
|
||||
</Container>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
{hasToolNode && (
|
||||
<>
|
||||
<Divider text={t('core.module.tool.Tool input')} />
|
||||
<Container>
|
||||
<RenderToolInput moduleId={moduleId} inputs={toolInputs} canEdit />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<Divider text={t('common.Input')} />
|
||||
<Container>
|
||||
<RenderInput
|
||||
moduleId={moduleId}
|
||||
flowInputList={commonInputs}
|
||||
CustomComponent={CustomComponents}
|
||||
/>
|
||||
</Container>
|
||||
</>
|
||||
<>
|
||||
<Divider text={t('common.Output')} />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
|
@@ -15,6 +15,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
import type {
|
||||
EditInputFieldMap,
|
||||
EditNodeFieldType,
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
@@ -32,13 +33,14 @@ const defaultCreateField: EditNodeFieldType = {
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
required: true
|
||||
};
|
||||
const createEditField = {
|
||||
const createEditField: EditInputFieldMap = {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: true,
|
||||
dataType: true,
|
||||
inputType: true
|
||||
inputType: true,
|
||||
isToolInput: true
|
||||
};
|
||||
|
||||
const NodePluginInput = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
@@ -73,7 +75,8 @@ const NodePluginInput = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
description: item.description,
|
||||
required: item.required
|
||||
required: item.required,
|
||||
isToolInput: !!item.toolDescription
|
||||
})
|
||||
}
|
||||
/>
|
||||
@@ -148,6 +151,7 @@ const NodePluginInput = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
type: data.inputType,
|
||||
required: data.required,
|
||||
description: data.description,
|
||||
toolDescription: data.isToolInput ? data.description : undefined,
|
||||
edit: true,
|
||||
editField: createEditField
|
||||
}
|
||||
@@ -191,6 +195,7 @@ const NodePluginInput = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
required: data.required,
|
||||
label: data.label,
|
||||
description: data.description,
|
||||
toolDescription: data.isToolInput ? data.description : undefined,
|
||||
...(data.inputType === FlowNodeInputTypeEnum.addInputParam
|
||||
? {
|
||||
editField: {
|
||||
@@ -218,7 +223,7 @@ const NodePluginInput = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
key: data.key,
|
||||
label: data.label
|
||||
};
|
||||
|
||||
console.log(data);
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
|
@@ -6,8 +6,10 @@ import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const NodeRunAPP = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
return (
|
||||
@@ -15,7 +17,7 @@ const NodeRunAPP = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
</Container>
|
||||
<Divider text="Output" />
|
||||
<Divider text={t('common.Output')} />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
@@ -6,23 +6,42 @@ import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import RenderToolInput from '../render/RenderToolInput';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useFlowProviderStore } from '../../FlowProvider';
|
||||
|
||||
const NodeSimple = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { splitToolInputs } = useFlowProviderStore();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { toolInputs, commonInputs } = splitToolInputs(inputs, moduleId);
|
||||
|
||||
const filterHiddenInputs = useMemo(
|
||||
() => commonInputs.filter((item) => item.type !== 'hidden'),
|
||||
[commonInputs]
|
||||
);
|
||||
|
||||
return (
|
||||
<NodeCard minW={'350px'} selected={selected} {...data}>
|
||||
{inputs.length > 0 && (
|
||||
{toolInputs.length > 0 && (
|
||||
<>
|
||||
<Divider text="Input" />
|
||||
<Divider text={t('core.module.tool.Tool input')} />
|
||||
<Container>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
<RenderToolInput moduleId={moduleId} inputs={toolInputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
{filterHiddenInputs.length > 0 && (
|
||||
<>
|
||||
<Divider text={t('common.Input')} />
|
||||
<Container>
|
||||
<RenderInput moduleId={moduleId} flowInputList={commonInputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
{outputs.length > 0 && (
|
||||
<>
|
||||
<Divider text="Output" />
|
||||
<Divider text={t('common.Output')} />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
|
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ToolSourceHandle } from '../render/ToolHandle';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
|
||||
const NodeTools = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
return (
|
||||
<NodeCard minW={'350px'} selected={selected} {...data}>
|
||||
<Divider text={t('common.Input')} />
|
||||
<Container>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
</Container>
|
||||
|
||||
<Divider text={t('common.Output')} />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
<Box position={'relative'}>
|
||||
<Box borderBottomLeftRadius={'md'} borderBottomRadius={'md'} overflow={'hidden'}>
|
||||
<Divider showBorderBottom={false} text={t('core.module.template.Tool module')} />
|
||||
</Box>
|
||||
<ToolSourceHandle moduleId={moduleId} />
|
||||
</Box>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeTools);
|
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -95,11 +95,12 @@ const FieldEditModal = ({
|
||||
});
|
||||
const inputType = watch('inputType');
|
||||
const outputType = watch('outputType');
|
||||
const valueType = watch('valueType');
|
||||
const required = watch('required');
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const showDataTypeSelect = useMemo(() => {
|
||||
if (!editField.dataType) return false;
|
||||
if (inputType === undefined) return true;
|
||||
if (inputType === FlowNodeInputTypeEnum.target) return true;
|
||||
if (outputType === FlowNodeOutputTypeEnum.source) return true;
|
||||
|
||||
@@ -109,8 +110,8 @@ const FieldEditModal = ({
|
||||
const showRequired = useMemo(() => {
|
||||
if (inputType === FlowNodeInputTypeEnum.addInputParam) return false;
|
||||
|
||||
return editField.required;
|
||||
}, [editField.required, inputType]);
|
||||
return editField.required || editField.defaultValue;
|
||||
}, [editField.defaultValue, editField.required, inputType]);
|
||||
|
||||
const showNameInput = useMemo(() => {
|
||||
return editField.name;
|
||||
@@ -126,6 +127,37 @@ const FieldEditModal = ({
|
||||
return editField.description;
|
||||
}, [editField.description]);
|
||||
|
||||
const onSubmitSuccess = useCallback(
|
||||
(data: EditNodeFieldType) => {
|
||||
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)
|
||||
});
|
||||
},
|
||||
[isCreate, keys, onSubmit, t, toast]
|
||||
);
|
||||
const onSubmitError = useCallback(
|
||||
(e: Object) => {
|
||||
for (const item of Object.values(e)) {
|
||||
if (item.message) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: item.message
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
[toast]
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
@@ -164,7 +196,31 @@ const FieldEditModal = ({
|
||||
{showRequired && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>{t('common.Require Input')}</Box>
|
||||
<Switch {...register('required')} />
|
||||
<Switch
|
||||
{...register('required', {
|
||||
onChange(e) {
|
||||
if (!e.target.checked) {
|
||||
setValue('defaultValue', '');
|
||||
}
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{showRequired && required && editField.defaultValue && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={['0 0 70px']}>{t('core.module.Default value')}</Box>
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
placeholder={t('core.module.Default value placeholder')}
|
||||
{...register('defaultValue')}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{editField.isToolInput && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>工具参数</Box>
|
||||
<Switch {...register('isToolInput')} />
|
||||
</Flex>
|
||||
)}
|
||||
{showDataTypeSelect && (
|
||||
@@ -194,18 +250,28 @@ const FieldEditModal = ({
|
||||
{showNameInput && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Name')}</Box>
|
||||
<Input placeholder="预约字段/sql语句……" {...register('label', { required: true })} />
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
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
|
||||
bg={'myGray.50'}
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', {
|
||||
required: true,
|
||||
pattern: {
|
||||
value: /^[a-zA-Z]+[0-9]*$/,
|
||||
message: '字段key必须是纯英文字母或数字,并且不能以数字开头。'
|
||||
},
|
||||
onChange: (e) => {
|
||||
const value = e.target.value;
|
||||
// auto fill label
|
||||
if (!showNameInput) {
|
||||
setValue('label', value);
|
||||
}
|
||||
@@ -215,10 +281,17 @@ const FieldEditModal = ({
|
||||
</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>
|
||||
<Box mb={5} alignItems={'flex-start'}>
|
||||
<Box flex={'0 0 70px'} mb={'1px'}>
|
||||
{t('core.module.Field Description')}
|
||||
</Box>
|
||||
<Textarea
|
||||
bg={'myGray.50'}
|
||||
placeholder={t('common.choosable')}
|
||||
rows={5}
|
||||
{...register('description')}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</ModalBody>
|
||||
|
||||
@@ -226,21 +299,7 @@ const FieldEditModal = ({
|
||||
<Button variant={'whiteBase'} 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)
|
||||
});
|
||||
})}
|
||||
>
|
||||
<Button onClick={handleSubmit(onSubmitSuccess, onSubmitError)}>
|
||||
{t('common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
@@ -1,14 +1,18 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, Flex, useTheme, MenuButton } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import type { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { onChangeNode, onCopyNode, onResetNode, onDelNode } from '../../FlowProvider';
|
||||
import {
|
||||
onChangeNode,
|
||||
onCopyNode,
|
||||
onResetNode,
|
||||
onDelNode,
|
||||
useFlowProviderStore
|
||||
} 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';
|
||||
@@ -16,7 +20,8 @@ import { getPreviewPluginModule } from '@/web/core/plugin/api';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { LOGO_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import MyMenu from '@/components/MyMenu';
|
||||
import { ToolTargetHandle } from './ToolHandle';
|
||||
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
|
||||
|
||||
type Props = FlowModuleItemType & {
|
||||
children?: React.ReactNode | React.ReactNode[] | string;
|
||||
@@ -37,12 +42,18 @@ const NodeCard = (props: Props) => {
|
||||
flowType,
|
||||
inputs,
|
||||
selected,
|
||||
forbidMenu
|
||||
forbidMenu,
|
||||
isTool = false
|
||||
} = props;
|
||||
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { setLoading } = useSystemStore();
|
||||
const { nodes, splitToolInputs } = useFlowProviderStore();
|
||||
const { onOpenModal: onOpenIntroModal, EditModal: EditIntroModal } = useEditTextarea({
|
||||
title: t('core.module.Edit intro'),
|
||||
tip: '调整该模块会对工具调用时机有影响。\n你可以通过精确的描述该模块功能,引导模型进行工具调用。',
|
||||
canEmpty: false
|
||||
});
|
||||
|
||||
// custom title edit
|
||||
const { onOpenModal, EditModal: EditTitleModal } = useEditTitle({
|
||||
@@ -53,13 +64,23 @@ const NodeCard = (props: Props) => {
|
||||
content: t('module.Confirm Sync Plugin')
|
||||
});
|
||||
|
||||
const menuList = useMemo(
|
||||
() => [
|
||||
const showToolHandle = useMemo(
|
||||
() => isTool && !!nodes.find((item) => item.data?.flowType === FlowNodeTypeEnum.tools),
|
||||
[isTool, nodes]
|
||||
);
|
||||
const moduleIsTool = useMemo(() => {
|
||||
const { isTool } = splitToolInputs([], moduleId);
|
||||
return isTool;
|
||||
}, [moduleId, splitToolInputs]);
|
||||
|
||||
const Header = useMemo(() => {
|
||||
const menuList = [
|
||||
...(flowType === FlowNodeTypeEnum.pluginModule
|
||||
? [
|
||||
{
|
||||
icon: 'common/refreshLight',
|
||||
label: t('plugin.Synchronous version'),
|
||||
variant: 'whiteBase',
|
||||
onClick: () => {
|
||||
const pluginId = inputs.find(
|
||||
(item) => item.key === ModuleInputKeyEnum.pluginId
|
||||
@@ -88,6 +109,7 @@ const NodeCard = (props: Props) => {
|
||||
{
|
||||
icon: 'edit',
|
||||
label: t('common.Rename'),
|
||||
variant: 'whiteBase',
|
||||
onClick: () =>
|
||||
onOpenModal({
|
||||
defaultVal: name,
|
||||
@@ -111,65 +133,130 @@ const NodeCard = (props: Props) => {
|
||||
{
|
||||
icon: 'copy',
|
||||
label: t('common.Copy'),
|
||||
variant: 'whiteBase',
|
||||
onClick: () => onCopyNode(moduleId)
|
||||
},
|
||||
{
|
||||
icon: 'delete',
|
||||
label: t('common.Delete'),
|
||||
variant: 'whiteDanger',
|
||||
onClick: () => onDelNode(moduleId)
|
||||
}
|
||||
],
|
||||
[flowType, inputs, moduleId, name, onOpenModal, openConfirm, setLoading, t, toast]
|
||||
);
|
||||
];
|
||||
|
||||
return (
|
||||
<Box className="custom-drag-handle" px={4} py={3} position={'relative'}>
|
||||
{showToolHandle && <ToolTargetHandle moduleId={moduleId} />}
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={avatar} borderRadius={'0'} objectFit={'contain'} w={'30px'} h={'30px'} />
|
||||
<Box ml={3} fontSize={'lg'}>
|
||||
{t(name)}
|
||||
</Box>
|
||||
</Flex>
|
||||
{!forbidMenu && (
|
||||
<Box
|
||||
className="nodrag controller-menu"
|
||||
display={'none'}
|
||||
flexDirection={'column'}
|
||||
gap={3}
|
||||
position={'absolute'}
|
||||
top={'-20px'}
|
||||
right={0}
|
||||
transform={'translateX(90%)'}
|
||||
pl={'17px'}
|
||||
pr={'10px'}
|
||||
pb={'20px'}
|
||||
pt={'20px'}
|
||||
>
|
||||
{menuList.map((item) => (
|
||||
<Box key={item.icon}>
|
||||
<Button
|
||||
size={'xs'}
|
||||
variant={item.variant}
|
||||
leftIcon={<MyIcon name={item.icon as any} w={'12px'} />}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
{item.label}
|
||||
</Button>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
<Flex alignItems={'flex-end'} py={1}>
|
||||
<Box fontSize={'xs'} color={'myGray.600'} flex={'1 0 0'}>
|
||||
{t(intro)}
|
||||
</Box>
|
||||
{moduleIsTool && (
|
||||
<Button
|
||||
size={'xs'}
|
||||
variant={'whiteBase'}
|
||||
onClick={() => {
|
||||
onOpenIntroModal({
|
||||
defaultVal: intro,
|
||||
onSuccess(e) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'attr',
|
||||
key: 'intro',
|
||||
value: e
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('core.module.Edit intro')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
}, [
|
||||
avatar,
|
||||
flowType,
|
||||
forbidMenu,
|
||||
inputs,
|
||||
intro,
|
||||
moduleId,
|
||||
moduleIsTool,
|
||||
name,
|
||||
onOpenIntroModal,
|
||||
onOpenModal,
|
||||
openConfirm,
|
||||
setLoading,
|
||||
showToolHandle,
|
||||
t,
|
||||
toast
|
||||
]);
|
||||
|
||||
const RenderModal = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<EditTitleModal maxLength={20} />
|
||||
{moduleIsTool && <EditIntroModal maxLength={500} />}
|
||||
<ConfirmModal />
|
||||
</>
|
||||
);
|
||||
}, [ConfirmModal, EditIntroModal, EditTitleModal, moduleIsTool]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
minW={minW}
|
||||
maxW={'500px'}
|
||||
maxW={'600px'}
|
||||
bg={'white'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={selected ? 'primary.600' : 'borderColor.base'}
|
||||
borderRadius={'md'}
|
||||
boxShadow={'1'}
|
||||
_hover={{
|
||||
boxShadow: '4'
|
||||
boxShadow: '4',
|
||||
'& .controller-menu': {
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box className="custom-drag-handle" px={4} py={3}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={avatar} borderRadius={'0'} objectFit={'contain'} w={'30px'} h={'30px'} />
|
||||
<Box ml={3} fontSize={'lg'}>
|
||||
{t(name)}
|
||||
</Box>
|
||||
<Box flex={1} />
|
||||
{!forbidMenu && (
|
||||
<MyMenu
|
||||
offset={[-60, 5]}
|
||||
width={120}
|
||||
Button={
|
||||
<MenuButton
|
||||
className={'nodrag'}
|
||||
_hover={{ bg: 'myWhite.600' }}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'more'} w={'14px'} p={2} />
|
||||
</MenuButton>
|
||||
}
|
||||
menuList={menuList}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Box fontSize={'xs'} color={'myGray.600'}>
|
||||
{t(intro)}
|
||||
</Box>
|
||||
</Box>
|
||||
{Header}
|
||||
{children}
|
||||
<EditTitleModal />
|
||||
<ConfirmModal />
|
||||
{RenderModal}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@@ -78,32 +78,33 @@ const RenderInput = ({ flowInputList, moduleId, CustomComponent }: Props) => {
|
||||
|
||||
const sortInputs = useMemo(
|
||||
() =>
|
||||
flowInputList.sort((a, b) => {
|
||||
if (a.type === FlowNodeInputTypeEnum.addInputParam) {
|
||||
return 1;
|
||||
}
|
||||
if (b.type === FlowNodeInputTypeEnum.addInputParam) {
|
||||
return -1;
|
||||
}
|
||||
JSON.stringify(
|
||||
[...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;
|
||||
}
|
||||
if (a.type === FlowNodeInputTypeEnum.switch) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}),
|
||||
return 0;
|
||||
})
|
||||
),
|
||||
[flowInputList]
|
||||
);
|
||||
const filterInputs = useMemo(
|
||||
() =>
|
||||
sortInputs.filter((input) => {
|
||||
if (mode === 'app' && input.hideInApp) return false;
|
||||
if (mode === 'plugin' && input.hideInPlugin) return false;
|
||||
const filterInputs = useMemo(() => {
|
||||
const parseSortInputs = JSON.parse(sortInputs) as FlowNodeInputItemType[];
|
||||
return parseSortInputs.filter((input) => {
|
||||
if (mode === 'app' && input.hideInApp) return false;
|
||||
if (mode === 'plugin' && input.hideInPlugin) return false;
|
||||
|
||||
return true;
|
||||
}),
|
||||
[mode, sortInputs]
|
||||
);
|
||||
return true;
|
||||
});
|
||||
}, [mode, sortInputs]);
|
||||
|
||||
const memoCustomComponent = useMemo(() => CustomComponent || {}, [CustomComponent]);
|
||||
|
||||
@@ -135,7 +136,7 @@ const RenderInput = ({ flowInputList, moduleId, CustomComponent }: Props) => {
|
||||
</Box>
|
||||
) : null;
|
||||
});
|
||||
}, [memoCustomComponent, filterInputs, mode, moduleId]);
|
||||
}, [filterInputs, memoCustomComponent, mode, moduleId]);
|
||||
|
||||
return <>{Render}</>;
|
||||
};
|
||||
|
@@ -3,14 +3,24 @@ import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import SelectAiModel from '@/components/Select/SelectAiModel';
|
||||
import { llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants';
|
||||
|
||||
const SelectAiModelRender = ({ item, inputs = [], moduleId }: RenderInputProps) => {
|
||||
const { llmModelList } = useSystemStore();
|
||||
const modelList = llmModelList.map((item) => ({
|
||||
model: item.model,
|
||||
name: item.name,
|
||||
maxResponse: item.maxResponse
|
||||
}));
|
||||
|
||||
const modelList = llmModelList
|
||||
.filter((model) => {
|
||||
if (!item.llmModelType) return true;
|
||||
const filterField = llmModelTypeFilterMap[item.llmModelType];
|
||||
if (!filterField) return true;
|
||||
//@ts-ignore
|
||||
return !!model[filterField];
|
||||
})
|
||||
.map((item) => ({
|
||||
model: item.model,
|
||||
name: item.name,
|
||||
maxResponse: item.maxResponse
|
||||
}));
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(e: string) => {
|
||||
|
@@ -48,7 +48,9 @@ const OutputLabel = ({
|
||||
label: item.label,
|
||||
description: item.description,
|
||||
valueType: item.valueType,
|
||||
outputType: item.type
|
||||
outputType: item.type,
|
||||
required: item.required,
|
||||
defaultValue: item.defaultValue
|
||||
})
|
||||
}
|
||||
/>
|
||||
@@ -74,7 +76,20 @@ const OutputLabel = ({
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box>{t(label)}</Box>
|
||||
<Box position={'relative'}>
|
||||
{item.required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'-2px'}
|
||||
left={'-5px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
{t(label)}
|
||||
</Box>
|
||||
|
||||
{item.type === FlowNodeOutputTypeEnum.source && (
|
||||
<SourceHandle handleKey={outputKey} valueType={item.valueType} />
|
||||
@@ -95,7 +110,9 @@ const OutputLabel = ({
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label,
|
||||
description: data.description
|
||||
description: data.description,
|
||||
required: data.required,
|
||||
defaultValue: data.defaultValue
|
||||
};
|
||||
|
||||
if (changeKey) {
|
||||
|
@@ -17,7 +17,7 @@ const RenderList: {
|
||||
}
|
||||
];
|
||||
|
||||
const RenderOutput = ({
|
||||
const RenderToolOutput = ({
|
||||
moduleId,
|
||||
flowOutputList
|
||||
}: {
|
||||
@@ -77,4 +77,4 @@ const RenderOutput = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(RenderOutput);
|
||||
export default React.memo(RenderToolOutput);
|
||||
|
@@ -43,6 +43,7 @@ const AddOutputParam = ({ outputs = [], item, moduleId }: RenderOutputProps) =>
|
||||
label: data.label,
|
||||
description: data.description,
|
||||
required: data.required,
|
||||
defaultValue: data.defaultValue,
|
||||
edit: true,
|
||||
editField: item.editField,
|
||||
targets: []
|
||||
|
@@ -0,0 +1,141 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import type { EditFieldModalProps } from './type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
Input,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Switch,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { defaultEditFormData } from './constants';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { onChangeNode } from '../../../FlowProvider';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
|
||||
const EditFieldModal = ({
|
||||
defaultValue = defaultEditFormData,
|
||||
moduleId,
|
||||
onClose
|
||||
}: EditFieldModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { register, getValues, setValue, handleSubmit, watch } = useForm<FlowNodeInputItemType>({
|
||||
defaultValues: defaultValue
|
||||
});
|
||||
|
||||
const selectTypeList = useRef([
|
||||
{
|
||||
label: '字符串',
|
||||
value: 'string'
|
||||
}
|
||||
]);
|
||||
|
||||
const { mutate: onclickSubmit } = useRequest({
|
||||
mutationFn: async (e: FlowNodeInputItemType) => {
|
||||
const inputConfig = {
|
||||
...e,
|
||||
label: e.key
|
||||
};
|
||||
if (defaultValue.key) {
|
||||
// edit
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: defaultValue.key,
|
||||
value: inputConfig
|
||||
});
|
||||
} else {
|
||||
// create
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
key: e.key,
|
||||
value: {
|
||||
...e,
|
||||
label: e.key
|
||||
}
|
||||
});
|
||||
}
|
||||
onClose();
|
||||
}
|
||||
});
|
||||
const onclickSubmitError = useCallback(
|
||||
(e: Object) => {
|
||||
for (const item of Object.values(e)) {
|
||||
if (item.message) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: item.message
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
[toast]
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal isOpen iconSrc="modal/edit" title={'工具字段参数配置'} onClose={onClose}>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 80px'}>{t('common.Require Input')}</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 80px'}>{t('core.module.Field key')}</Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect
|
||||
list={selectTypeList.current}
|
||||
value={getValues('valueType')}
|
||||
onchange={(e: any) => {
|
||||
setValue('valueType', e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 80px'}>{'字段key'}</Box>
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
{...register('key', {
|
||||
required: true,
|
||||
pattern: {
|
||||
value: /^[a-zA-Z]+[0-9]*$/,
|
||||
message: '字段key必须是纯英文字母或数字,并且不能以数字开头。'
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Box mb={5}>
|
||||
<Box flex={'0 0 80px'}>{t('core.module.Field Description')}</Box>
|
||||
<Textarea
|
||||
bg={'myGray.50'}
|
||||
rows={5}
|
||||
{...register('toolDescription', {
|
||||
required: true
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} mr={2} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit((data) => onclickSubmit(data), onclickSubmitError)}>
|
||||
{t('common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(EditFieldModal);
|
@@ -0,0 +1,11 @@
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
export const defaultEditFormData: FlowNodeInputItemType = {
|
||||
valueType: 'string',
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
key: '',
|
||||
label: '',
|
||||
toolDescription: '',
|
||||
required: true
|
||||
};
|
@@ -0,0 +1,120 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type {
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
} from '@fastgpt/global/core/module/node/type';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Flex
|
||||
} from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { defaultEditFormData } from './constants';
|
||||
import { onChangeNode } from '../../../FlowProvider';
|
||||
const EditFieldModal = dynamic(() => import('./EditFieldModal'));
|
||||
|
||||
const RenderToolInput = ({
|
||||
moduleId,
|
||||
inputs,
|
||||
canEdit = false
|
||||
}: {
|
||||
moduleId: string;
|
||||
inputs: FlowNodeInputItemType[];
|
||||
canEdit?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [editField, setEditField] = React.useState<FlowNodeInputItemType>();
|
||||
|
||||
return (
|
||||
<>
|
||||
{canEdit && (
|
||||
<Flex mb={2} alignItems={'center'}>
|
||||
<Box flex={'1 0 0'}>{t('common.Field')}</Box>
|
||||
<Button
|
||||
variant={'unstyled'}
|
||||
leftIcon={<MyIcon name={'common/addLight'} w={'14px'} />}
|
||||
size={'sm'}
|
||||
px={3}
|
||||
_hover={{
|
||||
bg: 'myGray.150'
|
||||
}}
|
||||
onClick={() => setEditField(defaultEditFormData)}
|
||||
>
|
||||
{t('core.module.extract.Add field')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
<Box borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th bg={'myGray.50'}>字段名</Th>
|
||||
<Th bg={'myGray.50'}>字段描述</Th>
|
||||
<Th bg={'myGray.50'}>必须</Th>
|
||||
{canEdit && <Th bg={'myGray.50'}></Th>}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{inputs.map((item, index) => (
|
||||
<Tr
|
||||
key={index}
|
||||
position={'relative'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.toolDescription}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
{canEdit && (
|
||||
<Td whiteSpace={'nowrap'}>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => setEditField(item)}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: item.key,
|
||||
value: ''
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
|
||||
{!!editField && (
|
||||
<EditFieldModal
|
||||
defaultValue={editField}
|
||||
moduleId={moduleId}
|
||||
onClose={() => setEditField(undefined)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(RenderToolInput);
|
5
projects/app/src/components/core/module/Flow/components/render/RenderToolInput/type.d.ts
vendored
Normal file
5
projects/app/src/components/core/module/Flow/components/render/RenderToolInput/type.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
export type EditFieldModalProps = {
|
||||
defaultValue?: EditFieldFormProps;
|
||||
moduleId: string;
|
||||
onClose: () => void;
|
||||
};
|
@@ -28,7 +28,7 @@ const SourceHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'50%'}
|
||||
right={'-18px'}
|
||||
right={'-20px'}
|
||||
transform={'translate(50%,-50%)'}
|
||||
{...props}
|
||||
>
|
||||
@@ -42,6 +42,8 @@ const SourceHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
style={{
|
||||
width: '14px',
|
||||
height: '14px',
|
||||
borderWidth: '3.5px',
|
||||
backgroundColor: 'white',
|
||||
...valueStyle
|
||||
}}
|
||||
type="source"
|
||||
|
@@ -27,7 +27,7 @@ const TargetHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'50%'}
|
||||
left={'-18px'}
|
||||
left={'-20px'}
|
||||
transform={'translate(50%,-50%)'}
|
||||
{...props}
|
||||
>
|
||||
@@ -41,6 +41,8 @@ const TargetHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
style={{
|
||||
width: '14px',
|
||||
height: '14px',
|
||||
borderWidth: '3.5px',
|
||||
backgroundColor: 'white',
|
||||
...valueStyle
|
||||
}}
|
||||
type="target"
|
||||
|
@@ -0,0 +1,103 @@
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import {
|
||||
ModuleIOValueTypeEnum,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum
|
||||
} from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Connection, Handle, Position } from 'reactflow';
|
||||
import { useFlowProviderStore } from '../../FlowProvider';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
type ToolHandleProps = BoxProps & {
|
||||
moduleId: string;
|
||||
};
|
||||
export const ToolTargetHandle = ({ moduleId, ...props }: ToolHandleProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const valueTypeMap = FlowValueTypeMap[ModuleIOValueTypeEnum.tools];
|
||||
|
||||
return (
|
||||
<Box position={'absolute'} left={'50%'} transform={'translate(-17px,-10px)'} {...props}>
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(valueTypeMap?.label),
|
||||
description: valueTypeMap?.description
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
style={{
|
||||
width: '14px',
|
||||
height: '14px',
|
||||
border: '4px solid #5E8FFF',
|
||||
borderRadius: '0',
|
||||
backgroundColor: 'transparent',
|
||||
transformOrigin: 'center',
|
||||
transform: 'rotate(45deg)'
|
||||
}}
|
||||
type="target"
|
||||
id={ModuleOutputKeyEnum.selectedTools}
|
||||
position={Position.Top}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const ToolSourceHandle = ({ moduleId, ...props }: ToolHandleProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { setEdges, nodes } = useFlowProviderStore();
|
||||
|
||||
const valueTypeMap = FlowValueTypeMap[ModuleIOValueTypeEnum.tools];
|
||||
|
||||
/* onConnect edge, delete tool input and switch */
|
||||
const onConnect = useCallback(
|
||||
(e: Connection) => {
|
||||
const node = nodes.find((node) => node.id === e.target);
|
||||
if (!node) return;
|
||||
const inputs = node.data.inputs;
|
||||
setEdges((edges) =>
|
||||
edges.filter((edge) => {
|
||||
const input = inputs.find((input) => input.key === edge.targetHandle);
|
||||
if (
|
||||
edge.target === node.id &&
|
||||
(!!input?.toolDescription || input?.key === ModuleInputKeyEnum.switch)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
);
|
||||
},
|
||||
[nodes, setEdges]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box position={'absolute'} left={'50%'} transform={'translate(-16px,-14px)'} {...props}>
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(valueTypeMap?.label),
|
||||
description: valueTypeMap?.description
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
style={{
|
||||
width: '14px',
|
||||
height: '14px',
|
||||
border: '4px solid #5E8FFF',
|
||||
borderRadius: '0',
|
||||
backgroundColor: 'transparent',
|
||||
transformOrigin: 'center',
|
||||
transform: 'rotate(45deg)'
|
||||
}}
|
||||
type="source"
|
||||
id={ModuleOutputKeyEnum.selectedTools}
|
||||
position={Position.Bottom}
|
||||
onConnect={onConnect}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
);
|
||||
};
|
@@ -33,7 +33,8 @@ const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
|
||||
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./components/nodes/NodePluginInput')),
|
||||
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodePluginOutput')),
|
||||
[FlowNodeTypeEnum.pluginModule]: NodeSimple,
|
||||
[FlowNodeTypeEnum.queryExtension]: NodeSimple
|
||||
[FlowNodeTypeEnum.queryExtension]: NodeSimple,
|
||||
[FlowNodeTypeEnum.tools]: dynamic(() => import('./components/nodes/NodeTools'))
|
||||
};
|
||||
const edgeTypes = {
|
||||
[EDGE_TYPE]: ButtonEdge
|
||||
|
@@ -14,6 +14,7 @@ export const flowNode2Modules = ({
|
||||
const modules: ModuleItemType[] = nodes.map((item) => ({
|
||||
moduleId: item.data.moduleId,
|
||||
name: item.data.name,
|
||||
intro: item.data.intro,
|
||||
avatar: item.data.avatar,
|
||||
flowType: item.data.flowType,
|
||||
showStatus: item.data.showStatus,
|
||||
@@ -38,10 +39,15 @@ export const flowNode2Modules = ({
|
||||
|
||||
module.outputs.forEach((output) => {
|
||||
output.targets = edges
|
||||
.filter(
|
||||
(edge) =>
|
||||
edge.source === module.moduleId && edge.sourceHandle === output.key && edge.targetHandle
|
||||
)
|
||||
.filter((edge) => {
|
||||
if (
|
||||
edge.source === module.moduleId &&
|
||||
edge.sourceHandle === output.key &&
|
||||
edge.targetHandle
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.map((edge) => ({
|
||||
moduleId: edge.target,
|
||||
key: edge.targetHandle || ''
|
||||
|
Reference in New Issue
Block a user