import { useEffect, useState, useRef } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useNavigate, useParams } from 'react-router-dom' import ReactJson from 'flowise-react-json-view' import { cloneDeep } from 'lodash' import { v4 as uuidv4 } from 'uuid' // material-ui import { Box, Card, Grid, Stack, Typography, OutlinedInput, IconButton, Button } from '@mui/material' import Embeddings from '@mui/icons-material/DynamicFeed' import { useTheme, styled } from '@mui/material/styles' import CardContent from '@mui/material/CardContent' import chunks_emptySVG from '@/assets/images/chunks_empty.svg' import { IconSearch, IconFileStack, IconDeviceFloppy, IconX } from '@tabler/icons-react' // project imports import MainCard from '@/ui-component/cards/MainCard' import { BackdropLoader } from '@/ui-component/loading/BackdropLoader' import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog' import ExpandedChunkDialog from './ExpandedChunkDialog' import ViewHeader from '@/layout/MainLayout/ViewHeader' import DocStoreInputHandler from '@/views/docstore/DocStoreInputHandler' import { PermissionButton } from '@/ui-component/button/RBACButtons' // API import documentsApi from '@/api/documentstore' import nodesApi from '@/api/nodes' // Hooks import useApi from '@/hooks/useApi' import { useAuth } from '@/hooks/useAuth' import useNotifier from '@/utils/useNotifier' import { baseURL } from '@/store/constant' import { initNode, showHideInputParams } from '@/utils/genericHelper' import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions' const CardWrapper = styled(MainCard)(({ theme }) => ({ background: theme.palette.card.main, color: theme.darkTextPrimary, overflow: 'auto', position: 'relative', boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)', cursor: 'pointer', '&:hover': { background: theme.palette.card.hover, boxShadow: '0 2px 14px 0 rgb(32 40 45 / 20%)' }, maxHeight: '250px', minHeight: '250px', maxWidth: '100%', overflowWrap: 'break-word', whiteSpace: 'pre-line', padding: 1 })) const VectorStoreQuery = () => { const customization = useSelector((state) => state.customization) const navigate = useNavigate() const theme = useTheme() const dispatch = useDispatch() const inputRef = useRef(null) const { hasAssignedWorkspace } = useAuth() useNotifier() const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) const { storeId } = useParams() const [documentChunks, setDocumentChunks] = useState([]) const [loading, setLoading] = useState(false) const [showExpandedChunkDialog, setShowExpandedChunkDialog] = useState(false) const [expandedChunkDialogProps, setExpandedChunkDialogProps] = useState({}) const [documentStore, setDocumentStore] = useState({}) const [query, setQuery] = useState('') const [timeTaken, setTimeTaken] = useState(-1) const [retrievalError, setRetrievalError] = useState(undefined) const getSpecificDocumentStoreApi = useApi(documentsApi.getSpecificDocumentStore) const queryVectorStoreApi = useApi(documentsApi.queryVectorStore) const getVectorStoreNodeDetailsApi = useApi(nodesApi.getSpecificNode) const [selectedVectorStoreProvider, setSelectedVectorStoreProvider] = useState({}) const handleVectorStoreProviderDataChange = ({ inputParam, newValue }) => { setSelectedVectorStoreProvider((prevData) => { const updatedData = { ...prevData } updatedData.inputs[inputParam.name] = newValue updatedData.inputParams = showHideInputParams(updatedData) return updatedData }) } const chunkSelected = (chunkId, selectedChunkNumber) => { const selectedChunk = documentChunks.find((chunk) => chunk.id === chunkId) const dialogProps = { data: { selectedChunk, selectedChunkNumber } } setExpandedChunkDialogProps(dialogProps) setShowExpandedChunkDialog(true) } const handleEnter = (e) => { // Check if IME composition is in progress const isIMEComposition = e.isComposing || e.keyCode === 229 if (e.key === 'Enter' && query && !isIMEComposition) { if (!e.shiftKey && query) { if (inputRef.current) { inputRef.current.blur() } doQuery() } } else if (e.key === 'Enter') { e.preventDefault() } } const doQuery = () => { setLoading(true) const data = { query: query, storeId: storeId, inputs: selectedVectorStoreProvider.inputs } queryVectorStoreApi.request(data) } const saveConfig = async () => { setLoading(true) const data = { storeId: storeId } if (selectedVectorStoreProvider.inputs) { data.vectorStoreConfig = {} data.vectorStoreName = selectedVectorStoreProvider.name Object.keys(selectedVectorStoreProvider.inputs).map((key) => { if (key === 'FLOWISE_CREDENTIAL_ID') { data.vectorStoreConfig['credential'] = selectedVectorStoreProvider.inputs[key] } else { data.vectorStoreConfig[key] = selectedVectorStoreProvider.inputs[key] } }) } try { const updateResp = await documentsApi.updateVectorStoreConfig(data) setLoading(false) if (updateResp.data) { enqueueSnackbar({ message: 'Vector Store Config Successfully Updated', options: { key: new Date().getTime() + Math.random(), variant: 'success', action: (key) => ( ) } }) } } catch (error) { setLoading(false) const errorData = error.response?.data || `${error.response?.status}: ${error.response?.statusText}` enqueueSnackbar({ message: `Failed to update vector store config: ${errorData}`, options: { key: new Date().getTime() + Math.random(), variant: 'error', persist: true, action: (key) => ( ) } }) } } useEffect(() => { if (queryVectorStoreApi.data) { setDocumentChunks(queryVectorStoreApi.data.docs) setTimeTaken(queryVectorStoreApi.data.timeTaken) setRetrievalError(undefined) setLoading(false) if (inputRef.current) { inputRef.current.focus() } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [queryVectorStoreApi.data]) useEffect(() => { if (queryVectorStoreApi.error) { if (queryVectorStoreApi.error.response?.data?.message) { const message = queryVectorStoreApi.error.response.data.message // remove the text 'documentStoreServices.queryVectorStore - ' from the error message to make it readable setRetrievalError(message.replace('documentStoreServices.queryVectorStore - ', '')) setDocumentChunks([]) setTimeTaken(-1) } setLoading(false) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [queryVectorStoreApi.error]) useEffect(() => { if (getVectorStoreNodeDetailsApi.data) { const node = getVectorStoreNodeDetailsApi.data fetchVectorStoreDetails(node) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [getVectorStoreNodeDetailsApi.data]) const fetchVectorStoreDetails = (component) => { const nodeData = cloneDeep(initNode(component, uuidv4())) if (documentStore.vectorStoreConfig) { nodeData.inputs = documentStore.vectorStoreConfig.config nodeData.credential = documentStore.vectorStoreConfig.config.credential } setSelectedVectorStoreProvider(nodeData) } useEffect(() => { getSpecificDocumentStoreApi.request(storeId) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { if (getSpecificDocumentStoreApi.data) { if (!hasAssignedWorkspace(getSpecificDocumentStoreApi.data.workspaceId)) { navigate('/unauthorized') return } setDocumentStore(getSpecificDocumentStoreApi.data) const vectorStoreConfig = getSpecificDocumentStoreApi.data.vectorStoreConfig if (vectorStoreConfig) { getVectorStoreNodeDetailsApi.request(vectorStoreConfig.name) } setLoading(false) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [getSpecificDocumentStoreApi.data]) return ( <> navigate(-1)} > } onClick={saveConfig} > Save Config
Enter your Query *
setQuery(e.target.value)} onKeyDown={handleEnter} value={query ?? ''} endAdornment={ } />
{selectedVectorStoreProvider.label ? ( {selectedVectorStoreProvider.label ) : ( )}
{selectedVectorStoreProvider.label}
{selectedVectorStoreProvider && Object.keys(selectedVectorStoreProvider).length > 0 && showHideInputParams(selectedVectorStoreProvider) .filter((inputParam) => !inputParam.hidden && inputParam.display !== false) .map((inputParam, index) => ( ))}
Retrieved Documents {timeTaken > -1 && ( Count: {documentChunks.length}. Time taken: {timeTaken} millis. )} {retrievalError && ( {retrievalError} )}
{!documentChunks.length && (
chunks_emptySVG
No Documents Retrieved
)} {documentChunks?.length > 0 && documentChunks.map((row, index) => ( chunkSelected(row.id, row.chunkNo)} sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }} > {`#${row.chunkNo}. Characters: ${row.pageContent.length}`} {row.pageContent} ))}
setShowExpandedChunkDialog(false)} isReadOnly={true} > {loading && } ) } export default VectorStoreQuery