mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 19:00:59 +03:00
Bugfix/Remove postgres vector store data when deletion (#5536)
Remove postgres vector store data when deletion - Introduced a new `doc_id` column in MySQL, Postgres, and SQLite record managers to support document identification. - Updated the `update` method to handle both string and object formats for keys, allowing for better flexibility in document updates. - Enhanced `listKeys` method to filter by `doc_id` when provided in options. - Updated vector store integrations to utilize the new `doc_id` filtering capability
This commit is contained in:
@@ -22,7 +22,10 @@ const refreshLoader = (storeId) => client.post(`/document-store/refresh/${storeI
|
||||
const insertIntoVectorStore = (body) => client.post(`/document-store/vectorstore/insert`, body)
|
||||
const saveVectorStoreConfig = (body) => client.post(`/document-store/vectorstore/save`, body)
|
||||
const updateVectorStoreConfig = (body) => client.post(`/document-store/vectorstore/update`, body)
|
||||
const deleteVectorStoreDataFromStore = (storeId) => client.delete(`/document-store/vectorstore/${storeId}`)
|
||||
const deleteVectorStoreDataFromStore = (storeId, docId) => {
|
||||
const url = docId ? `/document-store/vectorstore/${storeId}?docId=${docId}` : `/document-store/vectorstore/${storeId}`
|
||||
return client.delete(url)
|
||||
}
|
||||
const queryVectorStore = (body) => client.post(`/document-store/vectorstore/query`, body)
|
||||
const getVectorStoreProviders = () => client.get('/document-store/components/vectorstore')
|
||||
const getEmbeddingProviders = () => client.get('/document-store/components/embeddings')
|
||||
|
||||
@@ -18,11 +18,15 @@ import {
|
||||
TableContainer,
|
||||
TableRow,
|
||||
TableCell,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
DialogActions
|
||||
DialogActions,
|
||||
Card,
|
||||
Stack,
|
||||
Link
|
||||
} from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||
import SettingsIcon from '@mui/icons-material/Settings'
|
||||
import { IconAlertTriangle } from '@tabler/icons-react'
|
||||
import { TableViewOnly } from '@/ui-component/table/Table'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
@@ -36,12 +40,13 @@ import { initNode } from '@/utils/genericHelper'
|
||||
|
||||
const DeleteDocStoreDialog = ({ show, dialogProps, onCancel, onDelete }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const theme = useTheme()
|
||||
const [nodeConfigExpanded, setNodeConfigExpanded] = useState({})
|
||||
const [removeFromVS, setRemoveFromVS] = useState(false)
|
||||
const [vsFlowData, setVSFlowData] = useState([])
|
||||
const [rmFlowData, setRMFlowData] = useState([])
|
||||
|
||||
const getSpecificNodeApi = useApi(nodesApi.getSpecificNode)
|
||||
const getVectorStoreNodeApi = useApi(nodesApi.getSpecificNode)
|
||||
const getRecordManagerNodeApi = useApi(nodesApi.getSpecificNode)
|
||||
|
||||
const handleAccordionChange = (nodeName) => (event, isExpanded) => {
|
||||
const accordianNodes = { ...nodeConfigExpanded }
|
||||
@@ -52,42 +57,37 @@ const DeleteDocStoreDialog = ({ show, dialogProps, onCancel, onDelete }) => {
|
||||
useEffect(() => {
|
||||
if (dialogProps.recordManagerConfig) {
|
||||
const nodeName = dialogProps.recordManagerConfig.name
|
||||
if (nodeName) getSpecificNodeApi.request(nodeName)
|
||||
if (nodeName) getRecordManagerNodeApi.request(nodeName)
|
||||
}
|
||||
|
||||
if (dialogProps.vectorStoreConfig) {
|
||||
const nodeName = dialogProps.vectorStoreConfig.name
|
||||
if (nodeName) getSpecificNodeApi.request(nodeName)
|
||||
}
|
||||
if (dialogProps.vectorStoreConfig) {
|
||||
const nodeName = dialogProps.vectorStoreConfig.name
|
||||
if (nodeName) getVectorStoreNodeApi.request(nodeName)
|
||||
}
|
||||
|
||||
return () => {
|
||||
setNodeConfigExpanded({})
|
||||
setRemoveFromVS(false)
|
||||
setVSFlowData([])
|
||||
setRMFlowData([])
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dialogProps])
|
||||
|
||||
// Process Vector Store node data
|
||||
useEffect(() => {
|
||||
if (getSpecificNodeApi.data) {
|
||||
const nodeData = cloneDeep(initNode(getSpecificNodeApi.data, uuidv4()))
|
||||
|
||||
let config = 'vectorStoreConfig'
|
||||
if (nodeData.category === 'Record Manager') config = 'recordManagerConfig'
|
||||
if (getVectorStoreNodeApi.data && dialogProps.vectorStoreConfig) {
|
||||
const nodeData = cloneDeep(initNode(getVectorStoreNodeApi.data, uuidv4()))
|
||||
|
||||
const paramValues = []
|
||||
|
||||
for (const inputName in dialogProps[config].config) {
|
||||
for (const inputName in dialogProps.vectorStoreConfig.config) {
|
||||
const inputParam = nodeData.inputParams.find((inp) => inp.name === inputName)
|
||||
|
||||
if (!inputParam) continue
|
||||
|
||||
if (inputParam.type === 'credential') continue
|
||||
|
||||
let paramValue = {}
|
||||
|
||||
const inputValue = dialogProps[config].config[inputName]
|
||||
const inputValue = dialogProps.vectorStoreConfig.config[inputName]
|
||||
|
||||
if (!inputValue) continue
|
||||
|
||||
@@ -95,40 +95,71 @@ const DeleteDocStoreDialog = ({ show, dialogProps, onCancel, onDelete }) => {
|
||||
continue
|
||||
}
|
||||
|
||||
paramValue = {
|
||||
paramValues.push({
|
||||
label: inputParam?.label,
|
||||
name: inputParam?.name,
|
||||
type: inputParam?.type,
|
||||
value: inputValue
|
||||
}
|
||||
paramValues.push(paramValue)
|
||||
})
|
||||
}
|
||||
|
||||
if (config === 'vectorStoreConfig') {
|
||||
setVSFlowData([
|
||||
{
|
||||
label: nodeData.label,
|
||||
name: nodeData.name,
|
||||
category: nodeData.category,
|
||||
id: nodeData.id,
|
||||
paramValues
|
||||
}
|
||||
])
|
||||
} else if (config === 'recordManagerConfig') {
|
||||
setRMFlowData([
|
||||
{
|
||||
label: nodeData.label,
|
||||
name: nodeData.name,
|
||||
category: nodeData.category,
|
||||
id: nodeData.id,
|
||||
paramValues
|
||||
}
|
||||
])
|
||||
}
|
||||
setVSFlowData([
|
||||
{
|
||||
label: nodeData.label,
|
||||
name: nodeData.name,
|
||||
category: nodeData.category,
|
||||
id: nodeData.id,
|
||||
paramValues
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getSpecificNodeApi.data])
|
||||
}, [getVectorStoreNodeApi.data])
|
||||
|
||||
// Process Record Manager node data
|
||||
useEffect(() => {
|
||||
if (getRecordManagerNodeApi.data && dialogProps.recordManagerConfig) {
|
||||
const nodeData = cloneDeep(initNode(getRecordManagerNodeApi.data, uuidv4()))
|
||||
|
||||
const paramValues = []
|
||||
|
||||
for (const inputName in dialogProps.recordManagerConfig.config) {
|
||||
const inputParam = nodeData.inputParams.find((inp) => inp.name === inputName)
|
||||
|
||||
if (!inputParam) continue
|
||||
|
||||
if (inputParam.type === 'credential') continue
|
||||
|
||||
const inputValue = dialogProps.recordManagerConfig.config[inputName]
|
||||
|
||||
if (!inputValue) continue
|
||||
|
||||
if (typeof inputValue === 'string' && inputValue.startsWith('{{') && inputValue.endsWith('}}')) {
|
||||
continue
|
||||
}
|
||||
|
||||
paramValues.push({
|
||||
label: inputParam?.label,
|
||||
name: inputParam?.name,
|
||||
type: inputParam?.type,
|
||||
value: inputValue
|
||||
})
|
||||
}
|
||||
|
||||
setRMFlowData([
|
||||
{
|
||||
label: nodeData.label,
|
||||
name: nodeData.name,
|
||||
category: nodeData.category,
|
||||
id: nodeData.id,
|
||||
paramValues
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getRecordManagerNodeApi.data])
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
@@ -142,91 +173,130 @@ const DeleteDocStoreDialog = ({ show, dialogProps, onCancel, onDelete }) => {
|
||||
<DialogTitle sx={{ fontSize: '1rem', p: 3, pb: 0 }} id='alert-dialog-title'>
|
||||
{dialogProps.title}
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, maxHeight: '75vh', position: 'relative', px: 3, pb: 3 }}>
|
||||
<DialogContent
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
maxHeight: '75vh',
|
||||
position: 'relative',
|
||||
px: 3,
|
||||
pb: 3,
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<span style={{ marginTop: '20px' }}>{dialogProps.description}</span>
|
||||
{dialogProps.type === 'STORE' && dialogProps.recordManagerConfig && (
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={removeFromVS} onChange={(event) => setRemoveFromVS(event.target.checked)} />}
|
||||
label='Remove data from vector store and record manager'
|
||||
/>
|
||||
{dialogProps.vectorStoreConfig && !dialogProps.recordManagerConfig && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderRadius: 10,
|
||||
background: 'rgb(254,252,191)',
|
||||
padding: 10
|
||||
}}
|
||||
>
|
||||
<IconAlertTriangle size={70} color='orange' />
|
||||
<span style={{ color: 'rgb(116,66,16)', marginLeft: 10 }}>
|
||||
<strong>Note:</strong> Without a Record Manager configured, only the document chunks will be removed from the
|
||||
document store. The actual vector embeddings in your vector store database will remain unchanged. To enable
|
||||
automatic cleanup of vector store data, please configure a Record Manager.{' '}
|
||||
<Link
|
||||
href='https://docs.flowiseai.com/integrations/langchain/record-managers'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
sx={{ fontWeight: 500, color: 'rgb(116,66,16)', textDecoration: 'underline' }}
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{removeFromVS && (
|
||||
<div>
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||
<TableBody>
|
||||
<TableRow sx={{ '& td': { border: 0 } }}>
|
||||
<TableCell sx={{ pb: 0, pt: 0 }} colSpan={6}>
|
||||
<Box>
|
||||
{([...vsFlowData, ...rmFlowData] || []).map((node, index) => {
|
||||
return (
|
||||
<Accordion
|
||||
expanded={nodeConfigExpanded[node.name] || true}
|
||||
onChange={handleAccordionChange(node.name)}
|
||||
key={index}
|
||||
disableGutters
|
||||
>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls={`nodes-accordian-${node.name}`}
|
||||
id={`nodes-accordian-header-${node.name}`}
|
||||
{vsFlowData && vsFlowData.length > 0 && rmFlowData && rmFlowData.length > 0 && (
|
||||
<Card sx={{ borderColor: theme.palette.primary[200] + 75, p: 2 }} variant='outlined'>
|
||||
<Stack sx={{ mt: 1, mb: 2, ml: 1, alignItems: 'center' }} direction='row' spacing={2}>
|
||||
<SettingsIcon />
|
||||
<Typography variant='h4'>Configuration</Typography>
|
||||
</Stack>
|
||||
<Stack direction='column'>
|
||||
<TableContainer component={Paper} sx={{ maxHeight: '400px', overflow: 'auto' }}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||
<TableBody>
|
||||
<TableRow sx={{ '& td': { border: 0 } }}>
|
||||
<TableCell sx={{ pb: 0, pt: 0 }} colSpan={6}>
|
||||
<Box>
|
||||
{([...vsFlowData, ...rmFlowData] || []).map((node, index) => {
|
||||
return (
|
||||
<Accordion
|
||||
expanded={nodeConfigExpanded[node.name] || false}
|
||||
onChange={handleAccordionChange(node.name)}
|
||||
key={index}
|
||||
disableGutters
|
||||
>
|
||||
<div
|
||||
style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls={`nodes-accordian-${node.name}`}
|
||||
id={`nodes-accordian-header-${node.name}`}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
marginRight: 10,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'white'
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 7,
|
||||
width: 40,
|
||||
height: 40,
|
||||
marginRight: 10,
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
alt={node.name}
|
||||
src={`${baseURL}/api/v1/node-icon/${node.name}`}
|
||||
/>
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 7,
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
alt={node.name}
|
||||
src={`${baseURL}/api/v1/node-icon/${node.name}`}
|
||||
/>
|
||||
</div>
|
||||
<Typography variant='h5'>{node.label}</Typography>
|
||||
</div>
|
||||
<Typography variant='h5'>{node.label}</Typography>
|
||||
</div>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{node.paramValues[0] && (
|
||||
<TableViewOnly
|
||||
sx={{ minWidth: 150 }}
|
||||
rows={node.paramValues}
|
||||
columns={Object.keys(node.paramValues[0])}
|
||||
/>
|
||||
)}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)
|
||||
})}
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<span style={{ marginTop: '30px', fontStyle: 'italic', color: '#b35702' }}>
|
||||
* Only data that were upserted with Record Manager will be deleted from vector store
|
||||
</span>
|
||||
</div>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ p: 0 }}>
|
||||
{node.paramValues[0] && (
|
||||
<TableViewOnly
|
||||
sx={{ minWidth: 150 }}
|
||||
rows={node.paramValues}
|
||||
columns={Object.keys(node.paramValues[0])}
|
||||
/>
|
||||
)}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)
|
||||
})}
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Stack>
|
||||
</Card>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ pr: 3, pb: 3 }}>
|
||||
<Button onClick={onCancel} color='primary'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='contained' onClick={() => onDelete(dialogProps.type, dialogProps.file, removeFromVS)} color='error'>
|
||||
<Button variant='contained' onClick={() => onDelete(dialogProps.type, dialogProps.file)} color='error'>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
@@ -186,19 +186,19 @@ const DocumentStoreDetails = () => {
|
||||
setShowDocumentLoaderListDialog(true)
|
||||
}
|
||||
|
||||
const deleteVectorStoreDataFromStore = async (storeId) => {
|
||||
const deleteVectorStoreDataFromStore = async (storeId, docId) => {
|
||||
try {
|
||||
await documentsApi.deleteVectorStoreDataFromStore(storeId)
|
||||
await documentsApi.deleteVectorStoreDataFromStore(storeId, docId)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
const onDocStoreDelete = async (type, file, removeFromVectorStore) => {
|
||||
const onDocStoreDelete = async (type, file) => {
|
||||
setBackdropLoading(true)
|
||||
setShowDeleteDocStoreDialog(false)
|
||||
if (type === 'STORE') {
|
||||
if (removeFromVectorStore) {
|
||||
if (documentStore.recordManagerConfig) {
|
||||
await deleteVectorStoreDataFromStore(storeId)
|
||||
}
|
||||
try {
|
||||
@@ -239,6 +239,9 @@ const DocumentStoreDetails = () => {
|
||||
})
|
||||
}
|
||||
} else if (type === 'LOADER') {
|
||||
if (documentStore.recordManagerConfig) {
|
||||
await deleteVectorStoreDataFromStore(storeId, file.id)
|
||||
}
|
||||
try {
|
||||
const deleteResp = await documentsApi.deleteLoaderFromStore(storeId, file.id)
|
||||
setBackdropLoading(false)
|
||||
@@ -280,9 +283,40 @@ const DocumentStoreDetails = () => {
|
||||
}
|
||||
|
||||
const onLoaderDelete = (file, vectorStoreConfig, recordManagerConfig) => {
|
||||
// Get the display name in the format "LoaderName (sourceName)"
|
||||
const loaderName = file.loaderName || 'Unknown'
|
||||
let sourceName = ''
|
||||
|
||||
// Prefer files.name when files array exists and has items
|
||||
if (file.files && Array.isArray(file.files) && file.files.length > 0) {
|
||||
sourceName = file.files.map((f) => f.name).join(', ')
|
||||
} else if (file.source) {
|
||||
// Fallback to source logic
|
||||
if (typeof file.source === 'string' && file.source.includes('base64')) {
|
||||
sourceName = getFileName(file.source)
|
||||
} else if (typeof file.source === 'string' && file.source.startsWith('[') && file.source.endsWith(']')) {
|
||||
sourceName = JSON.parse(file.source).join(', ')
|
||||
} else if (typeof file.source === 'string') {
|
||||
sourceName = file.source
|
||||
}
|
||||
}
|
||||
|
||||
const displayName = sourceName ? `${loaderName} (${sourceName})` : loaderName
|
||||
|
||||
let description = `Delete "${displayName}"? This will delete all the associated document chunks from the document store.`
|
||||
|
||||
if (
|
||||
recordManagerConfig &&
|
||||
vectorStoreConfig &&
|
||||
Object.keys(recordManagerConfig).length > 0 &&
|
||||
Object.keys(vectorStoreConfig).length > 0
|
||||
) {
|
||||
description = `Delete "${displayName}"? This will delete all the associated document chunks from the document store and remove the actual data from the vector store database.`
|
||||
}
|
||||
|
||||
const props = {
|
||||
title: `Delete`,
|
||||
description: `Delete Loader ${file.loaderName} ? This will delete all the associated document chunks.`,
|
||||
description,
|
||||
vectorStoreConfig,
|
||||
recordManagerConfig,
|
||||
type: 'LOADER',
|
||||
@@ -294,9 +328,20 @@ const DocumentStoreDetails = () => {
|
||||
}
|
||||
|
||||
const onStoreDelete = (vectorStoreConfig, recordManagerConfig) => {
|
||||
let description = `Delete Store ${getSpecificDocumentStore.data?.name}? This will delete all the associated loaders and document chunks from the document store.`
|
||||
|
||||
if (
|
||||
recordManagerConfig &&
|
||||
vectorStoreConfig &&
|
||||
Object.keys(recordManagerConfig).length > 0 &&
|
||||
Object.keys(vectorStoreConfig).length > 0
|
||||
) {
|
||||
description = `Delete Store ${getSpecificDocumentStore.data?.name}? This will delete all the associated loaders and document chunks from the document store, and remove the actual data from the vector store database.`
|
||||
}
|
||||
|
||||
const props = {
|
||||
title: `Delete`,
|
||||
description: `Delete Store ${getSpecificDocumentStore.data?.name} ? This will delete all the associated loaders and document chunks.`,
|
||||
description,
|
||||
vectorStoreConfig,
|
||||
recordManagerConfig,
|
||||
type: 'STORE'
|
||||
@@ -481,7 +526,10 @@ const DocumentStoreDetails = () => {
|
||||
>
|
||||
<MenuItem
|
||||
disabled={documentStore?.totalChunks <= 0 || documentStore?.status === 'UPSERTING'}
|
||||
onClick={() => showStoredChunks('all')}
|
||||
onClick={() => {
|
||||
handleClose()
|
||||
showStoredChunks('all')
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<FileChunksIcon />
|
||||
@@ -490,7 +538,10 @@ const DocumentStoreDetails = () => {
|
||||
<Available permission={'documentStores:upsert-config'}>
|
||||
<MenuItem
|
||||
disabled={documentStore?.totalChunks <= 0 || documentStore?.status === 'UPSERTING'}
|
||||
onClick={() => showVectorStore(documentStore.id)}
|
||||
onClick={() => {
|
||||
handleClose()
|
||||
showVectorStore(documentStore.id)
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<NoteAddIcon />
|
||||
@@ -499,7 +550,10 @@ const DocumentStoreDetails = () => {
|
||||
</Available>
|
||||
<MenuItem
|
||||
disabled={documentStore?.totalChunks <= 0 || documentStore?.status !== 'UPSERTED'}
|
||||
onClick={() => showVectorStoreQuery(documentStore.id)}
|
||||
onClick={() => {
|
||||
handleClose()
|
||||
showVectorStoreQuery(documentStore.id)
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<SearchIcon />
|
||||
@@ -518,7 +572,10 @@ const DocumentStoreDetails = () => {
|
||||
</Available>
|
||||
<Divider sx={{ my: 0.5 }} />
|
||||
<MenuItem
|
||||
onClick={() => onStoreDelete(documentStore.vectorStoreConfig, documentStore.recordManagerConfig)}
|
||||
onClick={() => {
|
||||
handleClose()
|
||||
onStoreDelete(documentStore.vectorStoreConfig, documentStore.recordManagerConfig)
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<FileDeleteIcon />
|
||||
@@ -756,20 +813,26 @@ function LoaderRow(props) {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
const formatSources = (files, source) => {
|
||||
const formatSources = (files, source, loaderName) => {
|
||||
let sourceName = ''
|
||||
|
||||
// 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(', ')
|
||||
sourceName = files.map((file) => file.name).join(', ')
|
||||
} else if (source && typeof source === 'string' && source.includes('base64')) {
|
||||
// Fallback to original source logic
|
||||
sourceName = getFileName(source)
|
||||
} else if (source && typeof source === 'string' && source.startsWith('[') && source.endsWith(']')) {
|
||||
sourceName = JSON.parse(source).join(', ')
|
||||
} else if (source) {
|
||||
sourceName = source
|
||||
}
|
||||
|
||||
// Fallback to original source logic
|
||||
if (source && typeof source === 'string' && source.includes('base64')) {
|
||||
return getFileName(source)
|
||||
// Return format: "LoaderName (sourceName)" or just "LoaderName" if no source
|
||||
if (!sourceName) {
|
||||
return loaderName || 'No source'
|
||||
}
|
||||
if (source && typeof source === 'string' && source.startsWith('[') && source.endsWith(']')) {
|
||||
return JSON.parse(source).join(', ')
|
||||
}
|
||||
return source || 'No source'
|
||||
return loaderName ? `${loaderName} (${sourceName})` : sourceName
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -823,32 +886,62 @@ function LoaderRow(props) {
|
||||
onClose={handleClose}
|
||||
>
|
||||
<Available permission={'documentStores:preview-process'}>
|
||||
<MenuItem onClick={props.onEditClick} disableRipple>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleClose()
|
||||
props.onEditClick()
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<FileEditIcon />
|
||||
Preview & Process
|
||||
</MenuItem>
|
||||
</Available>
|
||||
<Available permission={'documentStores:preview-process'}>
|
||||
<MenuItem onClick={props.onViewChunksClick} disableRipple>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleClose()
|
||||
props.onViewChunksClick()
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<FileChunksIcon />
|
||||
View & Edit Chunks
|
||||
</MenuItem>
|
||||
</Available>
|
||||
<Available permission={'documentStores:preview-process'}>
|
||||
<MenuItem onClick={props.onChunkUpsert} disableRipple>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleClose()
|
||||
props.onChunkUpsert()
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<NoteAddIcon />
|
||||
Upsert Chunks
|
||||
</MenuItem>
|
||||
</Available>
|
||||
<Available permission={'documentStores:preview-process'}>
|
||||
<MenuItem onClick={props.onViewUpsertAPI} disableRipple>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleClose()
|
||||
props.onViewUpsertAPI()
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<CodeIcon />
|
||||
View API
|
||||
</MenuItem>
|
||||
</Available>
|
||||
<Divider sx={{ my: 0.5 }} />
|
||||
<Available permission={'documentStores:delete-loader'}>
|
||||
<MenuItem onClick={props.onDeleteClick} disableRipple>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleClose()
|
||||
props.onDeleteClick()
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<FileDeleteIcon />
|
||||
Delete
|
||||
</MenuItem>
|
||||
|
||||
@@ -26,6 +26,7 @@ import useApi from '@/hooks/useApi'
|
||||
import useConfirm from '@/hooks/useConfirm'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import { getFileName } from '@/utils/genericHelper'
|
||||
|
||||
// store
|
||||
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions'
|
||||
@@ -76,6 +77,7 @@ const ShowStoredChunks = () => {
|
||||
const [showExpandedChunkDialog, setShowExpandedChunkDialog] = useState(false)
|
||||
const [expandedChunkDialogProps, setExpandedChunkDialogProps] = useState({})
|
||||
const [fileNames, setFileNames] = useState([])
|
||||
const [loaderDisplayName, setLoaderDisplayName] = useState('')
|
||||
|
||||
const chunkSelected = (chunkId) => {
|
||||
const selectedChunk = documentChunks.find((chunk) => chunk.id === chunkId)
|
||||
@@ -212,13 +214,32 @@ const ShowStoredChunks = () => {
|
||||
setCurrentPage(data.currentPage)
|
||||
setStart(data.currentPage * 50 - 49)
|
||||
setEnd(data.currentPage * 50 > data.count ? data.count : data.currentPage * 50)
|
||||
|
||||
// Build the loader display name in format "LoaderName (sourceName)"
|
||||
const loaderName = data.file?.loaderName || data.storeName || ''
|
||||
let sourceName = ''
|
||||
|
||||
if (data.file?.files && data.file.files.length > 0) {
|
||||
const fileNames = []
|
||||
for (const attachedFile of data.file.files) {
|
||||
fileNames.push(attachedFile.name)
|
||||
}
|
||||
setFileNames(fileNames)
|
||||
sourceName = fileNames.join(', ')
|
||||
} else if (data.file?.source) {
|
||||
const source = data.file.source
|
||||
if (typeof source === 'string' && source.includes('base64')) {
|
||||
sourceName = getFileName(source)
|
||||
} else if (typeof source === 'string' && source.startsWith('[') && source.endsWith(']')) {
|
||||
sourceName = JSON.parse(source).join(', ')
|
||||
} else if (typeof source === 'string') {
|
||||
sourceName = source
|
||||
}
|
||||
}
|
||||
|
||||
// Set display name in format "LoaderName (sourceName)" or just "LoaderName"
|
||||
const displayName = sourceName ? `${loaderName} (${sourceName})` : loaderName
|
||||
setLoaderDisplayName(displayName)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -234,7 +255,7 @@ const ShowStoredChunks = () => {
|
||||
<ViewHeader
|
||||
isBackButton={true}
|
||||
search={false}
|
||||
title={getChunksApi.data?.file?.loaderName || getChunksApi.data?.storeName}
|
||||
title={loaderDisplayName}
|
||||
description={getChunksApi.data?.file?.splitterName || getChunksApi.data?.description}
|
||||
onBack={() => navigate(-1)}
|
||||
></ViewHeader>
|
||||
|
||||
@@ -40,7 +40,7 @@ import Storage from '@mui/icons-material/Storage'
|
||||
import DynamicFeed from '@mui/icons-material/Filter1'
|
||||
|
||||
// utils
|
||||
import { initNode, showHideInputParams } from '@/utils/genericHelper'
|
||||
import { initNode, showHideInputParams, getFileName } from '@/utils/genericHelper'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
|
||||
// const
|
||||
@@ -69,6 +69,7 @@ const VectorStoreConfigure = () => {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [documentStore, setDocumentStore] = useState({})
|
||||
const [dialogProps, setDialogProps] = useState({})
|
||||
const [currentLoader, setCurrentLoader] = useState(null)
|
||||
|
||||
const [showEmbeddingsListDialog, setShowEmbeddingsListDialog] = useState(false)
|
||||
const [selectedEmbeddingsProvider, setSelectedEmbeddingsProvider] = useState({})
|
||||
@@ -245,7 +246,8 @@ const VectorStoreConfigure = () => {
|
||||
const prepareConfigData = () => {
|
||||
const data = {
|
||||
storeId: storeId,
|
||||
docId: docId
|
||||
docId: docId,
|
||||
isStrictSave: true
|
||||
}
|
||||
// Set embedding config
|
||||
if (selectedEmbeddingsProvider.inputs) {
|
||||
@@ -353,6 +355,39 @@ const VectorStoreConfigure = () => {
|
||||
return Object.keys(selectedEmbeddingsProvider).length === 0
|
||||
}
|
||||
|
||||
const getLoaderDisplayName = (loader) => {
|
||||
if (!loader) return ''
|
||||
|
||||
const loaderName = loader.loaderName || 'Unknown'
|
||||
let sourceName = ''
|
||||
|
||||
// Prefer files.name when files array exists and has items
|
||||
if (loader.files && Array.isArray(loader.files) && loader.files.length > 0) {
|
||||
sourceName = loader.files.map((file) => file.name).join(', ')
|
||||
} else if (loader.source) {
|
||||
// Fallback to source logic
|
||||
if (typeof loader.source === 'string' && loader.source.includes('base64')) {
|
||||
sourceName = getFileName(loader.source)
|
||||
} else if (typeof loader.source === 'string' && loader.source.startsWith('[') && loader.source.endsWith(']')) {
|
||||
sourceName = JSON.parse(loader.source).join(', ')
|
||||
} else if (typeof loader.source === 'string') {
|
||||
sourceName = loader.source
|
||||
}
|
||||
}
|
||||
|
||||
// Return format: "LoaderName (sourceName)" or just "LoaderName" if no source
|
||||
return sourceName ? `${loaderName} (${sourceName})` : loaderName
|
||||
}
|
||||
|
||||
const getViewHeaderTitle = () => {
|
||||
const storeName = getSpecificDocumentStoreApi.data?.name || ''
|
||||
if (docId && currentLoader) {
|
||||
const loaderName = getLoaderDisplayName(currentLoader)
|
||||
return `${storeName} / ${loaderName}`
|
||||
}
|
||||
return storeName
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (saveVectorStoreConfigApi.data) {
|
||||
setLoading(false)
|
||||
@@ -411,6 +446,15 @@ const VectorStoreConfigure = () => {
|
||||
return
|
||||
}
|
||||
setDocumentStore(docStore)
|
||||
|
||||
// Find the current loader if docId is provided
|
||||
if (docId && docStore.loaders) {
|
||||
const loader = docStore.loaders.find((l) => l.id === docId)
|
||||
if (loader) {
|
||||
setCurrentLoader(loader)
|
||||
}
|
||||
}
|
||||
|
||||
if (docStore.embeddingConfig) {
|
||||
getEmbeddingNodeDetailsApi.request(docStore.embeddingConfig.name)
|
||||
}
|
||||
@@ -473,7 +517,7 @@ const VectorStoreConfigure = () => {
|
||||
<ViewHeader
|
||||
isBackButton={true}
|
||||
search={false}
|
||||
title={getSpecificDocumentStoreApi.data?.name}
|
||||
title={getViewHeaderTitle()}
|
||||
description='Configure Embeddings, Vector Store and Record Manager'
|
||||
onBack={() => navigate(-1)}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user