feat: http modules

This commit is contained in:
archer
2023-08-03 11:09:57 +08:00
parent b7934ecc27
commit 952da2a06e
31 changed files with 851 additions and 176 deletions

View File

@@ -3,9 +3,9 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase, App } from '@/service/mongo';
import { AppModuleInputItemType } from '@/types/app';
import { FlowModuleTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { FlowInputItemType } from '@/types/flow';
const chatModelInput = ({
model,
@@ -21,7 +21,7 @@ const chatModelInput = ({
systemPrompt: string;
limitPrompt: string;
kbList: { kbId: string }[];
}): AppModuleInputItemType[] => [
}): FlowInputItemType[] => [
{
key: 'model',
value: model,

View File

@@ -27,7 +27,8 @@ const NodeCQNode = ({
CustomComponent={{
[SpecialInputKeyEnum.agents]: ({
key: agentKey,
value: agents = []
value: agents = [],
...props
}: {
key: string;
value?: ClassifyQuestionAgentItemType[];
@@ -50,7 +51,11 @@ const NodeCQNode = ({
moduleId,
type: 'inputs',
key: agentKey,
value: newInputValue
value: {
...props,
key: agentKey,
value: newInputValue
}
});
onChangeNode({
moduleId,
@@ -77,8 +82,13 @@ const NodeCQNode = ({
);
onChangeNode({
moduleId,
type: 'inputs',
key: agentKey,
value: newVal
value: {
...props,
key: agentKey,
value: newVal
}
});
}}
/>
@@ -97,11 +107,16 @@ const NodeCQNode = ({
type: FlowOutputItemTypeEnum.hidden,
targets: []
});
onChangeNode({
moduleId,
type: 'inputs',
key: agentKey,
value: newInputValue
value: {
...props,
key: agentKey,
value: newInputValue
}
});
onChangeNode({
moduleId,

View File

@@ -48,33 +48,32 @@ const NodeChat = ({
onchange={(e) => {
onChangeNode({
moduleId,
type: 'inputs',
key: inputItem.key,
value: e
value: {
...inputItem,
value: e
}
});
// update max tokens
const model = chatModelList.find((item) => item.model === e);
const model =
chatModelList.find((item) => item.model === e) || chatModelList[0];
if (!model) return;
onChangeNode({
moduleId,
type: 'inputs',
key: 'maxToken',
valueKey: 'markList',
value: [
{ label: '100', value: 100 },
{ label: `${model.contextMaxToken}`, value: model.contextMaxToken }
]
});
onChangeNode({
moduleId,
key: 'maxToken',
valueKey: 'max',
value: model.contextMaxToken
});
onChangeNode({
moduleId,
key: 'maxToken',
valueKey: 'value',
value: model.contextMaxToken / 2
value: {
...inputs.find((input) => input.key === 'maxToken'),
markList: [
{ label: '100', value: 100 },
{ label: `${model.contextMaxToken}`, value: model.contextMaxToken }
],
max: model.contextMaxToken,
value: model.contextMaxToken / 2
}
});
}}
/>
@@ -100,8 +99,12 @@ const NodeChat = ({
onChange={(e) => {
onChangeNode({
moduleId,
type: 'inputs',
key: inputItem.key,
value: e
value: {
...inputItem,
value: e
}
});
}}
/>
@@ -115,7 +118,11 @@ const NodeChat = ({
<>
<Divider text="Output" />
<Container>
<RenderOutput flowOutputList={outputs} />
<RenderOutput
onChangeNode={onChangeNode}
moduleId={moduleId}
flowOutputList={outputs}
/>
</Container>
</>
)}

View File

@@ -31,10 +31,9 @@ const NodeExtract = ({
flowInputList={inputs}
CustomComponent={{
[ContextExtractEnum.extractKeys]: ({
key,
value: extractKeys = []
value: extractKeys = [],
...props
}: {
key: string;
value?: ContextExtractAgentItemType[];
}) => (
<Box pt={2}>
@@ -97,7 +96,10 @@ const NodeExtract = ({
moduleId,
type: 'inputs',
key: ContextExtractEnum.extractKeys,
value: newInputValue
value: {
...props,
value: newInputValue
}
});
onChangeNode({
moduleId,
@@ -121,7 +123,7 @@ const NodeExtract = ({
</Container>
<Divider text="Output" />
<Container>
<RenderOutput flowOutputList={outputs} />
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
</Container>
{!!editExtractFiled && (
@@ -160,7 +162,10 @@ const NodeExtract = ({
moduleId,
type: 'inputs',
key: ContextExtractEnum.extractKeys,
value: newInputs
value: {
...inputs.find((input) => input.key === ContextExtractEnum.extractKeys),
value: newInputs
}
});
onChangeNode({
moduleId,

View File

@@ -18,7 +18,11 @@ const NodeHistory = ({
</Container>
<Divider text="Output" />
<Container>
<RenderOutput flowOutputList={outputs} />
<RenderOutput
onChangeNode={onChangeNode}
moduleId={props.moduleId}
flowOutputList={outputs}
/>
</Container>
</NodeCard>
);

View File

@@ -0,0 +1,80 @@
import React from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import Divider from '../modules/Divider';
import Container from '../modules/Container';
import RenderInput from '../render/RenderInput';
import { Box, Button } from '@chakra-ui/react';
import { SmallAddIcon } from '@chakra-ui/icons';
import RenderOutput from '../render/RenderOutput';
import { FlowInputItemTypeEnum, FlowOutputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
const NodeHttp = ({
data: { moduleId, inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => {
return (
<NodeCard minW={'350px'} moduleId={moduleId} {...props}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<RenderInput moduleId={moduleId} onChangeNode={onChangeNode} flowInputList={inputs} />
<Button
variant={'base'}
mt={5}
leftIcon={<SmallAddIcon />}
onClick={() => {
const key = nanoid();
onChangeNode({
moduleId,
type: 'addInput',
key,
value: {
key,
value: '',
valueType: FlowValueTypeEnum.string,
type: FlowInputItemTypeEnum.target,
label: 'New Param',
edit: true
}
});
}}
>
</Button>
</Container>
<Divider text="Output" />
<Container>
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
<Box textAlign={'right'} mt={5}>
<Button
variant={'base'}
leftIcon={<SmallAddIcon />}
onClick={() => {
const key = nanoid();
onChangeNode({
moduleId,
type: 'outputs',
key,
value: outputs.concat([
{
key,
label: '出参1',
valueType: FlowValueTypeEnum.string,
type: FlowOutputItemTypeEnum.source,
edit: true,
targets: []
}
])
});
}}
>
</Button>
</Box>
</Container>
</NodeCard>
);
};
export default React.memo(NodeHttp);

View File

@@ -81,14 +81,19 @@ const NodeKbSearch = ({
onChangeNode={onChangeNode}
flowInputList={inputs}
CustomComponent={{
kbList: ({ key, value }) => (
kbList: ({ key, value, ...props }) => (
<KBSelect
activeKbs={value}
onChange={(e) => {
onChangeNode({
moduleId,
key,
value: e
type: 'inputs',
value: {
...props,
key,
value: e
}
});
}}
/>
@@ -98,7 +103,7 @@ const NodeKbSearch = ({
</Container>
<Divider text="Output" />
<Container>
<RenderOutput flowOutputList={outputs} />
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
</Container>
</NodeCard>
);

View File

@@ -9,7 +9,7 @@ import { FlowValueTypeEnum } from '@/constants/flow';
import SourceHandle from '../render/SourceHandle';
const QuestionInputNode = ({
data: { inputs, outputs, onChangeNode, ...props }
data: { inputs, outputs, ...props }
}: NodeProps<FlowModuleItemType>) => {
return (
<NodeCard minW={'240px'} {...props}>

View File

@@ -14,7 +14,7 @@ const NodeUserGuide = ({
data: { inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => {
const welcomeText = useMemo(
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText)?.value,
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText),
[inputs]
);
@@ -30,22 +30,27 @@ const NodeUserGuide = ({
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
<Textarea
className="nodrag"
rows={6}
resize={'both'}
defaultValue={welcomeText}
bg={'myWhite.500'}
placeholder={welcomeTextTip}
onChange={(e) => {
onChangeNode({
moduleId: props.moduleId,
key: SystemInputEnum.welcomeText,
type: 'inputs',
value: e.target.value
});
}}
/>
{welcomeText && (
<Textarea
className="nodrag"
rows={6}
resize={'both'}
defaultValue={welcomeText.value}
bg={'myWhite.500'}
placeholder={welcomeTextTip}
onChange={(e) => {
onChangeNode({
moduleId: props.moduleId,
key: SystemInputEnum.welcomeText,
type: 'inputs',
value: {
...welcomeText,
value: e.target.value
}
});
}}
/>
)}
</>
</Container>
</NodeCard>

View File

@@ -40,10 +40,13 @@ const NodeUserGuide = ({
moduleId: props.moduleId,
key: SystemInputEnum.variables,
type: 'inputs',
value
value: {
...inputs.find((item) => item.key === SystemInputEnum.variables),
value
}
});
},
[onChangeNode, props.moduleId]
[inputs, onChangeNode, props.moduleId]
);
const onclickSubmit = useCallback(

View File

@@ -0,0 +1,116 @@
import React, { useMemo, useState } from 'react';
import {
Box,
Button,
ModalHeader,
ModalFooter,
ModalBody,
Flex,
Switch,
Input,
FormControl
} from '@chakra-ui/react';
import type { ContextExtractAgentItemType, HttpFieldItemType } from '@/types/app';
import { useForm } from 'react-hook-form';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import MyModal from '@/components/MyModal';
import Avatar from '@/components/Avatar';
import MyTooltip from '@/components/MyTooltip';
import { FlowInputItemTypeEnum, FlowValueTypeEnum, FlowValueTypeStyle } from '@/constants/flow';
import { useTranslation } from 'react-i18next';
import MySelect from '@/components/Select';
import { FlowInputItemType } from '@/types/flow';
const typeSelectList = [
{
label: '字符串',
value: FlowValueTypeEnum.string
},
{
label: '数字',
value: FlowValueTypeEnum.number
},
{
label: '布尔',
value: FlowValueTypeEnum.boolean
},
{
label: '任意',
value: FlowValueTypeEnum.any
}
];
const SetInputFieldModal = ({
defaultField = {
label: '',
key: '',
type: FlowInputItemTypeEnum.target,
valueType: FlowValueTypeEnum.string,
description: '',
required: false
},
onClose,
onSubmit
}: {
defaultField?: FlowInputItemType;
onClose: () => void;
onSubmit: (data: FlowInputItemType) => void;
}) => {
const { t } = useTranslation();
const { register, getValues, setValue, handleSubmit } = useForm<FlowInputItemType>({
defaultValues: defaultField
});
const [refresh, setRefresh] = useState(false);
return (
<MyModal isOpen={true} onClose={onClose}>
<ModalHeader display={'flex'} alignItems={'center'}>
<Avatar src={'/imgs/module/extract.png'} mr={2} w={'20px'} objectFit={'cover'} />
{t('app.Input Field Settings')}
</ModalHeader>
<ModalBody>
<Flex alignItems={'center'}>
<Box flex={'0 0 70px'}></Box>
<Switch {...register('required')} />
</Flex>
<Flex mt={5} alignItems={'center'}>
<Box flex={'0 0 70px'}></Box>
<MySelect
w={'288px'}
list={typeSelectList}
value={getValues('valueType')}
onchange={(e: any) => {
setValue('valueType', e);
setRefresh(!refresh);
}}
/>
</Flex>
<Flex mt={5} alignItems={'center'}>
<Box flex={'0 0 70px'}></Box>
<Input
placeholder="预约字段/sql语句……"
{...register('label', { required: '字段名不能为空' })}
/>
</Flex>
<Flex mt={5} alignItems={'center'}>
<Box flex={'0 0 70px'}> key</Box>
<Input
placeholder="appointment/sql"
{...register('key', { required: '字段 key 不能为空' })}
/>
</Flex>
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={3} onClick={onClose}>
</Button>
<Button onClick={handleSubmit(onSubmit)}></Button>
</ModalFooter>
</MyModal>
);
};
export default React.memo(SetInputFieldModal);

View File

@@ -0,0 +1,105 @@
import React, { useMemo, useState } from 'react';
import {
Box,
Button,
ModalHeader,
ModalFooter,
ModalBody,
Flex,
Switch,
Input,
FormControl
} from '@chakra-ui/react';
import type { ContextExtractAgentItemType, HttpFieldItemType } from '@/types/app';
import { useForm } from 'react-hook-form';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import MyModal from '@/components/MyModal';
import Avatar from '@/components/Avatar';
import MyTooltip from '@/components/MyTooltip';
import { FlowOutputItemTypeEnum, FlowValueTypeEnum, FlowValueTypeStyle } from '@/constants/flow';
import { useTranslation } from 'react-i18next';
import MySelect from '@/components/Select';
import { FlowOutputItemType } from '@/types/flow';
const typeSelectList = [
{
label: '字符串',
value: FlowValueTypeEnum.string
},
{
label: '数字',
value: FlowValueTypeEnum.number
},
{
label: '布尔',
value: FlowValueTypeEnum.boolean
},
{
label: '任意',
value: FlowValueTypeEnum.any
}
];
const SetInputFieldModal = ({
defaultField,
onClose,
onSubmit
}: {
defaultField: FlowOutputItemType;
onClose: () => void;
onSubmit: (data: FlowOutputItemType) => void;
}) => {
const { t } = useTranslation();
const { register, getValues, setValue, handleSubmit } = useForm<FlowOutputItemType>({
defaultValues: defaultField
});
const [refresh, setRefresh] = useState(false);
return (
<MyModal isOpen={true} onClose={onClose}>
<ModalHeader display={'flex'} alignItems={'center'}>
<Avatar src={'/imgs/module/extract.png'} mr={2} w={'20px'} objectFit={'cover'} />
{t('app.Output Field Settings')}
</ModalHeader>
<ModalBody>
<Flex mt={5} alignItems={'center'}>
<Box flex={'0 0 70px'}></Box>
<MySelect
w={'288px'}
list={typeSelectList}
value={getValues('valueType')}
onchange={(e: any) => {
setValue('valueType', e);
setRefresh(!refresh);
}}
/>
</Flex>
<Flex mt={5} alignItems={'center'}>
<Box flex={'0 0 70px'}></Box>
<Input
placeholder="预约字段/sql语句……"
{...register('label', { required: '字段名不能为空' })}
/>
</Flex>
<Flex mt={5} alignItems={'center'}>
<Box flex={'0 0 70px'}> key</Box>
<Input
placeholder="appointment/sql"
{...register('key', { required: '字段 key 不能为空' })}
/>
</Flex>
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={3} onClick={onClose}>
</Button>
<Button onClick={handleSubmit(onSubmit)}></Button>
</ModalFooter>
</MyModal>
);
};
export default React.memo(SetInputFieldModal);

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import type { FlowInputItemType, FlowModuleItemType } from '@/types/flow';
import {
Box,
@@ -8,38 +8,129 @@ import {
NumberInputField,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper
NumberDecrementStepper,
Flex
} from '@chakra-ui/react';
import { FlowInputItemTypeEnum } from '@/constants/flow';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import dynamic from 'next/dynamic';
import MySelect from '@/components/Select';
import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip';
import TargetHandle from './TargetHandle';
import MyIcon from '@/components/Icon';
const SetInputFieldModal = dynamic(() => import('../modules/SetInputFieldModal'));
export const Label = ({
required = false,
children,
description
}: {
required?: boolean;
children: React.ReactNode | string;
description?: string;
}) => (
<Box as={'label'} display={'inline-block'} position={'relative'}>
{children}
{required && (
<Box position={'absolute'} top={'-2px'} right={'-10px'} color={'red.500'} fontWeight={'bold'}>
*
moduleId,
inputKey,
onChangeNode,
...item
}: FlowInputItemType & {
moduleId: string;
inputKey: string;
onChangeNode: FlowModuleItemType['onChangeNode'];
}) => {
const { required = false, description, edit, label, type, valueType } = item;
const [editField, setEditField] = useState<FlowInputItemType>();
return (
<Flex alignItems={'center'} position={'relative'}>
<Box position={'relative'}>
{label}
{description && (
<MyTooltip label={description} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
)}
{required && (
<Box
position={'absolute'}
top={'-2px'}
right={'-8px'}
color={'red.500'}
fontWeight={'bold'}
>
*
</Box>
)}
</Box>
)}
{description && (
<MyTooltip label={description} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
)}
</Box>
);
{(type === FlowInputItemTypeEnum.target || valueType) && (
<TargetHandle handleKey={inputKey} valueType={valueType} />
)}
{edit && (
<>
<MyIcon
name={'settingLight'}
w={'14px'}
cursor={'pointer'}
ml={3}
_hover={{ color: 'myBlue.600' }}
onClick={() =>
setEditField({
...item,
key: inputKey
})
}
/>
<MyIcon
className="delete"
name={'delete'}
w={'14px'}
cursor={'pointer'}
ml={2}
_hover={{ color: 'red.500' }}
onClick={() => {
{
console.log(moduleId, inputKey, valueType);
}
onChangeNode({
moduleId,
type: 'delInput',
key: inputKey,
value: ''
});
}}
/>
</>
)}
{!!editField && (
<SetInputFieldModal
defaultField={editField}
onClose={() => setEditField(undefined)}
onSubmit={(data) => {
// same key
if (editField.key === data.key) {
onChangeNode({
moduleId,
type: 'inputs',
key: inputKey,
value: data
});
} else {
// diff key. del and add
onChangeNode({
moduleId,
type: 'delInput',
key: editField.key,
value: ''
});
onChangeNode({
moduleId,
type: 'addInput',
key: editField.key,
value: data
});
}
setEditField(undefined);
}}
/>
)}
</Flex>
);
};
const RenderBody = ({
flowInputList,
@@ -59,13 +150,12 @@ const RenderBody = ({
item.type !== FlowInputItemTypeEnum.hidden && (
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
{!!item.label && (
<Label required={item.required} description={item.description}>
{item.label}
{(item.type === FlowInputItemTypeEnum.target || item.valueType) && (
<TargetHandle handleKey={item.key} valueType={item.valueType} />
)}
</Label>
<Label
moduleId={moduleId}
onChangeNode={onChangeNode}
inputKey={item.key}
{...item}
/>
)}
<Box mt={2} className={'nodrag'}>
{item.type === FlowInputItemTypeEnum.numberInput && (
@@ -76,8 +166,12 @@ const RenderBody = ({
onChange={(e) => {
onChangeNode({
moduleId,
type: 'inputs',
key: item.key,
value: Number(e)
value: {
...item,
value: Number(e)
}
});
}}
>
@@ -95,8 +189,12 @@ const RenderBody = ({
onChange={(e) => {
onChangeNode({
moduleId,
type: 'inputs',
key: item.key,
value: e.target.value
value: {
...item,
value: e.target.value
}
});
}}
/>
@@ -110,8 +208,12 @@ const RenderBody = ({
onChange={(e) => {
onChangeNode({
moduleId,
type: 'inputs',
key: item.key,
value: e.target.value
value: {
...item,
value: e.target.value
}
});
}}
/>
@@ -124,8 +226,12 @@ const RenderBody = ({
onchange={(e) => {
onChangeNode({
moduleId,
type: 'inputs',
key: item.key,
value: e
value: {
...item,
value: e
}
});
}}
/>
@@ -142,8 +248,12 @@ const RenderBody = ({
onChange={(e) => {
onChangeNode({
moduleId,
type: 'inputs',
key: item.key,
value: e
value: {
...item,
value: e
}
});
}}
/>

View File

@@ -1,37 +1,147 @@
import React from 'react';
import type { FlowOutputItemType } from '@/types/flow';
import React, { useCallback, useState } from 'react';
import type { FlowModuleItemType, FlowOutputItemType } from '@/types/flow';
import { Box, Flex } from '@chakra-ui/react';
import { FlowOutputItemTypeEnum } from '@/constants/flow';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { Handle, Position } from 'reactflow';
import MyTooltip from '@/components/MyTooltip';
import SourceHandle from './SourceHandle';
import MyIcon from '@/components/Icon';
import dynamic from 'next/dynamic';
const SetOutputFieldModal = dynamic(() => import('../modules/SetOutputFieldModal'));
const Label = ({
children,
description
}: {
children: React.ReactNode | string;
description?: string;
}) => (
<Flex as={'label'} justifyContent={'right'} alignItems={'center'} position={'relative'}>
{description && (
<MyTooltip label={description} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
</MyTooltip>
)}
{children}
</Flex>
);
moduleId,
outputKey,
delOutputByKey,
updateOutput,
onChangeNode,
addUpdateOutput,
...item
}: FlowOutputItemType & {
outputKey: string;
moduleId: string;
delOutputByKey: (key: string) => void;
updateOutput: (key: string, val: FlowOutputItemType) => void;
addUpdateOutput: (val: FlowOutputItemType) => void;
onChangeNode: FlowModuleItemType['onChangeNode'];
}) => {
const { label, description, edit } = item;
const [editField, setEditField] = useState<FlowOutputItemType>();
return (
<Flex as={'label'} justifyContent={'right'} alignItems={'center'} position={'relative'}>
{edit && (
<>
<MyIcon
name={'settingLight'}
w={'14px'}
cursor={'pointer'}
mr={3}
_hover={{ color: 'myBlue.600' }}
onClick={() =>
setEditField({
...item,
key: outputKey
})
}
/>
<MyIcon
className="delete"
name={'delete'}
w={'14px'}
cursor={'pointer'}
mr={3}
_hover={{ color: 'red.500' }}
onClick={() => delOutputByKey(outputKey)}
/>
</>
)}
{description && (
<MyTooltip label={description} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
</MyTooltip>
)}
<Box>{label}</Box>
{!!editField && (
<SetOutputFieldModal
defaultField={editField}
onClose={() => setEditField(undefined)}
onSubmit={(data) => {
console.log(data); // same key
if (editField.key === data.key) {
updateOutput(data.key, data);
} else {
delOutputByKey(editField.key);
addUpdateOutput(data);
}
setEditField(undefined);
}}
/>
)}
</Flex>
);
};
const RenderOutput = ({
moduleId,
flowOutputList,
onChangeNode
}: {
moduleId: string;
flowOutputList: FlowOutputItemType[];
onChangeNode: FlowModuleItemType['onChangeNode'];
}) => {
const delOutputByKey = useCallback(
(key: string) => {
onChangeNode({
moduleId,
type: 'outputs',
key: '',
value: flowOutputList.filter((output) => output.key !== key)
});
},
[moduleId, flowOutputList, onChangeNode]
);
const updateOutput = useCallback(
(key: string, val: FlowOutputItemType) => {
onChangeNode({
moduleId,
type: 'outputs',
key: '',
value: flowOutputList.map((output) => (output.key === key ? val : output))
});
},
[flowOutputList, moduleId, onChangeNode]
);
const addUpdateOutput = useCallback(
(val: FlowOutputItemType) => {
onChangeNode({
moduleId,
type: 'outputs',
key: '',
value: flowOutputList.concat(val)
});
},
[flowOutputList, moduleId, onChangeNode]
);
const RenderOutput = ({ flowOutputList }: { flowOutputList: FlowOutputItemType[] }) => {
return (
<>
{flowOutputList.map(
(item) =>
item.type !== FlowOutputItemTypeEnum.hidden && (
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
<Label description={item.description}>{item.label}</Label>
<Label
moduleId={moduleId}
onChangeNode={onChangeNode}
outputKey={item.key}
delOutputByKey={delOutputByKey}
addUpdateOutput={addUpdateOutput}
updateOutput={updateOutput}
{...item}
/>
<Box mt={FlowOutputItemTypeEnum.answer ? 0 : 2} className={'nodrag'}>
{item.type === FlowOutputItemTypeEnum.source && (
<SourceHandle handleKey={item.key} valueType={item.valueType} />

View File

@@ -16,7 +16,7 @@ const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
valueType
? FlowValueTypeStyle[valueType]
: (FlowValueTypeStyle[FlowValueTypeEnum.any] as any),
[]
[valueType]
);
return (

View File

@@ -71,6 +71,9 @@ const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'),
const NodeExtract = dynamic(() => import('./components/Nodes/NodeExtract'), {
ssr: false
});
const NodeHttp = dynamic(() => import('./components/Nodes/NodeHttp'), {
ssr: false
});
import 'reactflow/dist/style.css';
import styles from './index.module.scss';
@@ -87,7 +90,8 @@ const nodeTypes = {
[FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch,
[FlowModuleTypeEnum.answerNode]: NodeAnswer,
[FlowModuleTypeEnum.classifyQuestion]: NodeCQNode,
[FlowModuleTypeEnum.contentExtract]: NodeExtract
[FlowModuleTypeEnum.contentExtract]: NodeExtract,
[FlowModuleTypeEnum.httpRequest]: NodeHttp
// [FlowModuleTypeEnum.empty]: EmptyModule
};
const edgeTypes = {
@@ -124,11 +128,10 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
const flow2AppModules = useCallback(() => {
const modules: AppModuleItemType[] = nodes.map((item) => ({
moduleId: item.data.moduleId,
position: item.position,
flowType: item.data.flowType,
position: item.position,
inputs: item.data.inputs.map((item) => ({
key: item.key,
value: item.value,
...item,
connected: item.type !== FlowInputItemTypeEnum.target
})),
outputs: item.data.outputs.map((item) => ({
@@ -163,7 +166,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
return modules;
}, [edges, nodes]);
const onChangeNode = useCallback(
({ moduleId, key, type = 'inputs', value, valueKey = 'value' }: FlowModuleItemChangeProps) => {
({ moduleId, key, type = 'inputs', value }: FlowModuleItemChangeProps) => {
setNodes((nodes) =>
nodes.map((node) => {
if (node.id !== moduleId) return node;
@@ -172,18 +175,43 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
...node,
data: {
...node.data,
inputs: node.data.inputs.map((item) => {
if (item.key === key) {
return {
...item,
[valueKey]: value
};
}
return item;
})
inputs: node.data.inputs.map((item) => (item.key === key ? value : item))
}
};
}
if (type === 'addInput') {
const input = node.data.inputs.find((input) => input.key === value.key);
if (input) {
toast({
status: 'warning',
title: 'key 重复'
});
return {
...node,
data: {
...node.data,
inputs: node.data.inputs
}
};
}
return {
...node,
data: {
...node.data,
inputs: node.data.inputs.concat(value)
}
};
}
if (type === 'delInput') {
return {
...node,
data: {
...node.data,
inputs: node.data.inputs.filter((item) => item.key !== key)
}
};
}
console.log(value);
return {
...node,

View File

@@ -59,7 +59,7 @@ const FileSelect = ({
)}
{isCsv && (
<Box
my={3}
mt={1}
cursor={'pointer'}
textDecoration={'underline'}
color={'myBlue.600'}