mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-19 01:54:04 +00:00
global variable & interactive node dnd (#3764)
This commit is contained in:
@@ -1,23 +1,35 @@
|
|||||||
import { Box } from '@chakra-ui/react';
|
import { Box, Tbody } from '@chakra-ui/react';
|
||||||
import React, { useState } from 'react';
|
import React, { ReactNode, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
DragDropContext,
|
DragDropContext,
|
||||||
DroppableProps,
|
|
||||||
Droppable,
|
Droppable,
|
||||||
DraggableChildrenFn,
|
DraggableChildrenFn,
|
||||||
DragStart,
|
DragStart,
|
||||||
DropResult
|
DropResult,
|
||||||
|
DroppableProvided,
|
||||||
|
DroppableStateSnapshot
|
||||||
} from 'react-beautiful-dnd';
|
} from 'react-beautiful-dnd';
|
||||||
export * from 'react-beautiful-dnd';
|
export * from 'react-beautiful-dnd';
|
||||||
|
|
||||||
type Props<T = any> = {
|
type Props<T = any> = {
|
||||||
onDragEndCb: (result: T[]) => void;
|
onDragEndCb: (result: T[]) => void;
|
||||||
renderClone?: DraggableChildrenFn;
|
renderClone?: DraggableChildrenFn;
|
||||||
children: DroppableProps['children'];
|
children:
|
||||||
|
| ((provided: DroppableProvided, snapshot: DroppableStateSnapshot) => ReactNode)
|
||||||
|
| ReactNode;
|
||||||
dataList: T[];
|
dataList: T[];
|
||||||
|
isTable?: boolean;
|
||||||
|
zoom?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function DndDrag<T>({ children, renderClone, onDragEndCb, dataList }: Props<T>) {
|
function DndDrag<T>({
|
||||||
|
children,
|
||||||
|
renderClone,
|
||||||
|
onDragEndCb,
|
||||||
|
dataList,
|
||||||
|
isTable = false,
|
||||||
|
zoom = 1
|
||||||
|
}: Props<T>) {
|
||||||
const [draggingItemHeight, setDraggingItemHeight] = useState(0);
|
const [draggingItemHeight, setDraggingItemHeight] = useState(0);
|
||||||
|
|
||||||
const onDragStart = (start: DragStart) => {
|
const onDragStart = (start: DragStart) => {
|
||||||
@@ -45,10 +57,15 @@ function DndDrag<T>({ children, renderClone, onDragEndCb, dataList }: Props<T>)
|
|||||||
<DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
|
<DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
|
||||||
<Droppable droppableId="droppable" renderClone={renderClone}>
|
<Droppable droppableId="droppable" renderClone={renderClone}>
|
||||||
{(provided, snapshot) => {
|
{(provided, snapshot) => {
|
||||||
return (
|
return isTable ? (
|
||||||
|
<Tbody {...provided.droppableProps} ref={provided.innerRef}>
|
||||||
|
{typeof children !== 'function' && children}
|
||||||
|
{snapshot.isDraggingOver && <Box height={`${draggingItemHeight / zoom}px`} />}
|
||||||
|
</Tbody>
|
||||||
|
) : (
|
||||||
<Box {...provided.droppableProps} ref={provided.innerRef}>
|
<Box {...provided.droppableProps} ref={provided.innerRef}>
|
||||||
{children(provided, snapshot)}
|
{typeof children === 'function' && children(provided, snapshot)}
|
||||||
{snapshot.isDraggingOver && <Box height={`${draggingItemHeight}px`} />}
|
{snapshot.isDraggingOver && <Box height={`${draggingItemHeight / zoom}px`} />}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Flex,
|
Flex,
|
||||||
Table,
|
Table,
|
||||||
Thead,
|
Thead,
|
||||||
Tbody,
|
|
||||||
Tr,
|
Tr,
|
||||||
Th,
|
Th,
|
||||||
Td,
|
Td,
|
||||||
@@ -20,9 +19,8 @@ import {
|
|||||||
} from '@fastgpt/global/core/workflow/constants';
|
} from '@fastgpt/global/core/workflow/constants';
|
||||||
import type { VariableItemType } from '@fastgpt/global/core/app/type.d';
|
import type { VariableItemType } from '@fastgpt/global/core/app/type.d';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm, UseFormReset } from 'react-hook-form';
|
||||||
import { customAlphabet } from 'nanoid';
|
import { customAlphabet } from 'nanoid';
|
||||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
|
||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
@@ -32,6 +30,14 @@ import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
|||||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||||
import InputTypeConfig from '@/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig';
|
import InputTypeConfig from '@/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig';
|
||||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||||
|
import { useReactFlow, useViewport } from 'reactflow';
|
||||||
|
import DndDrag, {
|
||||||
|
Draggable,
|
||||||
|
DraggableProvided,
|
||||||
|
DraggableStateSnapshot
|
||||||
|
} from '@fastgpt/web/components/common/DndDrag';
|
||||||
|
|
||||||
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||||
|
|
||||||
export const defaultVariable: VariableItemType = {
|
export const defaultVariable: VariableItemType = {
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
@@ -61,6 +67,7 @@ const VariableEdit = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { zoom } = useViewport();
|
||||||
|
|
||||||
const form = useForm<VariableItemType>();
|
const form = useForm<VariableItemType>();
|
||||||
const { setValue, reset, watch, getValues } = form;
|
const { setValue, reset, watch, getValues } = form;
|
||||||
@@ -166,7 +173,7 @@ const VariableEdit = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box className="nodrag">
|
||||||
{/* Row box */}
|
{/* Row box */}
|
||||||
<Flex alignItems={'center'}>
|
<Flex alignItems={'center'}>
|
||||||
<MyIcon name={'core/app/simpleMode/variable'} w={'20px'} />
|
<MyIcon name={'core/app/simpleMode/variable'} w={'20px'} />
|
||||||
@@ -200,48 +207,40 @@ const VariableEdit = ({
|
|||||||
<Th>{t('common:common.Operation')}</Th>
|
<Th>{t('common:common.Operation')}</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
<Tbody>
|
<DndDrag<VariableItemType>
|
||||||
|
onDragEndCb={(list) => {
|
||||||
|
onChange(list);
|
||||||
|
}}
|
||||||
|
dataList={formatVariables}
|
||||||
|
renderClone={(provided, snapshot, rubric) => (
|
||||||
|
<TableItem
|
||||||
|
provided={provided}
|
||||||
|
snapshot={snapshot}
|
||||||
|
item={formatVariables[rubric.source.index]}
|
||||||
|
reset={reset}
|
||||||
|
onChange={onChange}
|
||||||
|
variables={variables}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
isTable
|
||||||
|
zoom={zoom}
|
||||||
|
>
|
||||||
{formatVariables.map((item, index) => (
|
{formatVariables.map((item, index) => (
|
||||||
<Tr key={item.id}>
|
<Draggable key={item.id} draggableId={item.id} index={index}>
|
||||||
<Td fontWeight={'medium'}>
|
{(provided, snapshot) => (
|
||||||
<Flex alignItems={'center'}>
|
<TableItem
|
||||||
<MyIcon name={item.icon as any} w={'16px'} color={'myGray.400'} mr={2} />
|
provided={provided}
|
||||||
{item.key}
|
snapshot={snapshot}
|
||||||
</Flex>
|
item={item}
|
||||||
</Td>
|
reset={reset}
|
||||||
<Td>
|
onChange={onChange}
|
||||||
<Flex alignItems={'center'}>
|
variables={variables}
|
||||||
{item.required ? (
|
key={item.id}
|
||||||
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
|
/>
|
||||||
) : (
|
)}
|
||||||
''
|
</Draggable>
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<Flex>
|
|
||||||
<MyIconButton
|
|
||||||
icon={'common/settingLight'}
|
|
||||||
onClick={() => {
|
|
||||||
const formattedItem = {
|
|
||||||
...item,
|
|
||||||
list: item.enums || []
|
|
||||||
};
|
|
||||||
reset(formattedItem);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<MyIconButton
|
|
||||||
icon={'delete'}
|
|
||||||
hoverColor={'red.500'}
|
|
||||||
onClick={() =>
|
|
||||||
onChange(variables.filter((variable) => variable.id !== item.id))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
))}
|
))}
|
||||||
</Tbody>
|
</DndDrag>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
)}
|
)}
|
||||||
@@ -339,4 +338,65 @@ const VariableEdit = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TableItem = ({
|
||||||
|
provided,
|
||||||
|
snapshot,
|
||||||
|
item,
|
||||||
|
reset,
|
||||||
|
onChange,
|
||||||
|
variables
|
||||||
|
}: {
|
||||||
|
provided: DraggableProvided;
|
||||||
|
snapshot: DraggableStateSnapshot;
|
||||||
|
item: VariableItemType & {
|
||||||
|
icon?: string;
|
||||||
|
};
|
||||||
|
reset: UseFormReset<VariableItemType>;
|
||||||
|
onChange: (data: VariableItemType[]) => void;
|
||||||
|
variables: VariableItemType[];
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Tr
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
style={{
|
||||||
|
...provided.draggableProps.style,
|
||||||
|
opacity: snapshot.isDragging ? 0.8 : 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Td fontWeight={'medium'}>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<MyIcon name={item.icon as any} w={'16px'} color={'myGray.400'} mr={1} />
|
||||||
|
{item.key}
|
||||||
|
</Flex>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
{item.required ? <MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} /> : ''}
|
||||||
|
</Flex>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Flex>
|
||||||
|
<MyIconButton
|
||||||
|
icon={'common/settingLight'}
|
||||||
|
onClick={() => {
|
||||||
|
const formattedItem = {
|
||||||
|
...item,
|
||||||
|
list: item.enums || []
|
||||||
|
};
|
||||||
|
reset(formattedItem);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<MyIconButton
|
||||||
|
icon={'delete'}
|
||||||
|
hoverColor={'red.500'}
|
||||||
|
onClick={() => onChange(variables.filter((variable) => variable.id !== item.id))}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default React.memo(VariableEdit);
|
export default React.memo(VariableEdit);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { NodeProps } from 'reactflow';
|
import { NodeProps, useViewport } from 'reactflow';
|
||||||
import NodeCard from '../render/NodeCard';
|
import NodeCard from '../render/NodeCard';
|
||||||
import Container from '../../components/Container';
|
import Container from '../../components/Container';
|
||||||
import RenderInput from '../render/RenderInput';
|
import RenderInput from '../render/RenderInput';
|
||||||
@@ -38,11 +38,17 @@ import InputFormEditModal, { defaultFormInput } from './InputFormEditModal';
|
|||||||
import RenderOutput from '../render/RenderOutput';
|
import RenderOutput from '../render/RenderOutput';
|
||||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||||
|
import DndDrag, {
|
||||||
|
Draggable,
|
||||||
|
DraggableProvided,
|
||||||
|
DraggableStateSnapshot
|
||||||
|
} from '@fastgpt/web/components/common/DndDrag';
|
||||||
|
|
||||||
const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||||
const { nodeId, inputs, outputs } = data;
|
const { nodeId, inputs, outputs } = data;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||||
|
const { zoom } = useViewport();
|
||||||
|
|
||||||
const [editField, setEditField] = useState<UserInputFormItemType>();
|
const [editField, setEditField] = useState<UserInputFormItemType>();
|
||||||
|
|
||||||
@@ -159,53 +165,79 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
<Th>{t('user:operations')}</Th>
|
<Th>{t('user:operations')}</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
<Tbody>
|
<DndDrag<UserInputFormItemType>
|
||||||
|
onDragEndCb={(list) => {
|
||||||
|
const sortedOutputs = [
|
||||||
|
outputs[0],
|
||||||
|
...outputs.slice(1).sort((a, b) => {
|
||||||
|
const aIndex = list.findIndex((item) => item.key === a.key);
|
||||||
|
const bIndex = list.findIndex((item) => item.key === b.key);
|
||||||
|
return aIndex - bIndex;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
onChangeNode({
|
||||||
|
nodeId,
|
||||||
|
type: 'updateInput',
|
||||||
|
key,
|
||||||
|
value: {
|
||||||
|
...props,
|
||||||
|
key,
|
||||||
|
value: list
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onChangeNode({
|
||||||
|
nodeId,
|
||||||
|
type: 'attr',
|
||||||
|
key: 'outputs',
|
||||||
|
value: sortedOutputs
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
dataList={inputs}
|
||||||
|
renderClone={(provided, snapshot, rubric) => {
|
||||||
|
const item = inputs[rubric.source.index];
|
||||||
|
const icon = FlowNodeInputMap[item.type as FlowNodeInputTypeEnum]?.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableItem
|
||||||
|
provided={provided}
|
||||||
|
snapshot={snapshot}
|
||||||
|
item={item}
|
||||||
|
icon={icon}
|
||||||
|
setEditField={setEditField}
|
||||||
|
onDelete={onDelete}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isTable
|
||||||
|
zoom={zoom}
|
||||||
|
>
|
||||||
{inputs.map((item, index) => {
|
{inputs.map((item, index) => {
|
||||||
const icon = FlowNodeInputMap[item.type as FlowNodeInputTypeEnum]?.icon;
|
const icon = FlowNodeInputMap[item.type as FlowNodeInputTypeEnum]?.icon;
|
||||||
return (
|
return (
|
||||||
<Tr key={index}>
|
<Draggable key={item.key} draggableId={item.key} index={index}>
|
||||||
<Td>
|
{(provided, snapshot) => (
|
||||||
<Flex alignItems={'center'} fontSize={'mini'} fontWeight={'medium'}>
|
<TableItem
|
||||||
{!!icon && (
|
provided={provided}
|
||||||
<MyIcon name={icon as any} w={'14px'} mr={1} color={'myGray.400'} />
|
snapshot={snapshot}
|
||||||
)}
|
key={item.key}
|
||||||
{item.label}
|
item={item}
|
||||||
</Flex>
|
icon={icon}
|
||||||
</Td>
|
setEditField={setEditField}
|
||||||
<Td>{item.description || '-'}</Td>
|
onDelete={onDelete}
|
||||||
<Td>
|
/>
|
||||||
{item.required ? (
|
)}
|
||||||
<Flex alignItems={'center'}>
|
</Draggable>
|
||||||
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
|
|
||||||
</Flex>
|
|
||||||
) : (
|
|
||||||
'-'
|
|
||||||
)}
|
|
||||||
</Td>
|
|
||||||
<Td>
|
|
||||||
<Flex>
|
|
||||||
<MyIconButton
|
|
||||||
icon={'common/settingLight'}
|
|
||||||
onClick={() => setEditField(item)}
|
|
||||||
/>
|
|
||||||
<MyIconButton
|
|
||||||
icon={'delete'}
|
|
||||||
hoverColor={'red.500'}
|
|
||||||
onClick={() => onDelete(item.key)}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Tbody>
|
</DndDrag>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[t, editField, onChangeNode, nodeId, outputs]
|
[t, editField, zoom, onChangeNode, nodeId, outputs]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -223,3 +255,54 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(NodeFormInput);
|
export default React.memo(NodeFormInput);
|
||||||
|
|
||||||
|
const TableItem = ({
|
||||||
|
provided,
|
||||||
|
snapshot,
|
||||||
|
item,
|
||||||
|
icon,
|
||||||
|
setEditField,
|
||||||
|
onDelete
|
||||||
|
}: {
|
||||||
|
provided: DraggableProvided;
|
||||||
|
snapshot: DraggableStateSnapshot;
|
||||||
|
item: UserInputFormItemType;
|
||||||
|
icon: string;
|
||||||
|
setEditField: (item: UserInputFormItemType) => void;
|
||||||
|
onDelete: (valueKey: string) => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Tr
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
style={{
|
||||||
|
...provided.draggableProps.style,
|
||||||
|
opacity: snapshot.isDragging ? 0.8 : 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Td>
|
||||||
|
<Flex alignItems={'center'} fontSize={'mini'} fontWeight={'medium'} whiteSpace={'nowrap'}>
|
||||||
|
{!!icon && <MyIcon name={icon as any} w={'14px'} mr={1} color={'myGray.400'} />}
|
||||||
|
{item.label}
|
||||||
|
</Flex>
|
||||||
|
</Td>
|
||||||
|
<Td>{item.description || '-'}</Td>
|
||||||
|
<Td>
|
||||||
|
{item.required ? (
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
'-'
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Flex>
|
||||||
|
<MyIconButton icon={'common/settingLight'} onClick={() => setEditField(item)} />
|
||||||
|
<MyIconButton icon={'delete'} hoverColor={'red.500'} onClick={() => onDelete(item.key)} />
|
||||||
|
</Flex>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@@ -3,7 +3,7 @@ import NodeCard from '../render/NodeCard';
|
|||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
import { NodeProps, Position } from 'reactflow';
|
import { NodeProps, Position, useViewport } from 'reactflow';
|
||||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||||
import { IfElseListItemType } from '@fastgpt/global/core/workflow/template/system/ifElse/type';
|
import { IfElseListItemType } from '@fastgpt/global/core/workflow/template/system/ifElse/type';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
@@ -18,6 +18,7 @@ import { IfElseResultEnum } from '@fastgpt/global/core/workflow/template/system/
|
|||||||
const NodeIfElse = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
const NodeIfElse = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { nodeId, inputs = [] } = data;
|
const { nodeId, inputs = [] } = data;
|
||||||
|
const { zoom } = useViewport();
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||||
const elseHandleId = getHandleId(nodeId, 'source', IfElseResultEnum.ELSE);
|
const elseHandleId = getHandleId(nodeId, 'source', IfElseResultEnum.ELSE);
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ const NodeIfElse = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
nodeId={nodeId}
|
nodeId={nodeId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
zoom={zoom}
|
||||||
>
|
>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<Box {...provided.droppableProps} ref={provided.innerRef}>
|
<Box {...provided.droppableProps} ref={provided.innerRef}>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { NodeProps, Position } from 'reactflow';
|
import { NodeProps, Position, useViewport } from 'reactflow';
|
||||||
import { Box, Button, HStack, Input } from '@chakra-ui/react';
|
import { Box, Button, HStack, Input } from '@chakra-ui/react';
|
||||||
import NodeCard from './render/NodeCard';
|
import NodeCard from './render/NodeCard';
|
||||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||||
@@ -18,86 +18,72 @@ import { WorkflowContext } from '../../context';
|
|||||||
import { UserSelectOptionItemType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
import { UserSelectOptionItemType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||||
import IOTitle from '../components/IOTitle';
|
import IOTitle from '../components/IOTitle';
|
||||||
import RenderOutput from './render/RenderOutput';
|
import RenderOutput from './render/RenderOutput';
|
||||||
|
import DndDrag, {
|
||||||
|
Draggable,
|
||||||
|
DraggableProvided,
|
||||||
|
DraggableStateSnapshot
|
||||||
|
} from '@fastgpt/web/components/common/DndDrag';
|
||||||
|
|
||||||
const NodeUserSelect = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
const NodeUserSelect = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { nodeId, inputs, outputs } = data;
|
const { nodeId, inputs, outputs } = data;
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||||
|
const { zoom } = useViewport();
|
||||||
|
|
||||||
const CustomComponent = useMemo(
|
const CustomComponent = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
[NodeInputKeyEnum.userSelectOptions]: ({
|
[NodeInputKeyEnum.userSelectOptions]: (v: FlowNodeInputItemType) => {
|
||||||
key: optionKey,
|
const { key: optionKey, value, ...props } = v;
|
||||||
value = [],
|
|
||||||
...props
|
|
||||||
}: FlowNodeInputItemType) => {
|
|
||||||
const options = value as UserSelectOptionItemType[];
|
const options = value as UserSelectOptionItemType[];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
{options.map((item, i) => (
|
<DndDrag<UserSelectOptionItemType>
|
||||||
<Box key={item.key} mb={4}>
|
onDragEndCb={(list) => {
|
||||||
<HStack spacing={1}>
|
onChangeNode({
|
||||||
<MyTooltip label={t('common:common.Delete')}>
|
nodeId,
|
||||||
<MyIcon
|
type: 'updateInput',
|
||||||
mt={0.5}
|
key: optionKey,
|
||||||
name={'minus'}
|
value: {
|
||||||
w={'0.8rem'}
|
...props,
|
||||||
cursor={'pointer'}
|
key: optionKey,
|
||||||
color={'myGray.600'}
|
value: list
|
||||||
_hover={{ color: 'red.600' }}
|
}
|
||||||
onClick={() => {
|
});
|
||||||
onChangeNode({
|
}}
|
||||||
nodeId,
|
dataList={options}
|
||||||
type: 'updateInput',
|
renderClone={(provided, snapshot, rubric) => (
|
||||||
key: optionKey,
|
<OptionItem
|
||||||
value: {
|
provided={provided}
|
||||||
...props,
|
snapshot={snapshot}
|
||||||
key: optionKey,
|
item={options[rubric.source.index]}
|
||||||
value: options.filter((input) => input.key !== item.key)
|
nodeId={nodeId}
|
||||||
}
|
itemValue={v}
|
||||||
});
|
index={rubric.source.index}
|
||||||
}}
|
/>
|
||||||
/>
|
)}
|
||||||
</MyTooltip>
|
zoom={zoom}
|
||||||
<Box color={'myGray.600'} fontWeight={'medium'} fontSize={'sm'}>
|
>
|
||||||
{t('common:option') + (i + 1)}
|
{(provided) => (
|
||||||
</Box>
|
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
||||||
</HStack>
|
{options.map((item, i) => (
|
||||||
<Box position={'relative'} mt={1}>
|
<Draggable key={item.key} index={i} draggableId={item.key}>
|
||||||
<Input
|
{(provided, snapshot) => (
|
||||||
defaultValue={item.value}
|
<OptionItem
|
||||||
bg={'white'}
|
provided={provided}
|
||||||
fontSize={'sm'}
|
snapshot={snapshot}
|
||||||
onChange={(e) => {
|
item={item}
|
||||||
const newVal = options.map((val) =>
|
nodeId={nodeId}
|
||||||
val.key === item.key
|
itemValue={v}
|
||||||
? {
|
index={i}
|
||||||
...val,
|
key={item.key}
|
||||||
value: e.target.value
|
/>
|
||||||
}
|
)}
|
||||||
: val
|
</Draggable>
|
||||||
);
|
))}
|
||||||
onChangeNode({
|
|
||||||
nodeId,
|
|
||||||
type: 'updateInput',
|
|
||||||
key: optionKey,
|
|
||||||
value: {
|
|
||||||
...props,
|
|
||||||
key: optionKey,
|
|
||||||
value: newVal
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SourceHandle
|
|
||||||
nodeId={nodeId}
|
|
||||||
handleId={getHandleId(nodeId, 'source', item.key)}
|
|
||||||
position={Position.Right}
|
|
||||||
translate={[34, 0]}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
))}
|
</DndDrag>
|
||||||
<Button
|
<Button
|
||||||
fontSize={'sm'}
|
fontSize={'sm'}
|
||||||
leftIcon={<MyIcon name={'common/addLight'} w={4} />}
|
leftIcon={<MyIcon name={'common/addLight'} w={4} />}
|
||||||
@@ -120,7 +106,7 @@ const NodeUserSelect = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[nodeId, onChangeNode, t]
|
[nodeId, onChangeNode, t, zoom]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -136,3 +122,100 @@ const NodeUserSelect = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default React.memo(NodeUserSelect);
|
export default React.memo(NodeUserSelect);
|
||||||
|
|
||||||
|
const OptionItem = ({
|
||||||
|
provided,
|
||||||
|
snapshot,
|
||||||
|
item,
|
||||||
|
nodeId,
|
||||||
|
itemValue,
|
||||||
|
index
|
||||||
|
}: {
|
||||||
|
provided: DraggableProvided;
|
||||||
|
snapshot: DraggableStateSnapshot;
|
||||||
|
item: UserSelectOptionItemType;
|
||||||
|
nodeId: string;
|
||||||
|
itemValue: FlowNodeInputItemType;
|
||||||
|
index: number;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||||
|
const { key: optionKey, value, ...props } = itemValue;
|
||||||
|
const options = value as UserSelectOptionItemType[];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
mb={4}
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
style={{
|
||||||
|
...provided.draggableProps.style,
|
||||||
|
opacity: snapshot.isDragging ? 0.8 : 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HStack spacing={1}>
|
||||||
|
<MyTooltip label={t('common:common.Delete')}>
|
||||||
|
<MyIcon
|
||||||
|
mt={0.5}
|
||||||
|
name={'minus'}
|
||||||
|
w={'0.8rem'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
color={'myGray.600'}
|
||||||
|
_hover={{ color: 'red.600' }}
|
||||||
|
onClick={() => {
|
||||||
|
onChangeNode({
|
||||||
|
nodeId,
|
||||||
|
type: 'updateInput',
|
||||||
|
key: optionKey,
|
||||||
|
value: {
|
||||||
|
...props,
|
||||||
|
key: optionKey,
|
||||||
|
value: options.filter((input) => input.key !== item.key)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
<Box color={'myGray.600'} fontWeight={'medium'} fontSize={'sm'}>
|
||||||
|
{t('common:option') + (index + 1)}
|
||||||
|
</Box>
|
||||||
|
</HStack>
|
||||||
|
<Box position={'relative'} mt={1}>
|
||||||
|
<Input
|
||||||
|
defaultValue={item.value}
|
||||||
|
bg={'white'}
|
||||||
|
fontSize={'sm'}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newVal = options.map((val) =>
|
||||||
|
val.key === item.key
|
||||||
|
? {
|
||||||
|
...val,
|
||||||
|
value: e.target.value
|
||||||
|
}
|
||||||
|
: val
|
||||||
|
);
|
||||||
|
onChangeNode({
|
||||||
|
nodeId,
|
||||||
|
type: 'updateInput',
|
||||||
|
key: optionKey,
|
||||||
|
value: {
|
||||||
|
...props,
|
||||||
|
key: optionKey,
|
||||||
|
value: newVal
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!snapshot.isDragging && (
|
||||||
|
<SourceHandle
|
||||||
|
nodeId={nodeId}
|
||||||
|
handleId={getHandleId(nodeId, 'source', item.key)}
|
||||||
|
position={Position.Right}
|
||||||
|
translate={[34, 0]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user