Flowise/packages/ui/src/views/docstore/DocumentStoreDetail.jsx

877 lines
40 KiB
JavaScript

import { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import * as PropTypes from 'prop-types'
import { useNavigate, useParams } from 'react-router-dom'
// material-ui
import {
Box,
Stack,
Typography,
TableContainer,
Paper,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
Chip,
Menu,
MenuItem,
Divider,
Button,
Skeleton
} from '@mui/material'
import { alpha, styled, useTheme } from '@mui/material/styles'
import { tableCellClasses } from '@mui/material/TableCell'
// project imports
import MainCard from '@/ui-component/cards/MainCard'
import AddDocStoreDialog from '@/views/docstore/AddDocStoreDialog'
import { BackdropLoader } from '@/ui-component/loading/BackdropLoader'
import DocumentLoaderListDialog from '@/views/docstore/DocumentLoaderListDialog'
import ErrorBoundary from '@/ErrorBoundary'
import { StyledButton } from '@/ui-component/button/StyledButton'
import ViewHeader from '@/layout/MainLayout/ViewHeader'
import DeleteDocStoreDialog from './DeleteDocStoreDialog'
import { Available } from '@/ui-component/rbac/available'
import { PermissionIconButton, StyledPermissionButton } from '@/ui-component/button/RBACButtons'
import DocumentStoreStatus from '@/views/docstore/DocumentStoreStatus'
import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog'
import DocStoreAPIDialog from './DocStoreAPIDialog'
// API
import documentsApi from '@/api/documentstore'
// Hooks
import useApi from '@/hooks/useApi'
import useNotifier from '@/utils/useNotifier'
import { useAuth } from '@/hooks/useAuth'
import { getFileName } from '@/utils/genericHelper'
import useConfirm from '@/hooks/useConfirm'
// icons
import { IconPlus, IconRefresh, IconX, IconVectorBezier2 } from '@tabler/icons-react'
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
import FileDeleteIcon from '@mui/icons-material/Delete'
import FileEditIcon from '@mui/icons-material/Edit'
import FileChunksIcon from '@mui/icons-material/AppRegistration'
import NoteAddIcon from '@mui/icons-material/NoteAdd'
import SearchIcon from '@mui/icons-material/Search'
import RefreshIcon from '@mui/icons-material/Refresh'
import CodeIcon from '@mui/icons-material/Code'
import doc_store_details_emptySVG from '@/assets/images/doc_store_details_empty.svg'
// store
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions'
import { useError } from '@/store/context/ErrorContext'
// ==============================|| DOCUMENTS ||============================== //
const StyledTableCell = styled(TableCell)(({ theme }) => ({
borderColor: theme.palette.grey[900] + 25,
padding: '6px 16px',
[`&.${tableCellClasses.head}`]: {
color: theme.palette.grey[900]
},
[`&.${tableCellClasses.body}`]: {
fontSize: 14,
height: 64
}
}))
const StyledTableRow = styled(TableRow)(() => ({
// hide last border
'&:last-child td, &:last-child th': {
border: 0
}
}))
const StyledMenu = styled((props) => (
<Menu
elevation={0}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right'
}}
{...props}
/>
))(({ theme }) => ({
'& .MuiPaper-root': {
borderRadius: 6,
marginTop: theme.spacing(1),
minWidth: 180,
boxShadow:
'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px',
'& .MuiMenu-list': {
padding: '4px 0'
},
'& .MuiMenuItem-root': {
'& .MuiSvgIcon-root': {
fontSize: 18,
color: theme.palette.text.secondary,
marginRight: theme.spacing(1.5)
},
'&:active': {
backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity)
}
}
}
}))
const DocumentStoreDetails = () => {
const theme = useTheme()
const customization = useSelector((state) => state.customization)
const navigate = useNavigate()
const dispatch = useDispatch()
const { hasAssignedWorkspace } = useAuth()
useNotifier()
const { confirm } = useConfirm()
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
const { error, setError } = useError()
const { hasPermission } = useAuth()
const getSpecificDocumentStore = useApi(documentsApi.getSpecificDocumentStore)
const [isLoading, setLoading] = useState(true)
const [isBackdropLoading, setBackdropLoading] = useState(false)
const [showDialog, setShowDialog] = useState(false)
const [documentStore, setDocumentStore] = useState({})
const [dialogProps, setDialogProps] = useState({})
const [showDocumentLoaderListDialog, setShowDocumentLoaderListDialog] = useState(false)
const [documentLoaderListDialogProps, setDocumentLoaderListDialogProps] = useState({})
const [showDeleteDocStoreDialog, setShowDeleteDocStoreDialog] = useState(false)
const [deleteDocStoreDialogProps, setDeleteDocStoreDialogProps] = useState({})
const [showDocStoreAPIDialog, setShowDocStoreAPIDialog] = useState(false)
const [docStoreAPIDialogProps, setDocStoreAPIDialogProps] = useState({})
const [anchorEl, setAnchorEl] = useState(null)
const open = Boolean(anchorEl)
const { storeId } = useParams()
const openPreviewSettings = (id) => {
navigate('/document-stores/' + storeId + '/' + id)
}
const showStoredChunks = (id) => {
navigate('/document-stores/chunks/' + storeId + '/' + id)
}
const showVectorStoreQuery = (id) => {
navigate('/document-stores/query/' + id)
}
const onDocLoaderSelected = (docLoaderComponentName) => {
setShowDocumentLoaderListDialog(false)
navigate('/document-stores/' + storeId + '/' + docLoaderComponentName)
}
const showVectorStore = (id) => {
navigate('/document-stores/vector/' + id)
}
const listLoaders = () => {
const dialogProp = {
title: 'Select Document Loader'
}
setDocumentLoaderListDialogProps(dialogProp)
setShowDocumentLoaderListDialog(true)
}
const deleteVectorStoreDataFromStore = async (storeId) => {
try {
await documentsApi.deleteVectorStoreDataFromStore(storeId)
} catch (error) {
console.error(error)
}
}
const onDocStoreDelete = async (type, file, removeFromVectorStore) => {
setBackdropLoading(true)
setShowDeleteDocStoreDialog(false)
if (type === 'STORE') {
if (removeFromVectorStore) {
await deleteVectorStoreDataFromStore(storeId)
}
try {
const deleteResp = await documentsApi.deleteDocumentStore(storeId)
setBackdropLoading(false)
if (deleteResp.data) {
enqueueSnackbar({
message: 'Store, Loader and associated document chunks deleted',
options: {
key: new Date().getTime() + Math.random(),
variant: 'success',
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
navigate('/document-stores/')
}
} catch (error) {
setBackdropLoading(false)
setError(error)
enqueueSnackbar({
message: `Failed to delete Document Store: ${
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
}`,
options: {
key: new Date().getTime() + Math.random(),
variant: 'error',
persist: true,
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
} else if (type === 'LOADER') {
try {
const deleteResp = await documentsApi.deleteLoaderFromStore(storeId, file.id)
setBackdropLoading(false)
if (deleteResp.data) {
enqueueSnackbar({
message: 'Loader and associated document chunks deleted',
options: {
key: new Date().getTime() + Math.random(),
variant: 'success',
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
onConfirm()
}
} catch (error) {
setError(error)
setBackdropLoading(false)
enqueueSnackbar({
message: `Failed to delete Document Loader: ${
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
}`,
options: {
key: new Date().getTime() + Math.random(),
variant: 'error',
persist: true,
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
}
}
const onLoaderDelete = (file, vectorStoreConfig, recordManagerConfig) => {
const props = {
title: `Delete`,
description: `Delete Loader ${file.loaderName} ? This will delete all the associated document chunks.`,
vectorStoreConfig,
recordManagerConfig,
type: 'LOADER',
file
}
setDeleteDocStoreDialogProps(props)
setShowDeleteDocStoreDialog(true)
}
const onStoreDelete = (vectorStoreConfig, recordManagerConfig) => {
const props = {
title: `Delete`,
description: `Delete Store ${getSpecificDocumentStore.data?.name} ? This will delete all the associated loaders and document chunks.`,
vectorStoreConfig,
recordManagerConfig,
type: 'STORE'
}
setDeleteDocStoreDialogProps(props)
setShowDeleteDocStoreDialog(true)
}
const onStoreRefresh = async (storeId) => {
const confirmPayload = {
title: `Refresh all loaders and upsert all chunks?`,
description: `This will re-process all loaders and upsert all chunks. This action might take some time.`,
confirmButtonName: 'Refresh',
cancelButtonName: 'Cancel'
}
const isConfirmed = await confirm(confirmPayload)
if (isConfirmed) {
setAnchorEl(null)
setBackdropLoading(true)
try {
const resp = await documentsApi.refreshLoader(storeId)
if (resp.data) {
enqueueSnackbar({
message: 'Document store refresh successfully!',
options: {
key: new Date().getTime() + Math.random(),
variant: 'success',
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
setBackdropLoading(false)
} catch (error) {
setBackdropLoading(false)
enqueueSnackbar({
message: `Failed to refresh document store: ${
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
}`,
options: {
key: new Date().getTime() + Math.random(),
variant: 'error',
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
}
}
const onEditClicked = () => {
const data = {
name: documentStore.name,
description: documentStore.description,
id: documentStore.id
}
const dialogProp = {
title: 'Edit Document Store',
type: 'EDIT',
cancelButtonName: 'Cancel',
confirmButtonName: 'Update',
data: data
}
setDialogProps(dialogProp)
setShowDialog(true)
}
const onConfirm = () => {
setShowDialog(false)
getSpecificDocumentStore.request(storeId)
}
const handleClick = (event) => {
event.preventDefault()
event.stopPropagation()
setAnchorEl(event.currentTarget)
}
const onViewUpsertAPI = (storeId, loaderId) => {
const props = {
title: `Upsert API`,
storeId,
loaderId
}
setDocStoreAPIDialogProps(props)
setShowDocStoreAPIDialog(true)
}
const handleClose = () => {
setAnchorEl(null)
}
useEffect(() => {
getSpecificDocumentStore.request(storeId)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
if (getSpecificDocumentStore.data) {
const workspaceId = getSpecificDocumentStore.data.workspaceId
if (!hasAssignedWorkspace(workspaceId)) {
navigate('/unauthorized')
return
}
setDocumentStore(getSpecificDocumentStore.data)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getSpecificDocumentStore.data])
useEffect(() => {
setLoading(getSpecificDocumentStore.loading)
}, [getSpecificDocumentStore.loading])
return (
<>
<MainCard>
{error ? (
<ErrorBoundary error={error} />
) : (
<Stack flexDirection='column' sx={{ gap: 3 }}>
<ViewHeader
isBackButton={true}
isEditButton={hasPermission('documentStores:create,documentStores:update')}
search={false}
title={documentStore?.name}
description={documentStore?.description}
onBack={() => navigate('/document-stores')}
onEdit={() => onEditClicked()}
>
{(documentStore?.status === 'STALE' || documentStore?.status === 'UPSERTING') && (
<PermissionIconButton
permissionId={'documentStores:view'}
onClick={onConfirm}
size='small'
color='primary'
title='Refresh Document Store'
>
<IconRefresh />
</PermissionIconButton>
)}
<StyledPermissionButton
permissionId={'documentStores:add-loader'}
variant='contained'
sx={{ ml: 2, minWidth: 200, borderRadius: 2, height: '100%', color: 'white' }}
startIcon={<IconPlus />}
onClick={listLoaders}
>
Add Document Loader
</StyledPermissionButton>
<Button
id='document-store-header-action-button'
aria-controls={open ? 'document-store-header-menu' : undefined}
aria-haspopup='true'
aria-expanded={open ? 'true' : undefined}
variant='outlined'
disableElevation
color='secondary'
onClick={handleClick}
sx={{ minWidth: 150 }}
endIcon={<KeyboardArrowDownIcon />}
>
More Actions
</Button>
<StyledMenu
id='document-store-header-menu'
MenuListProps={{
'aria-labelledby': 'document-store-header-menu-button'
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
<MenuItem
disabled={documentStore?.totalChunks <= 0 || documentStore?.status === 'UPSERTING'}
onClick={() => showStoredChunks('all')}
disableRipple
>
<FileChunksIcon />
View & Edit Chunks
</MenuItem>
<Available permission={'documentStores:upsert-config'}>
<MenuItem
disabled={documentStore?.totalChunks <= 0 || documentStore?.status === 'UPSERTING'}
onClick={() => showVectorStore(documentStore.id)}
disableRipple
>
<NoteAddIcon />
Upsert All Chunks
</MenuItem>
</Available>
<MenuItem
disabled={documentStore?.totalChunks <= 0 || documentStore?.status !== 'UPSERTED'}
onClick={() => showVectorStoreQuery(documentStore.id)}
disableRipple
>
<SearchIcon />
Retrieval Query
</MenuItem>
<Available permission={'documentStores:upsert-config'}>
<MenuItem
disabled={documentStore?.totalChunks <= 0 || documentStore?.status !== 'UPSERTED'}
onClick={() => onStoreRefresh(documentStore.id)}
disableRipple
title='Re-process all loaders and upsert all chunks'
>
<RefreshIcon />
Refresh
</MenuItem>
</Available>
<Divider sx={{ my: 0.5 }} />
<MenuItem
onClick={() => onStoreDelete(documentStore.vectorStoreConfig, documentStore.recordManagerConfig)}
disableRipple
>
<FileDeleteIcon />
Delete
</MenuItem>
</StyledMenu>
</ViewHeader>
<DocumentStoreStatus status={documentStore?.status} />
{getSpecificDocumentStore.data?.whereUsed?.length > 0 && (
<Stack flexDirection='row' sx={{ gap: 2, alignItems: 'center', flexWrap: 'wrap' }}>
<div
style={{
paddingLeft: '15px',
paddingRight: '15px',
paddingTop: '10px',
paddingBottom: '10px',
fontSize: '0.9rem',
width: 'max-content',
display: 'flex',
flexDirection: 'row',
alignItems: 'center'
}}
>
<IconVectorBezier2 style={{ marginRight: 5 }} size={17} />
Chatflows Used:
</div>
{getSpecificDocumentStore.data.whereUsed.map((chatflowUsed, index) => (
<Chip
key={index}
clickable
style={{
width: 'max-content',
borderRadius: '25px',
boxShadow: customization.isDarkMode
? '0 2px 14px 0 rgb(255 255 255 / 10%)'
: '0 2px 14px 0 rgb(32 40 45 / 10%)'
}}
label={chatflowUsed.name}
onClick={() => navigate('/canvas/' + chatflowUsed.id)}
></Chip>
))}
</Stack>
)}
{!isLoading && documentStore && !documentStore?.loaders?.length ? (
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
<Box sx={{ p: 2, height: 'auto' }}>
<img
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
src={doc_store_details_emptySVG}
alt='doc_store_details_emptySVG'
/>
</Box>
<div>No Document Added Yet</div>
<StyledButton
variant='contained'
sx={{ borderRadius: 2, height: '100%', mt: 2, color: 'white' }}
startIcon={<IconPlus />}
onClick={listLoaders}
>
Add Document Loader
</StyledButton>
</Stack>
) : (
<TableContainer
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
component={Paper}
>
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
<TableHead
sx={{
backgroundColor: customization.isDarkMode
? theme.palette.common.black
: theme.palette.grey[100],
height: 56
}}
>
<TableRow>
<StyledTableCell>&nbsp;</StyledTableCell>
<StyledTableCell>Loader</StyledTableCell>
<StyledTableCell>Splitter</StyledTableCell>
<StyledTableCell>Source(s)</StyledTableCell>
<StyledTableCell>Chunks</StyledTableCell>
<StyledTableCell>Chars</StyledTableCell>
<Available permission={'documentStores:preview-process,documentStores:delete-loader'}>
<StyledTableCell>Actions</StyledTableCell>
</Available>
</TableRow>
</TableHead>
<TableBody>
{isLoading ? (
<>
<StyledTableRow>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
<Available permission={'documentStores:preview-process,documentStores:delete-loader'}>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
</Available>
</StyledTableRow>
<StyledTableRow>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
<Available permission={'documentStores:preview-process,documentStores:delete-loader'}>
<StyledTableCell>
<Skeleton variant='text' />
</StyledTableCell>
</Available>
</StyledTableRow>
</>
) : (
<>
{documentStore?.loaders &&
documentStore?.loaders.length > 0 &&
documentStore?.loaders.map((loader, index) => (
<LoaderRow
key={index}
index={index}
loader={loader}
theme={theme}
onEditClick={() => openPreviewSettings(loader.id)}
onViewChunksClick={() => showStoredChunks(loader.id)}
onDeleteClick={() =>
onLoaderDelete(
loader,
documentStore?.vectorStoreConfig,
documentStore?.recordManagerConfig
)
}
onChunkUpsert={() =>
navigate(`/document-stores/vector/${documentStore.id}/${loader.id}`)
}
onViewUpsertAPI={() => onViewUpsertAPI(documentStore.id, loader.id)}
/>
))}
</>
)}
</TableBody>
</Table>
</TableContainer>
)}
{getSpecificDocumentStore.data?.status === 'STALE' && (
<div style={{ width: '100%', textAlign: 'center', marginTop: '20px' }}>
<Typography
color='warning'
style={{ color: 'darkred', fontWeight: 500, fontStyle: 'italic', fontSize: 12 }}
>
Some files are pending processing. Please Refresh to get the latest status.
</Typography>
</div>
)}
</Stack>
)}
</MainCard>
{showDialog && (
<AddDocStoreDialog
dialogProps={dialogProps}
show={showDialog}
onCancel={() => setShowDialog(false)}
onConfirm={onConfirm}
/>
)}
{showDocumentLoaderListDialog && (
<DocumentLoaderListDialog
show={showDocumentLoaderListDialog}
dialogProps={documentLoaderListDialogProps}
onCancel={() => setShowDocumentLoaderListDialog(false)}
onDocLoaderSelected={onDocLoaderSelected}
/>
)}
{showDeleteDocStoreDialog && (
<DeleteDocStoreDialog
show={showDeleteDocStoreDialog}
dialogProps={deleteDocStoreDialogProps}
onCancel={() => setShowDeleteDocStoreDialog(false)}
onDelete={onDocStoreDelete}
/>
)}
{showDocStoreAPIDialog && (
<DocStoreAPIDialog
show={showDocStoreAPIDialog}
dialogProps={docStoreAPIDialogProps}
onCancel={() => setShowDocStoreAPIDialog(false)}
/>
)}
{isBackdropLoading && <BackdropLoader open={isBackdropLoading} />}
<ConfirmDialog />
</>
)
}
function LoaderRow(props) {
const [anchorEl, setAnchorEl] = useState(null)
const open = Boolean(anchorEl)
const handleClick = (event) => {
event.preventDefault()
event.stopPropagation()
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
const formatSources = (files, source) => {
// Prefer files.name when files array exists and has items
if (files && Array.isArray(files) && files.length > 0) {
return files.map((file) => file.name).join(', ')
}
// Fallback to original source logic
if (source && typeof source === 'string' && source.includes('base64')) {
return getFileName(source)
}
if (source && typeof source === 'string' && source.startsWith('[') && source.endsWith(']')) {
return JSON.parse(source).join(', ')
}
return source || 'No source'
}
return (
<>
<TableRow hover key={props.index} sx={{ '&:last-child td, &:last-child th': { border: 0 }, cursor: 'pointer' }}>
<StyledTableCell onClick={props.onViewChunksClick} scope='row' style={{ width: '5%' }}>
<div
style={{
display: 'flex',
width: '20px',
height: '20px',
backgroundColor: props.loader?.status === 'SYNC' ? '#00e676' : '#ffe57f',
borderRadius: '50%'
}}
></div>
</StyledTableCell>
<StyledTableCell onClick={props.onViewChunksClick} scope='row'>
{props.loader.loaderName}
</StyledTableCell>
<StyledTableCell onClick={props.onViewChunksClick}>{props.loader.splitterName ?? 'None'}</StyledTableCell>
<StyledTableCell onClick={props.onViewChunksClick}>
{formatSources(props.loader.files, props.loader.source)}
</StyledTableCell>
<StyledTableCell onClick={props.onViewChunksClick}>
{props.loader.totalChunks && <Chip variant='outlined' size='small' label={props.loader.totalChunks.toLocaleString()} />}
</StyledTableCell>
<StyledTableCell onClick={props.onViewChunksClick}>
{props.loader.totalChars && <Chip variant='outlined' size='small' label={props.loader.totalChars.toLocaleString()} />}
</StyledTableCell>
<Available permission={'documentStores:preview-process,documentStores:delete-loader'}>
<StyledTableCell>
<div>
<Button
id='document-store-action-button'
aria-controls={open ? 'document-store-action-customized-menu' : undefined}
aria-haspopup='true'
aria-expanded={open ? 'true' : undefined}
disableElevation
onClick={(e) => handleClick(e)}
endIcon={<KeyboardArrowDownIcon />}
>
Options
</Button>
<StyledMenu
id='document-store-actions-customized-menu'
MenuListProps={{
'aria-labelledby': 'document-store-actions-customized-button'
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
<Available permission={'documentStores:preview-process'}>
<MenuItem onClick={props.onEditClick} disableRipple>
<FileEditIcon />
Preview & Process
</MenuItem>
</Available>
<Available permission={'documentStores:preview-process'}>
<MenuItem onClick={props.onViewChunksClick} disableRipple>
<FileChunksIcon />
View & Edit Chunks
</MenuItem>
</Available>
<Available permission={'documentStores:preview-process'}>
<MenuItem onClick={props.onChunkUpsert} disableRipple>
<NoteAddIcon />
Upsert Chunks
</MenuItem>
</Available>
<Available permission={'documentStores:preview-process'}>
<MenuItem onClick={props.onViewUpsertAPI} disableRipple>
<CodeIcon />
View API
</MenuItem>
</Available>
<Divider sx={{ my: 0.5 }} />
<Available permission={'documentStores:delete-loader'}>
<MenuItem onClick={props.onDeleteClick} disableRipple>
<FileDeleteIcon />
Delete
</MenuItem>
</Available>
</StyledMenu>
</div>
</StyledTableCell>
</Available>
</TableRow>
</>
)
}
LoaderRow.propTypes = {
loader: PropTypes.any,
index: PropTypes.number,
open: PropTypes.bool,
theme: PropTypes.any,
onViewChunksClick: PropTypes.func,
onEditClick: PropTypes.func,
onDeleteClick: PropTypes.func,
onChunkUpsert: PropTypes.func,
onViewUpsertAPI: PropTypes.func
}
export default DocumentStoreDetails