mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-02 20:58:12 +00:00
extract modules
This commit is contained in:
@@ -25,6 +25,7 @@ import { pushTaskBill } from '@/service/events/pushBill';
|
||||
import { BillSourceEnum } from '@/constants/user';
|
||||
import { ChatHistoryItemResType } from '@/types/chat';
|
||||
import { UserModelSchema } from '@/types/mongoSchema';
|
||||
import { dispatchContentExtract } from '@/service/moduleDispatch/agent/extract';
|
||||
|
||||
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
|
||||
type FastGptWebChatProps = {
|
||||
@@ -337,7 +338,8 @@ export async function dispatchModules({
|
||||
[FlowModuleTypeEnum.answerNode]: dispatchAnswer,
|
||||
[FlowModuleTypeEnum.chatNode]: dispatchChatCompletion,
|
||||
[FlowModuleTypeEnum.kbSearchNode]: dispatchKBSearch,
|
||||
[FlowModuleTypeEnum.classifyQuestion]: dispatchClassifyQuestion
|
||||
[FlowModuleTypeEnum.classifyQuestion]: dispatchClassifyQuestion,
|
||||
[FlowModuleTypeEnum.contentExtract]: dispatchContentExtract
|
||||
};
|
||||
if (callbackMap[module.flowType]) {
|
||||
return callbackMap[module.flowType](props);
|
||||
|
@@ -10,7 +10,7 @@ import type { ClassifyQuestionAgentItemType } from '@/types/app';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4);
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { FlowOutputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
|
||||
import { FlowOutputItemTypeEnum, FlowValueTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
|
||||
const NodeCQNode = ({
|
||||
@@ -25,7 +25,7 @@ const NodeCQNode = ({
|
||||
onChangeNode={onChangeNode}
|
||||
flowInputList={inputs}
|
||||
CustomComponent={{
|
||||
agents: ({
|
||||
[SpecialInputKeyEnum.agents]: ({
|
||||
key: agentKey,
|
||||
value: agents = []
|
||||
}: {
|
||||
|
@@ -0,0 +1,146 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Button, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { FlowModuleItemType } from '@/types/flow';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import Container from '../modules/Container';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import Divider from '../modules/Divider';
|
||||
import { ContextExtractAgentItemType } from '@/types/app';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import ExtractFieldModal from '../modules/ExtractFieldModal';
|
||||
import { ContextExtractEnum } from '@/constants/flow/flowField';
|
||||
|
||||
const NodeExtract = ({
|
||||
data: { inputs, outputs, moduleId, onChangeNode, ...props }
|
||||
}: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const [editExtractFiled, setEditExtractField] = useState<ContextExtractAgentItemType>();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'380px'} moduleId={moduleId} {...props}>
|
||||
<Divider text="Input" />
|
||||
<Container>
|
||||
<RenderInput
|
||||
moduleId={moduleId}
|
||||
onChangeNode={onChangeNode}
|
||||
flowInputList={inputs}
|
||||
CustomComponent={{
|
||||
[ContextExtractEnum.extractKeys]: ({
|
||||
key,
|
||||
value: extractKeys = []
|
||||
}: {
|
||||
key: string;
|
||||
value?: ContextExtractAgentItemType[];
|
||||
}) => (
|
||||
<Box>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>字段 key</Th>
|
||||
<Th>字段描述</Th>
|
||||
<Th>必填</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{extractKeys.map((item, index) => (
|
||||
<Tr key={index}>
|
||||
<Td>{item.key}</Td>
|
||||
<Td whiteSpace={'pre-line'} wordBreak={'break-all'}>
|
||||
{item.desc}{' '}
|
||||
</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td whiteSpace={'nowrap'}>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
setEditExtractField(item);
|
||||
}}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: ContextExtractEnum.extractKeys,
|
||||
value: extractKeys.filter((extract) => item.key !== extract.key)
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Box mt={2} textAlign={'right'}>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<AddIcon fontSize={'10px'} />}
|
||||
onClick={() =>
|
||||
setEditExtractField({
|
||||
desc: '',
|
||||
key: '',
|
||||
required: true
|
||||
})
|
||||
}
|
||||
>
|
||||
新增字段
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput flowOutputList={outputs} />
|
||||
</Container>
|
||||
|
||||
{!!editExtractFiled && (
|
||||
<ExtractFieldModal
|
||||
defaultField={editExtractFiled}
|
||||
onClose={() => setEditExtractField(undefined)}
|
||||
onSubmit={(data) => {
|
||||
const extracts: ContextExtractAgentItemType[] =
|
||||
inputs.find((item) => item.key === ContextExtractEnum.extractKeys)?.value || [];
|
||||
|
||||
const exists = extracts.find((item) => item.key === editExtractFiled.key);
|
||||
|
||||
if (exists) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: ContextExtractEnum.extractKeys,
|
||||
value: extracts.map((item) => (item.key === editExtractFiled.key ? data : item))
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: ContextExtractEnum.extractKeys,
|
||||
value: extracts.concat(data)
|
||||
});
|
||||
}
|
||||
|
||||
setEditExtractField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodeExtract;
|
@@ -1,8 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Box, useTheme } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const Divider = ({ text }: { text: 'Body' | 'Input' | 'Output' | string }) => {
|
||||
const Divider = ({ text }: { text: 'Input' | 'Output' | string }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box
|
||||
textAlign={'center'}
|
||||
@@ -12,7 +14,7 @@ const Divider = ({ text }: { text: 'Body' | 'Input' | 'Output' | string }) => {
|
||||
borderBottom={theme.borders.base}
|
||||
fontSize={'lg'}
|
||||
>
|
||||
{text}
|
||||
{t(`common.${text}`)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@@ -0,0 +1,74 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
FormControl
|
||||
} from '@chakra-ui/react';
|
||||
import type { ContextExtractAgentItemType } 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';
|
||||
|
||||
const ExtractFieldModal = ({
|
||||
defaultField = {
|
||||
desc: '',
|
||||
key: '',
|
||||
required: true
|
||||
},
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
defaultField?: ContextExtractAgentItemType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: ContextExtractAgentItemType) => void;
|
||||
}) => {
|
||||
const { register, handleSubmit } = useForm<ContextExtractAgentItemType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose}>
|
||||
<ModalHeader display={'flex'} alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/extract.png'} mr={2} w={'20px'} objectFit={'cover'} />
|
||||
提取字段配置
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box w={'70px'}>必填</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box w={'80px'}>字段描述</Box>
|
||||
<Input
|
||||
placeholder="姓名/年龄/sql语句……"
|
||||
{...register('desc', { required: '字段描述不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box w={'80px'}>字段 key</Box>
|
||||
<Input
|
||||
placeholder="name/age/sql"
|
||||
{...register('key', { required: '字段 key 不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>确认</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ExtractFieldModal);
|
@@ -29,7 +29,14 @@ const NodeCard = ({
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box minW={minW} bg={'white'} border={theme.borders.md} borderRadius={'md'} boxShadow={'sm'}>
|
||||
<Box
|
||||
minW={minW}
|
||||
maxW={'430px'}
|
||||
bg={'white'}
|
||||
border={theme.borders.md}
|
||||
borderRadius={'md'}
|
||||
boxShadow={'sm'}
|
||||
>
|
||||
<Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}>
|
||||
<Avatar src={logo} borderRadius={'md'} objectFit={'contain'} w={'30px'} h={'30px'} />
|
||||
<Box ml={3} fontSize={'lg'} color={'myGray.600'}>
|
||||
@@ -39,7 +46,7 @@ const NodeCard = ({
|
||||
<MyTooltip label={description} forceShow>
|
||||
<QuestionOutlineIcon
|
||||
display={['none', 'inline']}
|
||||
transform={'translateY(-1px)'}
|
||||
transform={'translateY(1px)'}
|
||||
ml={1}
|
||||
/>
|
||||
</MyTooltip>
|
||||
|
@@ -68,6 +68,9 @@ const NodeVariable = dynamic(() => import('./components/Nodes/NodeVariable'), {
|
||||
const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'), {
|
||||
ssr: false
|
||||
});
|
||||
const NodeExtract = dynamic(() => import('./components/Nodes/NodeExtract'), {
|
||||
ssr: false
|
||||
});
|
||||
|
||||
import 'reactflow/dist/style.css';
|
||||
import styles from './index.module.scss';
|
||||
@@ -83,7 +86,8 @@ const nodeTypes = {
|
||||
[FlowModuleTypeEnum.kbSearchNode]: NodeKbSearch,
|
||||
[FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch,
|
||||
[FlowModuleTypeEnum.answerNode]: NodeAnswer,
|
||||
[FlowModuleTypeEnum.classifyQuestion]: NodeCQNode
|
||||
[FlowModuleTypeEnum.classifyQuestion]: NodeCQNode,
|
||||
[FlowModuleTypeEnum.contentExtract]: NodeExtract
|
||||
// [FlowModuleTypeEnum.empty]: EmptyModule
|
||||
};
|
||||
const edgeTypes = {
|
||||
|
Reference in New Issue
Block a user