diff --git a/src/pages/tools/sizing.tsx b/src/pages/tools/sizing.tsx
index f7e85aea8..79bdc2b5c 100644
--- a/src/pages/tools/sizing.tsx
+++ b/src/pages/tools/sizing.tsx
@@ -1,458 +1,126 @@
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';
-
-// one million
-const $1M = Math.pow(10, 6);
-
-const defaultSizeContent = {
- size: REQUIRE_MORE,
+import FormSection from '@/parts/sizing/formSection';
+import ResultSection from '@/parts/sizing/resultSection';
+import {
+ DependencyComponentEnum,
+ ICalculateResult,
+ ModeEnum,
+} from '@/types/sizing';
+import { InfoFilled } from '@/components/icons';
+import ZillizAdv from '@/parts/blogs/zillizAdv';
+
+const etcdBaseValue = {
cpu: 0,
memory: 0,
- amount: 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,
+ },
};
-
-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]',
+ const { t } = useTranslation('sizingToolV2');
+ const [calculatedResult, setCalculatedResult] = useState
({
+ rawDataSize: 0,
+ memorySize: 0,
+ localDiskSize: 0,
+ nodeConfig: {
+ queryNode: {
+ cpu: 0,
+ memory: 0,
+ count: 0,
},
- },
- // 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]',
+ proxy: {
+ cpu: 0,
+ memory: 0,
+ count: 0,
},
- },
- // 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]',
+ mixCoord: {
+ cpu: 0,
+ memory: 0,
+ count: 0,
+ },
+ dataNode: {
+ cpu: 0,
+ memory: 0,
+ count: 0,
+ },
+ indexNode: {
+ cpu: 0,
+ memory: 0,
+ count: 0,
},
},
- // segment size
- segmentSize: {
- value: SEGMENT_SIZE_OPTIONS[0].value,
- showError: false,
+ dependencyConfig: {
+ etcd: {
+ ...etcdBaseValue,
+ },
+ minio: {
+ ...minioBaseValue,
+ },
+ pulsar: {
+ ...pulsarBaseValue,
+ },
+ kafka: {
+ ...kafkaBaseValue,
+ },
},
- apacheType: 'pulsar',
+ mode: ModeEnum.Standalone,
+ dependency: DependencyComponentEnum.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: ,
- });
+ const updateCalculatedResult = (result: ICalculateResult) => {
+ setCalculatedResult(result);
};
return (
@@ -470,434 +138,46 @@ export default function SizingTool() {
hrefLang="en"
/>
-
-
{t('title')}
-
+
+
+
{t('title')}
+
{t('content')}
+
-
{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/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')}
+
+
+
+ {t('blog:zillizAdv.btn')}
+
+
+
+
+
+ );
+}
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')}
+
+ -
+
+ ,
+ ]}
+ />
+
+
+ -
+
+ {t('setup.basic.cpu')}:
+
+
+ {t('setup.basic.core', { cpu: pulsar.bookie.cpu })}
+
+
+ -
+
+ {t('setup.basic.memory')}:
+
+
+ {t('setup.basic.gb', { memory: pulsar.bookie.memory })}
+
+
+ -
+
+ {t('setup.basic.journal')}:
+
+
+ {t('setup.basic.gb', { memory: pulsar.bookie.journal })}
+
+
+ -
+
+ {t('setup.basic.ledger')}:
+
+
+ {t('setup.basic.gb', { memory: pulsar.bookie.ledgers })}
+
+
+
+
+ -
+
+ ,
+ ]}
+ />
+
+
+ -
+
+ {t('setup.basic.cpu')}:
+
+
+ {t('setup.basic.core', { cpu: pulsar.broker.cpu })}
+
+
+ -
+
+ {t('setup.basic.memory')}:
+
+
+ {t('setup.basic.gb', { memory: pulsar.broker.memory })}
+
+
+
+
+ -
+
+ ,
+ ]}
+ />
+
+
+ -
+
+ {t('setup.basic.cpu')}:
+
+
+ {t('setup.basic.core', { cpu: pulsar.proxy.cpu })}
+
+
+ -
+
+ {t('setup.basic.memory')}:
+
+
+ {t('setup.basic.gb', { memory: pulsar.proxy.memory })}
+
+
+
+
+ -
+
+ ,
+ ]}
+ />
+
+
+ -
+
+ {t('setup.basic.cpu')}:
+
+
+ {t('setup.basic.core', { cpu: pulsar.zookeeper.cpu })}
+
+
+ -
+
+ {t('setup.basic.memory')}:
+
+
+ {t('setup.basic.gb', { memory: pulsar.zookeeper.memory })}
+
+
+ -
+
+ {t('setup.basic.pvcLabel')}:
+
+
+ {t('setup.basic.gb', { memory: pulsar.zookeeper.pvc })}
+
+
+
+
+
+
+ );
+ };
+
+ const KafkaInfo = (props: Pick) => {
+ const { kafka } = props;
+
+ return (
+
+
{t('setup.dependency.pulsar')}
+
+ -
+
+ ,
+ ]}
+ />
+
+
+ -
+
+ {t('setup.basic.cpu')}:
+
+
+ {t('setup.basic.core', { cpu: kafka.broker.cpu })}
+
+
+ -
+
+ {t('setup.basic.memory')}:
+
+
+ {t('setup.basic.gb', { memory: kafka.broker.memory })}
+
+
+ -
+
+ {t('setup.basic.pvcLabel')}:
+
+
+ {t('setup.basic.gb', { memory: kafka.broker.pvc })}
+
+
+
+
+
+ -
+
+ ,
+ ]}
+ />
+
+
+ -
+
+ {t('setup.basic.cpu')}:
+
+
+ {t('setup.basic.core', { cpu: kafka.zookeeper.cpu })}
+
+
+ -
+
+ {t('setup.basic.memory')}:
+
+
+ {t('setup.basic.gb', { memory: kafka.zookeeper.memory })}
+
+
+ -
+
+ {t('setup.basic.pvcLabel')}:
+
+
+ {t('setup.basic.gb', { memory: kafka.zookeeper.pvc })}
+
+
+
+
+
+
+ );
+ };
+
+ 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
new file mode 100644
index 000000000..f8a4e36a6
--- /dev/null
+++ b/src/parts/sizing/formSection.tsx
@@ -0,0 +1,397 @@
+import { use, useEffect, useMemo, 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,
+ MAX_NODE_DEGREE_RANGE_CONFIG,
+ SEGMENT_SIZE_OPTIONS,
+ 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';
+import { IndexTypeComponent } from './indexTypeComponent';
+import {
+ memoryAndDiskCalculator,
+ nodesConfigCalculator,
+ rawDataSizeCalculator,
+ $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);
+ 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,
+ },
+ 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,
+ 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,
+ [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;
+ 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: t('form.standaloneDesc'),
+ },
+ {
+ ...MODE_OPTIONS[1],
+ desc: t('form.clusterDesc'),
+ },
+ ];
+
+ 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;
+
+ setDisableStandalone(true);
+ } else if (rawDataSize < $10M768D) {
+ setDisableStandalone(false);
+ }
+ 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: currentMode,
+ });
+
+ updateCalculatedResult({
+ rawDataSize,
+ memorySize: memory,
+ localDiskSize: localDisk,
+ nodeConfig: nodeConfig,
+ dependencyConfig: dependencyConfig,
+ mode: currentMode,
+ dependency: form.dependency,
+ });
+ }, [form, indexTypeParams]);
+
+ useEffect(() => {
+ if (disableStandalone && form.mode === ModeEnum.Standalone) {
+ setForm({
+ ...form,
+ mode: ModeEnum.Cluster,
+ });
+ }
+ }, [disableStandalone, form.mode]);
+
+ return (
+
+
+
+ {
+ handleFormChange('vector', val);
+ }}
+ value={form.vector}
+ unit="Million"
+ />
+
+
+ {
+ handleFormChange('dimension', val);
+ }}
+ value={form.dimension}
+ />
+
+
+
+
{t('form.withScalar')}
+
{
+ handleFormChange('widthScalar', value);
+ }}
+ />
+
+
+
+
+
+
+ some test tooltip sad dsdsadasd dadsadsa dssad d dasd
+
+
+ {t('form.averageLength')}
+
+
+
+ }
+ unit={t('setup.basic.byte')}
+ value={form.scalarData.average}
+ onChange={handleAverageLengthChange}
+ fullWidth
+ classes={{
+ root: classes.marginBtm20,
+ }}
+ />
+
+
+
+
{t('form.offloading')}
+
+
+ ]}
+ />
+
+
+
+
+
+
+
+
+
{t('form.indexType')}
+
+
+
+
+
+
+
+
{t('form.segmentSize')}
+
+
+
+
{t('dependencyComp')}
+
+ {dependencyOptions.map(v => (
+
+ ))}
+
+
+
+
+
+
+
+ {t('form.mode')}
+
+
+ ]}
+ />
+
+
+
+
+
+ {modeOptions.map(v => (
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/parts/sizing/index.module.less b/src/parts/sizing/index.module.less
new file mode 100644
index 000000000..41a63f11c
--- /dev/null
+++ b/src/parts/sizing/index.module.less
@@ -0,0 +1,451 @@
+@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 {
+ border-bottom: 1px solid #ececee;
+ margin-bottom: 24px;
+
+ &:last-of-type {
+ border-bottom: none;
+ margin-bottom: 0;
+ padding-bottom: 0;
+ }
+ }
+
+ .commonLabel {
+ .paragraph4();
+ margin-bottom: 8px;
+ }
+
+ .smallerLabel {
+ .paragraph6();
+ }
+
+ .innerRow {
+ margin-top: 20px;
+ }
+
+ .offLoadingLabel {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 8px;
+ }
+
+ .offLoadingDesc {
+ .paragraph6-regular;
+ color: #2e373b;
+
+ a {
+ color: #2e373b;
+ text-decoration: underline;
+ }
+ }
+
+ .switchRow {
+ margin-bottom: 20px;
+ }
+
+ .collapsible {
+ overflow: hidden;
+ height: fit-content;
+ padding-top: 20px;
+ }
+
+ .visibleCollapse {
+ }
+ .invisibleCollapse {
+ height: 0;
+ padding-top: 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;
+ }
+
+ .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
new file mode 100644
index 000000000..ad34af168
--- /dev/null
+++ b/src/parts/sizing/indexTypeComponent.tsx
@@ -0,0 +1,139 @@
+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';
+
+type IndexTypeComponentProps = {
+ data: IIndexType;
+ onChange: (key: string, value: any) => void;
+};
+
+const SCANNComponent = (props: IndexTypeComponentProps) => {
+ const { data, onChange } = props;
+
+ return (
+
+
Index Parameters
+
With_raw_data
+
{
+ onChange('widthRawData', val === 'true');
+ }}
+ className={classes.radioGroup}
+ >
+
+
+
+
+ );
+};
+
+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 IVFFlatComponent = (props: IndexTypeComponentProps) => {
+ const { data, onChange } = props;
+ return (
+
+
Index Parameters
+
nlist
+
{
+ onChange('flatNList', value);
+ }}
+ placeholder={`[${N_LIST_RANGE_CONFIG.min}, ${N_LIST_RANGE_CONFIG.max}]`}
+ />
+
+ );
+};
+
+const IVFSQ8Component = (props: IndexTypeComponentProps) => {
+ const { data, onChange } = props;
+ return (
+
+
Index Parameters
+
nlist
+
{
+ onChange('sq8NList', value);
+ }}
+ placeholder={`[${N_LIST_RANGE_CONFIG.min}, ${N_LIST_RANGE_CONFIG.max}]`}
+ />
+
+ );
+};
+
+export const IndexTypeComponent = (props: {
+ data: IIndexType;
+ onChange: (key: string, value: any) => void;
+}) => {
+ const { data } = props;
+ switch (data.indexType) {
+ case IndexTypeEnum.FLAT:
+ return null;
+ case IndexTypeEnum.SCANN:
+ return ;
+ case IndexTypeEnum.HNSW:
+ return ;
+ case IndexTypeEnum.DISKANN:
+ return ;
+ case IndexTypeEnum.IVF_FLAT:
+ return ;
+ case IndexTypeEnum.IVFSQ8:
+ return ;
+ default:
+ return null;
+ }
+};
diff --git a/src/parts/sizing/resultSection.tsx b/src/parts/sizing/resultSection.tsx
new file mode 100644
index 000000000..4040c6d20
--- /dev/null
+++ b/src/parts/sizing/resultSection.tsx
@@ -0,0 +1,375 @@
+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;
+ 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;
+
+ console.log('mode--', mode);
+
+ 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}`}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
+ 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 13024fedb..b63bd1c4b 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 2481837cf..554ea00c8 100644
--- a/src/styles/sizingTool.module.less
+++ b/src/styles/sizingTool.module.less
@@ -3,317 +3,76 @@
.pageContainer {
background-color: #fff;
}
-.container {
+
+.sizingToolContainer {
padding-top: 40px;
- padding-bottom: 128px;
- font-family: Geist;
+ 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;
+ .tip {
+ .paragraph6-regular();
- @media (max-width: 1280px) {
- gap: 32px;
+ a {
+ color: #000;
+ text-decoration: underline;
+ }
}
+ }
- @media @tablet {
- gap: 40px;
- flex-direction: column;
- }
+ .contentContainer {
+ display: flex;
+ border-radius: 24px;
+ border: 2px solid #ececee;
+ margin-bottom: 80px;
- @media @phone {
- gap: 40px;
+ @media @tablet, @phone {
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%;
- }
-
- .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;
- 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;
- }
+ .leftSection {
+ flex: 0 1 446px;
}
- .rightPart {
+ .rightSection {
flex: 1;
+ min-width: 650px;
- .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;
+ @media @tablet, @phone {
+ min-width: 0;
+ width: 100%;
}
}
}
+
+ .zillizAdv {
+ margin-bottom: 50px;
+ }
}
diff --git a/src/types/sizing.ts b/src/types/sizing.ts
new file mode 100644
index 000000000..2cc7230ff
--- /dev/null
+++ b/src/types/sizing.ts
@@ -0,0 +1,92 @@
+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;
+ maxDegree: number;
+ flatNList: number;
+ sq8NList: 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/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/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..0469f4538
--- /dev/null
+++ b/src/utils/sizingToolV2.ts
@@ -0,0 +1,596 @@
+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, flatNList, sq8NList } =
+ indexTypeParams;
+ const segmentSizeByte = segSize * 1024;
+
+ let result = {
+ memory: 0,
+ disk: 0,
+ };
+ const rowSize = (d * 32) / 8;
+
+ switch (indexType) {
+ case IndexTypeEnum.FLAT:
+ result = {
+ memory: rawDataSize,
+ disk: 0,
+ };
+ break;
+ case IndexTypeEnum.IVF_FLAT:
+ result = {
+ memory: rawDataSize + flatNList * rowSize,
+ disk: 0,
+ };
+ break;
+ case IndexTypeEnum.IVFSQ8:
+ result = {
+ memory: rawDataSize / 4 + sq8NList * 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,
+ };
+};
diff --git a/tailwind.config.js b/tailwind.config.js
index 805d28c3d..0e7cbbc90 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: {