perf: v4.4.6-1 (#364)

This commit is contained in:
Archer
2023-09-28 17:30:05 +08:00
committed by GitHub
parent ab57bfcc4a
commit 36f5648cae
52 changed files with 753 additions and 594 deletions

View File

@@ -105,6 +105,7 @@
},
"common": {
"Add": "Add",
"Close": "Clow",
"Collect": "Collect",
"Copy": "Copy",
"Copy Successful": "Copy Successful",

View File

@@ -105,6 +105,7 @@
},
"common": {
"Add": "添加",
"Close": "关闭",
"Collect": "收藏",
"Copy": "复制",
"Copy Successful": "复制成功",

View File

@@ -1171,9 +1171,8 @@ export const useChatBox = () => {
const historyDom = document.getElementById('history');
if (!historyDom) return;
const dom = Array.from(historyDom.children).map((child, i) => {
const avatar = `<img src="${
child.querySelector<HTMLImageElement>('.avatar')?.src
}" alt="" />`;
const avatar = `<img src="${child.querySelector<HTMLImageElement>('.avatar')
?.src}" alt="" />`;
const chatContent = child.querySelector<HTMLDivElement>('.markdown');

View File

@@ -19,8 +19,8 @@ function MyLink(e: any) {
{text}
</Link>
) : (
<Box as={'ul'}>
<Box as={'li'}>
<Box as={'ul'} mt={'0 !important'}>
<Box as={'li'} mb={1}>
<Box
as={'span'}
color={'blue.600'}
@@ -47,6 +47,7 @@ const Guide = ({ text }: { text: string }) => {
rehypePlugins={[RehypeKatex]}
components={{
a: MyLink,
p: 'div',
img: Image
}}
>

View File

@@ -8,7 +8,7 @@ import {
FlowValueTypeEnum
} from './index';
import type { AppItemType } from '@/types/app';
import type { FlowModuleTemplateType } from '@/types/flow';
import type { FlowModuleTemplateType } from '@/types/core/app/flow';
import { chatModelList } from '@/store/static';
import {
Input_Template_History,
@@ -331,6 +331,7 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
'根据用户的历史记录和当前问题判断该次提问的类型。可以添加多组问题类型,下面是一个模板例子:\n类型1: 打招呼\n类型2: 关于 laf 通用问题\n类型3: 关于 laf 代码问题\n类型4: 其他问题',
showStatus: true,
inputs: [
Input_Template_TFSwitch,
{
key: 'systemPrompt',
type: FlowInputItemTypeEnum.textarea,

View File

@@ -1,4 +1,4 @@
import { FlowInputItemType } from '@/types/flow';
import type { FlowInputItemType } from '@/types/core/app/flow';
import { SystemInputEnum } from '../app';
import { FlowInputItemTypeEnum, FlowValueTypeEnum } from './index';

View File

@@ -94,9 +94,7 @@ function App({ Component, pageProps }: AppProps) {
/>
<link rel="icon" href="/favicon.ico" />
</Head>
{scripts?.map((item, i) => (
<Script key={i} strategy="lazyOnload" {...item}></Script>
))}
{scripts?.map((item, i) => <Script key={i} strategy="lazyOnload" {...item}></Script>)}
<QueryClientProvider client={queryClient}>
<ChakraProvider theme={theme}>

View File

@@ -38,8 +38,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
select id, q, a, source, file_id, (vector <#> '[${
vectors[0]
}]') * -1 AS score from ${PgDatasetTableName} where kb_id='${kbId}' AND user_id='${userId}' order by vector <#> '[${
vectors[0]
}]' limit 12;
vectors[0]
}]' limit 12;
COMMIT;`
);

View File

@@ -32,7 +32,7 @@ import { getSystemTime } from '@/utils/user';
import { authOutLinkChat } from '@/service/support/outLink/auth';
import requestIp from 'request-ip';
import { replaceVariable } from '@/utils/common/tools/text';
import { ModuleDispatchProps } from '@/types/core/modules';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import { selectShareResponse } from '@/utils/service/core/chat';
import { pushResult2Remote, updateOutLinkUsage } from '@/service/support/outLink';
import { updateApiKeyUsage } from '@/service/support/openapi';

View File

@@ -1,20 +1,15 @@
import React, { useState } from 'react';
import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import { AppModuleItemType } from '@/types/app';
import { useTranslation } from 'react-i18next';
import { useToast } from '@/hooks/useToast';
import { useFlowStore } from './Provider';
const ImportSettings = ({
onClose,
onSuccess
}: {
onClose: () => void;
onSuccess: (modules: AppModuleItemType[]) => void;
}) => {
const ImportSettings = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
const { toast } = useToast();
const [value, setValue] = useState('');
const { setNodes, setEdges, initData } = useFlowStore();
return (
<MyModal isOpen w={'600px'} onClose={onClose} title={t('app.Import Config')}>
@@ -35,7 +30,11 @@ const ImportSettings = ({
}
try {
const data = JSON.parse(value);
onSuccess(data);
setEdges([]);
setNodes([]);
setTimeout(() => {
initData(data);
}, 10);
onClose();
} catch (error) {
toast({
@@ -51,4 +50,4 @@ const ImportSettings = ({
);
};
export default ImportSettings;
export default React.memo(ImportSettings);

View File

@@ -1,18 +1,18 @@
import React from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import { FlowModuleItemType } from '@/types/core/app/flow';
import Container from '../modules/Container';
import RenderInput from '../render/RenderInput';
import RenderOutput from '../render/RenderOutput';
const NodeAnswer = ({ data }: NodeProps<FlowModuleItemType>) => {
const { moduleId, inputs, outputs, onChangeNode } = data;
const { moduleId, inputs, outputs } = data;
return (
<NodeCard minW={'400px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<RenderInput moduleId={moduleId} onChangeNode={onChangeNode} flowInputList={inputs} />
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
<RenderInput moduleId={moduleId} flowInputList={inputs} />
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
</Container>
</NodeCard>
);

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { NodeProps } from 'reactflow';
import { Box, Input, Button, Flex } from '@chakra-ui/react';
import { Box, Input, Button, Flex, Textarea } from '@chakra-ui/react';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import { FlowModuleItemType } from '@/types/core/app/flow';
import Divider from '../modules/Divider';
import Container from '../modules/Container';
import RenderInput from '../render/RenderInput';
@@ -11,17 +11,22 @@ import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4);
import MyIcon from '@/components/Icon';
import { FlowOutputItemTypeEnum, FlowValueTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
import { useTranslation } from 'react-i18next';
import SourceHandle from '../render/SourceHandle';
import MyTooltip from '@/components/MyTooltip';
import { useFlowStore } from '../Provider';
const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
const { moduleId, inputs, outputs, onChangeNode } = data;
const { t } = useTranslation();
const { moduleId, inputs, outputs } = data;
const { onChangeNode } = useFlowStore();
return (
<NodeCard minW={'400px'} {...data}>
<Divider text="Input" />
<Container>
<RenderInput
moduleId={moduleId}
onChangeNode={onChangeNode}
flowInputList={inputs}
CustomComponent={{
[SpecialInputKeyEnum.agents]: ({
@@ -34,51 +39,23 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
}) => (
<Box>
{agents.map((item, i) => (
<Flex key={item.key} mb={4} alignItems={'center'}>
<MyIcon
mr={2}
name={'minus'}
w={'14px'}
cursor={'pointer'}
color={'myGray.600'}
_hover={{ color: 'myGray.900' }}
onClick={() => {
const newInputValue = agents.filter((input) => input.key !== item.key);
const newOutputVal = outputs.filter((output) => output.key !== item.key);
onChangeNode({
moduleId,
type: 'inputs',
key: agentKey,
value: {
...props,
key: agentKey,
value: newInputValue
}
});
onChangeNode({
moduleId,
type: 'outputs',
key: '',
value: newOutputVal
});
}}
/>
<Box flex={1}>
<Box flex={1}>{i + 1}</Box>
<Box position={'relative'}>
<Input
<Box key={item.key} mb={4}>
<Flex alignItems={'center'}>
<MyTooltip label={t('common.Delete')}>
<MyIcon
mt={1}
defaultValue={item.value}
onChange={(e) => {
const newVal = agents.map((val) =>
val.key === item.key
? {
...val,
value: e.target.value
}
: val
mr={2}
name={'minus'}
w={'14px'}
cursor={'pointer'}
color={'myGray.600'}
_hover={{ color: 'red.600' }}
onClick={() => {
const newInputValue = agents.filter((input) => input.key !== item.key);
const newOutputVal = outputs.filter(
(output) => output.key !== item.key
);
onChangeNode({
moduleId,
type: 'inputs',
@@ -86,15 +63,49 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
value: {
...props,
key: agentKey,
value: newVal
value: newInputValue
}
});
onChangeNode({
moduleId,
type: 'outputs',
key: '',
value: newOutputVal
});
}}
/>
<SourceHandle handleKey={item.key} valueType={FlowValueTypeEnum.boolean} />
</Box>
</MyTooltip>
<Box flex={1}>{i + 1}</Box>
</Flex>
<Box position={'relative'}>
<Textarea
rows={2}
mt={1}
defaultValue={item.value}
onChange={(e) => {
const newVal = agents.map((val) =>
val.key === item.key
? {
...val,
value: e.target.value
}
: val
);
onChangeNode({
moduleId,
type: 'inputs',
key: agentKey,
value: {
...props,
key: agentKey,
value: newVal
}
});
}}
/>
<SourceHandle handleKey={item.key} valueType={FlowValueTypeEnum.boolean} />
</Box>
</Flex>
</Box>
))}
<Button
onClick={() => {

View File

@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import { FlowModuleItemType } from '@/types/core/app/flow';
import Divider from '../modules/Divider';
import Container from '../modules/Container';
import RenderInput from '../render/RenderInput';
@@ -9,16 +9,18 @@ import RenderOutput from '../render/RenderOutput';
import MySelect from '@/components/Select';
import { chatModelList } from '@/store/static';
import MySlider from '@/components/Slider';
import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react';
import { Box, Button, useDisclosure } from '@chakra-ui/react';
import { formatPrice } from '@fastgpt/common/bill/index';
import MyIcon from '@/components/Icon';
import dynamic from 'next/dynamic';
import { AIChatProps } from '@/types/core/aiChat';
import { useFlowStore } from '../Provider';
const AIChatSettingsModal = dynamic(() => import('../../../AIChatSettingsModal'));
const NodeChat = ({ data }: NodeProps<FlowModuleItemType>) => {
const { moduleId, inputs, outputs, onChangeNode } = data;
const { moduleId, inputs, outputs } = data;
const { onChangeNode } = useFlowStore();
const chatModulesData = useMemo(() => {
const obj: Record<string, any> = {};
@@ -40,7 +42,6 @@ const NodeChat = ({ data }: NodeProps<FlowModuleItemType>) => {
<Container>
<RenderInput
moduleId={moduleId}
onChangeNode={onChangeNode}
flowInputList={inputs}
CustomComponent={{
model: (inputItem) => {
@@ -140,7 +141,7 @@ const NodeChat = ({ data }: NodeProps<FlowModuleItemType>) => {
</Container>
<Divider text="Output" />
<Container>
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
</Container>
{isOpenAIChatSetting && (

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import { FlowModuleItemType } from '@/types/core/app/flow';
const NodeAnswer = ({ data }: NodeProps<FlowModuleItemType>) => {
return <NodeCard {...data}></NodeCard>;

View File

@@ -1,7 +1,7 @@
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 { FlowModuleItemType } from '@/types/core/app/flow';
import { useTranslation } from 'next-i18next';
import NodeCard from '../modules/NodeCard';
import Container from '../modules/Container';
@@ -14,11 +14,13 @@ import MyIcon from '@/components/Icon';
import ExtractFieldModal from '../modules/ExtractFieldModal';
import { ContextExtractEnum } from '@/constants/flow/flowField';
import { FlowOutputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
import { useFlowStore } from '../Provider';
const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
const { inputs, outputs, moduleId, onChangeNode, onDelEdge } = data;
const { inputs, outputs, moduleId } = data;
const { t } = useTranslation();
const [editExtractFiled, setEditExtractField] = useState<ContextExtractAgentItemType>();
const { onChangeNode, onDelEdge } = useFlowStore();
return (
<NodeCard minW={'400px'} {...data}>
@@ -26,7 +28,6 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
<Container>
<RenderInput
moduleId={moduleId}
onChangeNode={onChangeNode}
flowInputList={inputs}
CustomComponent={{
[ContextExtractEnum.extractKeys]: ({
@@ -125,7 +126,7 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
</Container>
<Divider text="Output" />
<Container>
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
</Container>
{!!editExtractFiled && (

View File

@@ -1,23 +1,23 @@
import React from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import { FlowModuleItemType } from '@/types/core/app/flow';
import Divider from '../modules/Divider';
import Container from '../modules/Container';
import RenderInput from '../render/RenderInput';
import RenderOutput from '../render/RenderOutput';
const NodeHistory = ({ data }: NodeProps<FlowModuleItemType>) => {
const { inputs, outputs, moduleId, onChangeNode } = data;
const { inputs, outputs, moduleId } = data;
return (
<NodeCard minW={'300px'} {...data}>
<Divider text="Input" />
<Container>
<RenderInput moduleId={moduleId} onChangeNode={onChangeNode} flowInputList={inputs} />
<RenderInput moduleId={moduleId} flowInputList={inputs} />
</Container>
<Divider text="Output" />
<Container>
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
</Container>
</NodeCard>
);

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import { FlowModuleItemType } from '@/types/core/app/flow';
import Divider from '../modules/Divider';
import Container from '../modules/Container';
import RenderInput from '../render/RenderInput';
@@ -12,13 +12,16 @@ import RenderOutput from '../render/RenderOutput';
import { FlowInputItemTypeEnum, FlowOutputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import { useFlowStore } from '../Provider';
const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
const { moduleId, inputs, outputs, onChangeNode } = data;
const { moduleId, inputs, outputs } = data;
const { onChangeNode } = useFlowStore();
return (
<NodeCard minW={'350px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<RenderInput moduleId={moduleId} onChangeNode={onChangeNode} flowInputList={inputs} />
<RenderInput moduleId={moduleId} flowInputList={inputs} />
<Button
variant={'base'}
mt={5}
@@ -44,7 +47,7 @@ const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
</Container>
<Divider text="Output" />
<Container>
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
<Box textAlign={'right'} mt={5}>
<Button
variant={'base'}

View File

@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { NodeProps } from 'reactflow';
import { FlowModuleItemType } from '@/types/flow';
import { FlowModuleItemType } from '@/types/core/app/flow';
import { Flex, Box, Button, useTheme, useDisclosure, Grid } from '@chakra-ui/react';
import { useDatasetStore } from '@/store/dataset';
import { useQuery } from '@tanstack/react-query';
@@ -12,6 +12,7 @@ import RenderOutput from '../render/RenderOutput';
import { DatasetSelectModal } from '../../../DatasetSelectModal';
import type { SelectedDatasetType } from '@/types/core/dataset';
import Avatar from '@/components/Avatar';
import { useFlowStore } from '../Provider';
const KBSelect = ({
activeKbs = [],
@@ -68,14 +69,15 @@ const KBSelect = ({
};
const NodeKbSearch = ({ data }: NodeProps<FlowModuleItemType>) => {
const { moduleId, inputs, outputs, onChangeNode } = data;
const { moduleId, inputs, outputs } = data;
const { onChangeNode } = useFlowStore();
return (
<NodeCard minW={'400px'} {...data}>
<Divider text="Input" />
<Container>
<RenderInput
moduleId={moduleId}
onChangeNode={onChangeNode}
flowInputList={inputs}
CustomComponent={{
kbList: ({ key, value, ...props }) => (
@@ -100,7 +102,7 @@ const NodeKbSearch = ({ data }: NodeProps<FlowModuleItemType>) => {
</Container>
<Divider text="Output" />
<Container>
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
</Container>
</NodeCard>
);

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { NodeProps } from 'reactflow';
import { Box } from '@chakra-ui/react';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import { FlowModuleItemType } from '@/types/core/app/flow';
import Container from '../modules/Container';
import { SystemInputEnum } from '@/constants/app';
import { FlowValueTypeEnum } from '@/constants/flow';

View File

@@ -3,7 +3,7 @@ import { Handle, Position, NodeProps } from 'reactflow';
import { Flex, Box } from '@chakra-ui/react';
import NodeCard from '../modules/NodeCard';
import { SystemInputEnum } from '@/constants/app';
import { FlowModuleItemType } from '@/types/flow';
import { FlowModuleItemType } from '@/types/core/app/flow';
import Divider from '../modules/Divider';
import Container from '../modules/Container';
import Label from '../modules/Label';

View File

@@ -15,9 +15,10 @@ import {
Switch
} from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { FlowModuleItemType } from '@/types/flow';
import { FlowModuleItemType } from '@/types/core/app/flow';
import { SystemInputEnum } from '@/constants/app';
import { welcomeTextTip, variableTip, questionGuideTip } from '@/constants/flow/ModuleTemplate';
import { useFlowStore } from '../Provider';
import VariableEditModal, { addVariable } from '../../../VariableEditModal';
import MyIcon from '@/components/Icon';
@@ -47,7 +48,8 @@ const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
export default React.memo(NodeUserGuide);
export function WelcomeText({ data }: { data: FlowModuleItemType }) {
const { inputs, moduleId, onChangeNode } = data;
const { inputs, moduleId } = data;
const { onChangeNode } = useFlowStore();
const welcomeText = useMemo(
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText),
@@ -89,7 +91,9 @@ export function WelcomeText({ data }: { data: FlowModuleItemType }) {
}
function ChatStartVariable({ data }: { data: FlowModuleItemType }) {
const { inputs, moduleId, onChangeNode } = data;
const { inputs, moduleId } = data;
const { onChangeNode } = useFlowStore();
const variables = useMemo(
() =>
(inputs.find((item) => item.key === SystemInputEnum.variables)
@@ -203,7 +207,9 @@ function ChatStartVariable({ data }: { data: FlowModuleItemType }) {
}
function QuestionGuide({ data }: { data: FlowModuleItemType }) {
const { inputs, moduleId, onChangeNode } = data;
const { inputs, moduleId } = data;
const { onChangeNode } = useFlowStore();
const questionGuide = useMemo(
() =>
(inputs.find((item) => item.key === SystemInputEnum.questionGuide)?.value as boolean) ||

View File

@@ -1,9 +1,10 @@
/* Abandon */
import React, { useCallback, useMemo, useState } from 'react';
import { NodeProps } from 'reactflow';
import { Box, Button, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import { FlowModuleItemType } from '@/types/core/app/flow';
import Container from '../modules/Container';
import { SystemInputEnum, VariableInputEnum } from '@/constants/app';
import type { VariableItemType } from '@/types/app';
@@ -11,6 +12,7 @@ import MyIcon from '@/components/Icon';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import VariableEditModal, { addVariable } from '../../../VariableEditModal';
import { useFlowStore } from '../Provider';
export const defaultVariable: VariableItemType = {
id: nanoid(),
@@ -23,7 +25,9 @@ export const defaultVariable: VariableItemType = {
};
const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
const { inputs, moduleId, onChangeNode } = data;
const { inputs, moduleId } = data;
const { onChangeNode } = useFlowStore();
const variables = useMemo(
() =>
(inputs.find((item) => item.key === SystemInputEnum.variables)

View File

@@ -0,0 +1,352 @@
import {
type Node,
type NodeChange,
type Edge,
type EdgeChange,
useNodesState,
useEdgesState,
XYPosition,
useViewport,
Connection,
addEdge
} from 'reactflow';
import type {
FlowModuleItemType,
FlowModuleTemplateType,
FlowOutputTargetItemType,
FlowModuleItemChangeProps
} from '@/types/core/app/flow';
import React, {
type SetStateAction,
type Dispatch,
useContext,
useCallback,
createContext,
useRef
} from 'react';
import { customAlphabet } from 'nanoid';
import { appModule2FlowEdge, appModule2FlowNode } from '@/utils/adapt';
import { useToast } from '@/hooks/useToast';
import { FlowModuleTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
import { useTranslation } from 'next-i18next';
import { AppModuleItemType } from '@/types/app';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
export type useFlowStoreType = {
reactFlowWrapper: null | React.RefObject<HTMLDivElement>;
nodes: Node<FlowModuleItemType, string | undefined>[];
setNodes: Dispatch<SetStateAction<Node<FlowModuleItemType, string | undefined>[]>>;
onNodesChange: OnChange<NodeChange>;
edges: Edge<any>[];
setEdges: Dispatch<SetStateAction<Edge<any>[]>>;
onEdgesChange: OnChange<EdgeChange>;
onFixView: () => void;
onAddNode: (e: { template: FlowModuleTemplateType; position: XYPosition }) => void;
onDelNode: (nodeId: string) => void;
onChangeNode: (e: FlowModuleItemChangeProps) => void;
onCopyNode: (nodeId: string) => void;
onDelEdge: (e: {
moduleId: string;
sourceHandle?: string | undefined;
targetHandle?: string | undefined;
}) => void;
onDelConnect: (id: string) => void;
onConnect: ({ connect }: { connect: Connection }) => any;
initData: (modules: AppModuleItemType[]) => void;
};
const StateContext = createContext<useFlowStoreType>({
reactFlowWrapper: null,
nodes: [],
setNodes: function (
value: React.SetStateAction<Node<FlowModuleItemType, string | undefined>[]>
): void {
return;
},
onNodesChange: function (changes: NodeChange[]): void {
return;
},
edges: [],
setEdges: function (value: React.SetStateAction<Edge<any>[]>): void {
return;
},
onEdgesChange: function (changes: EdgeChange[]): void {
return;
},
onFixView: function (): void {
return;
},
onAddNode: function (e: { template: FlowModuleTemplateType; position: XYPosition }): void {
return;
},
onDelNode: function (nodeId: string): void {
return;
},
onChangeNode: function (e: FlowModuleItemChangeProps): void {
return;
},
onCopyNode: function (nodeId: string): void {
return;
},
onDelEdge: function (e: {
moduleId: string;
sourceHandle?: string | undefined;
targetHandle?: string | undefined;
}): void {
return;
},
onDelConnect: function (id: string): void {
return;
},
onConnect: function ({ connect }: { connect: Connection }) {
return;
},
initData: function (modules: AppModuleItemType[]): void {
throw new Error('Function not implemented.');
}
});
export const useFlowStore = () => useContext(StateContext);
export const FlowProvider = ({ children }: { children: React.ReactNode }) => {
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const { t } = useTranslation();
const { toast } = useToast();
const [nodes = [], setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const { x, y, zoom } = useViewport();
const onFixView = useCallback(() => {
const btn = document.querySelector('.react-flow__controls-fitview') as HTMLButtonElement;
setTimeout(() => {
btn && btn.click();
}, 100);
}, []);
const onDelEdge = useCallback(
({
moduleId,
sourceHandle,
targetHandle
}: {
moduleId: string;
sourceHandle?: string | undefined;
targetHandle?: string | undefined;
}) => {
if (!sourceHandle && !targetHandle) return;
setEdges((state) =>
state.filter((edge) => {
if (edge.source === moduleId && edge.sourceHandle === sourceHandle) return false;
if (edge.target === moduleId && edge.targetHandle === targetHandle) return false;
return true;
})
);
},
[setEdges]
);
const onDelConnect = useCallback(
(id: string) => {
setEdges((state) => state.filter((item) => item.id !== id));
},
[setEdges]
);
const onConnect = useCallback(
({ connect }: { connect: Connection }) => {
const source = nodes.find((node) => node.id === connect.source)?.data;
const sourceType = (() => {
if (source?.flowType === FlowModuleTypeEnum.classifyQuestion) {
return FlowValueTypeEnum.boolean;
}
return source?.outputs.find((output) => output.key === connect.sourceHandle)?.valueType;
})();
const targetType = nodes
.find((node) => node.id === connect.target)
?.data?.inputs.find((input) => input.key === connect.targetHandle)?.valueType;
if (!sourceType || !targetType) {
return toast({
status: 'warning',
title: t('app.Connection is invalid')
});
}
if (
sourceType !== FlowValueTypeEnum.any &&
targetType !== FlowValueTypeEnum.any &&
sourceType !== targetType
) {
return toast({
status: 'warning',
title: t('app.Connection type is different')
});
}
setEdges((state) =>
addEdge(
{
...connect,
type: 'buttonedge',
animated: true,
data: {
onDelete: onDelConnect
}
},
state
)
);
},
[nodes, onDelConnect, setEdges, t, toast]
);
const onAddNode = useCallback(
({ template, position }: { template: FlowModuleTemplateType; position: XYPosition }) => {
if (!reactFlowWrapper.current) return;
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
const mouseX = (position.x - reactFlowBounds.left - x) / zoom - 100;
const mouseY = (position.y - reactFlowBounds.top - y) / zoom;
console.log(template);
setNodes((state) =>
state.concat(
appModule2FlowNode({
item: {
...template,
moduleId: nanoid(),
position: { x: mouseX, y: mouseY }
}
})
)
);
},
[setNodes, x, y, zoom]
);
const onDelNode = useCallback(
(nodeId: string) => {
setNodes((state) => state.filter((item) => item.id !== nodeId));
setEdges((state) => state.filter((edge) => edge.source !== nodeId && edge.target !== nodeId));
},
[setEdges, setNodes]
);
const onChangeNode = useCallback(
({ moduleId, key, type = 'inputs', value }: FlowModuleItemChangeProps) => {
setNodes((nodes) =>
nodes.map((node) => {
if (node.id !== moduleId) return node;
const updateObj: Record<string, any> = {};
if (type === 'inputs') {
updateObj.inputs = node.data.inputs.map((item) => (item.key === key ? value : item));
} else if (type === 'addInput') {
const input = node.data.inputs.find((input) => input.key === value.key);
if (input) {
toast({
status: 'warning',
title: 'key 重复'
});
updateObj.inputs = node.data.inputs;
} else {
updateObj.inputs = node.data.inputs.concat(value);
}
} else if (type === 'delInput') {
onDelEdge({ moduleId, targetHandle: key });
updateObj.inputs = node.data.inputs.filter((item) => item.key !== key);
} else if (type === 'attr') {
updateObj[key] = value;
} else if (type === 'outputs') {
// del output connect
const delOutputs = node.data.outputs.filter(
(item) => !value.find((output: FlowOutputTargetItemType) => output.key === item.key)
);
delOutputs.forEach((output) => {
onDelEdge({ moduleId, sourceHandle: output.key });
});
updateObj.outputs = value;
}
return {
...node,
data: {
...node.data,
...updateObj
}
};
})
);
},
[onDelEdge, setNodes, toast]
);
const onCopyNode = useCallback(
(nodeId: string) => {
setNodes((nodes) => {
const node = nodes.find((node) => node.id === nodeId);
if (!node) return nodes;
const template = {
logo: node.data.logo,
name: node.data.name,
intro: node.data.intro,
description: node.data.description,
flowType: node.data.flowType,
inputs: node.data.inputs,
outputs: node.data.outputs,
showStatus: node.data.showStatus
};
return nodes.concat(
appModule2FlowNode({
item: {
...template,
moduleId: nanoid(),
position: { x: node.position.x + 200, y: node.position.y + 50 }
}
})
);
});
},
[setNodes]
);
const initData = useCallback(
(modules: AppModuleItemType[]) => {
const edges = appModule2FlowEdge({
modules,
onDelete: onDelConnect
});
setEdges(edges);
setNodes(modules.map((item) => appModule2FlowNode({ item })));
onFixView();
},
[onDelConnect, setEdges, setNodes, onFixView]
);
const value = {
reactFlowWrapper,
nodes,
setNodes,
onNodesChange,
edges,
setEdges,
onEdgesChange,
onFixView,
onAddNode,
onDelNode,
onChangeNode,
onCopyNode,
onDelEdge,
onDelConnect,
onConnect,
initData
};
return <StateContext.Provider value={value}>{children}</StateContext.Provider>;
};
export default React.memo(FlowProvider);

View File

@@ -1,23 +1,23 @@
import React, { useMemo } from 'react';
import { Box, Flex } from '@chakra-ui/react';
import { ModuleTemplates } from '@/constants/flow/ModuleTemplate';
import { FlowModuleItemType, FlowModuleTemplateType } from '@/types/flow';
import type { Node, XYPosition } from 'reactflow';
import { FlowModuleItemType, FlowModuleTemplateType } from '@/types/core/app/flow';
import type { Node } from 'reactflow';
import { useGlobalStore } from '@/store/global';
import Avatar from '@/components/Avatar';
import { FlowModuleTypeEnum } from '@/constants/flow';
import { useFlowStore } from './Provider';
const ModuleTemplateList = ({
nodes,
isOpen,
onAddNode,
onClose
}: {
nodes?: Node<FlowModuleItemType>[];
isOpen: boolean;
onAddNode: (e: { template: FlowModuleTemplateType; position: XYPosition }) => void;
onClose: () => void;
}) => {
const { onAddNode } = useFlowStore();
const { isPc } = useGlobalStore();
const filterTemplates = useMemo(() => {
@@ -123,4 +123,4 @@ const ModuleTemplateList = ({
);
};
export default ModuleTemplateList;
export default React.memo(ModuleTemplateList);

View File

@@ -7,8 +7,7 @@ import {
ModalBody,
Flex,
Switch,
Input,
FormControl
Input
} from '@chakra-ui/react';
import type { ContextExtractAgentItemType } from '@/types/app';
import { useForm } from 'react-hook-form';

View File

@@ -2,12 +2,13 @@ import React, { useMemo } from 'react';
import { Box, Flex, useTheme, Menu, MenuButton, MenuList, MenuItem } from '@chakra-ui/react';
import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar';
import type { FlowModuleItemType } from '@/types/flow';
import type { FlowModuleItemType } from '@/types/core/app/flow';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { useTranslation } from 'react-i18next';
import { useEditTitle } from '@/hooks/useEditTitle';
import { useToast } from '@/hooks/useToast';
import { useFlowStore } from '../Provider';
type Props = FlowModuleItemType & {
children?: React.ReactNode | React.ReactNode[] | string;
@@ -21,11 +22,10 @@ const NodeCard = (props: Props) => {
name = '未知模块',
description,
minW = '300px',
onCopyNode,
onDelNode,
onChangeNode,
moduleId
} = props;
const { onCopyNode, onDelNode, onChangeNode } = useFlowStore();
const { t } = useTranslation();
const theme = useTheme();
const { toast } = useToast();
@@ -73,11 +73,11 @@ const NodeCard = (props: Props) => {
{
icon: 'back',
label: t('Cancel'),
label: t('common.Close'),
onClick: () => {}
}
],
[moduleId, onCopyNode, onDelNode, t]
[moduleId, name, onChangeNode, onCopyNode, onDelNode, onOpenModal, t, toast]
);
return (
@@ -92,6 +92,7 @@ const NodeCard = (props: Props) => {
<QuestionOutlineIcon
display={['none', 'inline']}
transform={'translateY(1px)'}
mb={'1px'}
ml={1}
/>
</MyTooltip>

View File

@@ -19,7 +19,7 @@ import MyTooltip from '@/components/MyTooltip';
import { FlowInputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
import { useTranslation } from 'react-i18next';
import MySelect from '@/components/Select';
import { FlowInputItemType } from '@/types/flow';
import type { FlowInputItemType } from '@/types/core/app/flow';
const typeSelectList = [
{

View File

@@ -1,16 +1,5 @@
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 { Box, Button, ModalHeader, ModalFooter, ModalBody, Flex, Input } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
@@ -20,7 +9,7 @@ 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';
import { FlowOutputItemType } from '@/types/core/app/flow';
const typeSelectList = [
{

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import type { FlowInputItemType, FlowModuleItemType } from '@/types/flow';
import type { FlowInputItemType } from '@/types/core/app/flow';
import {
Box,
Textarea,
@@ -20,19 +20,19 @@ import MyTooltip from '@/components/MyTooltip';
import TargetHandle from './TargetHandle';
import MyIcon from '@/components/Icon';
const SetInputFieldModal = dynamic(() => import('../modules/SetInputFieldModal'));
import { useFlowStore } from '../Provider';
export const Label = ({
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>();
const { onChangeNode } = useFlowStore();
return (
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
@@ -134,28 +134,20 @@ export const Label = ({
const RenderInput = ({
flowInputList,
moduleId,
CustomComponent = {},
onChangeNode
CustomComponent = {}
}: {
flowInputList: FlowInputItemType[];
moduleId: string;
CustomComponent?: Record<string, (e: FlowInputItemType) => React.ReactNode>;
onChangeNode: FlowModuleItemType['onChangeNode'];
}) => {
const { onChangeNode } = useFlowStore();
return (
<>
{flowInputList.map(
(item) =>
item.type !== FlowInputItemTypeEnum.hidden && (
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
{!!item.label && (
<Label
moduleId={moduleId}
onChangeNode={onChangeNode}
inputKey={item.key}
{...item}
/>
)}
{!!item.label && <Label moduleId={moduleId} inputKey={item.key} {...item} />}
<Box mt={2} className={'nodrag'}>
{item.type === FlowInputItemTypeEnum.numberInput && (
<NumberInput

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import type { FlowModuleItemType, FlowOutputItemType } from '@/types/flow';
import type { FlowOutputItemType } from '@/types/core/app/flow';
import { Box, Flex } from '@chakra-ui/react';
import { FlowOutputItemTypeEnum } from '@/constants/flow';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
@@ -8,21 +8,21 @@ import SourceHandle from './SourceHandle';
import MyIcon from '@/components/Icon';
import dynamic from 'next/dynamic';
const SetOutputFieldModal = dynamic(() => import('../modules/SetOutputFieldModal'));
import { useFlowStore } from '../Provider';
const Label = ({
moduleId,
outputKey,
outputs,
onChangeNode,
...item
}: FlowOutputItemType & {
outputKey: string;
moduleId: string;
outputs: FlowOutputItemType[];
onChangeNode: FlowModuleItemType['onChangeNode'];
}) => {
const { label, description, edit } = item;
const [editField, setEditField] = useState<FlowOutputItemType>();
const { onChangeNode } = useFlowStore();
return (
<Flex
@@ -122,12 +122,10 @@ const Label = ({
const RenderOutput = ({
moduleId,
flowOutputList,
onChangeNode
flowOutputList
}: {
moduleId: string;
flowOutputList: FlowOutputItemType[];
onChangeNode: FlowModuleItemType['onChangeNode'];
}) => {
return (
<>
@@ -135,13 +133,7 @@ const RenderOutput = ({
(item) =>
item.type !== FlowOutputItemTypeEnum.hidden && (
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
<Label
moduleId={moduleId}
onChangeNode={onChangeNode}
outputKey={item.key}
outputs={flowOutputList}
{...item}
/>
<Label moduleId={moduleId} outputKey={item.key} outputs={flowOutputList} {...item} />
<Box mt={FlowOutputItemTypeEnum.answer ? 0 : 2} className={'nodrag'}>
{item.type === FlowOutputItemTypeEnum.source && (
<SourceHandle handleKey={item.key} valueType={item.valueType} />

View File

@@ -1,89 +1,45 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import ReactFlow, {
Background,
Controls,
ReactFlowProvider,
addEdge,
useNodesState,
useEdgesState,
XYPosition,
Connection,
useViewport
} from 'reactflow';
import ReactFlow, { Background, Controls, ReactFlowProvider } from 'reactflow';
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
import { SmallCloseIcon } from '@chakra-ui/icons';
import {
edgeOptions,
connectionLineStyle,
FlowModuleTypeEnum,
FlowInputItemTypeEnum,
FlowValueTypeEnum
FlowInputItemTypeEnum
} from '@/constants/flow';
import { appModule2FlowNode, appModule2FlowEdge } from '@/utils/adapt';
import {
FlowModuleItemType,
FlowModuleTemplateType,
FlowOutputTargetItemType,
type FlowModuleItemChangeProps
} from '@/types/flow';
import { FlowOutputTargetItemType } from '@/types/core/app/flow';
import { AppModuleItemType } from '@/types/app';
import { customAlphabet } from 'nanoid';
import { useRequest } from '@/hooks/useRequest';
import type { AppSchema } from '@/types/mongoSchema';
import { useUserStore } from '@/store/user';
import { useToast } from '@/hooks/useToast';
import { useTranslation } from 'next-i18next';
import { useCopyData } from '@/hooks/useCopyData';
import dynamic from 'next/dynamic';
import styles from './index.module.scss';
import { AppTypeEnum } from '@/constants/app';
import MyIcon from '@/components/Icon';
import ButtonEdge from './components/modules/ButtonEdge';
import MyTooltip from '@/components/MyTooltip';
import TemplateList from './components/TemplateList';
import ChatTest, { type ChatTestComponentRef } from './components/ChatTest';
import FlowProvider, { useFlowStore } from './components/Provider';
const ImportSettings = dynamic(() => import('./components/ImportSettings'), {
ssr: false
});
const NodeChat = dynamic(() => import('./components/Nodes/NodeChat'), {
ssr: false
});
const NodeKbSearch = dynamic(() => import('./components/Nodes/NodeKbSearch'), {
ssr: false
});
const NodeHistory = dynamic(() => import('./components/Nodes/NodeHistory'), {
ssr: false
});
const NodeTFSwitch = dynamic(() => import('./components/Nodes/NodeTFSwitch'), {
ssr: false
});
const NodeAnswer = dynamic(() => import('./components/Nodes/NodeAnswer'), {
ssr: false
});
const NodeQuestionInput = dynamic(() => import('./components/Nodes/NodeQuestionInput'), {
ssr: false
});
const NodeCQNode = dynamic(() => import('./components/Nodes/NodeCQNode'), {
ssr: false
});
const NodeVariable = dynamic(() => import('./components/Nodes/NodeVariable'), {
ssr: false
});
const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'), {
ssr: false
});
const NodeExtract = dynamic(() => import('./components/Nodes/NodeExtract'), {
ssr: false
});
const NodeHttp = dynamic(() => import('./components/Nodes/NodeHttp'), {
ssr: false
});
const ImportSettings = dynamic(() => import('./components/ImportSettings'));
const NodeChat = dynamic(() => import('./components/Nodes/NodeChat'));
const NodeKbSearch = dynamic(() => import('./components/Nodes/NodeKbSearch'));
const NodeHistory = dynamic(() => import('./components/Nodes/NodeHistory'));
const NodeTFSwitch = dynamic(() => import('./components/Nodes/NodeTFSwitch'));
const NodeAnswer = dynamic(() => import('./components/Nodes/NodeAnswer'));
const NodeQuestionInput = dynamic(() => import('./components/Nodes/NodeQuestionInput'));
const NodeCQNode = dynamic(() => import('./components/Nodes/NodeCQNode'));
const NodeVariable = dynamic(() => import('./components/Nodes/NodeVariable'));
const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'));
const NodeExtract = dynamic(() => import('./components/Nodes/NodeExtract'));
const NodeHttp = dynamic(() => import('./components/Nodes/NodeHttp'));
import 'reactflow/dist/style.css';
import styles from './index.module.scss';
import { AppTypeEnum } from '@/constants/app';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
const nodeTypes = {
[FlowModuleTypeEnum.userGuide]: NodeUserGuide,
@@ -104,131 +60,17 @@ const edgeTypes = {
};
type Props = { app: AppSchema; onCloseSettings: () => void };
const AppEdit = ({ app, onCloseSettings }: Props) => {
function FlowHeader({ app, onCloseSettings }: Props & {}) {
const theme = useTheme();
const { toast } = useToast();
const { t } = useTranslation();
const { copyData } = useCopyData();
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const ChatTestRef = useRef<ChatTestComponentRef>(null);
const { updateAppDetail } = useUserStore();
const { x, y, zoom } = useViewport();
const [nodes, setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const {
isOpen: isOpenTemplate,
onOpen: onOpenTemplate,
onClose: onCloseTemplate
} = useDisclosure();
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const { updateAppDetail } = useUserStore();
const { nodes, edges, onFixView } = useFlowStore();
const [testModules, setTestModules] = useState<AppModuleItemType[]>();
const onFixView = useCallback(() => {
const btn = document.querySelector('.react-flow__controls-fitview') as HTMLButtonElement;
setTimeout(() => {
btn && btn.click();
}, 100);
}, []);
const onAddNode = useCallback(
({ template, position }: { template: FlowModuleTemplateType; position: XYPosition }) => {
if (!reactFlowWrapper.current) return;
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
const mouseX = (position.x - reactFlowBounds.left - x) / zoom - 100;
const mouseY = (position.y - reactFlowBounds.top - y) / zoom;
setNodes((state) =>
state.concat(
appModule2FlowNode({
item: {
...template,
moduleId: nanoid(),
position: { x: mouseX, y: mouseY }
},
onChangeNode,
onDelNode,
onDelEdge,
onCopyNode,
onCollectionNode
})
)
);
},
[x, zoom, y]
);
const onDelNode = useCallback(
(nodeId: string) => {
setNodes((state) => state.filter((item) => item.id !== nodeId));
setEdges((state) => state.filter((edge) => edge.source !== nodeId && edge.target !== nodeId));
},
[setEdges, setNodes]
);
const onDelEdge = useCallback(
({
moduleId,
sourceHandle,
targetHandle
}: {
moduleId: string;
sourceHandle?: string;
targetHandle?: string;
}) => {
if (!sourceHandle && !targetHandle) return;
setEdges((state) =>
state.filter((edge) => {
if (edge.source === moduleId && edge.sourceHandle === sourceHandle) return false;
if (edge.target === moduleId && edge.targetHandle === targetHandle) return false;
return true;
})
);
},
[setEdges]
);
const onCopyNode = useCallback(
(nodeId: string) => {
setNodes((nodes) => {
const node = nodes.find((node) => node.id === nodeId);
if (!node) return nodes;
const template = {
logo: node.data.logo,
name: node.data.name,
intro: node.data.intro,
description: node.data.description,
flowType: node.data.flowType,
inputs: node.data.inputs,
outputs: node.data.outputs,
showStatus: node.data.showStatus
};
return nodes.concat(
appModule2FlowNode({
item: {
...template,
moduleId: nanoid(),
position: { x: node.position.x + 200, y: node.position.y + 50 }
},
onChangeNode,
onDelNode,
onDelEdge,
onCopyNode,
onCollectionNode
})
);
});
},
[setNodes]
);
const onCollectionNode = useCallback(
(nodeId: string) => {
console.log(nodes.find((node) => node.id === nodeId));
},
[nodes]
);
const flow2AppModules = useCallback(() => {
const modules: AppModuleItemType[] = nodes.map((item) => ({
moduleId: item.data.moduleId,
@@ -271,106 +113,6 @@ const AppEdit = ({ app, onCloseSettings }: Props) => {
});
return modules;
}, [edges, nodes]);
const onChangeNode = useCallback(
({ moduleId, key, type = 'inputs', value }: FlowModuleItemChangeProps) => {
setNodes((nodes) =>
nodes.map((node) => {
if (node.id !== moduleId) return node;
const updateObj: Record<string, any> = {};
if (type === 'inputs') {
updateObj.inputs = node.data.inputs.map((item) => (item.key === key ? value : item));
} else if (type === 'addInput') {
const input = node.data.inputs.find((input) => input.key === value.key);
if (input) {
toast({
status: 'warning',
title: 'key 重复'
});
updateObj.inputs = node.data.inputs;
} else {
updateObj.inputs = node.data.inputs.concat(value);
}
} else if (type === 'delInput') {
onDelEdge({ moduleId, targetHandle: key });
updateObj.inputs = node.data.inputs.filter((item) => item.key !== key);
} else if (type === 'attr') {
updateObj[key] = value;
} else if (type === 'outputs') {
// del output connect
const delOutputs = node.data.outputs.filter(
(item) => !value.find((output: FlowOutputTargetItemType) => output.key === item.key)
);
delOutputs.forEach((output) => {
onDelEdge({ moduleId, sourceHandle: output.key });
});
updateObj.outputs = value;
}
return {
...node,
data: {
...node.data,
...updateObj
}
};
})
);
},
[]
);
const onDelConnect = useCallback((id: string) => {
setEdges((state) => state.filter((item) => item.id !== id));
}, []);
const onConnect = useCallback(
({ connect }: { connect: Connection }) => {
const source = nodes.find((node) => node.id === connect.source)?.data;
const sourceType = (() => {
if (source?.flowType === FlowModuleTypeEnum.classifyQuestion) {
return FlowValueTypeEnum.boolean;
}
return source?.outputs.find((output) => output.key === connect.sourceHandle)?.valueType;
})();
const targetType = nodes
.find((node) => node.id === connect.target)
?.data?.inputs.find((input) => input.key === connect.targetHandle)?.valueType;
if (!sourceType || !targetType) {
return toast({
status: 'warning',
title: t('app.Connection is invalid')
});
}
if (
sourceType !== FlowValueTypeEnum.any &&
targetType !== FlowValueTypeEnum.any &&
sourceType !== targetType
) {
return toast({
status: 'warning',
title: t('app.Connection type is different')
});
}
setEdges((state) =>
addEdge(
{
...connect,
type: 'buttonedge',
animated: true,
data: {
onDelete: onDelConnect
}
},
state
)
);
},
[nodes]
);
const { mutate: onclickSave, isLoading } = useRequest({
mutationFn: () => {
@@ -386,49 +128,8 @@ const AppEdit = ({ app, onCloseSettings }: Props) => {
}
});
const initData = useCallback(
(modules: AppModuleItemType[]) => {
const edges = appModule2FlowEdge({
modules,
onDelete: onDelConnect
});
setEdges(edges);
setNodes(
modules.map((item) =>
appModule2FlowNode({
item,
onChangeNode,
onDelNode,
onDelEdge,
onCopyNode,
onCollectionNode
})
)
);
onFixView();
},
[
onDelConnect,
setEdges,
setNodes,
onFixView,
onChangeNode,
onDelNode,
onDelEdge,
onCopyNode,
onCollectionNode
]
);
useEffect(() => {
initData(JSON.parse(JSON.stringify(app.modules)));
}, [app.modules]);
return (
<>
{/* header */}
<Flex
py={3}
px={[2, 5, 8]}
@@ -515,6 +216,38 @@ const AppEdit = ({ app, onCloseSettings }: Props) => {
/>
</MyTooltip>
</Flex>
{isOpenImport && <ImportSettings onClose={onCloseImport} />}
<ChatTest
ref={ChatTestRef}
modules={testModules}
app={app}
onClose={() => setTestModules(undefined)}
/>
</>
);
}
const Header = React.memo(FlowHeader);
const AppEdit = (props: Props) => {
const { app } = props;
const {
isOpen: isOpenTemplate,
onOpen: onOpenTemplate,
onClose: onCloseTemplate
} = useDisclosure();
const { reactFlowWrapper, nodes, onNodesChange, edges, onEdgesChange, onConnect, initData } =
useFlowStore();
useEffect(() => {
initData(JSON.parse(JSON.stringify(app.modules)));
}, [app.modules]);
return (
<>
{/* header */}
<Header {...props} />
<Box
minH={'400px'}
flex={'1 0 0'}
@@ -571,43 +304,24 @@ const AppEdit = ({ app, onCloseSettings }: Props) => {
<Controls position={'bottom-right'} style={{ display: 'flex' }} showInteractive={false} />
</ReactFlow>
<TemplateList
isOpen={isOpenTemplate}
nodes={nodes}
onAddNode={onAddNode}
onClose={onCloseTemplate}
/>
<ChatTest
ref={ChatTestRef}
modules={testModules}
app={app}
onClose={() => setTestModules(undefined)}
/>
<TemplateList isOpen={isOpenTemplate} nodes={nodes} onClose={onCloseTemplate} />
</Box>
{isOpenImport && (
<ImportSettings
onClose={onCloseImport}
onSuccess={(data) => {
setEdges([]);
setNodes([]);
setTimeout(() => {
initData(data);
}, 10);
}}
/>
)}
</>
);
};
const Flow = (data: Props) => (
<Box h={'100%'} position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>
<ReactFlowProvider>
<Flex h={'100%'} flexDirection={'column'} bg={'#fff'}>
{!!data.app._id && <AppEdit {...data} />}
</Flex>
</ReactFlowProvider>
</Box>
);
const Flow = (data: Props) => {
return (
<Box h={'100%'} position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>
<ReactFlowProvider>
<FlowProvider>
<Flex h={'100%'} flexDirection={'column'} bg={'#fff'}>
{!!data.app._id && <AppEdit {...data} />}
</Flex>
</FlowProvider>
</ReactFlowProvider>
</Box>
);
};
export default React.memo(Flow);

View File

@@ -17,15 +17,10 @@ import BasicEdit from './components/BasicEdit';
import { serviceSideProps } from '@/utils/web/i18n';
const AdEdit = dynamic(() => import('./components/AdEdit'), {
ssr: false,
loading: () => <Loading />
});
const OutLink = dynamic(() => import('./components/OutLink'), {
ssr: false
});
const Logs = dynamic(() => import('./components/Logs'), {
ssr: false
});
const OutLink = dynamic(() => import('./components/OutLink'), {});
const Logs = dynamic(() => import('./components/Logs'), {});
enum TabEnum {
'basicEdit' = 'basicEdit',

View File

@@ -7,7 +7,7 @@ import type { ClassifyQuestionAgentItemType } from '@/types/app';
import { SystemInputEnum } from '@/constants/app';
import { SpecialInputKeyEnum } from '@/constants/flow';
import { FlowModuleTypeEnum } from '@/constants/flow';
import { ModuleDispatchProps } from '@/types/core/modules';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import { replaceVariable } from '@/utils/common/tools/text';
import { Prompt_CQJson } from '@/prompts/core/agent';
import { defaultCQModel } from '@/pages/api/system/getInitData';

View File

@@ -6,7 +6,7 @@ import { getAIChatApi, axiosConfig } from '@fastgpt/core/aiApi/config';
import type { ContextExtractAgentItemType } from '@/types/app';
import { ContextExtractEnum } from '@/constants/flow/flowField';
import { FlowModuleTypeEnum } from '@/constants/flow';
import { ModuleDispatchProps } from '@/types/core/modules';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import { Prompt_ExtractJson } from '@/prompts/core/agent';
import { replaceVariable } from '@/utils/common/tools/text';
import { defaultExtractModel } from '@/pages/api/system/getInitData';

View File

@@ -19,7 +19,7 @@ import { defaultQuotePrompt, defaultQuoteTemplate } from '@/prompts/core/AIChat'
import type { AIChatProps } from '@/types/core/aiChat';
import { replaceVariable } from '@/utils/common/tools/text';
import { FlowModuleTypeEnum } from '@/constants/flow';
import { ModuleDispatchProps } from '@/types/core/modules';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import { Readable } from 'stream';
import { responseWrite, responseWriteController } from '@/service/common/stream';
import { addLog } from '@/service/utils/tools';

View File

@@ -1,7 +1,6 @@
import { SystemInputEnum } from '@/constants/app';
import { ChatItemType } from '@/types/chat';
import type { ModuleDispatchProps } from '@/types/core/modules';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
export type HistoryProps = ModuleDispatchProps<{
maxContext: number;
[SystemInputEnum.history]: ChatItemType[];

View File

@@ -1,6 +1,5 @@
import { SystemInputEnum } from '@/constants/app';
import type { ModuleDispatchProps } from '@/types/core/modules';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
export type UserChatInputProps = ModuleDispatchProps<{
[SystemInputEnum.userChatInput]: string;
}>;

View File

@@ -7,8 +7,7 @@ import type { SelectedDatasetType } from '@/types/core/dataset';
import type { QuoteItemType } from '@/types/chat';
import { PgDatasetTableName } from '@/constants/plugin';
import { FlowModuleTypeEnum } from '@/constants/flow';
import { ModuleDispatchProps } from '@/types/core/modules';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
type KBSearchProps = ModuleDispatchProps<{
kbList: SelectedDatasetType;
similarity: number;

View File

@@ -1,8 +1,7 @@
import { sseResponseEventEnum, TaskResponseKeyEnum } from '@/constants/chat';
import { sseResponse } from '@/service/utils/tools';
import { textAdaptGptResponse } from '@/utils/adapt';
import type { ModuleDispatchProps } from '@/types/core/modules';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
export type AnswerProps = ModuleDispatchProps<{
text: string;
}>;

View File

@@ -2,8 +2,7 @@ import { TaskResponseKeyEnum } from '@/constants/chat';
import { HttpPropsEnum } from '@/constants/flow/flowField';
import { ChatHistoryItemResType } from '@/types/chat';
import { FlowModuleTypeEnum } from '@/constants/flow';
import { ModuleDispatchProps } from '@/types/core/modules';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
export type HttpRequestProps = ModuleDispatchProps<{
[HttpPropsEnum.url]: string;
[key: string]: any;

View File

@@ -6,7 +6,11 @@ import {
ModulesInputItemTypeEnum,
VariableInputEnum
} from '../constants/app';
import type { FlowInputItemType, FlowOutputItemType, FlowOutputTargetItemType } from './flow';
import type {
FlowInputItemType,
FlowOutputItemType,
FlowOutputTargetItemType
} from '@/types/core/app/flow';
import type { AppSchema, ChatSchema, kbSchema } from './mongoSchema';
import { ChatModelType } from '@/constants/model';
import { FlowValueTypeEnum } from '@/constants/flow';

View File

@@ -59,17 +59,4 @@ export type FlowModuleTemplateType = {
};
export type FlowModuleItemType = FlowModuleTemplateType & {
moduleId: string;
onChangeNode: (e: FlowModuleItemChangeProps) => void;
onDelNode: (id: string) => void;
onCopyNode: (id: string) => void;
onCollectionNode: (id: string) => void;
onDelEdge: ({
moduleId,
sourceHandle,
targetHandle
}: {
moduleId: string;
sourceHandle?: string | undefined;
targetHandle?: string | undefined;
}) => void;
};

View File

@@ -1,3 +1,18 @@
import type { ChatCompletionRequestMessage } from '@fastgpt/core/aiApi/type';
import type { NextApiResponse } from 'next';
import { RunningModuleItemType } from '@/types/app';
import { UserModelSchema } from '@/types/mongoSchema';
export type MessageItemType = ChatCompletionRequestMessage & { dataId?: string };
// module dispatch props type
export type ModuleDispatchProps<T> = {
res: NextApiResponse;
moduleName: string;
stream: boolean;
detail: boolean;
variables: Record<string, any>;
outputs: RunningModuleItemType['outputs'];
userOpenaiAccount?: UserModelSchema['openaiAccount'];
inputs: T;
};

View File

@@ -1,15 +0,0 @@
import type { NextApiResponse } from 'next';
import { RunningModuleItemType } from '../app';
import { UserModelSchema } from '../mongoSchema';
// module dispatch props type
export type ModuleDispatchProps<T> = {
res: NextApiResponse;
moduleName: string;
stream: boolean;
detail: boolean;
variables: Record<string, any>;
outputs: RunningModuleItemType['outputs'];
userOpenaiAccount?: UserModelSchema['openaiAccount'];
inputs: T;
};

View File

@@ -6,7 +6,7 @@ import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/core/aiApi/consta
import { ChatRoleEnum } from '@/constants/chat';
import type { MessageItemType } from '@/types/core/chat/type';
import type { AppModuleItemType } from '@/types/app';
import type { FlowModuleItemType } from '@/types/flow';
import type { FlowModuleItemType } from '@/types/core/app/flow';
import type { Edge, Node } from 'reactflow';
import { connectionLineStyle } from '@/constants/flow';
import { customAlphabet } from 'nanoid';
@@ -61,19 +61,9 @@ export const textAdaptGptResponse = ({
};
export const appModule2FlowNode = ({
item,
onChangeNode,
onDelNode,
onDelEdge,
onCopyNode,
onCollectionNode
item
}: {
item: AppModuleItemType;
onChangeNode: FlowModuleItemType['onChangeNode'];
onDelNode: FlowModuleItemType['onDelNode'];
onDelEdge: FlowModuleItemType['onDelEdge'];
onCopyNode: FlowModuleItemType['onCopyNode'];
onCollectionNode: FlowModuleItemType['onCollectionNode'];
}): Node<FlowModuleItemType> => {
// init some static data
const template =
@@ -110,12 +100,7 @@ export const appModule2FlowNode = ({
...(templateOutput ? templateOutput : output),
targets: output.targets || []
};
}),
onChangeNode,
onDelNode,
onDelEdge,
onCopyNode,
onCollectionNode
})
};
return {

View File

@@ -8,7 +8,7 @@ import {
} from '@/constants/flow';
import { SystemInputEnum } from '@/constants/app';
import type { SelectedDatasetType } from '@/types/core/dataset';
import { FlowInputItemType } from '@/types/flow';
import type { FlowInputItemType } from '@/types/core/app/flow';
import type { AIChatProps } from '@/types/core/aiChat';
import { getGuideModule, splitGuideModule } from '@/components/ChatBox/utils';