Bladeren bron

fix: monitor changes in the data.form field of the categorize and relevant operators and then synchronize them to the edge #918 (#1469)

### What problem does this PR solve?
feat: monitor changes in the table of relevant operators and synchronize
them to the edge #918
feat: fixed the issue of repeated requests when opening the graph page
#918
feat: cache node anchor coordinate information #918
feat: monitor changes in the data.form field of the categorize and
relevant operators and then synchronize them to the edge #918
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
tags/v0.9.0
balibabu 1 jaar geleden
bovenliggende
commit
8d7fb12305
No account linked to committer's email address

+ 2
- 0
web/src/hooks/flow-hooks.ts Bestand weergeven

} = useQuery({ } = useQuery({
queryKey: ['flowDetail'], queryKey: ['flowDetail'],
initialData: {} as IFlow, initialData: {} as IFlow,
refetchOnReconnect: false,
refetchOnMount: false,
queryFn: async () => { queryFn: async () => {
const { data } = await flowService.getCanvas({}, id); const { data } = await flowService.getCanvas({}, id);



+ 1
- 1
web/src/locales/en.ts Bestand weergeven

answer: 'Answer', answer: 'Answer',
categorize: 'Categorize', categorize: 'Categorize',
relevant: 'Relevant', relevant: 'Relevant',
rewriteQuestion: 'RewriteQuestion',
rewriteQuestion: 'Rewrite',
rewrite: 'Rewrite', rewrite: 'Rewrite',
begin: 'Begin', begin: 'Begin',
message: 'Message', message: 'Message',

+ 7
- 4
web/src/pages/flow/canvas/edge/index.tsx Bestand weergeven

} from 'reactflow'; } from 'reactflow';
import useGraphStore from '../../store'; import useGraphStore from '../../store';


import { useFetchFlow } from '@/hooks/flow-hooks';
import { IFlow } from '@/interfaces/database/flow';
import { useQueryClient } from '@tanstack/react-query';
import { useMemo } from 'react'; import { useMemo } from 'react';
import styles from './index.less'; import styles from './index.less';


}; };


// highlight the nodes that the workflow passes through // highlight the nodes that the workflow passes through
const { data: flowDetail } = useFetchFlow();
const queryClient = useQueryClient();
const flowDetail = queryClient.getQueryData<IFlow>(['flowDetail']);

const graphPath = useMemo(() => { const graphPath = useMemo(() => {
// TODO: this will be called multiple times // TODO: this will be called multiple times
const path = flowDetail.dsl.path ?? [];
const path = flowDetail?.dsl.path ?? [];
// The second to last // The second to last
const previousGraphPath: string[] = path.at(-2) ?? []; const previousGraphPath: string[] = path.at(-2) ?? [];
let graphPath: string[] = path.at(-1) ?? []; let graphPath: string[] = path.at(-1) ?? [];
graphPath = [previousLatestElement, ...graphPath]; graphPath = [previousLatestElement, ...graphPath];
} }
return graphPath; return graphPath;
}, [flowDetail.dsl.path]);
}, [flowDetail?.dsl.path]);


const highlightStyle = useMemo(() => { const highlightStyle = useMemo(() => {
const idx = graphPath.findIndex((x) => x === source); const idx = graphPath.findIndex((x) => x === source);

+ 2
- 0
web/src/pages/flow/canvas/index.tsx Bestand weergeven

useSelectCanvasData, useSelectCanvasData,
useShowDrawer, useShowDrawer,
useValidateConnection, useValidateConnection,
useWatchNodeFormDataChange,
} from '../hooks'; } from '../hooks';
import { RagNode } from './node'; import { RagNode } from './node';


const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop(); const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop();


const { handleKeyUp } = useHandleKeyUp(); const { handleKeyUp } = useHandleKeyUp();
useWatchNodeFormDataChange();


return ( return (
<div className={styles.canvasWrapper}> <div className={styles.canvasWrapper}>

+ 61
- 15
web/src/pages/flow/canvas/node/categorize-node.tsx Bestand weergeven

import { useTranslate } from '@/hooks/commonHooks'; import { useTranslate } from '@/hooks/commonHooks';
import { Flex } from 'antd'; import { Flex } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { pick } from 'lodash';
import get from 'lodash/get'; import get from 'lodash/get';
import intersectionWith from 'lodash/intersectionWith';
import isEqual from 'lodash/isEqual';
import lowerFirst from 'lodash/lowerFirst'; import lowerFirst from 'lodash/lowerFirst';
import { Handle, NodeProps, Position } from 'reactflow';
import {
CategorizeAnchorPointPositions,
Operator,
operatorMap,
} from '../../constant';
import { NodeData } from '../../interface';
import { useEffect, useMemo, useState } from 'react';
import { Handle, NodeProps, Position, useUpdateNodeInternals } from 'reactflow';
import { Operator, operatorMap } from '../../constant';
import { IPosition, NodeData } from '../../interface';
import OperatorIcon from '../../operator-icon'; import OperatorIcon from '../../operator-icon';
import { buildNewPositionMap } from '../../utils';
import CategorizeHandle from './categorize-handle'; import CategorizeHandle from './categorize-handle';
import NodeDropdown from './dropdown'; import NodeDropdown from './dropdown';
import styles from './index.less'; import styles from './index.less';
import NodePopover from './popover'; import NodePopover from './popover';


export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) { export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) {
const categoryData = get(data, 'form.category_description') ?? {};
const updateNodeInternals = useUpdateNodeInternals();
const [postionMap, setPositionMap] = useState<Record<string, IPosition>>({});
const categoryData = useMemo(
() => get(data, 'form.category_description') ?? {},
[data],
);
const style = operatorMap[data.label as Operator]; const style = operatorMap[data.label as Operator];
const { t } = useTranslate('flow'); const { t } = useTranslate('flow');

useEffect(() => {
// Cache used coordinates
setPositionMap((state) => {
// index in use
const indexesInUse = Object.values(state).map((x) => x.idx);
const categoryDataKeys = Object.keys(categoryData);
const stateKeys = Object.keys(state);
if (!isEqual(categoryDataKeys.sort(), stateKeys.sort())) {
const intersectionKeys = intersectionWith(
stateKeys,
categoryDataKeys,
(categoryDataKey, postionMapKey) => categoryDataKey === postionMapKey,
);
const newPositionMap = buildNewPositionMap(
categoryDataKeys.filter(
(x) => !intersectionKeys.some((y) => y === x),
),
indexesInUse,
);
console.info('newPositionMap:', newPositionMap);

const nextPostionMap = {
...pick(state, intersectionKeys),
...newPositionMap,
};

return nextPostionMap;
}
return state;
});
}, [categoryData]);

useEffect(() => {
updateNodeInternals(id);
}, [id, updateNodeInternals, postionMap]);

return ( return (
<NodePopover nodeId={id}> <NodePopover nodeId={id}>
<section <section
id={'c'} id={'c'}
></Handle> ></Handle>
{Object.keys(categoryData).map((x, idx) => { {Object.keys(categoryData).map((x, idx) => {
const position = postionMap[x];
return ( return (
<CategorizeHandle
top={CategorizeAnchorPointPositions[idx].top}
right={CategorizeAnchorPointPositions[idx].right}
key={idx}
text={x}
idx={idx}
></CategorizeHandle>
position && (
<CategorizeHandle
top={position.top}
right={position.right}
key={idx}
text={x}
idx={idx}
></CategorizeHandle>
)
); );
})} })}
<Flex vertical align="center" justify="center" gap={6}> <Flex vertical align="center" justify="center" gap={6}>

+ 3
- 10
web/src/pages/flow/categorize-form/dynamic-categorize.tsx Bestand weergeven

import { useTranslate } from '@/hooks/commonHooks'; import { useTranslate } from '@/hooks/commonHooks';
import { CloseOutlined } from '@ant-design/icons'; import { CloseOutlined } from '@ant-design/icons';
import { Button, Card, Form, Input, Select } from 'antd'; import { Button, Card, Form, Input, Select } from 'antd';
import { humanId } from 'human-id';
import { useUpdateNodeInternals } from 'reactflow'; import { useUpdateNodeInternals } from 'reactflow';
import { Operator } from '../constant'; import { Operator } from '../constant';
import {
useBuildFormSelectOptions,
useHandleFormSelectChange,
} from '../form-hooks';
import { useBuildFormSelectOptions } from '../form-hooks';
import { ICategorizeItem } from '../interface'; import { ICategorizeItem } from '../interface';


interface IProps { interface IProps {
Operator.Categorize, Operator.Categorize,
nodeId, nodeId,
); );
const { handleSelectChange } = useHandleFormSelectChange(nodeId);
const { t } = useTranslate('flow'); const { t } = useTranslate('flow');


return ( return (
<Form.List name="items"> <Form.List name="items">
{(fields, { add, remove }) => { {(fields, { add, remove }) => {
const handleAdd = () => { const handleAdd = () => {
const idx = fields.length;
add({ name: `Categorize ${idx + 1}` });
add({ name: humanId() });
if (nodeId) updateNodeInternals(nodeId); if (nodeId) updateNodeInternals(nodeId);
}; };
return ( return (
form.getFieldValue(['items', field.name, 'to']), form.getFieldValue(['items', field.name, 'to']),
), ),
)} )}
onChange={handleSelectChange(
form.getFieldValue(['items', field.name, 'name']),
)}
/> />
</Form.Item> </Form.Item>
</Card> </Card>

+ 4
- 12
web/src/pages/flow/categorize-form/hooks.ts Bestand weergeven

import get from 'lodash/get'; import get from 'lodash/get';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import { Edge, Node } from 'reactflow';
import { import {
ICategorizeItem, ICategorizeItem,
ICategorizeItemResult, ICategorizeItemResult,
IOperatorForm, IOperatorForm,
NodeData,
} from '../interface'; } from '../interface';
import useGraphStore from '../store'; import useGraphStore from '../store';


*/ */
const buildCategorizeListFromObject = ( const buildCategorizeListFromObject = (
categorizeItem: ICategorizeItemResult, categorizeItem: ICategorizeItemResult,
edges: Edge[],
node?: Node<NodeData>,
) => { ) => {
// Categorize's to field has two data sources, with edges as the data source. // Categorize's to field has two data sources, with edges as the data source.
// Changes in the edge or to field need to be synchronized to the form field. // Changes in the edge or to field need to be synchronized to the form field.
return Object.keys(categorizeItem).reduce<Array<ICategorizeItem>>( return Object.keys(categorizeItem).reduce<Array<ICategorizeItem>>(
(pre, cur) => { (pre, cur) => {
// synchronize edge data to the to field // synchronize edge data to the to field
const edge = edges.find(
(x) => x.source === node?.id && x.sourceHandle === cur,
);
pre.push({ name: cur, ...categorizeItem[cur], to: edge?.target });

pre.push({ name: cur, ...categorizeItem[cur] });
return pre; return pre;
}, },
[], [],
form, form,
nodeId, nodeId,
}: IOperatorForm) => { }: IOperatorForm) => {
const edges = useGraphStore((state) => state.edges);
const getNode = useGraphStore((state) => state.getNode); const getNode = useGraphStore((state) => state.getNode);
const node = getNode(nodeId); const node = getNode(nodeId);


useEffect(() => { useEffect(() => {
const items = buildCategorizeListFromObject( const items = buildCategorizeListFromObject(
get(node, 'data.form.category_description', {}), get(node, 'data.form.category_description', {}),
edges,
node,
); );
console.info('effect:', items);
form?.setFieldsValue({ form?.setFieldsValue({
items, items,
}); });
}, [form, node, edges]);
}, [form, node]);


return { handleValuesChange }; return { handleValuesChange };
}; };

+ 5
- 0
web/src/pages/flow/form-hooks.ts Bestand weergeven

return buildCategorizeToOptions; return buildCategorizeToOptions;
}; };


/**
* dumped
* @param nodeId
* @returns
*/
export const useHandleFormSelectChange = (nodeId?: string) => { export const useHandleFormSelectChange = (nodeId?: string) => {
const { addEdge, deleteEdgeBySourceAndSourceHandle } = useGraphStore( const { addEdge, deleteEdgeBySourceAndSourceHandle } = useGraphStore(
(state) => state, (state) => state,

+ 1
- 14
web/src/pages/flow/generate-form/hooks.ts Bestand weergeven

const { getNode, updateNodeForm } = useGraphStore((state) => state); const { getNode, updateNodeForm } = useGraphStore((state) => state);
const node = getNode(nodeId); const node = getNode(nodeId);
const dataSource: IGenerateParameter[] = useMemo( const dataSource: IGenerateParameter[] = useMemo(
() => get(node, 'data.form.parameters', []),
() => get(node, 'data.form.parameters', []) as IGenerateParameter[],
[node], [node],
); );


// const [x, setDataSource] = useState<IGenerateParameter[]>([]);

const handleComponentIdChange = useCallback( const handleComponentIdChange = useCallback(
(row: IGenerateParameter) => (value: string) => { (row: IGenerateParameter) => (value: string) => {
const newData = [...dataSource]; const newData = [...dataSource];
}); });


updateNodeForm(nodeId, { parameters: newData }); updateNodeForm(nodeId, { parameters: newData });
// setDataSource(newData);
}, },
[updateNodeForm, nodeId, dataSource], [updateNodeForm, nodeId, dataSource],
); );
(id?: string) => () => { (id?: string) => () => {
const newData = dataSource.filter((item) => item.id !== id); const newData = dataSource.filter((item) => item.id !== id);
updateNodeForm(nodeId, { parameters: newData }); updateNodeForm(nodeId, { parameters: newData });
// setDataSource(newData);
}, },
[updateNodeForm, nodeId, dataSource], [updateNodeForm, nodeId, dataSource],
); );


const handleAdd = useCallback(() => { const handleAdd = useCallback(() => {
// setDataSource((state) => [
// ...state,
// {
// id: uuid(),
// key: '',
// component_id: undefined,
// },
// ]);
updateNodeForm(nodeId, { updateNodeForm(nodeId, {
parameters: [ parameters: [
...dataSource, ...dataSource,
}); });


updateNodeForm(nodeId, { parameters: newData }); updateNodeForm(nodeId, { parameters: newData });
// setDataSource(newData);
}; };


return { return {

+ 83
- 2
web/src/pages/flow/hooks.ts Bestand weergeven

useEffect, useEffect,
useState, useState,
} from 'react'; } from 'react';
import { Connection, Node, Position, ReactFlowInstance } from 'reactflow';
import { Connection, Edge, Node, Position, ReactFlowInstance } from 'reactflow';
// import { shallow } from 'zustand/shallow'; // import { shallow } from 'zustand/shallow';
import { variableEnabledFieldMap } from '@/constants/chat'; import { variableEnabledFieldMap } from '@/constants/chat';
import { import {
import { humanId } from 'human-id'; import { humanId } from 'human-id';
import trim from 'lodash/trim'; import trim from 'lodash/trim';
import { useParams } from 'umi'; import { useParams } from 'umi';
import { v4 as uuid } from 'uuid';
import { import {
NodeMap, NodeMap,
Operator, Operator,
initialRetrievalValues, initialRetrievalValues,
initialRewriteQuestionValues, initialRewriteQuestionValues,
} from './constant'; } from './constant';
import { ICategorizeForm, IRelevantForm } from './interface';
import useGraphStore, { RFState } from './store'; import useGraphStore, { RFState } from './store';
import { import {
buildDslComponentsByGraph, buildDslComponentsByGraph,
}; };


export const useFetchDataOnMount = () => { export const useFetchDataOnMount = () => {
const { loading, data } = useFetchFlow();
const { loading, data, refetch } = useFetchFlow();
const setGraphInfo = useSetGraphInfo(); const setGraphInfo = useSetGraphInfo();


useEffect(() => { useEffect(() => {


useFetchLlmList(); useFetchLlmList();


useEffect(() => {
refetch();
}, [refetch]);

return { loading, flowDetail: data }; return { loading, flowDetail: data };
}; };




return replaceIdWithText(output, getNameById); return replaceIdWithText(output, getNameById);
}; };

/**
* monitor changes in the data.form field of the categorize and relevant operators
* and then synchronize them to the edge
*/
export const useWatchNodeFormDataChange = () => {
const { getNode, nodes, setEdgesByNodeId } = useGraphStore((state) => state);

const buildCategorizeEdgesByFormData = useCallback(
(nodeId: string, form: ICategorizeForm) => {
// add
// delete
// edit
const categoryDescription = form.category_description;
const downstreamEdges = Object.keys(categoryDescription).reduce<Edge[]>(
(pre, sourceHandle) => {
const target = categoryDescription[sourceHandle]?.to;
if (target) {
pre.push({
id: uuid(),
source: nodeId,
target,
sourceHandle,
});
}

return pre;
},
[],
);

setEdgesByNodeId(nodeId, downstreamEdges);
},
[setEdgesByNodeId],
);

const buildRelevantEdgesByFormData = useCallback(
(nodeId: string, form: IRelevantForm) => {
const downstreamEdges = ['yes', 'no'].reduce<Edge[]>((pre, cur) => {
const target = form[cur as keyof IRelevantForm] as string;
if (target) {
pre.push({ id: uuid(), source: nodeId, target, sourceHandle: cur });
}

return pre;
}, []);

setEdgesByNodeId(nodeId, downstreamEdges);
},
[setEdgesByNodeId],
);

useEffect(() => {
nodes.forEach((node) => {
const currentNode = getNode(node.id);
const form = currentNode?.data.form ?? {};
const operatorType = currentNode?.data.label;
switch (operatorType) {
case Operator.Relevant:
buildRelevantEdgesByFormData(node.id, form as IRelevantForm);
break;
case Operator.Categorize:
buildCategorizeEdgesByFormData(node.id, form as ICategorizeForm);
break;
default:
break;
}
});
}, [
nodes,
buildCategorizeEdgesByFormData,
getNode,
buildRelevantEdgesByFormData,
]);
};

+ 2
- 0
web/src/pages/flow/interface.ts Bestand weergeven

color: string; color: string;
form: IBeginForm | IRetrievalForm | IGenerateForm | ICategorizeForm; form: IBeginForm | IRetrievalForm | IGenerateForm | ICategorizeForm;
}; };

export type IPosition = { top: number; right: number; idx: number };

+ 1
- 7
web/src/pages/flow/relevant-form/index.tsx Bestand weergeven

import { useTranslate } from '@/hooks/commonHooks'; import { useTranslate } from '@/hooks/commonHooks';
import { Form, Select } from 'antd'; import { Form, Select } from 'antd';
import { Operator } from '../constant'; import { Operator } from '../constant';
import {
useBuildFormSelectOptions,
useHandleFormSelectChange,
} from '../form-hooks';
import { useBuildFormSelectOptions } from '../form-hooks';
import { useSetLlmSetting } from '../hooks'; import { useSetLlmSetting } from '../hooks';
import { IOperatorForm } from '../interface'; import { IOperatorForm } from '../interface';
import { useWatchConnectionChanges } from './hooks'; import { useWatchConnectionChanges } from './hooks';
node?.id, node?.id,
); );
useWatchConnectionChanges({ nodeId: node?.id, form }); useWatchConnectionChanges({ nodeId: node?.id, form });
const { handleSelectChange } = useHandleFormSelectChange(node?.id);


return ( return (
<Form <Form
<Select <Select
allowClear allowClear
options={buildRelevantOptions([form?.getFieldValue('no')])} options={buildRelevantOptions([form?.getFieldValue('no')])}
onChange={handleSelectChange('yes')}
/> />
</Form.Item> </Form.Item>
<Form.Item label={t('no')} name={'no'}> <Form.Item label={t('no')} name={'no'}>
<Select <Select
allowClear allowClear
options={buildRelevantOptions([form?.getFieldValue('yes')])} options={buildRelevantOptions([form?.getFieldValue('yes')])}
onChange={handleSelectChange('no')}
/> />
</Form.Item> </Form.Item>
</Form> </Form>

+ 53
- 4
web/src/pages/flow/store.ts Bestand weergeven

import type {} from '@redux-devtools/extension'; import type {} from '@redux-devtools/extension';
import { humanId } from 'human-id'; import { humanId } from 'human-id';
import differenceWith from 'lodash/differenceWith';
import intersectionWith from 'lodash/intersectionWith';
import lodashSet from 'lodash/set'; import lodashSet from 'lodash/set';
import { import {
Connection, Connection,
import { immer } from 'zustand/middleware/immer'; import { immer } from 'zustand/middleware/immer';
import { Operator } from './constant'; import { Operator } from './constant';
import { NodeData } from './interface'; import { NodeData } from './interface';
import { isEdgeEqual } from './utils';


export type RFState = { export type RFState = {
nodes: Node<NodeData>[]; nodes: Node<NodeData>[];
onConnect: OnConnect; onConnect: OnConnect;
setNodes: (nodes: Node[]) => void; setNodes: (nodes: Node[]) => void;
setEdges: (edges: Edge[]) => void; setEdges: (edges: Edge[]) => void;
setEdgesByNodeId: (nodeId: string, edges: Edge[]) => void;
updateNodeForm: (nodeId: string, values: any, path?: string[]) => void; updateNodeForm: (nodeId: string, values: any, path?: string[]) => void;
onSelectionChange: OnSelectionChangeFunc; onSelectionChange: OnSelectionChangeFunc;
addNode: (nodes: Node) => void; addNode: (nodes: Node) => void;
setEdges: (edges: Edge[]) => { setEdges: (edges: Edge[]) => {
set({ edges }); set({ edges });
}, },
setEdgesByNodeId: (nodeId: string, currentDownstreamEdges: Edge[]) => {
const { edges, setEdges } = get();
// the previous downstream edge of this node
const previousDownstreamEdges = edges.filter(
(x) => x.source === nodeId,
);
const isDifferent =
previousDownstreamEdges.length !== currentDownstreamEdges.length ||
!previousDownstreamEdges.every((x) =>
currentDownstreamEdges.some(
(y) =>
y.source === x.source &&
y.target === x.target &&
y.sourceHandle === x.sourceHandle,
),
) ||
!currentDownstreamEdges.every((x) =>
previousDownstreamEdges.some(
(y) =>
y.source === x.source &&
y.target === x.target &&
y.sourceHandle === x.sourceHandle,
),
);

const intersectionDownstreamEdges = intersectionWith(
previousDownstreamEdges,
currentDownstreamEdges,
isEdgeEqual,
);
if (isDifferent) {
// other operator's edges
const irrelevantEdges = edges.filter((x) => x.source !== nodeId);
// the abandoned edges
const selfAbandonedEdges = [];
// the added downstream edges
const selfAddedDownstreamEdges = differenceWith(
currentDownstreamEdges,
intersectionDownstreamEdges,
isEdgeEqual,
);
setEdges([
...irrelevantEdges,
...intersectionDownstreamEdges,
...selfAddedDownstreamEdges,
]);
}
},

addNode: (node: Node) => { addNode: (node: Node) => {
set({ nodes: get().nodes.concat(node) }); set({ nodes: get().nodes.concat(node) });
}, },
set({ set({
nodes: get().nodes.map((node) => { nodes: get().nodes.map((node) => {
if (node.id === nodeId) { if (node.id === nodeId) {
// node.data = {
// ...node.data,
// form: { ...node.data.form, ...values },
// };
let nextForm: Record<string, unknown> = { ...node.data.form }; let nextForm: Record<string, unknown> = { ...node.data.form };
if (path.length === 0) { if (path.length === 0) {
nextForm = Object.assign(nextForm, values); nextForm = Object.assign(nextForm, values);

+ 27
- 3
web/src/pages/flow/utils.ts Bestand weergeven

import { removeUselessFieldsFromValues } from '@/utils/form'; import { removeUselessFieldsFromValues } from '@/utils/form';
import dagre from 'dagre'; import dagre from 'dagre';
import { humanId } from 'human-id'; import { humanId } from 'human-id';
import { curry } from 'lodash';
import { curry, sample } from 'lodash';
import pipe from 'lodash/fp/pipe'; import pipe from 'lodash/fp/pipe';
import isObject from 'lodash/isObject'; import isObject from 'lodash/isObject';
import { Edge, Node, Position } from 'reactflow'; import { Edge, Node, Position } from 'reactflow';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { NodeMap, Operator } from './constant';
import { ICategorizeItemResult, NodeData } from './interface';
import { CategorizeAnchorPointPositions, NodeMap, Operator } from './constant';
import { ICategorizeItemResult, IPosition, NodeData } from './interface';


const buildEdges = ( const buildEdges = (
operatorIds: string[], operatorIds: string[],


return obj; return obj;
}; };

export const isEdgeEqual = (previous: Edge, current: Edge) =>
previous.source === current.source &&
previous.target === current.target &&
previous.sourceHandle === current.sourceHandle;

export const buildNewPositionMap = (
categoryDataKeys: string[],
indexesInUse: number[],
) => {
return categoryDataKeys.reduce<Record<string, IPosition>>((pre, cur) => {
// take a coordinate
const effectiveIdxes = CategorizeAnchorPointPositions.map(
(x, idx) => idx,
).filter((x) => !indexesInUse.some((y) => y === x));
const idx = sample(effectiveIdxes);
if (idx !== undefined) {
indexesInUse.push(idx);
pre[cur] = { ...CategorizeAnchorPointPositions[idx], idx };
}

return pre;
}, {});
};

Laden…
Annuleren
Opslaan