From f49665e49d11fd58d96c202e0ed2aaf52c348209 Mon Sep 17 00:00:00 2001 From: kartik-gupta-ij Date: Tue, 27 Aug 2024 18:54:51 +0530 Subject: [PATCH 1/3] feat: Add Create Collection button to Collections page --- .../CreateCollection/CreateCollectionForm.jsx | 184 ++++++++++++++++++ .../CreateCollection/VectorConfig.jsx | 177 +++++++++++++++++ src/components/CreateCollection/index.jsx | 66 +++++++ src/pages/Collections.jsx | 8 +- 4 files changed, 434 insertions(+), 1 deletion(-) create mode 100644 src/components/CreateCollection/CreateCollectionForm.jsx create mode 100644 src/components/CreateCollection/VectorConfig.jsx create mode 100644 src/components/CreateCollection/index.jsx diff --git a/src/components/CreateCollection/CreateCollectionForm.jsx b/src/components/CreateCollection/CreateCollectionForm.jsx new file mode 100644 index 00000000..c5194517 --- /dev/null +++ b/src/components/CreateCollection/CreateCollectionForm.jsx @@ -0,0 +1,184 @@ +import React, { useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import { StepContent, Stepper, Typography, Box, StepLabel, Step, Paper, Button, TextField } from '@mui/material'; +import VectorConfig from './VectorConfig'; +import { useClient } from '../../context/client-context'; + +const CreateCollectionForm = ({ collections, onComplete, sx, handleCreated }) => { + const { client: qdrantClient } = useClient(); + const [activeStep, setActiveStep] = useState(0); + const [collectionName, setCollectionName] = useState(''); + const [vectors, setVectors] = useState([{ dimension: '', distance: '', name: '' }]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const handleNext = () => { + setActiveStep((prevActiveStep) => prevActiveStep + 1); + }; + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + + useEffect(() => { + if (activeStep === 2) { + setLoading(true); + const VectorConfig = + vectors.length === 1 + ? { + vectors: { size: parseInt(vectors[0].dimension), distance: vectors[0].distance }, + } + : { + vectors: vectors.reduce((acc, vector) => { + acc[vector.name] = { size: parseInt(vector.dimension), distance: vector.distance }; + return acc; + }, {}), + }; + + qdrantClient + .createCollection(collectionName, VectorConfig) + .then(() => { + setLoading(false); + setError(null); + onComplete(); + handleCreated(); + }) + .catch((error) => { + setLoading(false); + setError(error); + }); + } + }, [activeStep]); + + return ( + + + {/* Step 1 start - enter a collection name*/} + + {activeStep === 0 ? 'Enter a collection name' : `Collection name: ${collectionName}`} + + + {/* Step 1 end - enter a collection name*/} + {/* Step 2 start - vector config */} + + Step 2 - Vector config + + + {/* Step 2 end - vector config */} + + {activeStep === 2 && ( + + {loading ? ( + Creating collection... + ) : error ? ( + {error.message} + ) : ( + Collection created successfully 🎉 + )} + + )} + + ); +}; + +// props validation +CreateCollectionForm.propTypes = { + collections: PropTypes.array, + onComplete: PropTypes.func.isRequired, + sx: PropTypes.object, + handleCreated: PropTypes.func, +}; + +export default CreateCollectionForm; + +const CollectionNameTextBox = ({ collectionName, setCollectionName, collections, handleNext, activeStep }) => { + const [formError, setFormError] = useState(false); + const [formMessage, setFormMessage] = useState(''); + const textFieldRef = useRef(null); + + function validateCollectionName(value) { + const INVALID_CHARS = ['<', '>', ':', '"', '/', '\\', '|', '?', '*', '\0', '\u{1F}']; + + const invalidChar = INVALID_CHARS.find((c) => value.includes(c)); + + if (invalidChar !== undefined) { + return `Collection name cannot contain "${invalidChar}" char`; + } else { + return null; + } + } + + function collectionExists(name) { + if (collections?.some((collection) => collection.name === name)) { + return `Collection with name "${name}" already exists`; + } + return null; + } + + const MAX_COLLECTION_NAME_LENGTH = 255; + + useEffect(() => { + if (activeStep === 0) { + textFieldRef.current.focus(); + } + return () => {}; + }, [activeStep]); + + const handleTextChange = (event) => { + // if there will be more forms use schema validation instead + const newCollectionName = event.target.value; + const hasForbiddenSymbolsMessage = validateCollectionName(newCollectionName); + const hasForbiddenSymbols = hasForbiddenSymbolsMessage !== null; + const collectionExistsMessage = collectionExists(newCollectionName); + const collectionExistsError = collectionExistsMessage !== null; + const isTooShort = newCollectionName?.length < 1; + const isTooLong = newCollectionName?.length > MAX_COLLECTION_NAME_LENGTH; + + setCollectionName(newCollectionName); + setFormError(isTooShort || isTooLong || hasForbiddenSymbols || collectionExistsError); + setFormMessage( + isTooShort + ? 'Collection name is too short' + : isTooLong + ? 'Collection name is too long' + : (hasForbiddenSymbolsMessage || collectionExistsMessage) ?? '' + ); + }; + + return ( + + + Collection name must be new + + + + + + + ); +}; + +CollectionNameTextBox.propTypes = { + collectionName: PropTypes.string.isRequired, + setCollectionName: PropTypes.func.isRequired, + collections: PropTypes.array, + handleNext: PropTypes.func.isRequired, + activeStep: PropTypes.number.isRequired, +}; diff --git a/src/components/CreateCollection/VectorConfig.jsx b/src/components/CreateCollection/VectorConfig.jsx new file mode 100644 index 00000000..10001df2 --- /dev/null +++ b/src/components/CreateCollection/VectorConfig.jsx @@ -0,0 +1,177 @@ +import React, { useState } from 'react'; +import { + Box, + Button, + TextField, + StepContent, + InputLabel, + FormControl, + Select, + MenuItem, + FormHelperText, + IconButton, + Tooltip, +} from '@mui/material'; +import PropTypes from 'prop-types'; +import { AddCircleOutline, RemoveCircleOutline } from '@mui/icons-material'; + +const distancesOptions = ['Cosine', 'Euclid', 'Dot', 'Manhattan']; + +const VectorRow = ({ vectors, index, setVectors, errors, length, setErrors }) => { + const handleAddVector = () => { + setVectors([...vectors, { dimension: '', distance: '', name: '' }]); + setErrors([...errors, { dimension: '', distance: '' }]); // Add corresponding error object + }; + + const handleRemoveVector = (index) => { + const newVectors = vectors.filter((_, i) => i !== index); + const newErrors = errors.filter((_, i) => i !== index); + setVectors(newVectors); + setErrors(newErrors); + }; + + const validate = (index, field, value) => { + const newErrors = [...errors]; + if (!value) { + newErrors[index][field] = `${field} is required`; + } else if (field === 'dimension' && isNaN(value)) { + newErrors[index][field] = 'Dimension must be a number'; + } else { + newErrors[index][field] = ''; + } + setErrors(newErrors); + }; + + const handleVectorChange = (index, field, value) => { + const newVectors = [...vectors]; + newVectors[index][field] = value; + setVectors(newVectors); + validate(index, field, value); + }; + + return ( + + + Distance + + {errors[index].distance} + + handleVectorChange(index, 'dimension', e.target.value)} + error={!!errors[index].dimension} + helperText={errors[index].dimension} + sx={{ mr: 2 }} + /> + {length > 1 && ( + <> + handleVectorChange(index, 'name', e.target.value)} + error={!!errors[index].name} + helperText={errors[index].name} + sx={{ mr: 2 }} + /> + + handleRemoveVector(index)} color="error"> + + + + + )} + {length - 1 === index && ( + + + + + + )} + + ); +}; + +VectorRow.propTypes = { + vectors: PropTypes.array.isRequired, + errors: PropTypes.array.isRequired, + index: PropTypes.number.isRequired, + length: PropTypes.number.isRequired, + setVectors: PropTypes.func.isRequired, + setErrors: PropTypes.func.isRequired, +}; + +const VectorConfig = ({ handleNext, handleBack, vectors, setVectors }) => { + const [errors, setErrors] = useState([{ dimension: '', distance: '', name: '' }]); + + const validateAll = () => { + const newErrors = vectors.map((vector) => { + const error = {}; + if (!vector.dimension) { + error.dimension = 'Dimension is required'; + } else if (isNaN(vector.dimension)) { + error.dimension = 'Dimension must be a number'; + } + if (!vector.distance) { + error.distance = 'Distance is required'; + } + if (vectors.length > 1 && !vector.name) { + error.name = 'Vector name is required'; + } + return error; + }); + setErrors(newErrors); + return newErrors.every((error) => !Object.values(error).length); + }; + + const handleContinue = () => { + if (validateAll()) { + handleNext(); + } + }; + + return ( + + + {vectors.map((_, index) => ( + + ))} + + + + + + + ); +}; + +VectorConfig.propTypes = { + handleNext: PropTypes.func.isRequired, + handleBack: PropTypes.func.isRequired, + vectors: PropTypes.array.isRequired, + setVectors: PropTypes.func.isRequired, +}; + +export default VectorConfig; diff --git a/src/components/CreateCollection/index.jsx b/src/components/CreateCollection/index.jsx new file mode 100644 index 00000000..c35a0cd4 --- /dev/null +++ b/src/components/CreateCollection/index.jsx @@ -0,0 +1,66 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useTheme } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import Box from '@mui/material/Box'; +import Tooltip from '@mui/material/Tooltip'; +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import { Add } from '@mui/icons-material'; +import CreateCollectionForm from './CreateCollectionForm'; +import { Alert } from '@mui/material'; +import { Link } from 'react-router-dom'; + +const CreateCollection = ({ onComplete, collections, sx }) => { + const theme = useTheme(); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + const [open, setOpen] = React.useState(false); + const handleCreateClick = () => { + setOpen(true); + }; + + const handleCreated = () => { + setTimeout(() => { + setOpen(false); + }, 1000); + }; + + return ( + + + + + setOpen(false)} + aria-labelledby="Create collection dialog" + aria-describedby="Create collection dialog" + > + Create Collection + + + Create a new collection with a name and vector configuration. For advanced options, use the{' '} + console. + + + + + + ); +}; + +// props validation +CreateCollection.propTypes = { + onComplete: PropTypes.func, + collections: PropTypes.array, + sx: PropTypes.object, +}; + +export default CreateCollection; diff --git a/src/pages/Collections.jsx b/src/pages/Collections.jsx index cb6f42b3..efe96764 100644 --- a/src/pages/Collections.jsx +++ b/src/pages/Collections.jsx @@ -8,6 +8,7 @@ import { SnapshotsUpload } from '../components/Snapshots/SnapshotsUpload'; import { getErrorMessage } from '../lib/get-error-message'; import CollectionsList from '../components/Collections/CollectionsList'; import { debounce } from 'lodash'; +import CreateCollection from '../components/CreateCollection/Index'; function Collections() { const [rawCollections, setRawCollections] = useState(null); @@ -114,8 +115,13 @@ function Collections() { Collections - + getCollectionsCall(currentPage)} key={'snapshots'} /> + getCollectionsCall(currentPage)} + key={'create-collection'} + collections={collections} + /> From b37fd1ee855c4293cf3d9ae2297c2d3ac7b026fe Mon Sep 17 00:00:00 2001 From: kartik-gupta-ij Date: Fri, 27 Sep 2024 11:22:52 +0530 Subject: [PATCH 2/3] refactor: Update CreateCollectionForm and VectorConfig components - Refactor CreateCollectionForm component to handle sparse vectors and multivector configuration. - Update VectorConfig component to include options for sparse vectors and multivector configuration. - Update CreateCollection index component to set maxWidth to 'lg' in the Dialog. --- .../CreateCollection/CreateCollectionForm.jsx | 37 ++-- .../CreateCollection/VectorConfig.jsx | 172 ++++++++++++------ src/components/CreateCollection/index.jsx | 2 +- 3 files changed, 147 insertions(+), 64 deletions(-) diff --git a/src/components/CreateCollection/CreateCollectionForm.jsx b/src/components/CreateCollection/CreateCollectionForm.jsx index c5194517..6c6be876 100644 --- a/src/components/CreateCollection/CreateCollectionForm.jsx +++ b/src/components/CreateCollection/CreateCollectionForm.jsx @@ -8,7 +8,9 @@ const CreateCollectionForm = ({ collections, onComplete, sx, handleCreated }) => const { client: qdrantClient } = useClient(); const [activeStep, setActiveStep] = useState(0); const [collectionName, setCollectionName] = useState(''); - const [vectors, setVectors] = useState([{ dimension: '', distance: '', name: '' }]); + const [vectors, setVectors] = useState([ + { dimension: '', distance: '', name: '', multivector_config: null, sparse_vectors: false }, + ]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -23,20 +25,35 @@ const CreateCollectionForm = ({ collections, onComplete, sx, handleCreated }) => useEffect(() => { if (activeStep === 2) { setLoading(true); - const VectorConfig = - vectors.length === 1 + const sparseVectors = vectors.filter((vector) => vector.sparse_vectors); + const denseVectors = vectors.filter((vector) => !vector.sparse_vectors); + + const vectorConfig = + denseVectors.length === 1 ? { - vectors: { size: parseInt(vectors[0].dimension), distance: vectors[0].distance }, + vectors: { + size: parseInt(denseVectors[0].dimension), + distance: denseVectors[0].distance, + multivector_config: denseVectors[0].multivector_config, + }, } : { - vectors: vectors.reduce((acc, vector) => { - acc[vector.name] = { size: parseInt(vector.dimension), distance: vector.distance }; - return acc; + vectors: denseVectors.reduce((config, vector) => { + config[vector.name] = { + size: parseInt(vector.dimension), + distance: vector.distance, + multivector_config: vector.multivector_config, + }; + return config; }, {}), }; + vectorConfig.sparse_vectors = sparseVectors.reduce((sparseConfig, vector) => { + sparseConfig[vector.name] = {}; + return sparseConfig; + }, {}); qdrantClient - .createCollection(collectionName, VectorConfig) + .createCollection(collectionName, vectorConfig) .then(() => { setLoading(false); setError(null); @@ -53,7 +70,6 @@ const CreateCollectionForm = ({ collections, onComplete, sx, handleCreated }) => return ( - {/* Step 1 start - enter a collection name*/} {activeStep === 0 ? 'Enter a collection name' : `Collection name: ${collectionName}`} activeStep={activeStep} /> - {/* Step 1 end - enter a collection name*/} - {/* Step 2 start - vector config */} Step 2 - Vector config - {/* Step 2 end - vector config */} {activeStep === 2 && ( diff --git a/src/components/CreateCollection/VectorConfig.jsx b/src/components/CreateCollection/VectorConfig.jsx index 10001df2..b843d541 100644 --- a/src/components/CreateCollection/VectorConfig.jsx +++ b/src/components/CreateCollection/VectorConfig.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Box, Button, @@ -11,16 +11,20 @@ import { FormHelperText, IconButton, Tooltip, + FormControlLabel, + Checkbox, } from '@mui/material'; import PropTypes from 'prop-types'; -import { AddCircleOutline, RemoveCircleOutline } from '@mui/icons-material'; - -const distancesOptions = ['Cosine', 'Euclid', 'Dot', 'Manhattan']; +import { AddCircleOutline, DeleteOutline } from '@mui/icons-material'; const VectorRow = ({ vectors, index, setVectors, errors, length, setErrors }) => { + const [distancesOptions, setDistancesOptions] = useState([]); const handleAddVector = () => { - setVectors([...vectors, { dimension: '', distance: '', name: '' }]); - setErrors([...errors, { dimension: '', distance: '' }]); // Add corresponding error object + setVectors([ + ...vectors, + { dimension: '', distance: '', name: '', multivector_config: null, sparse_vectors: false }, + ]); + setErrors([...errors, { dimension: '', distance: '', name: '' }]); }; const handleRemoveVector = (index) => { @@ -49,50 +53,110 @@ const VectorRow = ({ vectors, index, setVectors, errors, length, setErrors }) => validate(index, field, value); }; + const handleVectorConfigChange = (index, field, value) => { + if (value) { + const newVectors = [...vectors]; + + field === 'multivector_config' + ? (newVectors[index][field] = { comparator: 'max_sim' }) + : (newVectors[index][field] = true); + field === 'multivector_config' + ? (newVectors[index].sparse_vectors = false) + : (newVectors[index].multivector_config = null); + + setVectors(newVectors); + } else { + const newVectors = [...vectors]; + newVectors[index][field] = null; + setVectors(newVectors); + } + }; + + useEffect(() => { + const getDistanceOptions = async () => { + const response = await fetch(import.meta.env.BASE_URL + './openapi.json'); + const openapi = await response.json(); + setDistancesOptions(openapi.components.schemas.Distance.enum); + }; + getDistanceOptions(); + }, []); + return ( - - Distance - - {errors[index].distance} - - handleVectorChange(index, 'dimension', e.target.value)} - error={!!errors[index].dimension} - helperText={errors[index].dimension} - sx={{ mr: 2 }} - /> - {length > 1 && ( + {!vectors[index].sparse_vectors && ( <> + + Distance + + {errors[index].distance} + handleVectorChange(index, 'name', e.target.value)} - error={!!errors[index].name} - helperText={errors[index].name} + label="Dimension" + value={vectors[index].dimension} + onChange={(e) => handleVectorChange(index, 'dimension', e.target.value)} + error={!!errors[index].dimension} + helperText={errors[index].dimension} sx={{ mr: 2 }} /> - - handleRemoveVector(index)} color="error"> - - - )} + + {(length > 1 || vectors[index].sparse_vectors) && ( + handleVectorChange(index, 'name', e.target.value)} + error={!!errors[index].name} + helperText={errors[index].name} + sx={{ mr: 2 }} + /> + )} + { + handleVectorConfigChange(index, 'multivector_config', e.target.checked); + }} + checked={!!vectors[index].multivector_config} + /> + } + label="Mutivector" + labelPlacement="start" + /> + { + handleVectorConfigChange(index, 'sparse_vectors', e.target.checked); + }} + checked={!!vectors[index].sparse_vectors} + /> + } + label="Sparse Vectors" + labelPlacement="start" + /> + + {length > 1 && ( + + handleRemoveVector(index)} color="error"> + + + + )} {length - 1 === index && ( - + @@ -117,16 +181,22 @@ const VectorConfig = ({ handleNext, handleBack, vectors, setVectors }) => { const validateAll = () => { const newErrors = vectors.map((vector) => { const error = {}; - if (!vector.dimension) { - error.dimension = 'Dimension is required'; - } else if (isNaN(vector.dimension)) { - error.dimension = 'Dimension must be a number'; - } - if (!vector.distance) { - error.distance = 'Distance is required'; - } - if (vectors.length > 1 && !vector.name) { - error.name = 'Vector name is required'; + if (!vector.sparse_vectors) { + if (!vector.dimension) { + error.dimension = 'Dimension is required'; + } else if (isNaN(vector.dimension)) { + error.dimension = 'Dimension must be a number'; + } + if (!vector.distance) { + error.distance = 'Distance is required'; + } + if (vectors.length > 1 && !vector.name) { + error.name = 'Vector name is required'; + } + } else { + if (!vector.name) { + error.name = 'Vector name is required'; + } } return error; }); diff --git a/src/components/CreateCollection/index.jsx b/src/components/CreateCollection/index.jsx index c35a0cd4..ed4446ef 100644 --- a/src/components/CreateCollection/index.jsx +++ b/src/components/CreateCollection/index.jsx @@ -37,7 +37,7 @@ const CreateCollection = ({ onComplete, collections, sx }) => { setOpen(false)} aria-labelledby="Create collection dialog" From 0904e94c8ef7698fa553320e570b909c37450b6a Mon Sep 17 00:00:00 2001 From: kartik-gupta-ij Date: Fri, 27 Sep 2024 11:26:11 +0530 Subject: [PATCH 3/3] refactor: Update rollup and vite versions --- package-lock.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 715eb9f9..851e1d24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7478,9 +7478,9 @@ } }, "node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "bin": { "rollup": "dist/bin/rollup" }, @@ -8341,9 +8341,9 @@ } }, "node_modules/vite": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", - "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz", + "integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==", "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -8637,9 +8637,9 @@ } }, "node_modules/vite/node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", "bin": { "rollup": "dist/bin/rollup" }, @@ -13954,9 +13954,9 @@ } }, "rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "requires": { "fsevents": "~2.3.2" } @@ -14557,9 +14557,9 @@ } }, "vite": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", - "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz", + "integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==", "requires": { "esbuild": "^0.18.10", "fsevents": "~2.3.2", @@ -14568,9 +14568,9 @@ }, "dependencies": { "rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", "requires": { "fsevents": "~2.3.2" }