Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

utils.ts 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. import { DSLComponents } from '@/interfaces/database/flow';
  2. import { removeUselessFieldsFromValues } from '@/utils/form';
  3. import { FormInstance, FormListFieldData } from 'antd';
  4. import { humanId } from 'human-id';
  5. import { curry, get, intersectionWith, isEqual, sample } from 'lodash';
  6. import pipe from 'lodash/fp/pipe';
  7. import isObject from 'lodash/isObject';
  8. import { Edge, Node, Position } from 'reactflow';
  9. import { v4 as uuidv4 } from 'uuid';
  10. import { CategorizeAnchorPointPositions, NodeMap, Operator } from './constant';
  11. import { ICategorizeItemResult, IPosition, NodeData } from './interface';
  12. const buildEdges = (
  13. operatorIds: string[],
  14. currentId: string,
  15. allEdges: Edge[],
  16. isUpstream = false,
  17. componentName: string,
  18. nodeParams: Record<string, unknown>,
  19. ) => {
  20. operatorIds.forEach((cur) => {
  21. const source = isUpstream ? cur : currentId;
  22. const target = isUpstream ? currentId : cur;
  23. if (!allEdges.some((e) => e.source === source && e.target === target)) {
  24. const edge: Edge = {
  25. id: uuidv4(),
  26. label: '',
  27. // type: 'step',
  28. source: source,
  29. target: target,
  30. // markerEnd: {
  31. // type: MarkerType.ArrowClosed,
  32. // color: 'rgb(157 149 225)',
  33. // width: 20,
  34. // height: 20,
  35. // },
  36. };
  37. if (componentName === Operator.Categorize && !isUpstream) {
  38. const categoryDescription =
  39. nodeParams.category_description as ICategorizeItemResult;
  40. const name = Object.keys(categoryDescription).find(
  41. (x) => categoryDescription[x].to === target,
  42. );
  43. if (name) {
  44. edge.sourceHandle = name;
  45. }
  46. }
  47. allEdges.push(edge);
  48. }
  49. });
  50. };
  51. export const buildNodesAndEdgesFromDSLComponents = (data: DSLComponents) => {
  52. const nodes: Node[] = [];
  53. let edges: Edge[] = [];
  54. Object.entries(data).forEach(([key, value]) => {
  55. const downstream = [...value.downstream];
  56. const upstream = [...value.upstream];
  57. const { component_name: componentName, params } = value.obj;
  58. nodes.push({
  59. id: key,
  60. type: NodeMap[value.obj.component_name as Operator] || 'ragNode',
  61. position: { x: 0, y: 0 },
  62. data: {
  63. label: componentName,
  64. name: humanId(),
  65. form: params,
  66. },
  67. sourcePosition: Position.Left,
  68. targetPosition: Position.Right,
  69. });
  70. buildEdges(upstream, key, edges, true, componentName, params);
  71. buildEdges(downstream, key, edges, false, componentName, params);
  72. });
  73. return { nodes, edges };
  74. };
  75. const buildComponentDownstreamOrUpstream = (
  76. edges: Edge[],
  77. nodeId: string,
  78. isBuildDownstream = true,
  79. ) => {
  80. return edges
  81. .filter((y) => y[isBuildDownstream ? 'source' : 'target'] === nodeId)
  82. .map((y) => y[isBuildDownstream ? 'target' : 'source']);
  83. };
  84. const removeUselessDataInTheOperator = curry(
  85. (operatorName: string, params: Record<string, unknown>) => {
  86. if (
  87. operatorName === Operator.Generate ||
  88. operatorName === Operator.Categorize
  89. ) {
  90. return removeUselessFieldsFromValues(params, '');
  91. }
  92. return params;
  93. },
  94. );
  95. // initialize data for operators without parameters
  96. // const initializeOperatorParams = curry((operatorName: string, values: any) => {
  97. // if (isEmpty(values)) {
  98. // return initialFormValuesMap[operatorName as Operator];
  99. // }
  100. // return values;
  101. // });
  102. const buildOperatorParams = (operatorName: string) =>
  103. pipe(
  104. removeUselessDataInTheOperator(operatorName),
  105. // initializeOperatorParams(operatorName), // Final processing, for guarantee
  106. );
  107. // construct a dsl based on the node information of the graph
  108. export const buildDslComponentsByGraph = (
  109. nodes: Node<NodeData>[],
  110. edges: Edge[],
  111. oldDslComponents: DSLComponents,
  112. ): DSLComponents => {
  113. const components: DSLComponents = {};
  114. nodes
  115. .filter((x) => x.data.label !== Operator.Note)
  116. .forEach((x) => {
  117. const id = x.id;
  118. const operatorName = x.data.label;
  119. components[id] = {
  120. obj: {
  121. ...(oldDslComponents[id]?.obj ?? {}),
  122. component_name: operatorName,
  123. params:
  124. buildOperatorParams(operatorName)(
  125. x.data.form as Record<string, unknown>,
  126. ) ?? {},
  127. },
  128. downstream: buildComponentDownstreamOrUpstream(edges, id, true),
  129. upstream: buildComponentDownstreamOrUpstream(edges, id, false),
  130. };
  131. });
  132. return components;
  133. };
  134. export const receiveMessageError = (res: any) =>
  135. res && (res?.response.status !== 200 || res?.data?.code !== 0);
  136. // Replace the id in the object with text
  137. export const replaceIdWithText = (
  138. obj: Record<string, unknown> | unknown[] | unknown,
  139. getNameById: (id?: string) => string | undefined,
  140. ) => {
  141. if (isObject(obj)) {
  142. const ret: Record<string, unknown> | unknown[] = Array.isArray(obj)
  143. ? []
  144. : {};
  145. Object.keys(obj).forEach((key) => {
  146. const val = (obj as Record<string, unknown>)[key];
  147. const text = typeof val === 'string' ? getNameById(val) : undefined;
  148. (ret as Record<string, unknown>)[key] = text
  149. ? text
  150. : replaceIdWithText(val, getNameById);
  151. });
  152. return ret;
  153. }
  154. return obj;
  155. };
  156. export const isEdgeEqual = (previous: Edge, current: Edge) =>
  157. previous.source === current.source &&
  158. previous.target === current.target &&
  159. previous.sourceHandle === current.sourceHandle;
  160. export const buildNewPositionMap = (
  161. currentKeys: string[],
  162. previousPositionMap: Record<string, IPosition>,
  163. ) => {
  164. // index in use
  165. const indexesInUse = Object.values(previousPositionMap).map((x) => x.idx);
  166. const previousKeys = Object.keys(previousPositionMap);
  167. const intersectionKeys = intersectionWith(
  168. previousKeys,
  169. currentKeys,
  170. (categoryDataKey: string, positionMapKey: string) =>
  171. categoryDataKey === positionMapKey,
  172. );
  173. // difference set
  174. const currentDifferenceKeys = currentKeys.filter(
  175. (x) => !intersectionKeys.some((y: string) => y === x),
  176. );
  177. const newPositionMap = currentDifferenceKeys.reduce<
  178. Record<string, IPosition>
  179. >((pre, cur) => {
  180. // take a coordinate
  181. const effectiveIdxes = CategorizeAnchorPointPositions.map(
  182. (x, idx) => idx,
  183. ).filter((x) => !indexesInUse.some((y) => y === x));
  184. const idx = sample(effectiveIdxes);
  185. if (idx !== undefined) {
  186. indexesInUse.push(idx);
  187. pre[cur] = { ...CategorizeAnchorPointPositions[idx], idx };
  188. }
  189. return pre;
  190. }, {});
  191. return { intersectionKeys, newPositionMap };
  192. };
  193. export const isKeysEqual = (currentKeys: string[], previousKeys: string[]) => {
  194. return isEqual(currentKeys.sort(), previousKeys.sort());
  195. };
  196. export const getOperatorIndex = (handleTitle: string) => {
  197. return handleTitle.split(' ').at(-1);
  198. };
  199. // Get the value of other forms except itself
  200. export const getOtherFieldValues = (
  201. form: FormInstance,
  202. formListName: string = 'items',
  203. field: FormListFieldData,
  204. latestField: string,
  205. ) =>
  206. (form.getFieldValue([formListName]) ?? [])
  207. .map((x: any) => {
  208. return get(x, latestField);
  209. })
  210. .filter(
  211. (x: string) =>
  212. x !== form.getFieldValue([formListName, field.name, latestField]),
  213. );
  214. export const generateSwitchHandleText = (idx: number) => {
  215. return `Case ${idx + 1}`;
  216. };
  217. export const getNodeDragHandle = (nodeType?: string) => {
  218. return nodeType === Operator.Note ? '.note-drag-handle' : undefined;
  219. };
  220. const splitName = (name: string) => {
  221. const names = name.split('_');
  222. const type = names.at(0);
  223. const index = Number(names.at(-1));
  224. return { type, index };
  225. };
  226. export const generateNodeNamesWithIncreasingIndex = (
  227. name: string,
  228. nodes: Node[],
  229. ) => {
  230. const templateNameList = nodes
  231. .filter((x) => {
  232. const temporaryName = x.data.name;
  233. const { type, index } = splitName(temporaryName);
  234. return (
  235. temporaryName.match(/_/g)?.length === 1 &&
  236. type === name &&
  237. !isNaN(index)
  238. );
  239. })
  240. .map((x) => {
  241. const temporaryName = x.data.name;
  242. const { index } = splitName(temporaryName);
  243. return {
  244. idx: index,
  245. name: temporaryName,
  246. };
  247. })
  248. .sort((a, b) => a.idx - b.idx);
  249. let index: number = 0;
  250. for (let i = 0; i < templateNameList.length; i++) {
  251. const idx = templateNameList[i]?.idx;
  252. const nextIdx = templateNameList[i + 1]?.idx;
  253. if (idx + 1 !== nextIdx) {
  254. index = idx + 1;
  255. break;
  256. }
  257. }
  258. return `${name}_${index}`;
  259. };
  260. export const duplicateNodeForm = (nodeData?: NodeData) => {
  261. const form: Record<string, any> = { ...(nodeData?.form ?? {}) };
  262. // Delete the downstream node corresponding to the to field of the Categorize operator
  263. if (nodeData?.label === Operator.Categorize) {
  264. form.category_description = Object.keys(form.category_description).reduce<
  265. Record<string, Record<string, any>>
  266. >((pre, cur) => {
  267. pre[cur] = {
  268. ...form.category_description[cur],
  269. to: undefined,
  270. };
  271. return pre;
  272. }, {});
  273. }
  274. // Delete the downstream nodes corresponding to the yes and no fields of the Relevant operator
  275. if (nodeData?.label === Operator.Relevant) {
  276. form.yes = undefined;
  277. form.no = undefined;
  278. }
  279. return {
  280. ...(nodeData ?? {}),
  281. form,
  282. };
  283. };
  284. export const getDrawerWidth = () => {
  285. return window.innerWidth > 1278 ? '40%' : 470;
  286. };