From 8504aa42e1b7a8ea177f78ee8b4a3083bb76427b Mon Sep 17 00:00:00 2001 From: ThyeeZz Date: Mon, 23 Dec 2024 09:57:47 +0800 Subject: [PATCH 1/3] complete new sizing tool --- package.json | 6 + pnpm-lock.yaml | 491 +++++++++- src/archive/sizing.txt | 903 ++++++++++++++++++ src/components/sizing/index.ts | 3 + .../sizing/sizingInput/index.module.less | 54 ++ src/components/sizing/sizingInput/index.tsx | 72 ++ .../sizing/sizingRange/index.module.less | 43 + src/components/sizing/sizingRange/index.tsx | 106 ++ .../sizing/sizingSwitch/index.module.less | 0 src/components/sizing/sizingSwitch/index.tsx | 50 + src/components/ui/checkbox.tsx | 28 + src/components/ui/collapsible.tsx | 9 + src/components/ui/index.ts | 6 + src/components/ui/radio-group.tsx | 42 + src/components/ui/select.tsx | 2 +- src/components/ui/tooltip.tsx | 28 + src/consts/sizing.ts | 69 ++ src/pages/tools/sizing.tsx | 839 +--------------- src/parts/sizing/formSection.tsx | 249 +++++ src/parts/sizing/index.module.less | 93 ++ src/parts/sizing/indexTypeComponent.tsx | 48 + src/parts/sizing/resultSection.tsx | 7 + src/styles/sizingTool.module.less | 29 + src/types/sizing.ts | 22 + src/utils/sizing.ts | 29 + tailwind.config.js | 1 + 26 files changed, 2400 insertions(+), 829 deletions(-) create mode 100644 src/archive/sizing.txt create mode 100644 src/components/sizing/index.ts create mode 100644 src/components/sizing/sizingInput/index.module.less create mode 100644 src/components/sizing/sizingInput/index.tsx create mode 100644 src/components/sizing/sizingRange/index.module.less create mode 100644 src/components/sizing/sizingRange/index.tsx create mode 100644 src/components/sizing/sizingSwitch/index.module.less create mode 100644 src/components/sizing/sizingSwitch/index.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/collapsible.tsx create mode 100644 src/components/ui/index.ts create mode 100644 src/components/ui/radio-group.tsx create mode 100644 src/components/ui/tooltip.tsx create mode 100644 src/consts/sizing.ts create mode 100644 src/parts/sizing/formSection.tsx create mode 100644 src/parts/sizing/index.module.less create mode 100644 src/parts/sizing/indexTypeComponent.tsx create mode 100644 src/parts/sizing/resultSection.tsx create mode 100644 src/types/sizing.ts create mode 100644 src/utils/sizing.ts diff --git a/package.json b/package.json index 771ca6e67..538dd5fc0 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,13 @@ "@mui/material": "^5.2.5", "@mui/x-tree-view": "^6.17.0", "@octokit/core": "^3.5.1", + "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-collapsible": "^1.1.2", + "@radix-ui/react-radio-group": "^1.2.2", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.1.2", + "@radix-ui/react-tooltip": "^1.1.6", "@zilliz/toolkit": "^0.1.11", "axios": "^0.28.0", "class-variance-authority": "^0.7.0", @@ -34,6 +39,7 @@ "cropperjs": "^1.5.12", "cssnano-preset-lite": "^2.0.2", "d3-format": "^3.1.0", + "d3-scale": "^4.0.2", "dayjs": "^1.10.7", "gestalt": "^146.5.4", "gray-matter": "^4.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 211158274..a2a18ce6f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,12 +50,27 @@ importers: '@octokit/core': specifier: ^3.5.1 version: 3.6.0 + '@radix-ui/react-checkbox': + specifier: ^1.1.3 + version: 1.1.3(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-collapsible': + specifier: ^1.1.2 + version: 1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-radio-group': + specifier: ^1.2.2 + version: 1.2.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-select': specifier: ^2.1.1 version: 2.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-slot': specifier: ^1.0.2 version: 1.0.2(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-switch': + specifier: ^1.1.2 + version: 1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-tooltip': + specifier: ^1.1.6 + version: 1.1.6(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@zilliz/toolkit': specifier: ^0.1.11 version: 0.1.11 @@ -77,6 +92,9 @@ importers: d3-format: specifier: ^3.1.0 version: 3.1.0 + d3-scale: + specifier: ^4.0.2 + version: 4.0.2 dayjs: specifier: ^1.10.7 version: 1.11.11 @@ -1131,6 +1149,9 @@ packages: '@radix-ui/primitive@1.1.0': resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} + '@radix-ui/primitive@1.1.1': + resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} + '@radix-ui/react-arrow@1.1.0': resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==} peerDependencies: @@ -1144,6 +1165,45 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-arrow@1.1.1': + resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.1.3': + resolution: {integrity: sha512-HD7/ocp8f1B3e6OHygH0n7ZKjONkhciy1Nh0yuBgObqThc3oyx+vuMfFHKAknXRHHWVE9XvXStxJFyjUmB8PIw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.2': + resolution: {integrity: sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.0': resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==} peerDependencies: @@ -1157,6 +1217,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-collection@1.1.1': + resolution: {integrity: sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-compose-refs@1.0.1': resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -1175,6 +1248,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-compose-refs@1.1.1': + resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context@1.1.0': resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} peerDependencies: @@ -1184,6 +1266,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.1': + resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-direction@1.1.0': resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} peerDependencies: @@ -1206,6 +1297,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dismissable-layer@1.1.3': + resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-guards@1.1.0': resolution: {integrity: sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==} peerDependencies: @@ -1250,6 +1354,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popper@1.2.1': + resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-portal@1.1.1': resolution: {integrity: sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==} peerDependencies: @@ -1263,6 +1380,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-portal@1.1.3': + resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.2': + resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.0.0': resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} peerDependencies: @@ -1276,6 +1419,45 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.0.1': + resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.2.2': + resolution: {integrity: sha512-E0MLLGfOP0l8P/NxgVzfXJ8w3Ch8cdO6UDzJfDChu4EJDy+/WdO5LqpdY8PYnCErkmZH3gZhDL1K7kQ41fAHuQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.1': + resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-select@2.1.1': resolution: {integrity: sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==} peerDependencies: @@ -1307,6 +1489,41 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.1.1': + resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.1.2': + resolution: {integrity: sha512-zGukiWHjEdBCRyXvKR6iXAQG6qXm2esuAD6kDOi9Cn+1X6ev3ASo4+CsYaD6Fov9r/AQFekqnD/7+V0Cs6/98g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.1.6': + resolution: {integrity: sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-use-callback-ref@1.1.0': resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} peerDependencies: @@ -1383,6 +1600,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-visually-hidden@1.1.1': + resolution: {integrity: sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/rect@1.1.0': resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} @@ -2356,10 +2586,34 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + d3-format@3.1.0: resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} engines: {node: '>=12'} + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -3094,6 +3348,10 @@ packages: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -6271,6 +6529,8 @@ snapshots: '@radix-ui/primitive@1.1.0': {} + '@radix-ui/primitive@1.1.1': {} + '@radix-ui/react-arrow@1.1.0(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -6279,6 +6539,44 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-arrow@1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-checkbox@1.1.3(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-presence': 1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-collapsible@1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-presence': 1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-collection@1.1.0(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) @@ -6290,6 +6588,17 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-collection@1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-compose-refs@1.0.1(@types/react@18.3.3)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.6 @@ -6303,12 +6612,24 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.3)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-context@1.1.0(@types/react@18.3.3)(react@18.2.0)': dependencies: react: 18.2.0 optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-context@1.1.1(@types/react@18.3.3)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-direction@1.1.0(@types/react@18.3.3)(react@18.2.0)': dependencies: react: 18.2.0 @@ -6327,6 +6648,18 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-dismissable-layer@1.1.3(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-focus-guards@1.1.0(@types/react@18.3.3)(react@18.2.0)': dependencies: react: 18.2.0 @@ -6367,6 +6700,23 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-popper@1.2.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@floating-ui/react-dom': 2.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-arrow': 1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/rect': 1.1.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-portal@1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -6376,6 +6726,24 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-portal@1.1.3(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-presence@1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-primitive@2.0.0(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.2.0) @@ -6384,6 +6752,47 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-primitive@2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-radio-group@1.2.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-presence': 1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-roving-focus': 1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-roving-focus@1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-select@2.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/number': 1.1.0 @@ -6427,6 +6836,46 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-slot@1.1.1(@types/react@18.3.3)(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-switch@1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-tooltip@1.1.6(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-popper': 1.2.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-portal': 1.1.3(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-presence': 1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-visually-hidden': 1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.3)(react@18.2.0)': dependencies: react: 18.2.0 @@ -6481,6 +6930,14 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-visually-hidden@1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/rect@1.1.0': {} '@rushstack/eslint-patch@1.10.3': {} @@ -8033,8 +8490,34 @@ snapshots: csstype@3.1.3: {} + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + d3-format@3.1.0: {} + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + damerau-levenshtein@1.0.8: {} data-view-buffer@1.0.1: @@ -8304,7 +8787,7 @@ snapshots: eslint: 8.19.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 2.7.1(eslint-plugin-import@2.29.1(eslint@8.19.0))(eslint@8.19.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.19.0)(typescript@5.4.5))(eslint-import-resolver-typescript@2.7.1)(eslint@8.19.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.19.0)(typescript@5.4.5))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.29.1(eslint@8.19.0))(eslint@8.19.0))(eslint@8.19.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.19.0) eslint-plugin-react: 7.34.2(eslint@8.19.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.19.0) @@ -8326,7 +8809,7 @@ snapshots: dependencies: debug: 4.3.4 eslint: 8.19.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.19.0)(typescript@5.4.5))(eslint-import-resolver-typescript@2.7.1)(eslint@8.19.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.19.0)(typescript@5.4.5))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.29.1(eslint@8.19.0))(eslint@8.19.0))(eslint@8.19.0) glob: 7.2.3 is-glob: 4.0.3 resolve: 1.22.8 @@ -8345,7 +8828,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.19.0)(typescript@5.4.5))(eslint-import-resolver-typescript@2.7.1)(eslint@8.19.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.19.0)(typescript@5.4.5))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.29.1(eslint@8.19.0))(eslint@8.19.0))(eslint@8.19.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -8995,6 +9478,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.0.6 + internmap@2.0.3: {} + invariant@2.2.4: dependencies: loose-envify: 1.4.0 diff --git a/src/archive/sizing.txt b/src/archive/sizing.txt new file mode 100644 index 000000000..f7e85aea8 --- /dev/null +++ b/src/archive/sizing.txt @@ -0,0 +1,903 @@ +import React, { useState, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import Layout from '@/components/layout/commonLayout'; +import classes from '@/styles/sizingTool.module.less'; +import pageClasses from '@/styles/responsive.module.less'; +import { InfoFilled, DownloadIcon } from '@/components/icons'; +import SizingToolCard from '@/components/card/sizingToolCard'; +import SizingConfigCard from '@/components/card/sizingToolCard/sizingConfigCard'; +import clsx from 'clsx'; +import Slider from '@mui/material/Slider'; +import TextField from '@mui/material/TextField'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import FormControl from '@mui/material/FormControl'; +import Select from '@mui/material/Select'; +import Radio from '@mui/material/Radio'; +import RadioGroup from '@mui/material/RadioGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Head from 'next/head'; +import CustomButton from '@/components/customButton'; + +import { + memorySizeCalculator, + rawFileSizeCalculator, + commonCoordCalculator, + unitBYTE2Any, + indexNodeCalculator, + queryNodeCalculator, + isBetween, + rootCoordCalculator, + dataNodeCalculator, + proxyCalculator, + helmYmlGenerator, + operatorYmlGenerator, + etcdCalculator, + minioCalculator, + pulsarCalculator, + kafkaCalculator, + mixCoordCalculator, + unitAny2BYTE, +} from '@/utils/sizingTool'; +import { CustomizedContentDialogs } from '@/components/dialog/Dialog'; +import HighlightBlock from '@/components/card/sizingToolCard/codeBlock'; +import { + HELM_CONFIG_FILE_NAME, + OPERATOR_CONFIG_FILE_NAME, + REQUIRE_MORE, + INDEX_TYPE_OPTIONS, + SEGMENT_SIZE_OPTIONS, + IndexTypeEnum, +} from '@/components/card/sizingToolCard/constants'; +import { ABSOLUTE_BASE_URL } from '@/consts'; + +// one million +const $1M = Math.pow(10, 6); + +const defaultSizeContent = { + size: REQUIRE_MORE, + cpu: 0, + memory: 0, + amount: 0, +}; + +enum FromKeysEnum { + Nb = 'nb', + D = 'd', + IndexType = 'indexType', + M = 'm', + Nlist = 'nlist', + SegmentSize = 'segmentSize', + aApacheType = 'apacheType', +} + +export default function SizingTool() { + const { t } = useTranslation('sizingTool'); + + const [dialogState, setDialogState] = useState({ + open: false, + title: '', + children: <>, + }); + const [form, setForm] = useState<{ [key in FromKeysEnum]: any }>({ + // number of vectors + nb: { + value: 1, + showError: false, + helpText: '', + placeholder: `[1, 10000]`, + validation: { + validate: isBetween, + params: { min: 1, max: 10000 }, + errorMsg: 'Number of vectors should be an integer between [1, 10000]', + }, + }, + // dimensions + d: { + value: 128, + showError: false, + helpText: '', + placeholder: '[1, 10000]', + validation: { + validate: isBetween, + params: { min: 1, max: 10000 }, + errorMsg: 'Dimensions should be an integer between [1, 10000]', + }, + }, + // index type + indexType: { + value: INDEX_TYPE_OPTIONS[0].value, + showError: false, + }, + // index parameters + m: { + value: 8, + showError: false, + }, + // ivf parameters + nlist: { + value: 1024, + showError: false, + helpText: '', + placeholder: '[1, 10000]', + validation: { + validate: isBetween, + params: { min: 1, max: 10000 }, + errorMsg: 'nList should be an integer between [1, 10000]', + }, + }, + // segment size + segmentSize: { + value: SEGMENT_SIZE_OPTIONS[0].value, + showError: false, + }, + apacheType: 'pulsar', + }); + + const handleFormValueChange = (val: any, key: FromKeysEnum) => { + const { validation } = form[key]; + + if (!validation) { + setForm(v => ({ + ...v, + [key]: { + ...v[key], + value: val, + }, + })); + return; + } + + const isValidated = validation.validate(val, validation.params); + if (isValidated) { + setForm(v => ({ + ...v, + [key]: { + ...v[key], + value: val, + showError: false, + helpText: '', + }, + })); + } else { + setForm(v => ({ + ...v, + [key]: { + ...v[key], + value: val, + showError: true, + helpText: validation.errorMsg, + }, + })); + } + }; + + const hanldeRemovePromot = key => { + if (!form[key].showError) { + return; + } + setForm(v => ({ + ...v, + [key]: { + ...v[key], + value: '', + showError: false, + helpText: '', + }, + })); + }; + + const handleApacheChange = (e, value) => { + setForm(v => ({ + ...v, + apacheType: value, + })); + }; + + const calcResult = useMemo(() => { + const { nb, d, indexType, nlist, m, segmentSize } = form; + + const nbVal = Number(nb.value) * $1M || 0; + const dVal = Number(d.value) || 0; + const nlistVal = Number(nlist.value) || 0; + const mVal = Number(m.value) || 0; + const sVal = Number(segmentSize.value) || 0; + + const isErrorParameters = Object.values(form).some( + v => v.showError || v.value === '' + ); + + if (isErrorParameters) { + const etcdData = etcdCalculator(); + const minioData = minioCalculator(); + const pulsarData = pulsarCalculator(); + const kafkaData = kafkaCalculator(); + return { + memorySize: { size: 0, unit: 'B' }, + rawFileSize: { size: 0, unit: 'B' }, + dataNode: defaultSizeContent, + queryNode: defaultSizeContent, + indexNode: defaultSizeContent, + proxy: defaultSizeContent, + mixCoord: defaultSizeContent, + commonCoord: defaultSizeContent, + etcdData, + minioData, + pulsarData, + kafkaData, + }; + } + + const { memorySize, theorySize } = memorySizeCalculator({ + nb: nbVal, + d: dVal, + nlist: nlistVal, + M: mVal, + indexType: indexType.value, + }); + + const rawFileSize = rawFileSizeCalculator({ d: dVal, nb: nbVal }); + const dataNode = dataNodeCalculator(nbVal); + const indexNode = indexNodeCalculator(theorySize, sVal); + const proxy = proxyCalculator(memorySize); + const queryNode = queryNodeCalculator(memorySize); + const commonCoord = commonCoordCalculator(memorySize); + const mixCoord = mixCoordCalculator(nbVal); + const etcdData = etcdCalculator(rawFileSize); + const minioData = minioCalculator(rawFileSize, theorySize); + const pulsarData = pulsarCalculator(rawFileSize); + const kafkaData = kafkaCalculator(rawFileSize); + return { + memorySize: unitBYTE2Any(memorySize), + rawFileSizeByte: rawFileSize, + rawFileSize: unitBYTE2Any(rawFileSize), + mixCoord, + indexNode, + dataNode, + queryNode, + proxy, + commonCoord, + etcdData, + minioData, + pulsarData, + kafkaData, + }; + }, [form]); + const { milvusData, dependencyData, totalData, queryNodeDiskData } = + useMemo(() => { + const calculateList = [ + calcResult.proxy, + calcResult.mixCoord, + calcResult.indexNode, + calcResult.dataNode, + calcResult.queryNode, + ]; + + const milvusData = { + core: calculateList.reduce((acc, cur) => { + acc += cur.cpu * cur.amount; + return acc; + }, 0), + memory: calculateList.reduce((acc, cur) => { + acc += cur.memory * cur.amount; + return acc; + }, 0), + }; + + const secondPartData = { + core: + calcResult.etcdData.cpu * calcResult.etcdData.podNumber + + calcResult.minioData.cpu * calcResult.minioData.podNumber, + memory: + calcResult.etcdData.memory * calcResult.etcdData.podNumber + + calcResult.minioData.memory * calcResult.minioData.podNumber, + ssd: + calcResult.etcdData.pvcPerPodUnit === 'G' + ? calcResult.etcdData.pvcPerPodSize * calcResult.etcdData.podNumber + : (calcResult.etcdData.pvcPerPodSize * + calcResult.etcdData.podNumber) / + 1024, + disk: + calcResult.minioData.pvcPerPodUnit === 'G' + ? calcResult.minioData.pvcPerPodSize * + calcResult.minioData.podNumber + : (calcResult.minioData.pvcPerPodSize * + calcResult.minioData.podNumber) / + 1024, + }; + + const pulsarBookieData = + calcResult.pulsarData.bookie.ledgers.unit === 'G' + ? calcResult.pulsarData.bookie.ledgers.size * + calcResult.pulsarData.bookie.podNum.value + : (calcResult.pulsarData.bookie.ledgers.size * + calcResult.pulsarData.bookie.podNum.value) / + 1024; + const pulsarZookeeperData = + calcResult.pulsarData.zookeeper.pvc.unit === 'G' + ? calcResult.pulsarData.zookeeper.pvc.size * + calcResult.pulsarData.zookeeper.podNum.value + : (calcResult.pulsarData.zookeeper.pvc.size * + calcResult.pulsarData.zookeeper.podNum.value) / + 1024; + const pulsarData = { + core: Object.values(calcResult.pulsarData).reduce((acc, cur) => { + acc += cur.cpu.size * cur.podNum.value; + return acc; + }, 0), + memory: Object.values(calcResult.pulsarData).reduce((acc, cur) => { + acc += cur.memory.size * cur.podNum.value; + return acc; + }, 0), + ssd: pulsarBookieData + pulsarZookeeperData, + disk: + calcResult.pulsarData.bookie.journal.unit === 'G' + ? calcResult.pulsarData.bookie.journal.size * + calcResult.pulsarData.bookie.podNum.value + : (calcResult.pulsarData.bookie.journal.size * + calcResult.pulsarData.bookie.podNum.value) / + 1024, + }; + + const kafkaData = { + core: Object.values(calcResult.kafkaData).reduce((acc, cur) => { + acc += cur.cpu.size * cur.podNum.value; + return acc; + }, 0), + memory: Object.values(calcResult.kafkaData).reduce((acc, cur) => { + acc += cur.memory.size * cur.podNum.value; + return acc; + }, 0), + ssd: Object.values(calcResult.kafkaData).reduce((acc, cur) => { + if (cur.pvc?.isSSD) { + if (cur.pvc.unit === 'G') { + acc += cur.pvc.size * cur.podNum.value; + } else { + acc += (cur.pvc.size * cur.podNum.value) / 1024; + } + } + return acc; + }, 0), + disk: Object.values(calcResult.kafkaData).reduce((acc, cur) => { + if (!cur.pvc?.isSSD) { + if (cur.pvc.unit === 'G') { + acc += cur.pvc.size * cur.podNum.value; + } else { + acc += (cur.pvc.size * cur.podNum.value) / 1024; + } + } + return acc; + }, 0), + }; + + const thirdPartData = + form.apacheType === 'pulsar' ? pulsarData : kafkaData; + + let queryNodeDiskData = undefined; + + if (form.indexType.value === IndexTypeEnum.DISKANN) { + const rawFileSizeValue = unitBYTE2Any(calcResult.rawFileSizeByte, 'GB'); + queryNodeDiskData = { + totalQueryNodeDisk: Math.ceil(rawFileSizeValue.size * 10) / 10, + key: 'SSD', + value: `${ + Math.ceil( + (rawFileSizeValue.size / calcResult.queryNode.amount) * 10 + ) / 10 + } GB`, + }; + } + + const totalData = { + core: milvusData.core + secondPartData.core + thirdPartData.core, + memory: + milvusData.memory + secondPartData.memory + thirdPartData.memory, + ssd: queryNodeDiskData + ? Math.ceil(secondPartData.ssd + thirdPartData.ssd) + + queryNodeDiskData.totalQueryNodeDisk + : Math.ceil(secondPartData.ssd + thirdPartData.ssd), + disk: Math.ceil(secondPartData.disk + thirdPartData.disk), + }; + + const dependencyData = { + core: secondPartData.core + thirdPartData.core, + memory: secondPartData.memory + thirdPartData.memory, + ssd: Math.ceil(secondPartData.ssd + thirdPartData.ssd), + disk: Math.ceil(secondPartData.disk + thirdPartData.disk), + }; + + return { + milvusData, + dependencyData, + totalData, + queryNodeDiskData, + }; + }, [calcResult, form.apacheType]); + + const handleDownloadYmlFile = (content, fileName) => { + if (typeof window !== 'undefined') { + const blob = new Blob([content], { + type: 'text/plain', + }); + + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = `${fileName}.yml`; + a.click(); + } + }; + + const handleDownloadHelm = () => { + const content = helmYmlGenerator(calcResult, form.apacheType); + handleDownloadYmlFile(content, HELM_CONFIG_FILE_NAME); + }; + const handleDownloadOperator = () => { + const content = operatorYmlGenerator(calcResult, form.apacheType); + handleDownloadYmlFile(content, OPERATOR_CONFIG_FILE_NAME); + }; + + const handleCloseDialog = () => { + setDialogState(v => ({ + ...v, + open: false, + })); + }; + + const handleOpenInstallGuide = type => { + const title = type === 'helm' ? t('buttons.helm') : t('buttons.operator'); + + setDialogState({ + open: true, + title, + children: , + }); + }; + + return ( +
+ + + + Milvus Sizing Tool · Vector Database built for scalable similarity + search + + + + +
+

{t('title')}

+
+ + + +

{t('subTitle')}

+
+
+
+
+

{t('labels.dataSize')}

+ +
+

{t('labels.vector')}

+ hanldeRemovePromot('nb')} + onChange={e => { + handleFormValueChange(e.target.value, FromKeysEnum.Nb); + }} + /> +
+ +
+

{t('labels.dimension')}

+ hanldeRemovePromot('d')} + onChange={e => { + handleFormValueChange(e.target.value, FromKeysEnum.D); + }} + /> +
+
+ +
+

{t('labels.indexType')}

+ +
+ + {t('labels.index')} + + +
+ +
+ {form.indexType.value === 'FLAT' ? null : form.indexType + .value === 'HNSW' ? ( + <> +

+ {t('labels.indexParam')} +

+

+ {t('labels.m')} +

+
+ { + handleFormValueChange(value, FromKeysEnum.M); + }} + marks={[ + { label: '4', value: 4 }, + { label: '64', value: 64 }, + ]} + /> +
+ + ) : ( + <> +

{t('labels.m')}

+ hanldeRemovePromot('nlist')} + onChange={e => { + handleFormValueChange( + e.target.value, + FromKeysEnum.Nlist + ); + }} + /> + + )} +
+ +
+

{t('labels.segmentSize')}

+ + {t('labels.segment')} + + + + + + } + label="Pulsar" + /> + } + label="Kafka" + /> + + +
+
+
+
+
+

{t('capacity')}

+ +
+ + +
+
+ +
+

{t('setups.title')}

+ +
+
+
+

{t('total')}

+

+ {t('coreInfo', { + core: totalData.core, + memory: totalData.memory, + })} +

+
+
+
+
+

{t('ssd')}

+

+ {t('sizeInfo', { size: totalData.ssd })} +

+
+
+

{t('disk')}

+

+ {t('sizeInfo', { size: totalData.disk })} +

+
+
+
+
+
+
+

{t('milvus')}

+
+
+

{t('total')}: 

+

+ {t('coreInfo', { + core: milvusData.core, + memory: milvusData.memory, + })} +

+
+ {queryNodeDiskData && ( +
+

SSD: 

+

+ {queryNodeDiskData.totalQueryNodeDisk} GB +

+
+ )} +
+
+ +
+
+ + +
+ +
+ + + +
+
+
+
+
+

{t('dependency')}

+
+
+

{t('total')}: 

+

+ {t('coreInfo', { + core: dependencyData.core, + memory: dependencyData.memory, + })} +

+
+
+

{t('ssd')}: 

+

+ {t('sizeInfo', { size: dependencyData.ssd })} +

+
+
+

{t('disk')}: 

+

+ {t('sizeInfo', { size: dependencyData.disk })} +

+
+
+
+ +
+ + + +

{t('dependencyNote')}

+
+ +
+ + {/* Minio */} + +
+ + {/* pulsar or kafka */} +
+ {form.apacheType === 'pulsar' ? ( + + ) : ( + + )} +
+
+ +
+
+ } + > + {t('buttons.helm')} + + handleOpenInstallGuide('helm')} + variant="outlined" + > + {t('buttons.guide')} + +
+
+ } + > + {t('buttons.operator')} + + handleOpenInstallGuide('operator')} + variant="outlined" + > + {t('buttons.guide')} + +
+
+
+
+
+
+ +
+ ); +} diff --git a/src/components/sizing/index.ts b/src/components/sizing/index.ts new file mode 100644 index 000000000..dad92bf79 --- /dev/null +++ b/src/components/sizing/index.ts @@ -0,0 +1,3 @@ +export * from './sizingInput'; +export * from './sizingRange'; +export * from './sizingSwitch'; diff --git a/src/components/sizing/sizingInput/index.module.less b/src/components/sizing/sizingInput/index.module.less new file mode 100644 index 000000000..7f27c748e --- /dev/null +++ b/src/components/sizing/sizingInput/index.module.less @@ -0,0 +1,54 @@ +@import url('@/styles/global.module.less'); + +.inputContainer { + width: 140px; + max-width: 140px; +} + +.fullWidth { + width: 100%; + max-width: 100%; +} + +.inputLabel { + .paragraph6(); + margin-bottom: 10px; +} + +.inputWrapper { + border-radius: 8px; + border: 1px solid #e8eaee; + background: #fff; + position: relative; + + input { + .paragraph6(); + width: 100%; + height: 100%; + border: none; + outline: none; + padding: 0; + margin: 0; + } +} + +.mediumSize { + height: 36px; + padding: 9px 12px; + padding-right: 52px; +} +.smallSize { + height: 28px; + padding: 5px 12px; + padding-right: 52px; +} +.unit { + .paragraph6-regular(); + position: absolute; + display: flex; + align-items: center; + right: 12px; + top: 0; + bottom: 0; + color: #9ca6b4; +} diff --git a/src/components/sizing/sizingInput/index.tsx b/src/components/sizing/sizingInput/index.tsx new file mode 100644 index 000000000..a5682c96c --- /dev/null +++ b/src/components/sizing/sizingInput/index.tsx @@ -0,0 +1,72 @@ +import classes from './index.module.less'; + +import * as React from 'react'; +import { cn } from '@/utils/merge'; + +interface SizingInputPropsType extends React.ComponentProps<'input'> { + unit?: string; + label?: React.ReactNode; + customSize?: 'small' | 'medium'; + fullWidth?: boolean; + classes?: { + root?: string; + input?: string; + unit?: string; + }; +} + +const SizingInput = React.forwardRef( + ( + { + className, + unit = '', + label = '', + customSize = 'medium', + classes: customClasses, + fullWidth = false, + type, + ...props + }, + ref + ) => { + const { + root = '', + input: inputClass = '', + unit: unitClass = '', + } = customClasses || {}; + return ( +
+ {label &&

{label}

} +
+ + {unit && {unit}} +
+
+ ); + } +); +SizingInput.displayName = 'SizingInput'; + +export { SizingInput }; diff --git a/src/components/sizing/sizingRange/index.module.less b/src/components/sizing/sizingRange/index.module.less new file mode 100644 index 000000000..3eaed554d --- /dev/null +++ b/src/components/sizing/sizingRange/index.module.less @@ -0,0 +1,43 @@ +@import url('@/styles/global.module.less'); + +.rangeContainer { + width: 100%; +} + +.label { + .paragraph4(); +} + +.customMarkLabel { + .paragraph6(); + top: 20px; + color: #667176; +} + +.customMark { + background-color: #00b3ff; +} + +.customRail { + height: 4px; + background-color: #e0f2fc; +} + +.customTrack { + background-color: #00b3ff; + border: none; +} + +.customThumb { + width: 14px; + height: 14px; + border-radius: 50%; + background-color: #00b3ff; + border: 2px solid #fff; +} + +.rangeWrapper { + display: flex; + align-items: center; + gap: 32px; +} diff --git a/src/components/sizing/sizingRange/index.tsx b/src/components/sizing/sizingRange/index.tsx new file mode 100644 index 000000000..c35fc12f2 --- /dev/null +++ b/src/components/sizing/sizingRange/index.tsx @@ -0,0 +1,106 @@ +import * as React from 'react'; +import { styled } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import Slider from '@mui/material/Slider'; +import classes from './index.module.less'; +import clsx from 'clsx'; +import { scaleLinear, scalePow } from 'd3-scale'; +import { SizingInput } from '../sizingInput'; + +type RangeConfigType = { + min: number; + max: number; + defaultValue: number; + range: number[]; + domain: number[]; +}; + +interface SizingRangePropsType { + rangeConfig: RangeConfigType; + label?: React.ReactNode; + classes?: { + root?: string; + label?: string; + }; + value: number; + onRangeChange: (value: number) => void; + unit?: string; +} + +export const SizingRange = (props: SizingRangePropsType) => { + const { + rangeConfig, + label, + classes: customClasses = {}, + onRangeChange, + unit, + value, + } = props; + const { root, label: labelCLass } = customClasses; + + // domain: 0 - 100 under progress + // range: real value + const getRangeValue = scaleLinear() + .domain(rangeConfig.domain) + .range(rangeConfig.range); + + const getDomainValue = scaleLinear() + .domain(rangeConfig.range) + .range(rangeConfig.domain); + + const marks = rangeConfig.domain.map((item, index) => { + return { + value: item, + label: rangeConfig.range[index], + }; + }); + + function valuetext(value: number) { + return `${value}`; + } + + const handleRangeChange = (newValue: number | number[]) => { + const domainValue = Math.round(getRangeValue(newValue)); + onRangeChange(domainValue); + }; + + const handleInputChange = (e: React.ChangeEvent) => { + const realValue = Number(e.target.value); + handleRangeChange(realValue); + }; + + return ( +
+ {label &&

{label}

} +
+ { + handleRangeChange(newVal); + }} + classes={{ + markLabel: classes.customMarkLabel, + markLabelActive: classes.customMarkLabel, + mark: classes.customMark, + markActive: classes.customMark, + rail: classes.customRail, + track: classes.customTrack, + thumb: classes.customThumb, + }} + /> + +
+
+ ); +}; diff --git a/src/components/sizing/sizingSwitch/index.module.less b/src/components/sizing/sizingSwitch/index.module.less new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/sizing/sizingSwitch/index.tsx b/src/components/sizing/sizingSwitch/index.tsx new file mode 100644 index 000000000..652055928 --- /dev/null +++ b/src/components/sizing/sizingSwitch/index.tsx @@ -0,0 +1,50 @@ +import classes from './index.module.less'; +import * as React from 'react'; +import * as SwitchPrimitives from '@radix-ui/react-switch'; + +import { cn } from '@/utils/merge'; + +const SizingSwitch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, checked, onChange, ...props }, ref) => ( + + + + + + + + + +)); +SizingSwitch.displayName = 'SizingSwitch'; + +export { SizingSwitch }; diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 000000000..e80572aea --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; +import { Check } from 'lucide-react'; + +import { cn } from '@/utils/merge'; + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx new file mode 100644 index 000000000..a23e7a281 --- /dev/null +++ b/src/components/ui/collapsible.tsx @@ -0,0 +1,9 @@ +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" + +const Collapsible = CollapsiblePrimitive.Root + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts new file mode 100644 index 000000000..a3a2caaa4 --- /dev/null +++ b/src/components/ui/index.ts @@ -0,0 +1,6 @@ +export * from './checkbox'; +export * from './collapsible'; +export * from './radio-group'; +export * from './checkbox'; +export * from './select'; +export * from './tooltip'; diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx new file mode 100644 index 000000000..adb1dbef5 --- /dev/null +++ b/src/components/ui/radio-group.tsx @@ -0,0 +1,42 @@ +import * as React from "react" +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" +import { Circle } from "lucide-react" + +import { cn } from "@/utils/merge" + +const RadioGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + ) +}) +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName + +const RadioGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + + + + + ) +}) +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName + +export { RadioGroup, RadioGroupItem } diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 608ccbbc3..ed2c5a744 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -35,7 +35,7 @@ const SelectTrigger = React.forwardRef< )); -SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; +SelectTrigger.displayName = 'SelectTrigger'; const SelectScrollUpButton = React.forwardRef< React.ElementRef, diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx new file mode 100644 index 000000000..fa23a5d3e --- /dev/null +++ b/src/components/ui/tooltip.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import * as TooltipPrimitive from '@radix-ui/react-tooltip'; + +import { cn } from '@/utils/merge'; + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/src/consts/sizing.ts b/src/consts/sizing.ts new file mode 100644 index 000000000..7138b66f1 --- /dev/null +++ b/src/consts/sizing.ts @@ -0,0 +1,69 @@ +import { SegmentSizeEnum, IndexTypeEnum } from '@/types/sizing'; + +export const ONE_MILLION = Math.pow(10, 6); + +export const VECTOR_RANGE_CONFIG = { + min: 1, + max: 10000, + defaultValue: 1, + domain: [0, 25, 50, 75, 100], + range: [1, 10, 100, 1000, 10000], +}; + +export const DIMENSION_RANGE_CONFIG = { + min: 1, + max: 10000, + defaultValue: 128, + domain: [0, 25, 50, 75, 100], + range: [32, 128, 768, 1536, 32768], +}; + +export const NODE_DEGREE_RANGE_CONFIG = { + min: 1, + max: 10000, + defaultValue: 1, + domain: [1, 5, 15, 20, 25, 30], + range: [1, 5, 15, 20, 25, 30], +}; + +export const SEGMENT_SIZE_OPTIONS = [ + { + label: '512MB', + value: SegmentSizeEnum._512MB, + }, + { + label: '1024MB', + value: SegmentSizeEnum._1024MB, + }, + { + label: '2048MB', + value: SegmentSizeEnum._2048MB, + }, +]; + +export const INDEX_TYPE_OPTIONS = [ + { + label: 'FLAT', + value: IndexTypeEnum.FLAT, + }, + { + label: 'SCANN', + value: IndexTypeEnum.SCANN, + }, + { + label: 'HNSW', + value: IndexTypeEnum.HNSW, + }, + { + label: 'IVF_FLAT', + value: IndexTypeEnum.IVF_FLAT, + }, + { + label: 'IVFSQ8', + value: IndexTypeEnum.IVFSQ8, + }, + { + label: 'DISKANN', + value: IndexTypeEnum.DISKANN, + }, +]; diff --git a/src/pages/tools/sizing.tsx b/src/pages/tools/sizing.tsx index f7e85aea8..706de841a 100644 --- a/src/pages/tools/sizing.tsx +++ b/src/pages/tools/sizing.tsx @@ -50,411 +50,12 @@ import { IndexTypeEnum, } from '@/components/card/sizingToolCard/constants'; import { ABSOLUTE_BASE_URL } from '@/consts'; - -// one million -const $1M = Math.pow(10, 6); - -const defaultSizeContent = { - size: REQUIRE_MORE, - cpu: 0, - memory: 0, - amount: 0, -}; - -enum FromKeysEnum { - Nb = 'nb', - D = 'd', - IndexType = 'indexType', - M = 'm', - Nlist = 'nlist', - SegmentSize = 'segmentSize', - aApacheType = 'apacheType', -} +import FormSection from '@/parts/sizing/formSection'; +import ResultSection from '@/parts/sizing/resultSection'; export default function SizingTool() { const { t } = useTranslation('sizingTool'); - const [dialogState, setDialogState] = useState({ - open: false, - title: '', - children: <>, - }); - const [form, setForm] = useState<{ [key in FromKeysEnum]: any }>({ - // number of vectors - nb: { - value: 1, - showError: false, - helpText: '', - placeholder: `[1, 10000]`, - validation: { - validate: isBetween, - params: { min: 1, max: 10000 }, - errorMsg: 'Number of vectors should be an integer between [1, 10000]', - }, - }, - // dimensions - d: { - value: 128, - showError: false, - helpText: '', - placeholder: '[1, 10000]', - validation: { - validate: isBetween, - params: { min: 1, max: 10000 }, - errorMsg: 'Dimensions should be an integer between [1, 10000]', - }, - }, - // index type - indexType: { - value: INDEX_TYPE_OPTIONS[0].value, - showError: false, - }, - // index parameters - m: { - value: 8, - showError: false, - }, - // ivf parameters - nlist: { - value: 1024, - showError: false, - helpText: '', - placeholder: '[1, 10000]', - validation: { - validate: isBetween, - params: { min: 1, max: 10000 }, - errorMsg: 'nList should be an integer between [1, 10000]', - }, - }, - // segment size - segmentSize: { - value: SEGMENT_SIZE_OPTIONS[0].value, - showError: false, - }, - apacheType: 'pulsar', - }); - - const handleFormValueChange = (val: any, key: FromKeysEnum) => { - const { validation } = form[key]; - - if (!validation) { - setForm(v => ({ - ...v, - [key]: { - ...v[key], - value: val, - }, - })); - return; - } - - const isValidated = validation.validate(val, validation.params); - if (isValidated) { - setForm(v => ({ - ...v, - [key]: { - ...v[key], - value: val, - showError: false, - helpText: '', - }, - })); - } else { - setForm(v => ({ - ...v, - [key]: { - ...v[key], - value: val, - showError: true, - helpText: validation.errorMsg, - }, - })); - } - }; - - const hanldeRemovePromot = key => { - if (!form[key].showError) { - return; - } - setForm(v => ({ - ...v, - [key]: { - ...v[key], - value: '', - showError: false, - helpText: '', - }, - })); - }; - - const handleApacheChange = (e, value) => { - setForm(v => ({ - ...v, - apacheType: value, - })); - }; - - const calcResult = useMemo(() => { - const { nb, d, indexType, nlist, m, segmentSize } = form; - - const nbVal = Number(nb.value) * $1M || 0; - const dVal = Number(d.value) || 0; - const nlistVal = Number(nlist.value) || 0; - const mVal = Number(m.value) || 0; - const sVal = Number(segmentSize.value) || 0; - - const isErrorParameters = Object.values(form).some( - v => v.showError || v.value === '' - ); - - if (isErrorParameters) { - const etcdData = etcdCalculator(); - const minioData = minioCalculator(); - const pulsarData = pulsarCalculator(); - const kafkaData = kafkaCalculator(); - return { - memorySize: { size: 0, unit: 'B' }, - rawFileSize: { size: 0, unit: 'B' }, - dataNode: defaultSizeContent, - queryNode: defaultSizeContent, - indexNode: defaultSizeContent, - proxy: defaultSizeContent, - mixCoord: defaultSizeContent, - commonCoord: defaultSizeContent, - etcdData, - minioData, - pulsarData, - kafkaData, - }; - } - - const { memorySize, theorySize } = memorySizeCalculator({ - nb: nbVal, - d: dVal, - nlist: nlistVal, - M: mVal, - indexType: indexType.value, - }); - - const rawFileSize = rawFileSizeCalculator({ d: dVal, nb: nbVal }); - const dataNode = dataNodeCalculator(nbVal); - const indexNode = indexNodeCalculator(theorySize, sVal); - const proxy = proxyCalculator(memorySize); - const queryNode = queryNodeCalculator(memorySize); - const commonCoord = commonCoordCalculator(memorySize); - const mixCoord = mixCoordCalculator(nbVal); - const etcdData = etcdCalculator(rawFileSize); - const minioData = minioCalculator(rawFileSize, theorySize); - const pulsarData = pulsarCalculator(rawFileSize); - const kafkaData = kafkaCalculator(rawFileSize); - return { - memorySize: unitBYTE2Any(memorySize), - rawFileSizeByte: rawFileSize, - rawFileSize: unitBYTE2Any(rawFileSize), - mixCoord, - indexNode, - dataNode, - queryNode, - proxy, - commonCoord, - etcdData, - minioData, - pulsarData, - kafkaData, - }; - }, [form]); - const { milvusData, dependencyData, totalData, queryNodeDiskData } = - useMemo(() => { - const calculateList = [ - calcResult.proxy, - calcResult.mixCoord, - calcResult.indexNode, - calcResult.dataNode, - calcResult.queryNode, - ]; - - const milvusData = { - core: calculateList.reduce((acc, cur) => { - acc += cur.cpu * cur.amount; - return acc; - }, 0), - memory: calculateList.reduce((acc, cur) => { - acc += cur.memory * cur.amount; - return acc; - }, 0), - }; - - const secondPartData = { - core: - calcResult.etcdData.cpu * calcResult.etcdData.podNumber + - calcResult.minioData.cpu * calcResult.minioData.podNumber, - memory: - calcResult.etcdData.memory * calcResult.etcdData.podNumber + - calcResult.minioData.memory * calcResult.minioData.podNumber, - ssd: - calcResult.etcdData.pvcPerPodUnit === 'G' - ? calcResult.etcdData.pvcPerPodSize * calcResult.etcdData.podNumber - : (calcResult.etcdData.pvcPerPodSize * - calcResult.etcdData.podNumber) / - 1024, - disk: - calcResult.minioData.pvcPerPodUnit === 'G' - ? calcResult.minioData.pvcPerPodSize * - calcResult.minioData.podNumber - : (calcResult.minioData.pvcPerPodSize * - calcResult.minioData.podNumber) / - 1024, - }; - - const pulsarBookieData = - calcResult.pulsarData.bookie.ledgers.unit === 'G' - ? calcResult.pulsarData.bookie.ledgers.size * - calcResult.pulsarData.bookie.podNum.value - : (calcResult.pulsarData.bookie.ledgers.size * - calcResult.pulsarData.bookie.podNum.value) / - 1024; - const pulsarZookeeperData = - calcResult.pulsarData.zookeeper.pvc.unit === 'G' - ? calcResult.pulsarData.zookeeper.pvc.size * - calcResult.pulsarData.zookeeper.podNum.value - : (calcResult.pulsarData.zookeeper.pvc.size * - calcResult.pulsarData.zookeeper.podNum.value) / - 1024; - const pulsarData = { - core: Object.values(calcResult.pulsarData).reduce((acc, cur) => { - acc += cur.cpu.size * cur.podNum.value; - return acc; - }, 0), - memory: Object.values(calcResult.pulsarData).reduce((acc, cur) => { - acc += cur.memory.size * cur.podNum.value; - return acc; - }, 0), - ssd: pulsarBookieData + pulsarZookeeperData, - disk: - calcResult.pulsarData.bookie.journal.unit === 'G' - ? calcResult.pulsarData.bookie.journal.size * - calcResult.pulsarData.bookie.podNum.value - : (calcResult.pulsarData.bookie.journal.size * - calcResult.pulsarData.bookie.podNum.value) / - 1024, - }; - - const kafkaData = { - core: Object.values(calcResult.kafkaData).reduce((acc, cur) => { - acc += cur.cpu.size * cur.podNum.value; - return acc; - }, 0), - memory: Object.values(calcResult.kafkaData).reduce((acc, cur) => { - acc += cur.memory.size * cur.podNum.value; - return acc; - }, 0), - ssd: Object.values(calcResult.kafkaData).reduce((acc, cur) => { - if (cur.pvc?.isSSD) { - if (cur.pvc.unit === 'G') { - acc += cur.pvc.size * cur.podNum.value; - } else { - acc += (cur.pvc.size * cur.podNum.value) / 1024; - } - } - return acc; - }, 0), - disk: Object.values(calcResult.kafkaData).reduce((acc, cur) => { - if (!cur.pvc?.isSSD) { - if (cur.pvc.unit === 'G') { - acc += cur.pvc.size * cur.podNum.value; - } else { - acc += (cur.pvc.size * cur.podNum.value) / 1024; - } - } - return acc; - }, 0), - }; - - const thirdPartData = - form.apacheType === 'pulsar' ? pulsarData : kafkaData; - - let queryNodeDiskData = undefined; - - if (form.indexType.value === IndexTypeEnum.DISKANN) { - const rawFileSizeValue = unitBYTE2Any(calcResult.rawFileSizeByte, 'GB'); - queryNodeDiskData = { - totalQueryNodeDisk: Math.ceil(rawFileSizeValue.size * 10) / 10, - key: 'SSD', - value: `${ - Math.ceil( - (rawFileSizeValue.size / calcResult.queryNode.amount) * 10 - ) / 10 - } GB`, - }; - } - - const totalData = { - core: milvusData.core + secondPartData.core + thirdPartData.core, - memory: - milvusData.memory + secondPartData.memory + thirdPartData.memory, - ssd: queryNodeDiskData - ? Math.ceil(secondPartData.ssd + thirdPartData.ssd) + - queryNodeDiskData.totalQueryNodeDisk - : Math.ceil(secondPartData.ssd + thirdPartData.ssd), - disk: Math.ceil(secondPartData.disk + thirdPartData.disk), - }; - - const dependencyData = { - core: secondPartData.core + thirdPartData.core, - memory: secondPartData.memory + thirdPartData.memory, - ssd: Math.ceil(secondPartData.ssd + thirdPartData.ssd), - disk: Math.ceil(secondPartData.disk + thirdPartData.disk), - }; - - return { - milvusData, - dependencyData, - totalData, - queryNodeDiskData, - }; - }, [calcResult, form.apacheType]); - - const handleDownloadYmlFile = (content, fileName) => { - if (typeof window !== 'undefined') { - const blob = new Blob([content], { - type: 'text/plain', - }); - - const url = URL.createObjectURL(blob); - - const a = document.createElement('a'); - a.href = url; - a.download = `${fileName}.yml`; - a.click(); - } - }; - - const handleDownloadHelm = () => { - const content = helmYmlGenerator(calcResult, form.apacheType); - handleDownloadYmlFile(content, HELM_CONFIG_FILE_NAME); - }; - const handleDownloadOperator = () => { - const content = operatorYmlGenerator(calcResult, form.apacheType); - handleDownloadYmlFile(content, OPERATOR_CONFIG_FILE_NAME); - }; - - const handleCloseDialog = () => { - setDialogState(v => ({ - ...v, - open: false, - })); - }; - - const handleOpenInstallGuide = type => { - const title = type === 'helm' ? t('buttons.helm') : t('buttons.operator'); - - setDialogState({ - open: true, - title, - children: , - }); - }; - return (
@@ -470,434 +71,22 @@ export default function SizingTool() { hrefLang="en" /> -
-

{t('title')}

-
- - - -

{t('subTitle')}

-
-
-
-
-

{t('labels.dataSize')}

- -
-

{t('labels.vector')}

- hanldeRemovePromot('nb')} - onChange={e => { - handleFormValueChange(e.target.value, FromKeysEnum.Nb); - }} - /> -
- -
-

{t('labels.dimension')}

- hanldeRemovePromot('d')} - onChange={e => { - handleFormValueChange(e.target.value, FromKeysEnum.D); - }} - /> -
-
- -
-

{t('labels.indexType')}

- -
- - {t('labels.index')} - - -
- -
- {form.indexType.value === 'FLAT' ? null : form.indexType - .value === 'HNSW' ? ( - <> -

- {t('labels.indexParam')} -

-

- {t('labels.m')} -

-
- { - handleFormValueChange(value, FromKeysEnum.M); - }} - marks={[ - { label: '4', value: 4 }, - { label: '64', value: 64 }, - ]} - /> -
- - ) : ( - <> -

{t('labels.m')}

- hanldeRemovePromot('nlist')} - onChange={e => { - handleFormValueChange( - e.target.value, - FromKeysEnum.Nlist - ); - }} - /> - - )} -
- -
-

{t('labels.segmentSize')}

- - {t('labels.segment')} - - - - - - } - label="Pulsar" - /> - } - label="Kafka" - /> - - -
-
-
-
-
-

{t('capacity')}

- -
- - -
-
- -
-

{t('setups.title')}

- -
-
-
-

{t('total')}

-

- {t('coreInfo', { - core: totalData.core, - memory: totalData.memory, - })} -

-
-
-
-
-

{t('ssd')}

-

- {t('sizeInfo', { size: totalData.ssd })} -

-
-
-

{t('disk')}

-

- {t('sizeInfo', { size: totalData.disk })} -

-
-
-
-
-
-
-

{t('milvus')}

-
-
-

{t('total')}: 

-

- {t('coreInfo', { - core: milvusData.core, - memory: milvusData.memory, - })} -

-
- {queryNodeDiskData && ( -
-

SSD: 

-

- {queryNodeDiskData.totalQueryNodeDisk} GB -

-
- )} -
-
- -
-
- - -
- -
- - - -
-
-
-
-
-

{t('dependency')}

-
-
-

{t('total')}: 

-

- {t('coreInfo', { - core: dependencyData.core, - memory: dependencyData.memory, - })} -

-
-
-

{t('ssd')}: 

-

- {t('sizeInfo', { size: dependencyData.ssd })} -

-
-
-

{t('disk')}: 

-

- {t('sizeInfo', { size: dependencyData.disk })} -

-
-
-
- -
- - - -

{t('dependencyNote')}

-
- -
- - {/* Minio */} - -
- - {/* pulsar or kafka */} -
- {form.apacheType === 'pulsar' ? ( - - ) : ( - - )} -
-
-
-
- } - > - {t('buttons.helm')} - - handleOpenInstallGuide('helm')} - variant="outlined" - > - {t('buttons.guide')} - -
-
- } - > - {t('buttons.operator')} - - handleOpenInstallGuide('operator')} - variant="outlined" - > - {t('buttons.guide')} - -
-
-
+
+

Milvus Sizing Tool

+

+ Note: all the recommendations are calculated based on our lab data, + you should adjust it with your own testing before deploying to + production. If you have any question, please contact us. +

+
+ +
-
); } diff --git a/src/parts/sizing/formSection.tsx b/src/parts/sizing/formSection.tsx new file mode 100644 index 000000000..a44a288ce --- /dev/null +++ b/src/parts/sizing/formSection.tsx @@ -0,0 +1,249 @@ +import { useEffect, useRef, useState } from 'react'; +import classes from './index.module.less'; +import { SizingInput, SizingRange, SizingSwitch } from '@/components/sizing'; +import { + Collapsible, + Checkbox, + Tooltip, + TooltipTrigger, + TooltipContent, + TooltipProvider, + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, +} from '@/components/ui'; +import { + VECTOR_RANGE_CONFIG, + DIMENSION_RANGE_CONFIG, + NODE_DEGREE_RANGE_CONFIG, + SEGMENT_SIZE_OPTIONS, + INDEX_TYPE_OPTIONS, +} from '@/consts/sizing'; +import clsx from 'clsx'; +import { IIndexType } from '@/types/sizing'; +import { IndexTypeComponent } from './indexTypeComponent'; + +export default function FormSection(props: { className: string }) { + const { className } = props; + const [value, setValues] = useState('21'); + const [checked, setChecked] = useState(false); + const [collapseHeight, setCollapseHeight] = useState(0); + const collapseEle = useRef(null); + + const [form, setForm] = useState({ + vector: VECTOR_RANGE_CONFIG.defaultValue, + dimension: DIMENSION_RANGE_CONFIG.defaultValue, + widthScalar: false, + scalarData: { + average: 0, + offLoading: false, + }, + }); + + const [indexTypeParams, setIndexTypeParams] = useState({ + indexType: INDEX_TYPE_OPTIONS[0].value, + widthRawData: false, + nodeDegree: 0, + nlist: 0, + maxDegree: 0, + }); + + const handleInputChange = e => { + setValues(e.target.value); + }; + + const handleRangeChange = (value: number) => { + setForm({ + ...form, + vector: value, + }); + }; + + const handleCheckChange = (val: boolean) => { + setChecked(val); + }; + + const handleFormChange = (key: string, value: any) => { + console.log('value--', value, key); + setForm({ + ...form, + [key]: value, + }); + }; + + const handleIndexTypeParamsChange = (key: string, value: any) => { + setIndexTypeParams({ + ...indexTypeParams, + [key]: value, + }); + }; + + const handleAverageLengthChange = (e: any) => { + const length = Number(e.target.value) || 0; + setForm({ + ...form, + scalarData: { + ...form.scalarData, + average: length, + }, + }); + }; + + const handleOffLoadingChange = (value: boolean) => { + setForm({ + ...form, + scalarData: { + ...form.scalarData, + offLoading: value, + }, + }); + }; + + useEffect(() => { + const targetEle = collapseEle?.current; + + const height = targetEle.offsetHeight; + console.log('height', height); + setCollapseHeight(height); + }, []); + + return ( +
+
+
+ {/* + */} + { + handleFormChange('vector', val); + }} + value={form.vector} + unit="Million" + /> +
+
+ { + handleFormChange('dimension', val); + }} + value={form.dimension} + /> +
+
+
+

With Scalar Filed

+ { + handleFormChange('widthScalar', value); + }} + /> +
+ +
+ + + + some test tooltip sad dsdsadasd dadsadsa dssad d dasd + + + Average Data Size Per Row + + + + } + unit="Bytes" + value={form.scalarData.average} + onChange={handleAverageLengthChange} + fullWidth + classes={{ + root: classes.marginBtm20, + }} + /> +
+
+ +

+ Offloading Fields to Disk +

+
+

+ Milvus uses Mmap to enable direct memory access to large files + on disk without reading the entire files into memory. +

+
+
+
+
+
+
+
+

Index Type

+ + + +
+
+
+
+
+
+
+ ); +} diff --git a/src/parts/sizing/index.module.less b/src/parts/sizing/index.module.less new file mode 100644 index 000000000..ccdfc9aa4 --- /dev/null +++ b/src/parts/sizing/index.module.less @@ -0,0 +1,93 @@ +@import url('@/styles/global.module.less'); + +.formSection { + padding: 30px 24px; + border-right: 1px solid #ececee; + + .singlePart { + padding-bottom: 24px; + border-bottom: 1px solid #ececee; + margin-bottom: 24px; + } + .singleRow { + margin-bottom: 20px; + } + + .commonLabel { + .paragraph4(); + margin-bottom: 8px; + } + + .smallerLabel { + .paragraph6(); + } + + .innerRow { + margin-top: 20px; + } + .marginBtm20 { + margin-bottom: 20px; + } + + .marginBtm0 { + margin-bottom: 0; + } + + .tooltipTrigger { + text-decoration: underline dashed #d0d7dc; + } + + .offLoadingLabel { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + } + + .offLoadingDesc { + .paragraph6-regular; + color: #2e373b; + } + + .flexRow { + display: flex; + gap: 8px; + align-items: center; + } + + .switchRow { + margin-bottom: 20px; + } + + .collapsible { + overflow: hidden; + transition: height 0.3s linear; + will-change: height; + } + + .visibleCollapse { + } + .invisibleCollapse { + height: 0; + } + + .selectTrigger { + .paragraph6-regular(); + padding: 10px 12px; + height: 36px; + border-radius: 8px; + border: 1px solid #e8eaee; + background: #fff; + } + + .indexParamLabel { + .paragraph4-regular(); + margin-bottom: 8px; + } + + .radioGroup { + display: flex; + gap: 20px; + align-items: center; + } +} diff --git a/src/parts/sizing/indexTypeComponent.tsx b/src/parts/sizing/indexTypeComponent.tsx new file mode 100644 index 000000000..a00f17da6 --- /dev/null +++ b/src/parts/sizing/indexTypeComponent.tsx @@ -0,0 +1,48 @@ +import { IIndexType, IndexTypeEnum } from '@/types/sizing'; +import { RadioGroupItem, RadioGroup } from '@/components/ui'; +import classes from './index.module.less'; + +const SCANNComponent = (props: { + data: IIndexType; + onChange: (key: string, value: any) => void; +}) => { + const { data, onChange } = props; + + return ( +
+

Index Parameters

+

With_raw_data

+ { + onChange('widthRawData', val === 'true'); + }} + className={classes.radioGroup} + > +
+ +

True

+
+
+ False +

False

+
+
+
+ ); +}; + +export const IndexTypeComponent = (props: { + data: IIndexType; + onChange: (key: string, value: any) => void; +}) => { + const { data, onChange } = props; + switch (data.indexType) { + case IndexTypeEnum.FLAT: + return null; + case IndexTypeEnum.SCANN: + return ; + default: + return null; + } +}; diff --git a/src/parts/sizing/resultSection.tsx b/src/parts/sizing/resultSection.tsx new file mode 100644 index 000000000..d0e7eae6d --- /dev/null +++ b/src/parts/sizing/resultSection.tsx @@ -0,0 +1,7 @@ +import clsx from 'clsx'; +import classes from './index.module.less'; + +export default function ResultSection(props: { className: string }) { + const { className } = props; + return
result
; +} diff --git a/src/styles/sizingTool.module.less b/src/styles/sizingTool.module.less index f5aa51379..697672be0 100644 --- a/src/styles/sizingTool.module.less +++ b/src/styles/sizingTool.module.less @@ -317,3 +317,32 @@ } } } + +.sizingToolContainer { + padding-top: 40px; + padding-bottom: 40px; + + .title { + .heading1(); + margin-bottom: 40px; + } + + .desc { + .paragraph6(); + margin-bottom: 20px; + } + + .contentContainer { + display: flex; + border-radius: 24px; + border: 2px solid #ececee; + + .leftSection { + flex: 0 0 446px; + } + + .rightSection { + flex: 1; + } + } +} diff --git a/src/types/sizing.ts b/src/types/sizing.ts new file mode 100644 index 000000000..1287ae9d0 --- /dev/null +++ b/src/types/sizing.ts @@ -0,0 +1,22 @@ +export enum SegmentSizeEnum { + _512MB = '512', + _1024MB = '1024', + _2048MB = '2048', +} + +export enum IndexTypeEnum { + FLAT = 'FLAT', + SCANN = 'SCANN', + HNSW = 'HNSW', + IVF_FLAT = 'IVF_FLAT', + IVFSQ8 = 'IVFSQ8', + DISKANN = 'DISKANN', +} + +export interface IIndexType { + indexType: IndexTypeEnum; + widthRawData: boolean; + nodeDegree: number; + nlist: number; + maxDegree: number; +} diff --git a/src/utils/sizing.ts b/src/utils/sizing.ts new file mode 100644 index 000000000..0e2612585 --- /dev/null +++ b/src/utils/sizing.ts @@ -0,0 +1,29 @@ +// Converts a number to a string with commas separating groups of three digits. +export const convertNumToString = (params: { + amount: number; + scale?: number; + decimal?: number; + showZeroDecimal?: boolean; +}): string => { + const { amount, scale, decimal = 0, showZeroDecimal = false } = params; + const decimalCoefficient = Math.pow(10, decimal); + const scaleNum = scale || 1; + const scaledNum = + Math.round((amount / scaleNum) * decimalCoefficient) / decimalCoefficient; + const formattedNumber = new Intl.NumberFormat( + 'en-US', + showZeroDecimal + ? { + minimumFractionDigits: decimal, + maximumFractionDigits: decimal, + } + : {} + ).format(scaledNum); + return formattedNumber; +}; + +// Converts a string containing digits to a number. +export const convertStringToNum = (value: string, scale?: number): number => { + const scaleNum = scale || 1; + return Number(value.replace(/[^0-9.-]+/g, '')) * scaleNum; +}; diff --git a/tailwind.config.js b/tailwind.config.js index 09513785c..b2e8607fc 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -19,6 +19,7 @@ module.exports = { black3: '#D0D7DC', black4: '#ececee', blue1: '#00B3FF', + gray1: '#CDD8E8', textPrimary: 'var(--color-text-primary)', }, boxShadow: { From 41beeb50b45fad0e80d3d224cbcf585e66fd37f3 Mon Sep 17 00:00:00 2001 From: ThyeeZz Date: Thu, 26 Dec 2024 15:58:54 +0800 Subject: [PATCH 2/3] complete calculator section --- public/images/sizing-tool/kafka.svg | 3 + public/images/sizing-tool/pulsar.svg | 3 + src/components/customButton/index.tsx | 9 + src/components/icons/ArrowRightAltIcon.tsx | 6 +- src/components/icons/index.tsx | 40 +- .../sizing/sizingInput/index.module.less | 4 +- src/components/sizing/sizingInput/index.tsx | 7 +- src/components/sizing/sizingRange/index.tsx | 108 +++- .../sizing/sizingSwitch/index.module.less | 10 + src/components/sizing/sizingSwitch/index.tsx | 68 +- src/components/ui/checkbox.tsx | 2 +- src/components/ui/radio-group.tsx | 30 +- src/consts/sizing.ts | 266 +++++++- src/i18n/en/sizingToolV2.json | 58 ++ src/i18n/index.ts | 2 + src/pages/tools/sizing.tsx | 197 ++++-- src/parts/sizing/dependencyComponent.tsx | 348 ++++++++++ src/parts/sizing/formSection.tsx | 241 +++++-- src/parts/sizing/index.module.less | 399 +++++++++++- src/parts/sizing/indexTypeComponent.tsx | 82 ++- src/parts/sizing/resultSection.tsx | 372 ++++++++++- src/styles/reset.css | 1 + src/styles/sizingTool.module.less | 337 +--------- src/types/sizing.ts | 73 ++- src/utils/sizingTool.ts | 10 +- src/utils/sizingToolV2.ts | 594 ++++++++++++++++++ 26 files changed, 2736 insertions(+), 534 deletions(-) create mode 100644 public/images/sizing-tool/kafka.svg create mode 100644 public/images/sizing-tool/pulsar.svg create mode 100644 src/i18n/en/sizingToolV2.json create mode 100644 src/parts/sizing/dependencyComponent.tsx create mode 100644 src/utils/sizingToolV2.ts diff --git a/public/images/sizing-tool/kafka.svg b/public/images/sizing-tool/kafka.svg new file mode 100644 index 000000000..38ecef9cd --- /dev/null +++ b/public/images/sizing-tool/kafka.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/sizing-tool/pulsar.svg b/public/images/sizing-tool/pulsar.svg new file mode 100644 index 000000000..911b51527 --- /dev/null +++ b/public/images/sizing-tool/pulsar.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/customButton/index.tsx b/src/components/customButton/index.tsx index c88afbec8..4ae584bc1 100644 --- a/src/components/customButton/index.tsx +++ b/src/components/customButton/index.tsx @@ -8,6 +8,7 @@ export interface CustomButtonProps { variant?: 'contained' | 'outlined' | 'text'; color?: 'primary' | 'secondary'; endIcon?: React.ReactNode; + startIcon?: React.ReactNode; disabled?: boolean; size?: 'small' | 'medium' | 'large'; classes?: { @@ -25,6 +26,7 @@ function CustomLink(props: CustomButtonProps & { computedClasses: any }) { variant = 'contained', color = 'primary', endIcon, + startIcon, disabled = false, classes: customClasses = {}, computedClasses, @@ -46,6 +48,9 @@ function CustomLink(props: CustomButtonProps & { computedClasses: any }) { href={href} className={clsx(classes.linkButton, className, root, computedClasses)} > + {startIcon && ( + {startIcon} + )} {children} {endIcon && ( {endIcon} @@ -76,6 +81,7 @@ const CustomButton = (props: CustomButtonProps) => { variant = 'contained', color = 'primary', endIcon, + startIcon, disabled = false, classes: customClasses = {}, size = 'medium', @@ -106,6 +112,9 @@ const CustomButton = (props: CustomButtonProps) => { {...rest} className={clsx(classes.linkButton, className, root, computedClasses)} > + {startIcon && ( + {startIcon} + )} {children} {endIcon && ( {endIcon} diff --git a/src/components/icons/ArrowRightAltIcon.tsx b/src/components/icons/ArrowRightAltIcon.tsx index 44d5ef8e1..1c77dc409 100644 --- a/src/components/icons/ArrowRightAltIcon.tsx +++ b/src/components/icons/ArrowRightAltIcon.tsx @@ -11,9 +11,9 @@ export const ArrowRightAltIcon = (props: { size?: number; color?: string }) => { ); diff --git a/src/components/icons/index.tsx b/src/components/icons/index.tsx index 0c782c42c..e706e7975 100644 --- a/src/components/icons/index.tsx +++ b/src/components/icons/index.tsx @@ -71,11 +71,11 @@ export const DownloadIcon = () => ( > ); @@ -350,3 +350,39 @@ export const ThumbDownIcon: FC<{ color?: string }> = ({ /> ); + +export const ExternalLinkIcon: FC<{ color?: string }> = ({ color }) => ( + + + + +); + +export const ArrowTop = () => ( + + + +); diff --git a/src/components/sizing/sizingInput/index.module.less b/src/components/sizing/sizingInput/index.module.less index 7f27c748e..02a2dc2dc 100644 --- a/src/components/sizing/sizingInput/index.module.less +++ b/src/components/sizing/sizingInput/index.module.less @@ -35,11 +35,13 @@ .mediumSize { height: 36px; padding: 9px 12px; - padding-right: 52px; } .smallSize { height: 28px; padding: 5px 12px; +} + +.largePadding { padding-right: 52px; } .unit { diff --git a/src/components/sizing/sizingInput/index.tsx b/src/components/sizing/sizingInput/index.tsx index a5682c96c..ad96a12de 100644 --- a/src/components/sizing/sizingInput/index.tsx +++ b/src/components/sizing/sizingInput/index.tsx @@ -19,12 +19,13 @@ const SizingInput = React.forwardRef( ( { className, - unit = '', + unit, label = '', customSize = 'medium', classes: customClasses, fullWidth = false, type, + placeholder, ...props }, ref @@ -44,11 +45,12 @@ const SizingInput = React.forwardRef( root )} > - {label &&

{label}

} + {label &&
{label}
}
( className )} ref={ref} + placeholder={placeholder} {...props} /> {unit && {unit}} diff --git a/src/components/sizing/sizingRange/index.tsx b/src/components/sizing/sizingRange/index.tsx index c35fc12f2..dac319b8b 100644 --- a/src/components/sizing/sizingRange/index.tsx +++ b/src/components/sizing/sizingRange/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import { useState, useEffect } from 'react'; import { styled } from '@mui/material/styles'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; @@ -7,6 +7,7 @@ import classes from './index.module.less'; import clsx from 'clsx'; import { scaleLinear, scalePow } from 'd3-scale'; import { SizingInput } from '../sizingInput'; +import { init } from 'next/dist/compiled/webpack/webpack'; type RangeConfigType = { min: number; @@ -26,6 +27,7 @@ interface SizingRangePropsType { value: number; onRangeChange: (value: number) => void; unit?: string; + placeholder?: string; } export const SizingRange = (props: SizingRangePropsType) => { @@ -36,18 +38,48 @@ export const SizingRange = (props: SizingRangePropsType) => { onRangeChange, unit, value, + placeholder, } = props; const { root, label: labelCLass } = customClasses; // domain: 0 - 100 under progress // range: real value - const getRangeValue = scaleLinear() - .domain(rangeConfig.domain) - .range(rangeConfig.range); + const getRangeValue = (value: number) => { + const rangeScale = scaleLinear() + .domain(rangeConfig.domain) + .range(rangeConfig.range); - const getDomainValue = scaleLinear() - .domain(rangeConfig.range) - .range(rangeConfig.domain); + const rangeValue = Math.floor(rangeScale(value)); + + if (rangeValue <= rangeConfig.min) { + return rangeConfig.min; + } + if (rangeValue >= rangeConfig.max) { + return rangeConfig.max; + } + return rangeValue; + }; + + const getDomainValue = (value: number) => { + const domainScale = scaleLinear() + .domain(rangeConfig.range) + .range(rangeConfig.domain); + + const domainValue = domainScale(value); + if (domainValue <= 0) { + return 0; + } + if (domainValue >= 100) { + return 100; + } + return domainValue; + }; + + const [initValue, setInitValue] = useState({ + rangeValue: value, + domainValue: getDomainValue(value), + inputValue: `${value}`, + }); const marks = rangeConfig.domain.map((item, index) => { return { @@ -60,14 +92,61 @@ export const SizingRange = (props: SizingRangePropsType) => { return `${value}`; } + // props is domain value; [0,100] const handleRangeChange = (newValue: number | number[]) => { - const domainValue = Math.round(getRangeValue(newValue)); - onRangeChange(domainValue); + const rangeValue = getRangeValue(newValue as number); + setInitValue({ + rangeValue: rangeValue, + domainValue: newValue, + inputValue: `${rangeValue}`, + }); + onRangeChange(rangeValue); }; const handleInputChange = (e: React.ChangeEvent) => { - const realValue = Number(e.target.value); - handleRangeChange(realValue); + const inputValue = e.target.value; + let value = Number(inputValue); // range value + if (isNaN(value)) { + return; + } + + value = + value <= rangeConfig.min + ? rangeConfig.min + : value >= rangeConfig.max + ? rangeConfig.max + : value; + + const domainValue = getDomainValue(value); + + setInitValue({ + rangeValue: value, + domainValue, + inputValue, + }); + onRangeChange(value); + }; + + const handleFormatInput = (e: React.ChangeEvent) => { + const inputValue = e.target.value; + let value = Number(inputValue); // range value + + value = + value <= rangeConfig.min + ? rangeConfig.min + : value >= rangeConfig.max + ? rangeConfig.max + : value; + + const domainValue = getDomainValue(value); + console.log(value); + + setInitValue({ + rangeValue: value, + domainValue, + inputValue: `${value}`, + }); + onRangeChange(value); }; return ( @@ -78,8 +157,7 @@ export const SizingRange = (props: SizingRangePropsType) => { track="normal" aria-labelledby="track-false-slider" getAriaValueText={valuetext} - defaultValue={getDomainValue(rangeConfig.defaultValue)} - value={getDomainValue(value)} + value={initValue.domainValue} marks={marks} onChange={(e, newVal) => { handleRangeChange(newVal); @@ -95,10 +173,12 @@ export const SizingRange = (props: SizingRangePropsType) => { }} />
diff --git a/src/components/sizing/sizingSwitch/index.module.less b/src/components/sizing/sizingSwitch/index.module.less index e69de29bb..fec4fcdb6 100644 --- a/src/components/sizing/sizingSwitch/index.module.less +++ b/src/components/sizing/sizingSwitch/index.module.less @@ -0,0 +1,10 @@ +.visible { + visibility: visible; + opacity: 1; + width: 12px; + height: 12px; +} +.invisible { + visibility: hidden; + opacity: 0; +} diff --git a/src/components/sizing/sizingSwitch/index.tsx b/src/components/sizing/sizingSwitch/index.tsx index 652055928..548656fab 100644 --- a/src/components/sizing/sizingSwitch/index.tsx +++ b/src/components/sizing/sizingSwitch/index.tsx @@ -7,44 +7,46 @@ import { cn } from '@/utils/merge'; const SizingSwitch = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, checked, onChange, ...props }, ref) => ( - - (({ className, checked, onChange, ...props }, ref) => { + return ( + - - - - - - - -)); + + + + + + + ); +}); SizingSwitch.displayName = 'SizingSwitch'; export { SizingSwitch }; diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx index e80572aea..c9b095a2d 100644 --- a/src/components/ui/checkbox.tsx +++ b/src/components/ui/checkbox.tsx @@ -11,7 +11,7 @@ const Checkbox = React.forwardRef< , @@ -10,13 +10,13 @@ const RadioGroup = React.forwardRef< >(({ className, ...props }, ref) => { return ( - ) -}) -RadioGroup.displayName = RadioGroupPrimitive.Root.displayName + ); +}); +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; const RadioGroupItem = React.forwardRef< React.ElementRef, @@ -26,17 +26,17 @@ const RadioGroupItem = React.forwardRef< - - + + - ) -}) -RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName + ); +}); +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName; -export { RadioGroup, RadioGroupItem } +export { RadioGroup, RadioGroupItem }; diff --git a/src/consts/sizing.ts b/src/consts/sizing.ts index 7138b66f1..f5832fe12 100644 --- a/src/consts/sizing.ts +++ b/src/consts/sizing.ts @@ -1,6 +1,12 @@ -import { SegmentSizeEnum, IndexTypeEnum } from '@/types/sizing'; +import { + SegmentSizeEnum, + IndexTypeEnum, + DependencyComponentEnum, + ModeEnum, +} from '@/types/sizing'; export const ONE_MILLION = Math.pow(10, 6); +export const ONE_BILLION = Math.pow(10, 9); export const VECTOR_RANGE_CONFIG = { min: 1, @@ -11,19 +17,35 @@ export const VECTOR_RANGE_CONFIG = { }; export const DIMENSION_RANGE_CONFIG = { - min: 1, - max: 10000, + min: 2, + max: 32768, defaultValue: 128, domain: [0, 25, 50, 75, 100], - range: [32, 128, 768, 1536, 32768], + range: [2, 128, 768, 1536, 32768], +}; + +export const MAX_NODE_DEGREE_RANGE_CONFIG = { + min: 2, + max: 2048, + defaultValue: 1, + domain: [0, 20, 40, 60, 80, 100], + range: [2, 8, 32, 128, 512, 2048], }; -export const NODE_DEGREE_RANGE_CONFIG = { +export const M_RANGE_CONFIG = { + min: 1, + max: 2048, + defaultValue: 1, + domain: [0, 20, 40, 60, 80, 100], + range: [1, 8, 32, 128, 512, 2048], +}; + +export const N_LIST_RANGE_CONFIG = { min: 1, max: 10000, defaultValue: 1, - domain: [1, 5, 15, 20, 25, 30], - range: [1, 5, 15, 20, 25, 30], + domain: [0, 10, 20, 40, 70, 100], + range: [1, 16, 256, 4096, 16384, 65536], }; export const SEGMENT_SIZE_OPTIONS = [ @@ -67,3 +89,233 @@ export const INDEX_TYPE_OPTIONS = [ value: IndexTypeEnum.DISKANN, }, ]; + +export const DEPENDENCY_COMPONENTS = [ + { + label: 'Pulsar', + value: DependencyComponentEnum.Pulsar, + }, + { + label: 'Kafka', + value: DependencyComponentEnum.Kafka, + }, +]; + +export const MODE_OPTIONS = [ + { + label: 'Standalone', + value: ModeEnum.Standalone, + }, + { + label: 'Cluster', + value: ModeEnum.Cluster, + }, +]; + +export const FIXED_QUERY_NODE_CONFIG = [ + { + memory: [0, 8], + queryNode: { + cpu: 2, + memory: 8, + count: 1, + }, + proxy: { + cpu: 1, + memory: 4, + count: 1, + }, + mixCoord: { + cpu: 1, + memory: 4, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 2, + memory: 8, + count: 1, + }, + }, + { + memory: [8, 16], + queryNode: { + cpu: 4, + memory: 16, + count: 1, + }, + proxy: { + cpu: 2, + memory: 8, + count: 1, + }, + mixCoord: { + cpu: 2, + memory: 8, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 4, + memory: 16, + count: 1, + }, + }, + { + memory: [16, 32], + queryNode: { + cpu: 4, + memory: 16, + count: 2, + }, + proxy: { + cpu: 2, + memory: 8, + count: 1, + }, + mixCoord: { + cpu: 2, + memory: 8, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 4, + memory: 16, + count: 2, + }, + }, + { + memory: [32, 48], + queryNode: { + cpu: 4, + memory: 16, + count: 3, + }, + proxy: { + cpu: 2, + memory: 8, + count: 1, + }, + mixCoord: { + cpu: 2, + memory: 8, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 4, + memory: 16, + count: 2, + }, + }, + { + memory: [48, 64], + queryNode: { + cpu: 4, + memory: 16, + count: 4, + }, + proxy: { + cpu: 2, + memory: 8, + count: 1, + }, + mixCoord: { + cpu: 2, + memory: 8, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 4, + memory: 16, + count: 2, + }, + }, + { + memory: [64, 80], + queryNode: { + cpu: 4, + memory: 16, + count: 5, + }, + proxy: { + cpu: 2, + memory: 8, + count: 1, + }, + mixCoord: { + cpu: 2, + memory: 8, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 4, + memory: 16, + count: 4, + }, + }, + { + memory: [80, 96], + queryNode: { + cpu: 4, + memory: 16, + count: 6, + }, + proxy: { + cpu: 2, + memory: 8, + count: 1, + }, + mixCoord: { + cpu: 2, + memory: 8, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 4, + memory: 16, + count: 4, + }, + }, +]; + +export const helmCodeExample = `helm repo add milvus https://zilliztech.github.io/milvus-helm/helm repo update helm install my-release milvus/milvus -f helmConfigYml.yml +`; + +export const operatorCodeExample = `helm repo add milvus-operator https://zilliztech.github.io/milvus-operator/ +helm repo update milvus-operator +helm -n milvus-operator upgrade --install milvus-operator milvus-operator/milvus-operator +kubectl create -f operatorConfigYml.yml +`; diff --git a/src/i18n/en/sizingToolV2.json b/src/i18n/en/sizingToolV2.json new file mode 100644 index 000000000..7f7e1cac7 --- /dev/null +++ b/src/i18n/en/sizingToolV2.json @@ -0,0 +1,58 @@ +{ + "title": "Milvus Sizing Tool", + "content": "A tool for estimate the resources required to set up Milvus based on your dataset.", + "tooltip": "Note: all the recommendations are calculated based on our lab data, you should adjust it with your own testing before deploying to production. If you have any question, please <0>contact us.", + "overview": { + "title": "Approximate Capacity", + "raw": "Raw Data Size", + "rawTooltip": "Raw data size - Original data size, depending on the number of Vector, Vector Dimension and Scalar Field data size", + "memory": "Loading Memory", + "memoryTooltip": "Memory consumption for query nodes, depending on raw data size, index type and params" + }, + "setup": { + "title": "Minimum Milvus cluster setup in total", + "share": "Scaling cluster or dependencies", + "milvus": { + "title": "Milvus", + "proxy": "Proxy", + "mixCoord": "Mix Coord", + "dataNode": "Data Node", + "indexNode": "Index Node", + "queryNode": "Query Node" + }, + "dependency": { + "title": "Dependencies", + "etcd": "ETCD", + "minio": "Minio", + "pulsar": "Pulsar", + "kafka": "Kafka", + "bookie": "Bookie <0>x{{count}}", + "broker": "Broker <0>x{{count}}", + "proxy": "Proxy <0>x{{count}}", + "zookeeper": "Zookeeper <0>x{{count}}" + }, + "basic": { + "cpuAndMemory": "CPU and Memory: <0>{{cpu}} Core {{memory}} GB", + "storageWithValue": "Storage: <0>{{storage}} GB", + "diskWithValue": "Local Disk: <0>{{disk}} {{unit}}", + "cpu": "CPU", + "memory": "Memory", + "disk": "Local Disk", + "storage": "Storage", + "podNum": "Pod Number", + "pvc": "Pcv per Pod: <0>{{pvc}} GB", + "pvcLabel": "Pcv per Pod", + "journal": "Journal", + "ledger": "Ledgers", + "config": "{{cpu}} Core {{memory}} GB", + "core": "{{cpu}} Core", + "gb": "{{memory}} GB" + } + }, + "install": { + "title": "Install Milvus", + "helm": "Helm Chart configuration.yml", + "operator": "Milvus Operator configuration.yml", + "adv": "Too many resources consumed? Try Zilliz Cloud (managed Milvus) to save resources and cut deployment and operation costs. Check out more details with the Zilliz Cloud calculator. <0>Go to Zilliz Cloud calculator." + } +} diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 1dbd2f366..98d4709f6 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -11,6 +11,7 @@ import sizingToolEn from './en/sizingTool.json'; import useCaseEn from './en/useCase.json'; import communityEn from './en/community.json'; import notFoundEn from './en/404.json'; +import sizingToolV2 from './en/sizingToolV2.json'; import docsCn from './cn/docs.json'; import headerCn from './cn/header.json'; @@ -39,6 +40,7 @@ export const resources = { demo: demoEn, community: communityEn, notFound: notFoundEn, + sizingToolV2, }, cn: { common: commonCn, diff --git a/src/pages/tools/sizing.tsx b/src/pages/tools/sizing.tsx index 706de841a..fc224406b 100644 --- a/src/pages/tools/sizing.tsx +++ b/src/pages/tools/sizing.tsx @@ -1,60 +1,127 @@ import React, { useState, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import Layout from '@/components/layout/commonLayout'; import classes from '@/styles/sizingTool.module.less'; import pageClasses from '@/styles/responsive.module.less'; -import { InfoFilled, DownloadIcon } from '@/components/icons'; -import SizingToolCard from '@/components/card/sizingToolCard'; -import SizingConfigCard from '@/components/card/sizingToolCard/sizingConfigCard'; import clsx from 'clsx'; -import Slider from '@mui/material/Slider'; -import TextField from '@mui/material/TextField'; -import InputLabel from '@mui/material/InputLabel'; -import MenuItem from '@mui/material/MenuItem'; -import FormControl from '@mui/material/FormControl'; -import Select from '@mui/material/Select'; -import Radio from '@mui/material/Radio'; -import RadioGroup from '@mui/material/RadioGroup'; -import FormControlLabel from '@mui/material/FormControlLabel'; import Head from 'next/head'; -import CustomButton from '@/components/customButton'; -import { - memorySizeCalculator, - rawFileSizeCalculator, - commonCoordCalculator, - unitBYTE2Any, - indexNodeCalculator, - queryNodeCalculator, - isBetween, - rootCoordCalculator, - dataNodeCalculator, - proxyCalculator, - helmYmlGenerator, - operatorYmlGenerator, - etcdCalculator, - minioCalculator, - pulsarCalculator, - kafkaCalculator, - mixCoordCalculator, - unitAny2BYTE, -} from '@/utils/sizingTool'; -import { CustomizedContentDialogs } from '@/components/dialog/Dialog'; -import HighlightBlock from '@/components/card/sizingToolCard/codeBlock'; -import { - HELM_CONFIG_FILE_NAME, - OPERATOR_CONFIG_FILE_NAME, - REQUIRE_MORE, - INDEX_TYPE_OPTIONS, - SEGMENT_SIZE_OPTIONS, - IndexTypeEnum, -} from '@/components/card/sizingToolCard/constants'; import { ABSOLUTE_BASE_URL } from '@/consts'; import FormSection from '@/parts/sizing/formSection'; import ResultSection from '@/parts/sizing/resultSection'; +import { + DependencyComponentEnum, + ICalculateResult, + ModeEnum, +} from '@/types/sizing'; +import { InfoFilled } from '@/components/icons'; + +const etcdBaseValue = { + cpu: 0, + memory: 0, + pvc: 0, + count: 0, +}; +const minioBaseValue = { + cpu: 0, + memory: 0, + pvc: 0, + count: 0, +}; +const pulsarBaseValue = { + bookie: { + cpu: 0, + memory: 0, + count: 0, + journal: 0, + ledgers: 0, + }, + broker: { + cpu: 0, + memory: 0, + count: 0, + }, + proxy: { + cpu: 0, + memory: 0, + count: 0, + }, + zookeeper: { + cpu: 0, + memory: 0, + count: 0, + pvc: 0, + }, +}; +const kafkaBaseValue = { + broker: { + cpu: 0, + memory: 0, + count: 0, + pvc: 0, + }, + zookeeper: { + cpu: 0, + memory: 0, + count: 0, + pvc: 0, + }, +}; export default function SizingTool() { - const { t } = useTranslation('sizingTool'); + const { t } = useTranslation('sizingToolV2'); + const [calculatedResult, setCalculatedResult] = useState({ + rawDataSize: 0, + memorySize: 0, + localDiskSize: 0, + nodeConfig: { + queryNode: { + cpu: 0, + memory: 0, + count: 0, + }, + proxy: { + cpu: 0, + memory: 0, + count: 0, + }, + mixCoord: { + cpu: 0, + memory: 0, + count: 0, + }, + dataNode: { + cpu: 0, + memory: 0, + count: 0, + }, + indexNode: { + cpu: 0, + memory: 0, + count: 0, + }, + }, + dependencyConfig: { + etcd: { + ...etcdBaseValue, + }, + minio: { + ...minioBaseValue, + }, + pulsar: { + ...pulsarBaseValue, + }, + kafka: { + ...kafkaBaseValue, + }, + }, + mode: ModeEnum.Standalone, + dependency: DependencyComponentEnum.Pulsar, + }); + + const updateCalculatedResult = (result: ICalculateResult) => { + setCalculatedResult(result); + }; return (
@@ -73,17 +140,39 @@ export default function SizingTool() {
-

Milvus Sizing Tool

-

- Note: all the recommendations are calculated based on our lab data, - you should adjust it with your own testing before deploying to - production. If you have any question, please contact us. -

+

{t('title')}

+

{t('content')}

+
+ + + +

+ , + ]} + /> +

+
- - + +
diff --git a/src/parts/sizing/dependencyComponent.tsx b/src/parts/sizing/dependencyComponent.tsx new file mode 100644 index 000000000..16b2d82d1 --- /dev/null +++ b/src/parts/sizing/dependencyComponent.tsx @@ -0,0 +1,348 @@ +import { + ModeEnum, + DependencyComponentEnum, + DependencyConfigType, +} from '@/types/sizing'; +import classes from './index.module.less'; +import { Trans, useTranslation } from 'react-i18next'; +import clsx from 'clsx'; + +export const DependencyComponent = (props: { + data: DependencyConfigType; + mode: ModeEnum; + dependency: DependencyComponentEnum; +}) => { + const { t } = useTranslation('sizingToolV2'); + const { data, mode, dependency } = props; + + const { etcd, minio, pulsar, kafka } = data; + + const PulsarInfo = (props: Pick) => { + const { pulsar } = props; + + return ( +
+
{t('setup.dependency.pulsar')}
+
    +
  • +

    + , + ]} + /> +

    +
      +
    1. + + {t('setup.basic.cpu')}: + + + {t('setup.basic.core', { cpu: pulsar.bookie.cpu })} + +
    2. +
    3. + + {t('setup.basic.memory')}: + + + {t('setup.basic.gb', { memory: pulsar.bookie.memory })} + +
    4. +
    5. + + {t('setup.basic.journal')}: + + + {t('setup.basic.gb', { memory: pulsar.bookie.journal })} + +
    6. +
    7. + + {t('setup.basic.ledger')}: + + + {t('setup.basic.gb', { memory: pulsar.bookie.ledgers })} + +
    8. +
    +
  • +
  • +

    + , + ]} + /> +

    +
      +
    1. + + {t('setup.basic.cpu')}: + + + {t('setup.basic.core', { cpu: pulsar.broker.cpu })} + +
    2. +
    3. + + {t('setup.basic.memory')}: + + + {t('setup.basic.gb', { memory: pulsar.broker.memory })} + +
    4. +
    +
  • +
  • +

    + , + ]} + /> +

    +
      +
    1. + + {t('setup.basic.cpu')}: + + + {t('setup.basic.core', { cpu: pulsar.proxy.cpu })} + +
    2. +
    3. + + {t('setup.basic.memory')}: + + + {t('setup.basic.gb', { memory: pulsar.proxy.memory })} + +
    4. +
    +
  • +
  • +

    + , + ]} + /> +

    +
      +
    1. + + {t('setup.basic.cpu')}: + + + {t('setup.basic.core', { cpu: pulsar.zookeeper.cpu })} + +
    2. +
    3. + + {t('setup.basic.memory')}: + + + {t('setup.basic.gb', { memory: pulsar.zookeeper.memory })} + +
    4. +
    5. + + {t('setup.basic.pvcLabel')}: + + + {t('setup.basic.gb', { memory: pulsar.zookeeper.pvc })} + +
    6. +
    +
  • +
+
+ ); + }; + + const KafkaInfo = (props: Pick) => { + const { kafka } = props; + + return ( +
+
{t('setup.dependency.pulsar')}
+
    +
  • +

    + , + ]} + /> +

    +
      +
    1. + + {t('setup.basic.cpu')}: + + + {t('setup.basic.core', { cpu: kafka.broker.cpu })} + +
    2. +
    3. + + {t('setup.basic.memory')}: + + + {t('setup.basic.gb', { memory: kafka.broker.memory })} + +
    4. +
    5. + + {t('setup.basic.pvcLabel')}: + + + {t('setup.basic.gb', { memory: kafka.broker.pvc })} + +
    6. +
    +
  • + +
  • +

    + , + ]} + /> +

    +
      +
    1. + + {t('setup.basic.cpu')}: + + + {t('setup.basic.core', { cpu: kafka.zookeeper.cpu })} + +
    2. +
    3. + + {t('setup.basic.memory')}: + + + {t('setup.basic.gb', { memory: kafka.zookeeper.memory })} + +
    4. +
    5. + + {t('setup.basic.pvcLabel')}: + + + {t('setup.basic.gb', { memory: kafka.zookeeper.pvc })} + +
    6. +
    +
  • +
+
+ ); + }; + + return ( +
+
+ + ]} + /> +

+ } + classname={classes.card} + /> + + ]} + /> +

+ } + classname={classes.card} + /> +
+ {mode === ModeEnum.Cluster && ( +
+ <> + {dependency === DependencyComponentEnum.Pulsar ? ( + + ) : ( + + )} + +
+ )} +
+ ); +}; + +export const DataCard = (props: { + name: React.ReactNode; + data: string; + desc?: React.ReactNode; + count?: number; + classname?: string; + size?: string; +}) => { + const { name, data, count, desc, classname = '', size = 'medium' } = props; + return ( +
+
+ {name} +
+

+ {data} +

+ {desc &&
{desc}
} + {count &&

x{count}

} +
+ ); +}; diff --git a/src/parts/sizing/formSection.tsx b/src/parts/sizing/formSection.tsx index a44a288ce..afdd6cd49 100644 --- a/src/parts/sizing/formSection.tsx +++ b/src/parts/sizing/formSection.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import classes from './index.module.less'; import { SizingInput, SizingRange, SizingSwitch } from '@/components/sizing'; import { @@ -19,18 +19,29 @@ import { import { VECTOR_RANGE_CONFIG, DIMENSION_RANGE_CONFIG, - NODE_DEGREE_RANGE_CONFIG, + MAX_NODE_DEGREE_RANGE_CONFIG, SEGMENT_SIZE_OPTIONS, INDEX_TYPE_OPTIONS, + DEPENDENCY_COMPONENTS, + MODE_OPTIONS, } from '@/consts/sizing'; import clsx from 'clsx'; -import { IIndexType } from '@/types/sizing'; +import { ICalculateResult, IIndexType, ModeEnum } from '@/types/sizing'; import { IndexTypeComponent } from './indexTypeComponent'; +import { + memoryAndDiskCalculator, + nodesConfigCalculator, + rawDataSizeCalculator, + $10M768D, + dependencyCalculator, +} from '@/utils/sizingToolV2'; + +export default function FormSection(props: { + className: string; + updateCalculatedResult: (params: ICalculateResult) => void; +}) { + const { className, updateCalculatedResult } = props; -export default function FormSection(props: { className: string }) { - const { className } = props; - const [value, setValues] = useState('21'); - const [checked, setChecked] = useState(false); const [collapseHeight, setCollapseHeight] = useState(0); const collapseEle = useRef(null); @@ -42,33 +53,20 @@ export default function FormSection(props: { className: string }) { average: 0, offLoading: false, }, + segmentSize: SEGMENT_SIZE_OPTIONS[0].value, + dependency: DEPENDENCY_COMPONENTS[0].value, + mode: MODE_OPTIONS[0].value, }); const [indexTypeParams, setIndexTypeParams] = useState({ indexType: INDEX_TYPE_OPTIONS[0].value, widthRawData: false, - nodeDegree: 0, - nlist: 0, maxDegree: 0, + nlist: 0, + m: 0, }); - const handleInputChange = e => { - setValues(e.target.value); - }; - - const handleRangeChange = (value: number) => { - setForm({ - ...form, - vector: value, - }); - }; - - const handleCheckChange = (val: boolean) => { - setChecked(val); - }; - const handleFormChange = (key: string, value: any) => { - console.log('value--', value, key); setForm({ ...form, [key]: value, @@ -105,28 +103,90 @@ export default function FormSection(props: { className: string }) { useEffect(() => { const targetEle = collapseEle?.current; - const height = targetEle.offsetHeight; - console.log('height', height); setCollapseHeight(height); }, []); + const selectedSegmentSize = useMemo(() => { + return SEGMENT_SIZE_OPTIONS.find(v => v.value === form.segmentSize); + }, [form.segmentSize]); + + const dependencyOptions = [ + { + ...DEPENDENCY_COMPONENTS[0], + icon: '/images/sizing-tool/pulsar.svg', + }, + { + ...DEPENDENCY_COMPONENTS[1], + icon: '/images/sizing-tool/kafka.svg', + }, + ]; + + const modeOptions = [ + { + ...MODE_OPTIONS[0], + desc: 'Suitable for small datasize and poc env.', + }, + { + ...MODE_OPTIONS[1], + desc: 'Suitable for large datasize and production env.', + }, + ]; + + useEffect(() => { + let currentMode = form.mode; + const rawDataSize = rawDataSizeCalculator({ + num: form.vector, + d: form.dimension, + withScalar: form.widthScalar, + scalarAvg: form.scalarData.average, + }); + + if (rawDataSize >= $10M768D) { + currentMode = ModeEnum.Cluster; + setForm({ + ...form, + mode: currentMode, + }); + } + const { memory, disk: localDisk } = memoryAndDiskCalculator({ + rawDataSize, + indexTypeParams, + d: form.dimension, + num: form.vector, + withScalar: form.widthScalar, + offLoading: form.scalarData.offLoading, + scalarAvg: form.scalarData.average, + segSize: Number(form.segmentSize), + }); + + const nodeConfig = nodesConfigCalculator({ + memory: memory, + }); + + const dependencyConfig = dependencyCalculator({ + num: form.vector, + d: form.dimension, + withScalar: form.widthScalar, + scalarAvg: form.scalarData.average, + mode: form.mode, + }); + + updateCalculatedResult({ + rawDataSize, + memorySize: memory, + localDiskSize: localDisk, + nodeConfig: nodeConfig, + dependencyConfig: dependencyConfig, + mode: currentMode, + dependency: form.dependency, + }); + }, [form]); + return (
-
-
- {/* - */} +
+
-
+
{ handleFormChange('dimension', val); }} value={form.dimension} />
-
-
-

With Scalar Filed

+
+
+

With Scalar Fields

{ handleFormChange('widthScalar', value); }} @@ -163,16 +223,13 @@ export default function FormSection(props: { className: string }) { [classes.visibleCollapse]: form.widthScalar, [classes.invisibleCollapse]: !form.widthScalar, })} - style={{ - height: form.widthScalar ? `${collapseHeight}px` : '0px', - }} >
- + some test tooltip sad dsdsadasd dadsadsa dssad d dasd
-
-
+
+

Index Type

{ + handleFormChange('segmentSize', val); + }} + > + + {selectedSegmentSize?.label} + + + {SEGMENT_SIZE_OPTIONS.map(v => ( + + {v.label} + + ))} + + +
+
+

Dependency Component

+
+ {dependencyOptions.map(v => ( + + ))} +
+
+
+

+ + + + Mode + + + With data growth, you can migrate data from standalone mode to + cluster mode. View More + + + +

+
+ {modeOptions.map(v => ( + + ))} +
+
+
); } diff --git a/src/parts/sizing/index.module.less b/src/parts/sizing/index.module.less index ccdfc9aa4..c1f1d706b 100644 --- a/src/parts/sizing/index.module.less +++ b/src/parts/sizing/index.module.less @@ -1,16 +1,51 @@ @import url('@/styles/global.module.less'); +.tooltipTrigger { + text-decoration: underline dashed #d0d7dc; +} + +.flexRow { + display: flex; + gap: 8px; + align-items: center; +} + +.flexColumn { + display: flex; + flex-direction: column; + gap: 8px; +} + +.marginBtm20 { + margin-bottom: 20px; +} + +.marginBtm0 { + margin-bottom: 0; +} + +.paddingBtm20 { + padding-bottom: 20px; +} + .formSection { padding: 30px 24px; border-right: 1px solid #ececee; + @media @tablet, @phone { + border-right: none; + border-bottom: 1px solid #ececee; + } + .singlePart { - padding-bottom: 24px; border-bottom: 1px solid #ececee; margin-bottom: 24px; - } - .singleRow { - margin-bottom: 20px; + + &:last-of-type { + border-bottom: none; + margin-bottom: 0; + padding-bottom: 0; + } } .commonLabel { @@ -25,17 +60,6 @@ .innerRow { margin-top: 20px; } - .marginBtm20 { - margin-bottom: 20px; - } - - .marginBtm0 { - margin-bottom: 0; - } - - .tooltipTrigger { - text-decoration: underline dashed #d0d7dc; - } .offLoadingLabel { display: flex; @@ -49,26 +73,21 @@ color: #2e373b; } - .flexRow { - display: flex; - gap: 8px; - align-items: center; - } - .switchRow { margin-bottom: 20px; } .collapsible { overflow: hidden; - transition: height 0.3s linear; - will-change: height; + height: fit-content; + padding-top: 20px; } .visibleCollapse { } .invisibleCollapse { height: 0; + padding-top: 0; } .selectTrigger { @@ -90,4 +109,338 @@ gap: 20px; align-items: center; } + + .cardsWrapper { + display: flex; + gap: 12px; + + .card { + flex: 1; + border-radius: 10px; + border: 1px solid #e4eaf1; + background: #fff; + cursor: pointer; + } + + .dependencyCard { + .flexRow(); + padding: 3px 12px; + justify-content: center; + align-items: center; + + img { + width: 28px; + height: 28px; + } + + .depName { + .paragraph4-regular(); + } + } + .modeCard { + padding: 12px; + .flexColumn(); + + .modeName { + .paragraph4(); + } + + .modeDesc { + .paragraph6-regular(); + text-align: left; + } + } + + .activeCard { + border-color: @color-blue1; + } + + .switchLabel { + .paragraph4(); + } + } +} + +.resultContainer { + padding: 40px 20px; + + .dataSection { + margin-bottom: 40px; + } + + .sectionLabel { + .paragraph3-bold(); + margin-bottom: 16px; + } + + .dataCardName { + .paragraph4-bold(); + margin-bottom: 4px; + } + + .sectionLabelBar { + .flexRow(); + align-items: center; + justify-content: space-between; + margin-bottom: 16px; + + .title { + .paragraph3-bold(); + } + + .buttonWrapper { + display: flex; + align-items: center; + gap: 4px; + + &:hover { + text-decoration: underline solid #000; + } + + span { + .paragraph5(); + color: #000; + } + } + } + + .totalDataContent { + background-color: @color-white1; + border-radius: 12px; + padding: 20px 0; + margin-bottom: 40px; + } + .dataSummary { + display: flex; + border-bottom: 1px solid @color-black4; + padding: 0 20px 20px 20px; + + .summaryCard { + flex: 1; + } + } + + .dataDetail { + padding: 0 20px; + border-bottom: 1px solid @color-black4; + + &:last-of-type { + border-bottom: none; + } + + .commonCollapseTitle { + width: 100%; + display: flex; + gap: 12px; + align-items: center; + padding: 24px 0; + + .collapseTitle { + flex: 1; + display: flex; + justify-content: space-between; + align-items: center; + + @media @phone { + flex-direction: column; + gap: 8px; + align-items: flex-start; + justify-content: center; + } + + .titleName { + .paragraph3-bold(); + } + .titleOverview { + .paragraph3(); + + .valueInfo { + .paragraph3-bold(); + color: @color-blue2; + letter-spacing: 150%; + } + } + } + .collapseIcon { + flex: 0 0 16px; + } + + .activeIcon { + transform: rotate(180deg); + } + } + + .milvusDataDetail { + display: flex; + flex-wrap: wrap; + + .detailCard { + flex: 0 1 33.33%; + } + } + + .depDataItem { + flex: 1; + } + } + + .installationSection { + .sectionTitle { + .paragraph3-bold(); + margin-bottom: 16px; + } + .installButton { + .paragraph4(); + padding: 9px 0; + width: 100%; + justify-content: center; + margin-bottom: 16px; + } + .installCodeWrapper { + padding: 32px 24px 20px 24px; + background-color: @color-white1; + border-radius: 16px; + position: relative; + + code { + .paragraph4-regular(); + display: block; + width: 100%; + overflow-x: auto; + padding-bottom: 10px; + } + + .copyBtn { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + padding: 0; + margin: 0; + outline: none; + position: absolute; + top: 12px; + right: 12px; + cursor: pointer; + + &:hover { + opacity: 0.7; + } + } + } + + .advCard { + .paragraph4-regular(); + padding: 12px 24px; + border-radius: 12px; + background: linear-gradient( + 91deg, + rgba(208, 131, 255, 0.2) 0.09%, + rgba(142, 221, 255, 0.2) 100.63% + ); + + a { + color: #000; + text-decoration: underline; + } + } + } +} + +.dataCard { + width: 100%; + padding: 8px 20px; + border-radius: 12px; + background: @color-white1; + + .dataName { + .paragraph4-bold(); + margin-bottom: 6px; + } + + .dataInfo { + .paragraph3-bold(); + color: #008dc8; + } + + .dataDesc { + } + + .dataCount { + .paragraph3-regular(); + margin-top: 6px; + } + + .largeName { + } + .largeData { + .heading4(); + } +} + +.dependencyDetailContainer { + .firstRow { + display: flex; + gap: 20px; + margin-bottom: 20px; + + .card { + flex: 1; + padding: 0 16px; + + .dataDesc { + .paragraph3-regular(); + + span { + font-weight: 500; + color: @color-blue2; + } + } + } + } +} + +.configContainer { + padding: 0 16px; + + .configName { + .paragraph4-bold(); + margin-bottom: 8px; + } + + .configList { + display: flex; + gap: 16px; + list-style: none; + + .configItem { + flex: 1; + display: flex; + flex-direction: column; + + .columnName { + .paragraph4(); + margin-bottom: 4px; + } + + .columnValue { + .paragraph4(); + color: @color-blue2; + } + + .columnList { + list-style: none; + + .columnItem { + margin-right: 4px; + + .columnLabel { + .paragraph4-regular(); + margin-right: 6px; + } + } + } + } + } } diff --git a/src/parts/sizing/indexTypeComponent.tsx b/src/parts/sizing/indexTypeComponent.tsx index a00f17da6..e3441f27e 100644 --- a/src/parts/sizing/indexTypeComponent.tsx +++ b/src/parts/sizing/indexTypeComponent.tsx @@ -1,17 +1,26 @@ import { IIndexType, IndexTypeEnum } from '@/types/sizing'; import { RadioGroupItem, RadioGroup } from '@/components/ui'; import classes from './index.module.less'; +import clsx from 'clsx'; +import { SizingRange } from '@/components/sizing'; +import { + MAX_NODE_DEGREE_RANGE_CONFIG, + N_LIST_RANGE_CONFIG, + M_RANGE_CONFIG, +} from '@/consts/sizing'; -const SCANNComponent = (props: { +type IndexTypeComponentProps = { data: IIndexType; onChange: (key: string, value: any) => void; -}) => { +}; + +const SCANNComponent = (props: IndexTypeComponentProps) => { const { data, onChange } = props; return ( -
-

Index Parameters

-

With_raw_data

+
+

Index Parameters

+

With_raw_data

{ @@ -32,6 +41,62 @@ const SCANNComponent = (props: { ); }; +const HNSWComponent = (props: IndexTypeComponentProps) => { + const { data, onChange } = props; + return ( +
+

Index Parameters

+

+ M(Maximum degree of the node) +

+ { + onChange('m', value); + }} + placeholder={`[${M_RANGE_CONFIG.min}, ${M_RANGE_CONFIG.max}]`} + /> +
+ ); +}; + +const DISKANNComponent = (props: IndexTypeComponentProps) => { + const { data, onChange } = props; + return ( +
+

Index Parameters

+

nlist

+ { + onChange('maxDegree', value); + }} + placeholder={`[${MAX_NODE_DEGREE_RANGE_CONFIG.min}, ${MAX_NODE_DEGREE_RANGE_CONFIG.max}]`} + /> +
+ ); +}; + +const IVFComponent = (props: IndexTypeComponentProps) => { + const { data, onChange } = props; + return ( +
+

Index Parameters

+

nlist

+ { + onChange('nlist', value); + }} + placeholder={`[${N_LIST_RANGE_CONFIG.min}, ${N_LIST_RANGE_CONFIG.max}]`} + /> +
+ ); +}; + export const IndexTypeComponent = (props: { data: IIndexType; onChange: (key: string, value: any) => void; @@ -42,7 +107,12 @@ export const IndexTypeComponent = (props: { return null; case IndexTypeEnum.SCANN: return ; + case IndexTypeEnum.HNSW: + return ; + case IndexTypeEnum.DISKANN: + return ; default: - return null; + // IVF_FLAT, IVFSQ8 use the same component + return ; } }; diff --git a/src/parts/sizing/resultSection.tsx b/src/parts/sizing/resultSection.tsx index d0e7eae6d..ec33c6743 100644 --- a/src/parts/sizing/resultSection.tsx +++ b/src/parts/sizing/resultSection.tsx @@ -1,7 +1,373 @@ import clsx from 'clsx'; import classes from './index.module.less'; +import { + TooltipProvider, + Tooltip, + TooltipTrigger, + TooltipContent, + Collapsible, + CollapsibleTrigger, + CollapsibleContent, +} from '@/components/ui'; +import { + ExternalLinkIcon, + ArrowTop, + DownloadIcon, + copyIconTpl, +} from '@/components/icons'; +import { ICalculateResult } from '@/types/sizing'; +import { unitBYTE2Any } from '@/utils/sizingTool'; +import { + milvusOverviewDataCalculator, + dependencyOverviewDataCalculator, +} from '@/utils/sizingToolV2'; +import { DataCard, DependencyComponent } from './dependencyComponent'; +import { Trans, useTranslation } from 'react-i18next'; +import { useState } from 'react'; +import CustomButton from '@/components/customButton'; +import { helmCodeExample, operatorCodeExample } from '@/consts/sizing'; +import { useCopyCode } from '@/hooks/enhanceCodeBlock'; -export default function ResultSection(props: { className: string }) { - const { className } = props; - return
result
; +export default function ResultSection(props: { + className?: string; + calculatedResult: ICalculateResult; +}) { + const { t } = useTranslation('sizingToolV2'); + const { className, calculatedResult } = props; + + const { + rawDataSize, + memorySize, + localDiskSize, + nodeConfig, + dependencyConfig, + mode, + dependency, + } = calculatedResult; + const { queryNode, proxy, mixCoord, dataNode, indexNode } = nodeConfig; + + const { size: totalRawDataSize, unit: rawDataUnit } = + unitBYTE2Any(rawDataSize); + const { size: totalMemorySize, unit: memoryUnit } = unitBYTE2Any(memorySize); + const { size: diskSize, unit: diskUnit } = unitBYTE2Any(localDiskSize); + + const { milvusCpu, milvusMemory } = milvusOverviewDataCalculator(nodeConfig); + const { dependencyCpu, dependencyMemory, dependencyStorage } = + dependencyOverviewDataCalculator({ mode, dependency, dependencyConfig }); + + const totalCpu = milvusCpu + dependencyCpu; + const totalMemory = milvusMemory + dependencyMemory; + + const [isMilvusOpen, setIsMilvusOpen] = useState(false); + const [isDependencyOpen, setIsDependencyOpen] = useState(false); + + useCopyCode([helmCodeExample, operatorCodeExample]); + + return ( +
+
+

{t('overview.title')}

+
+ + + + {t('overview.raw')} + + + {t('overview.rawTooltip')} + + + + } + data={`${totalRawDataSize} ${rawDataUnit}`} + /> + + + + {t('overview.memory')} + + + {t('overview.memoryTooltip')} + + + + } + data={`${totalMemorySize} ${memoryUnit}`} + /> +
+
+
+
+

{t('setup.title')}

+ + {t('setup.share')} + + +
+ +
+
+ + + + +
+ +
+ { + setIsMilvusOpen(isOpen); + }} + > + +
+

{t('setup.milvus.title')}

+
+

+ , + ]} + /> +

+

+ , + ]} + /> +

+
+
+ + + +
+ + +
+ + + + + +
+
+
+
+ +
+ { + setIsDependencyOpen(isOpen); + }} + > + +
+

+ {t('setup.dependency.title')} +

+
+

+ , + ]} + /> +

+

+ , + ]} + /> +

+
+
+ + + +
+ + + + +
+
+
+ +
+

{t('install.title')}

+
+ } + classes={{ + root: classes.installButton, + }} + > + {t('install.helm')} + +
+              {helmCodeExample}
+              
+            
+
+
+ } + classes={{ + root: classes.installButton, + }} + > + {t('install.operator')} + +
+              {operatorCodeExample}
+              
+            
+
+ +
+ , + ]} + /> +
+
+
+
+ ); } diff --git a/src/styles/reset.css b/src/styles/reset.css index e92544d3c..cb2be2b1a 100644 --- a/src/styles/reset.css +++ b/src/styles/reset.css @@ -144,6 +144,7 @@ a { } button { + font-family: Inter; background-color: transparent; padding: 0; margin: 0; diff --git a/src/styles/sizingTool.module.less b/src/styles/sizingTool.module.less index 697672be0..74d104e06 100644 --- a/src/styles/sizingTool.module.less +++ b/src/styles/sizingTool.module.less @@ -3,346 +3,71 @@ .pageContainer { background-color: #fff; } -.container { + +.sizingToolContainer { padding-top: 40px; - padding-bottom: 128px; - font-family: Inter; + padding-bottom: 40px; - h1 { - font-weight: 600; - font-size: 42px; - line-height: 1.5em; - margin-bottom: 40px; + .title { + .heading2(); + margin-bottom: 12px; text-align: center; } - h2 { - font-size: 14px; - line-height: 1.5em; - font-weight: 400; - } - - h3 { - font-weight: 600; - font-size: 18px; - line-height: 1.5em; - margin-bottom: 12px; - color: @color-black1; + .desc { + .paragraph6-regular(); + margin-bottom: 40px; + text-align: center; } - .note { + .tipWrapper { display: flex; align-items: flex-start; - background-color: #e0f2fc; - padding: 16px; - border-radius: 12px; - margin-bottom: 20px; gap: 8px; + padding: 20px 0; .iconWrapper { - width: 21px; - height: 21px; + flex: 0 0 20px; + height: 20px; + line-height: 0; + svg { width: 100%; height: 100%; } } - } - - .contentWrapper { - display: flex; - gap: 64px; - - @media (max-width: 1280px) { - gap: 32px; - } - - @media @tablet { - gap: 40px; - flex-direction: column; - } - - @media @phone { - gap: 40px; - flex-direction: column; - } - - .leftPart { - flex: 0 1 412px; - padding: 32px 49px 32px 40px; - display: flex; - flex-direction: column; - gap: 0; - border: 1px solid @color-black3; - border-radius: 12px; - - @media @tablet { - gap: 64px; - } - @media @phone { - gap: 32px; - } - - .dataSize, - .indexType { - width: 100%; - } + .tip { + .paragraph6-regular(); - .dataItem { - margin-bottom: 18px; - - .label { - font-weight: 400; - font-size: 12px; - line-height: 1.5em; - margin-bottom: 16px; - } - - .interpretation { - font-weight: 400; - font-size: 12px; - line-height: 1.5em; - margin-bottom: 24px; - } - - .sliderWrapper { - padding: 0 8px; - } - - .shortMargin { - margin-bottom: 8px; - } - - .largeMargin { - margin-bottom: 32px; - } - } - - .arrowIcon { - display: inline-block; - width: 11px; - height: 9px; - background-color: #000; - clip-path: polygon(0 0, 100% 0, 50% 100%, 0 0); - position: absolute; - right: 16px; - top: 50%; - transform: translateY(-50%); - cursor: pointer; - } - - .dropdownController { - border-radius: 2em; - padding: 19px 52px 19px 34px; - font-weight: 400; - font-size: 14px; - line-height: 20px; + a { color: #000; - } - - .dropdownMenu { - margin-top: 8px; - background: #ffffff; - box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.08); - border-radius: 29px; - padding: 8px 0; - max-height: 300px; - - & > div { - padding: 14px 32px; - - &:hover { - background-color: #eff9fe; - color: #1493cc; - } - } - } - - .radioGroup { - display: flex; - margin-top: 16px; - } - } - - .rightPart { - flex: 1; - - .contentClassName { - color: #1493cc; - font-weight: 600; - font-size: 24px; - line-height: 34px; - } - - .capacity { - margin-bottom: 40px; - - .cardsWrapper { - display: flex; - gap: 20px; - } - } - - .cluster { - margin-bottom: 40px; - - .singleRowCard { - display: flex; - padding: 20px; - background-color: @color-white1; - border-radius: 4px; - gap: 68px; - margin-bottom: 20px; - - .totalWrapper { - flex: 1; - - display: flex; - - .singlePart { - flex: 1; - - .label { - font-size: 18px; - line-height: 26px; - font-weight: 600; - margin-bottom: 4px; - } - - .value { - font-size: 24px; - font-weight: 600; - line-height: 34px; - color: @color-text-primary; - } - } - } - } - - .titleRow { - display: flex; - justify-content: space-between; - gap: 24px; - margin-bottom: 12px; - - .title { - margin-bottom: 0px; - } - - .detailWrapper { - display: flex; - gap: 20px; - - .detailInfo { - display: flex; - - .label { - font-size: 14px; - font-weight: 600; - line-height: 1.5em; - } - - .value { - font-size: 14px; - font-weight: 600; - line-height: 1.5em; - color: @color-primary; - } - } - } - } - - .cardsWrapper { - display: grid; - grid-template-columns: repeat(4, 1fr); - column-gap: 20px; - row-gap: 20px; - - @media @tablet { - grid-template-columns: repeat(3, 1fr); - } - - @media @phone { - grid-template-columns: repeat(2, 1fr); - } - } - - .cardLayoutWrapper { - .cardsRow { - display: flex; - gap: 20px; - - div { - flex: 1; - } - } - - .cardsRow + .cardsRow { - margin-top: 12px; - } - } - } - - .btnContainer { - display: flex; - gap: 20px; - - @media (max-width: 800px) { - flex-direction: column; - gap: 24px; - } - - .btnsWrapper { - flex: 1; - display: flex; - flex-direction: column; - align-items: stretch; - gap: 12px; - - .ctaButton { - .paragraph4(); - justify-content: center; - } - } - } - - .line { - display: flex; - gap: 20px; - justify-content: space-evenly; - align-items: stretch; - margin-bottom: 40px; + text-decoration: underline; } } } -} - -.sizingToolContainer { - padding-top: 40px; - padding-bottom: 40px; - - .title { - .heading1(); - margin-bottom: 40px; - } - - .desc { - .paragraph6(); - margin-bottom: 20px; - } .contentContainer { display: flex; border-radius: 24px; border: 2px solid #ececee; + @media @tablet, @phone { + flex-direction: column; + } + .leftSection { - flex: 0 0 446px; + flex: 0 1 446px; } .rightSection { flex: 1; + min-width: 650px; + + @media @tablet, @phone { + min-width: 0; + width: 100%; + } } } } diff --git a/src/types/sizing.ts b/src/types/sizing.ts index 1287ae9d0..02b093f56 100644 --- a/src/types/sizing.ts +++ b/src/types/sizing.ts @@ -16,7 +16,76 @@ export enum IndexTypeEnum { export interface IIndexType { indexType: IndexTypeEnum; widthRawData: boolean; - nodeDegree: number; - nlist: number; maxDegree: number; + nlist: number; + m: number; +} + +export enum DependencyComponentEnum { + Pulsar, + Kafka, +} + +export enum ModeEnum { + Standalone, + Cluster, +} + +const NodesType = { + queryNode: 'Query Node', + proxy: 'Proxy', + mixCoord: 'Mix Coord', + dataNode: 'Data Node', + indexNode: 'Index Node', +} as const; + +export type NodesKeyType = keyof typeof NodesType; +export type NodesValueType = { + cpu: number; + memory: number; + count: number; +}; + +type DependencyBaseConfigType = { + cpu: number; + memory: number; + count: number; +}; + +export type DependencyConfigType = { + etcd: DependencyBaseConfigType & { + pvc: number; + }; + minio: DependencyBaseConfigType & { + pvc: number; + }; + pulsar?: { + bookie: DependencyBaseConfigType & { + journal: number; + ledgers: number; + }; + broker: DependencyBaseConfigType; + proxy: DependencyBaseConfigType; + zookeeper: DependencyBaseConfigType & { + pvc: number; + }; + }; + kafka?: { + broker: DependencyBaseConfigType & { + pvc: number; + }; + zookeeper: DependencyBaseConfigType & { + pvc: number; + }; + }; +}; + +export interface ICalculateResult { + rawDataSize: number; + memorySize: number; + localDiskSize: number; + nodeConfig: Record; + dependencyConfig: DependencyConfigType; + mode: ModeEnum; + dependency: DependencyComponentEnum; } diff --git a/src/utils/sizingTool.ts b/src/utils/sizingTool.ts index 1394f036c..dcf07c17a 100644 --- a/src/utils/sizingTool.ts +++ b/src/utils/sizingTool.ts @@ -55,14 +55,14 @@ export const unitBYTE2Any = (size: number, unit?: DataSizeUnit) => { sizeStatus === 1 ? (baseUnit = 'B') : sizeStatus === 2 - ? (baseUnit = 'K') + ? (baseUnit = 'KB') : sizeStatus === 3 - ? (baseUnit = 'M') + ? (baseUnit = 'MB') : sizeStatus === 4 - ? (baseUnit = 'G') + ? (baseUnit = 'GB') : sizeStatus === 5 - ? (baseUnit = 'T') - : (baseUnit = 'K'); + ? (baseUnit = 'TB') + : (baseUnit = 'KB'); size = Math.ceil(size * 10) / 10; return { diff --git a/src/utils/sizingToolV2.ts b/src/utils/sizingToolV2.ts new file mode 100644 index 000000000..cdb9bc2bd --- /dev/null +++ b/src/utils/sizingToolV2.ts @@ -0,0 +1,594 @@ +import { ONE_MILLION, FIXED_QUERY_NODE_CONFIG } from '@/consts/sizing'; +import { + DependencyComponentEnum, + DependencyConfigType, + IIndexType, + IndexTypeEnum, + ModeEnum, +} from '@/types/sizing'; +import { unitAny2BYTE } from './sizingTool'; +import { NodesKeyType, NodesValueType } from '@/types/sizing'; +/** + * params list: + * num: number: number of vectors + * d: number: dimension of vectors + * withScalar: boolean: whether with scalar data + * scalarAvg: number: average length of scalar data + * maxDegree: number: maximum degree of the node + * segSize: number: segment size + * + */ + +// raw data size calculator +export const rawDataSizeCalculator = (params: { + num: number; + d: number; + withScalar: boolean; + scalarAvg: number; +}) => { + const { num, d, withScalar, scalarAvg } = params; + + const vectorRaw = (num * d * ONE_MILLION * 32) / 8; // bytes + const scalarRaw = withScalar ? num * scalarAvg * ONE_MILLION : 0; // bytes + + return vectorRaw + scalarRaw; +}; + +const $1M768D = rawDataSizeCalculator({ + num: 1, + d: 768, + withScalar: false, + scalarAvg: 0, +}); +export const $10M768D = rawDataSizeCalculator({ + num: 10, + d: 768, + withScalar: false, + scalarAvg: 0, +}); +const $100M768D = rawDataSizeCalculator({ + num: 100, + d: 768, + withScalar: false, + scalarAvg: 0, +}); +const $1B768D = rawDataSizeCalculator({ + num: 1000, + d: 768, + withScalar: false, + scalarAvg: 0, +}); + +// loading memory and disk calculator +export const memoryAndDiskCalculator = (params: { + rawDataSize: number; + indexTypeParams: IIndexType; + d: number; + num: number; + withScalar: boolean; + offLoading: boolean; + scalarAvg: number; + segSize: number; +}) => { + const { + rawDataSize, + num, + d, + withScalar, + scalarAvg, + offLoading, + indexTypeParams, + segSize, + } = params; + const { indexType, widthRawData, maxDegree, m, nlist } = indexTypeParams; + const segmentSizeByte = segSize * 1024; + + let result = { + memory: 0, + disk: 0, + }; + const rowSize = rawDataSize / num; + + switch (indexType) { + case IndexTypeEnum.FLAT: + result = { + memory: rawDataSize, + disk: 0, + }; + break; + case IndexTypeEnum.IVF_FLAT: + result = { + memory: rawDataSize + nlist * rowSize, + disk: 0, + }; + break; + case IndexTypeEnum.IVFSQ8: + result = { + memory: rawDataSize / 4 + nlist * rowSize, + disk: 0, + }; + break; + case IndexTypeEnum.SCANN: + result = { + memory: widthRawData + ? (1 + 1 / 8) * rawDataSize + : (1 / 8) * rawDataSize, + disk: 0, + }; + break; + case IndexTypeEnum.HNSW: + result = { + memory: (1 + (2 * m) / d) * rawDataSize, + disk: 0, + }; + break; + case IndexTypeEnum.DISKANN: + result = { + memory: rawDataSize / 4, + disk: (1 + maxDegree / d) * rawDataSize, + }; + break; + default: + break; + } + + const scalarLoadingMemory = withScalar + ? offLoading + ? (num * scalarAvg * ONE_MILLION) / 10 + : num * scalarAvg * ONE_MILLION + : 0; + + const scalarLocalDisk = offLoading ? num * scalarAvg * ONE_MILLION : 0; + + if (indexType === IndexTypeEnum.DISKANN) { + const vectorLoadingMemory = result.memory * 1.15; + return { + memory: vectorLoadingMemory + scalarLoadingMemory, // bytes + disk: scalarLocalDisk + result.disk, // bytes + }; + } + + const vectorLoadingMemory = (result.memory + segmentSizeByte * 2) * 1.15; + return { + memory: vectorLoadingMemory + scalarLoadingMemory, // bytes + disk: scalarLocalDisk + result.disk, // bytes + }; +}; + +// query nodes calculator +export const nodesConfigCalculator = (params: { memory: number }) => { + const { memory } = params; + const memoryGB = Math.ceil((memory * 10) / 1024 / 1024 / 1024) / 10; + + const queryNodeCount = + memoryGB >= 2048 + ? Math.ceil(memoryGB / 128) + : memoryGB >= 512 + ? Math.ceil(memoryGB / 64) + : memoryGB >= 96 + ? Math.ceil(memoryGB / 32) + : 0; + + const basicLargeNodeConfig = { + proxy: { + cpu: 8, + memory: 32, + count: 1, + }, + mixCoord: { + cpu: 8, + memory: 32, + count: 1, + }, + dataNode: { + cpu: 8, + memory: 32, + count: 1, + }, + indexNode: { + cpu: 8, + memory: 16, + count: 8, + }, + }; + const queryNodesConfig = [ + ...FIXED_QUERY_NODE_CONFIG, + { + memory: [96, 512], + queryNode: { + cpu: 8, + memory: 32, + count: queryNodeCount, + }, + ...basicLargeNodeConfig, + }, + { + memory: [512, 2048], + queryNode: { + cpu: 16, + memory: 64, + count: queryNodeCount, + }, + ...basicLargeNodeConfig, + }, + { + memory: [2048, Infinity], + queryNode: { + cpu: 32, + memory: 128, + count: queryNodeCount, + }, + ...basicLargeNodeConfig, + }, + ]; + + const properMemorySection = queryNodesConfig.find( + v => v.memory[0] <= memoryGB && v.memory[1] > memoryGB + ); + return { + queryNode: properMemorySection?.queryNode, + proxy: properMemorySection?.proxy, + mixCoord: properMemorySection?.mixCoord, + dataNode: properMemorySection?.dataNode, + indexNode: properMemorySection?.indexNode, + }; +}; + +export const dependencyCalculator = (params: { + num: number; + d: number; + mode: ModeEnum; + withScalar: boolean; + scalarAvg: number; +}): DependencyConfigType => { + const { num, d, mode, scalarAvg, withScalar } = params; + + const rawDataSize = rawDataSizeCalculator({ + num, + d, + withScalar, + scalarAvg, + }); + + const thresholds = [$1M768D, $10M768D, $100M768D, $1B768D]; + + let etcdBaseConfig = { + cpu: 0, + memory: 0, + pvc: 0, + count: 0, + }; + let minioBaseConfig = { + cpu: 0, + memory: 0, + pvc: 0, + count: 0, + }; + let pulsarBaseConfig = { + bookie: { + cpu: 0, + memory: 0, + count: 0, + journal: 0, + ledgers: 0, + }, + broker: { + cpu: 0, + memory: 0, + count: 0, + }, + proxy: { + cpu: 0, + memory: 0, + count: 0, + }, + zookeeper: { + cpu: 0, + memory: 0, + count: 0, + pvc: 0, + }, + }; + let kafkaBaseConfig = { + broker: { + cpu: 0, + memory: 0, + count: 0, + pvc: 0, + }, + zookeeper: { + cpu: 0, + memory: 0, + count: 0, + pvc: 0, + }, + }; + + switch (mode) { + case ModeEnum.Standalone: + etcdBaseConfig = { + cpu: 0.5, + memory: 1, + pvc: 10, + count: 1, + }; + minioBaseConfig = { + cpu: 1, + memory: 4, + pvc: 20, + count: 1, + }; + + if (rawDataSize >= thresholds[0]) { + etcdBaseConfig.memory = 2; + minioBaseConfig.memory = 8; + minioBaseConfig.pvc = 120; + } + + return { + etcd: etcdBaseConfig, + minio: minioBaseConfig, + pulsar: undefined, + kafka: undefined, + }; + case ModeEnum.Cluster: + etcdBaseConfig = { + cpu: 1, + memory: 4, + pvc: 20, + count: 3, + }; + minioBaseConfig = { + cpu: 1, + memory: 8, + pvc: 30, + count: 4, + }; + + pulsarBaseConfig = { + bookie: { + cpu: 1, + memory: 8, + count: 3, + journal: 30, + ledgers: 40, + }, + broker: { + cpu: 1, + memory: 4, + count: 2, + }, + proxy: { + cpu: 0.5, + memory: 4, + count: 2, + }, + zookeeper: { + cpu: 0.5, + memory: 2, + count: 3, + pvc: 30, + }, + }; + + kafkaBaseConfig = { + broker: { + cpu: 2, + memory: 8, + count: 3, + pvc: 100, + }, + zookeeper: { + cpu: 0.5, + memory: 2, + count: 3, + pvc: 30, + }, + }; + + if (rawDataSize >= thresholds[1] && rawDataSize < thresholds[2]) { + minioBaseConfig = { + cpu: 1, + memory: 8, + pvc: 300, + count: 4, + }; + pulsarBaseConfig = { + bookie: { + cpu: 1, + memory: 8, + count: 3, + journal: 50, + ledgers: 400, + }, + broker: { + cpu: 1, + memory: 4, + count: 2, + }, + proxy: { + cpu: 0.5, + memory: 4, + count: 2, + }, + zookeeper: { + cpu: 0.5, + memory: 2, + count: 3, + pvc: 30, + }, + }; + + kafkaBaseConfig = { + broker: { + cpu: 2, + memory: 8, + count: 3, + pvc: 400, + }, + zookeeper: { + cpu: 0.5, + memory: 2, + count: 3, + pvc: 30, + }, + }; + } + + if (rawDataSize >= thresholds[2] && rawDataSize < thresholds[3]) { + etcdBaseConfig = { + cpu: 1, + memory: 8, + pvc: 50, + count: 3, + }; + minioBaseConfig = { + cpu: 1, + memory: 8, + pvc: 3000, + count: 4, + }; + + pulsarBaseConfig = { + bookie: { + cpu: 2, + memory: 16, + count: 3, + journal: 100, + ledgers: 4000, + }, + broker: { + cpu: 2, + memory: 8, + count: 2, + }, + proxy: { + cpu: 1, + memory: 4, + count: 2, + }, + zookeeper: { + cpu: 0.5, + memory: 4, + count: 3, + pvc: 100, + }, + }; + + kafkaBaseConfig = { + broker: { + cpu: 4, + memory: 16, + count: 3, + pvc: 4000, + }, + zookeeper: { + cpu: 0.5, + memory: 4, + count: 3, + pvc: 100, + }, + }; + } + + return { + etcd: etcdBaseConfig, + minio: minioBaseConfig, + pulsar: pulsarBaseConfig, + kafka: kafkaBaseConfig, + }; + } +}; + +export const milvusOverviewDataCalculator = ( + params: Record +) => { + const { queryNode, proxy, mixCoord, dataNode, indexNode } = params; + const milvusCpu = + queryNode.cpu * queryNode.count + + proxy.cpu * proxy.count + + mixCoord.cpu * mixCoord.count + + dataNode.cpu * dataNode.count + + indexNode.cpu * indexNode.count; + + const milvusMemory = + queryNode.memory * queryNode.count + + proxy.memory * proxy.count + + mixCoord.memory * mixCoord.count + + dataNode.memory * dataNode.count + + indexNode.memory * indexNode.count; + + return { + milvusCpu, + milvusMemory, + }; +}; + +export const dependencyOverviewDataCalculator = (params: { + mode: ModeEnum; + dependency: DependencyComponentEnum; + dependencyConfig: DependencyConfigType; +}) => { + const { mode, dependency, dependencyConfig } = params; + const { etcd, minio, pulsar, kafka } = dependencyConfig; + + const pulsarCpu = + mode === ModeEnum.Standalone + ? 0 + : pulsar.bookie.cpu * pulsar.bookie.count + + pulsar.broker.cpu * pulsar.broker.count + + pulsar.zookeeper.cpu * pulsar.zookeeper.count + + pulsar.proxy.cpu * pulsar.proxy.count; + const kafkaCpu = + mode === ModeEnum.Standalone + ? 0 + : kafka.broker.cpu * kafka.broker.count + + kafka.zookeeper.cpu * kafka.zookeeper.count; + const streamPlatformCpu = + dependency === DependencyComponentEnum.Pulsar ? pulsarCpu : kafkaCpu; + const dependencyCpu = + etcd.cpu * etcd.count + minio.cpu * etcd.count + streamPlatformCpu; + + const pulsarMemory = + mode === ModeEnum.Standalone + ? 0 + : pulsar.bookie.memory * pulsar.bookie.count + + pulsar.broker.memory * pulsar.broker.count + + pulsar.zookeeper.memory * pulsar.zookeeper.count + + pulsar.proxy.memory * pulsar.proxy.count; + const kafkaMemory = + mode === ModeEnum.Standalone + ? 0 + : kafka.broker.memory * kafka.broker.count + + kafka.zookeeper.memory * kafka.zookeeper.count; + + const streamPlatformMemory = + dependency === DependencyComponentEnum.Pulsar ? pulsarMemory : kafkaMemory; + + const dependencyMemory = + etcd.memory * etcd.count + minio.memory * etcd.count + streamPlatformMemory; + + const pulsarStorage = + mode === ModeEnum.Standalone + ? 0 + : (pulsar.bookie.journal + pulsar.bookie.ledgers) * pulsar.bookie.count + + pulsar.zookeeper.pvc * pulsar.zookeeper.count; + const kafkaStorage = + mode === ModeEnum.Standalone + ? 0 + : kafka.broker.pvc * kafka.broker.count + + kafka.zookeeper.pvc * kafka.zookeeper.count; + const streamPlatformStorage = + dependency === DependencyComponentEnum.Pulsar + ? pulsarStorage + : kafkaStorage; + + const dependencyStorage = + etcd.pvc * etcd.count + minio.pvc * etcd.count + streamPlatformStorage; + + return { + dependencyCpu, + dependencyMemory, + dependencyStorage, + }; +}; From 482ee6ffd5906fe3ebc9b60da7881b3b080e229e Mon Sep 17 00:00:00 2001 From: ThyeeZz Date: Thu, 26 Dec 2024 17:48:55 +0800 Subject: [PATCH 3/3] basicly complete sizing tool --- src/consts/sizing.ts | 18 ++-- src/i18n/en/sizingToolV2.json | 27 +++++- src/pages/blog/index.tsx | 48 +---------- src/pages/error.tsx | 14 +-- src/pages/tools/sizing.tsx | 4 +- src/parts/blogs/zillizAdv/index.module.less | 96 +++++++++++++++++++++ src/parts/blogs/zillizAdv/index.tsx | 47 ++++++++++ src/parts/sizing/formSection.tsx | 77 +++++++++++------ src/parts/sizing/index.module.less | 5 ++ src/parts/sizing/indexTypeComponent.tsx | 33 +++++-- src/parts/sizing/resultSection.tsx | 2 + src/styles/sizingTool.module.less | 5 ++ src/types/sizing.ts | 3 +- src/utils/sizingToolV2.ts | 10 ++- 14 files changed, 286 insertions(+), 103 deletions(-) create mode 100644 src/parts/blogs/zillizAdv/index.module.less create mode 100644 src/parts/blogs/zillizAdv/index.tsx diff --git a/src/consts/sizing.ts b/src/consts/sizing.ts index f5832fe12..56c7a6638 100644 --- a/src/consts/sizing.ts +++ b/src/consts/sizing.ts @@ -25,27 +25,27 @@ export const DIMENSION_RANGE_CONFIG = { }; export const MAX_NODE_DEGREE_RANGE_CONFIG = { - min: 2, + min: 1, max: 2048, - defaultValue: 1, + defaultValue: 56, domain: [0, 20, 40, 60, 80, 100], - range: [2, 8, 32, 128, 512, 2048], + range: [1, 8, 32, 128, 512, 2048], }; export const M_RANGE_CONFIG = { - min: 1, + min: 2, max: 2048, - defaultValue: 1, + defaultValue: 30, domain: [0, 20, 40, 60, 80, 100], - range: [1, 8, 32, 128, 512, 2048], + range: [2, 8, 32, 128, 512, 2048], }; export const N_LIST_RANGE_CONFIG = { min: 1, - max: 10000, - defaultValue: 1, + max: 65536, + defaultValue: 128, domain: [0, 10, 20, 40, 70, 100], - range: [1, 16, 256, 4096, 16384, 65536], + range: [1, 16, 128, 4096, 16384, 65536], }; export const SEGMENT_SIZE_OPTIONS = [ diff --git a/src/i18n/en/sizingToolV2.json b/src/i18n/en/sizingToolV2.json index 7f7e1cac7..7f5d5e2b6 100644 --- a/src/i18n/en/sizingToolV2.json +++ b/src/i18n/en/sizingToolV2.json @@ -2,6 +2,30 @@ "title": "Milvus Sizing Tool", "content": "A tool for estimate the resources required to set up Milvus based on your dataset.", "tooltip": "Note: all the recommendations are calculated based on our lab data, you should adjust it with your own testing before deploying to production. If you have any question, please <0>contact us.", + "form": { + "num": "Number of Vector", + "dim": "Vector Dimension", + "withScalar": "With Scalar Fields", + "averageLength": "Average Data Size Per Row", + "offloading": "Offloading Fields to Disk", + "mmp": "Milvus uses <0>Mmap to enable direct memory access to large files on disk without reading the entire files into memory.", + "indexType": "Index Type", + "withRawData": "With_raw_data", + "m": "M(Maximum degree of the node)", + "nlist": "nlist", + "maxDegree": "Max Degree", + "segmentSize": "Segment Size", + "dependencyComp": "Dependency Component", + "mode": "Mode", + "modeTip": "With data growth, you can migrate data from standalone mode to cluster mode. <0>View More", + "modeDisableTip": "Don‘t support standalone mode when number of vector exceed 10 Million or XXXXXXXX.", + "pulsar": "Pulsar", + "kafka": "Kafka", + "standalone": "Standalone", + "standaloneDesc": "Suitable for small data size and poc env.", + "cluster": "Cluster", + "clusterDesc": "Suitable for large datasize and production env. " + }, "overview": { "title": "Approximate Capacity", "raw": "Raw Data Size", @@ -46,7 +70,8 @@ "ledger": "Ledgers", "config": "{{cpu}} Core {{memory}} GB", "core": "{{cpu}} Core", - "gb": "{{memory}} GB" + "gb": "{{memory}} GB", + "byte": "Bytes" } }, "install": { diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx index d06b93cd7..02677ebc3 100644 --- a/src/pages/blog/index.tsx +++ b/src/pages/blog/index.tsx @@ -10,8 +10,6 @@ import { ABSOLUTE_BASE_URL } from '@/consts'; import { AirplaneArrowIcon, ClockIcon, - ListItemTickIcon, - RightTopArrowIcon, RocketIcon, SearchIcon, TrendingIcon, @@ -32,6 +30,7 @@ import dayjs from 'dayjs'; import { Trans, useTranslation } from 'react-i18next'; import { InkeepCustomTriggerWrapper } from '@/components/inkeep/inkeepChat'; import { debounce } from '@mui/material'; +import ZillizAdv from '@/parts/blogs/zillizAdv'; const PAGE_SIZE = 9; const TITLE = 'Learn Milvus: Insights and Innovations in VectorDB Technology'; @@ -48,9 +47,6 @@ const SEARCH_QUERY_KEY = 'q'; const ELLIPSIS = 'ellipsis'; const ELLIPSIS_2 = 'ellipsis2'; -const CTA_LINK = - 'https://cloud.zilliz.com/signup?utm_source=partner&utm_medium=referral&utm_campaign=2024-12-19_blog_overview-page_milvusio'; - const generatePaginationNavigators = ( currentPage: number, totalPages: number @@ -502,46 +498,6 @@ const Blog: React.FC = props => { ); }; - const renderZillizAdv = () => { - const features = [ - t('blog:zillizAdv.feature1'), - t('blog:zillizAdv.feature2'), - ]; - - const featureItems = features.map(f => ( -
  • - - {f} -
  • - )); - - return ( -
    -
    -
    - {t('blog:zillizAdv.smallTitle')} -
    -

    - {t('blog:zillizAdv.title')} -

    -
      {featureItems}
    - - {t('blog:zillizAdv.btn')} - - -
    -
    -
    - ); - }; - const renderAuthors = () => { const authors = [ { @@ -615,7 +571,7 @@ const Blog: React.FC = props => { {renderPaging()} {renderAIService()} - {renderZillizAdv()} + {renderAuthors()}
    diff --git a/src/pages/error.tsx b/src/pages/error.tsx index e0b803276..0f0ba9e47 100644 --- a/src/pages/error.tsx +++ b/src/pages/error.tsx @@ -12,13 +12,13 @@ const RELOAD_LIMIT = 3; export default function Error(props: { error: Error; reset: () => void }) { const count = useRef(0); - useEffect(() => { - if (count.current >= RELOAD_LIMIT) { - redirect('/'); - } - count.current += 1; - window.location.reload(); - }, []); + // useEffect(() => { + // if (count.current >= RELOAD_LIMIT) { + // redirect('/'); + // } + // count.current += 1; + // window.location.reload(); + // }, []); return (
    diff --git a/src/pages/tools/sizing.tsx b/src/pages/tools/sizing.tsx index fc224406b..79bdc2b5c 100644 --- a/src/pages/tools/sizing.tsx +++ b/src/pages/tools/sizing.tsx @@ -5,7 +5,6 @@ import classes from '@/styles/sizingTool.module.less'; import pageClasses from '@/styles/responsive.module.less'; import clsx from 'clsx'; import Head from 'next/head'; - import { ABSOLUTE_BASE_URL } from '@/consts'; import FormSection from '@/parts/sizing/formSection'; import ResultSection from '@/parts/sizing/resultSection'; @@ -15,6 +14,7 @@ import { ModeEnum, } from '@/types/sizing'; import { InfoFilled } from '@/components/icons'; +import ZillizAdv from '@/parts/blogs/zillizAdv'; const etcdBaseValue = { cpu: 0, @@ -174,6 +174,8 @@ export default function SizingTool() { calculatedResult={calculatedResult} />
    + +
    diff --git a/src/parts/blogs/zillizAdv/index.module.less b/src/parts/blogs/zillizAdv/index.module.less new file mode 100644 index 000000000..16aeccdb9 --- /dev/null +++ b/src/parts/blogs/zillizAdv/index.module.less @@ -0,0 +1,96 @@ +@import url('@/styles/global.module.less'); + +.zilliz-adv { + border-radius: 16px; + border: 1px solid @color-black4; + background: @color-white; + padding: 30px 60px; + display: flex; + align-items: center; + gap: 50px; + min-height: 392px; + + @media @phone, @tablet { + flex-wrap: wrap; + padding: 30px; + gap: 20px; + } + + &-main { + flex: 1 1 50%; + } + + &-small-title { + font-size: 16px; + font-weight: 500; + line-height: 1.5; + text-transform: uppercase; + font-family: 'Geist Mono'; + } + + &-title { + font-size: 32px; + font-weight: 600; + line-height: 1.4; + } + + &-features { + margin-top: 12px; + + &-item { + color: @color-black2; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + list-style: none; + display: flex; + align-items: center; + font-family: 'Geist Mono'; + + &:not(:first-of-type) { + margin-top: 8px; + } + + &-icon { + color: @color-blue1; + margin-right: 2px; + flex: 0 0 auto; + } + } + } + + &-btn { + padding: 9.5px 28px; + font-size: 14px; + font-weight: 500; + line-height: 1.5; + border-radius: 6px; + background-color: @color-black1; + color: @color-white; + display: inline-flex; + align-items: center; + gap: 8px; + margin-top: 36px; + + &:hover { + opacity: 0.7; + } + + @media @phone, @tablet { + width: 100%; + justify-content: center; + } + } + + &-logo { + min-height: 230px; + flex: 1 1 50%; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + + @media @tablet { + margin-top: 30px; + } + } +} diff --git a/src/parts/blogs/zillizAdv/index.tsx b/src/parts/blogs/zillizAdv/index.tsx new file mode 100644 index 000000000..da2518436 --- /dev/null +++ b/src/parts/blogs/zillizAdv/index.tsx @@ -0,0 +1,47 @@ +import { ListItemTickIcon, RightTopArrowIcon } from '@/components/icons'; +import styles from './index.module.less'; +import { useTranslation } from 'react-i18next'; +import Link from 'next/link'; +import clsx from 'clsx'; + +const CTA_LINK = + 'https://cloud.zilliz.com/signup?utm_source=partner&utm_medium=referral&utm_campaign=2024-12-19_blog_overview-page_milvusio'; + +export default function ZillizAdv(props: { className?: string }) { + const { t } = useTranslation('blog'); + const { className = '' } = props; + const features = [t('blog:zillizAdv.feature1'), t('blog:zillizAdv.feature2')]; + + const featureItems = features.map(f => ( +
  • + + {f} +
  • + )); + + return ( +
    +
    +
    + {t('blog:zillizAdv.smallTitle')} +
    +

    + {t('blog:zillizAdv.title')} +

    +
      {featureItems}
    + + {t('blog:zillizAdv.btn')} + + +
    +
    +
    + ); +} diff --git a/src/parts/sizing/formSection.tsx b/src/parts/sizing/formSection.tsx index afdd6cd49..f8a4e36a6 100644 --- a/src/parts/sizing/formSection.tsx +++ b/src/parts/sizing/formSection.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState } from 'react'; +import { use, useEffect, useMemo, useRef, useState } from 'react'; import classes from './index.module.less'; import { SizingInput, SizingRange, SizingSwitch } from '@/components/sizing'; import { @@ -24,6 +24,8 @@ import { INDEX_TYPE_OPTIONS, DEPENDENCY_COMPONENTS, MODE_OPTIONS, + N_LIST_RANGE_CONFIG, + M_RANGE_CONFIG, } from '@/consts/sizing'; import clsx from 'clsx'; import { ICalculateResult, IIndexType, ModeEnum } from '@/types/sizing'; @@ -35,11 +37,13 @@ import { $10M768D, dependencyCalculator, } from '@/utils/sizingToolV2'; +import { Trans, useTranslation } from 'react-i18next'; export default function FormSection(props: { className: string; updateCalculatedResult: (params: ICalculateResult) => void; }) { + const { t } = useTranslation('sizingToolV2'); const { className, updateCalculatedResult } = props; const [collapseHeight, setCollapseHeight] = useState(0); @@ -61,11 +65,14 @@ export default function FormSection(props: { const [indexTypeParams, setIndexTypeParams] = useState({ indexType: INDEX_TYPE_OPTIONS[0].value, widthRawData: false, - maxDegree: 0, - nlist: 0, - m: 0, + maxDegree: MAX_NODE_DEGREE_RANGE_CONFIG.defaultValue, + flatNList: N_LIST_RANGE_CONFIG.defaultValue, + sq8NList: N_LIST_RANGE_CONFIG.defaultValue, + m: M_RANGE_CONFIG.defaultValue, }); + const [disableStandalone, setDisableStandalone] = useState(false); + const handleFormChange = (key: string, value: any) => { setForm({ ...form, @@ -125,11 +132,11 @@ export default function FormSection(props: { const modeOptions = [ { ...MODE_OPTIONS[0], - desc: 'Suitable for small datasize and poc env.', + desc: t('form.standaloneDesc'), }, { ...MODE_OPTIONS[1], - desc: 'Suitable for large datasize and production env.', + desc: t('form.clusterDesc'), }, ]; @@ -144,10 +151,10 @@ export default function FormSection(props: { if (rawDataSize >= $10M768D) { currentMode = ModeEnum.Cluster; - setForm({ - ...form, - mode: currentMode, - }); + + setDisableStandalone(true); + } else if (rawDataSize < $10M768D) { + setDisableStandalone(false); } const { memory, disk: localDisk } = memoryAndDiskCalculator({ rawDataSize, @@ -169,7 +176,7 @@ export default function FormSection(props: { d: form.dimension, withScalar: form.widthScalar, scalarAvg: form.scalarData.average, - mode: form.mode, + mode: currentMode, }); updateCalculatedResult({ @@ -181,7 +188,16 @@ export default function FormSection(props: { mode: currentMode, dependency: form.dependency, }); - }, [form]); + }, [form, indexTypeParams]); + + useEffect(() => { + if (disableStandalone && form.mode === ModeEnum.Standalone) { + setForm({ + ...form, + mode: ModeEnum.Cluster, + }); + } + }, [disableStandalone, form.mode]); return (
    @@ -189,7 +205,7 @@ export default function FormSection(props: {
    { handleFormChange('vector', val); }} @@ -200,7 +216,7 @@ export default function FormSection(props: {
    { handleFormChange('dimension', val); }} @@ -209,7 +225,7 @@ export default function FormSection(props: {
    -

    With Scalar Fields

    +

    {t('form.withScalar')}

    { @@ -238,12 +254,12 @@ export default function FormSection(props: { classes.tooltipTrigger )} > - Average Data Size Per Row + {t('form.averageLength')} } - unit="Bytes" + unit={t('setup.basic.byte')} value={form.scalarData.average} onChange={handleAverageLengthChange} fullWidth @@ -257,13 +273,14 @@ export default function FormSection(props: { checked={form.scalarData.offLoading} onCheckedChange={handleOffLoadingChange} /> -

    - Offloading Fields to Disk -

    +

    {t('form.offloading')}

    - Milvus uses Mmap to enable direct memory access to large files - on disk without reading the entire files into memory. + ]} + />

    @@ -272,7 +289,7 @@ export default function FormSection(props: {
    -

    Index Type

    +

    {t('form.indexType')}

    { @@ -319,7 +336,7 @@ export default function FormSection(props: {
    -

    Dependency Component

    +

    {t('dependencyComp')}

    {dependencyOptions.map(v => (