Feature/Add new doc store upsert and refresh API (#3556)

add new doc store upsert and refresh API
This commit is contained in:
Henry Heng 2024-11-25 15:47:13 +00:00 committed by GitHub
parent 36496b1611
commit a2c36b4447
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1424 additions and 803 deletions

View File

@ -305,6 +305,10 @@ paths:
type: string type: string
format: binary format: binary
description: Files to be uploaded description: Files to be uploaded
base64:
type: boolean
default: false
description: Return contents of the files in base64 format
required: required:
- files - files
required: true required: true
@ -618,171 +622,109 @@ paths:
'500': '500':
description: Internal server error description: Internal server error
/document-store/loader/preview: /document-store/upsert/{id}:
post: post:
tags: tags:
- document-store - document-store
security: security:
- bearerAuth: [] - bearerAuth: []
summary: Preview document chunks summary: Upsert new document to document store
description: Preview document chunks from loader description: Upsert new document to document store
operationId: previewChunking operationId: upsertDocument
parameters:
- in: path
name: id
required: true
schema:
type: string
format: uuid
description: Document store ID
requestBody: requestBody:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/DocumentStoreLoaderForPreview' $ref: '#/components/schemas/DocumentStoreLoaderForUpsert'
required: true multipart/form-data:
responses:
'200':
description: Successfully preview chunks
content:
application/json:
schema: schema:
type: object type: object
properties: properties:
chunks: files:
type: array type: array
items: items:
$ref: '#/components/schemas/Document' type: string
totalChunks: format: binary
type: integer description: Files to be uploaded
example: 10 loader:
previewChunkCount: type: string
type: integer nullable: true
example: 5 example: '{"name":"plainText","config":{"text":"why the sky is blue"}}'
'400': description: Loader configurations
description: Invalid request body splitter:
'500': type: string
description: Internal server error nullable: true
example: '{"name":"recursiveCharacterTextSplitter","config":{"chunkSize":2000}}'
/document-store/loader/process: description: Splitter configurations
post: embedding:
tags: type: string
- document-store nullable: true
security: example: '{"name":"openAIEmbeddings","config":{"modelName":"text-embedding-ada-002"}}'
- bearerAuth: [] description: Embedding configurations
summary: Process loading & chunking operation vectorStore:
description: Process loading & chunking operation of document from loader type: string
operationId: processChunking nullable: true
requestBody: example: '{"name":"faiss"}'
content: description: Vector Store configurations
application/json: recordManager:
schema: type: string
type: object nullable: true
example: '{"name":"postgresRecordManager"}'
description: Record Manager configurations
required: required:
- storeId - files
- id
properties:
storeId:
type: string
description: Document store ID
example: '603a7b51-ae7c-4b0a-8865-e454ed2f6766'
id:
type: string
description: Document loader ID. If your URL is /document-stores/{storeId}/{id}, then id is the last part of the URL
example: 'c427e569-b81a-469a-b14c-fa73dd5bae49'
required: true required: true
responses: responses:
'200': '200':
description: Successfully process chunking operation description: Successfully execute upsert operation
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/DocumentStoreFileChunkPagedResponse' $ref: '#/components/schemas/VectorUpsertResponse'
'400': '400':
description: Invalid request body description: Invalid request body
'500': '500':
description: Internal server error description: Internal server error
/document-store/vectorstore/save: /document-store/refresh/{id}:
post: post:
tags: tags:
- document-store - document-store
security: security:
- bearerAuth: [] - bearerAuth: []
summary: Save upsert configuration of document store summary: Re-process and upsert all documents in document store
description: Save upsert configuration of document store description: Re-process and upsert all existing documents in document store
operationId: saveVectorStoreConfig operationId: refreshDocument
parameters:
- in: path
name: id
required: true
schema:
type: string
format: uuid
description: Document store ID
requestBody: requestBody:
content: content:
application/json: application/json:
schema: schema:
type: object $ref: '#/components/schemas/DocumentStoreLoaderForRefresh'
required:
- storeId
properties:
storeId:
type: string
description: Document store ID
example: '603a7b51-ae7c-4b0a-8865-e454ed2f6766'
embeddingName:
type: string
description: Name of the embedding
example: 'openAIEmbeddings'
embeddingConfig:
type: object
description: Configuration of the embedding
example: { 'model': 'text-embedding-ada-002', 'credential': '1eba5808-c55b-4817-a285-b0c92846a7ad' }
vectorStoreName:
type: string
description: Name of the vector store
example: 'faiss'
vectorStoreConfig:
type: object
description: Configuration of the embedding
example: { 'basePath': './faiss' }
recordManagerName:
type: string
description: Name of the record manager
example: 'SQLiteRecordManager'
recordManagerConfig:
type: object
description: Configuration of the embedding
example: { 'databaseFilePath': './recordManager.db' }
required: true required: true
responses: responses:
'200': '200':
description: Successfully save upsert configuration of document store description: Successfully execute refresh operation
content: content:
application/json: application/json:
schema: type: array
$ref: '#/components/schemas/DocumentStore' items:
'400':
description: Invalid request body
'500':
description: Internal server error
/document-store/vectorstore/insert:
post:
tags:
- document-store
security:
- bearerAuth: []
summary: Upsert chunks from document store
description: Upsert chunks from document store using the saved configuration
operationId: insertIntoVectorStore
requestBody:
content:
application/json:
schema:
type: object
required:
- storeId
properties:
storeId:
type: string
description: Document store ID
example: '603a7b51-ae7c-4b0a-8865-e454ed2f6766'
required: true
responses:
'200':
description: Successfully save upsert configuration of document store
content:
application/json:
schema:
$ref: '#/components/schemas/VectorUpsertResponse' $ref: '#/components/schemas/VectorUpsertResponse'
'400': '400':
@ -2220,6 +2162,72 @@ components:
description: description:
type: string type: string
DocumentStoreLoaderForUpsert:
type: object
properties:
docId:
type: string
format: uuid
description: Document ID within the store. If provided, existing configuration from the document will be used for the new document
loader:
type: object
properties:
name:
type: string
example: plainText
description: Name of the loader (camelCase)
config:
type: object
description: Configuration for the loader
splitter:
type: object
properties:
name:
type: string
example: recursiveCharacterTextSplitter
description: Name of the text splitter (camelCase)
config:
type: object
description: Configuration for the text splitter
embedding:
type: object
properties:
name:
type: string
example: openAIEmbeddings
description: Name of the embedding generator (camelCase)
config:
type: object
description: Configuration for the embedding generator
vectorStore:
type: object
properties:
name:
type: string
example: faiss
description: Name of the vector store (camelCase)
config:
type: object
description: Configuration for the vector store
recordManager:
type: object
properties:
name:
type: string
example: postgresRecordManager
description: Name of the record manager (camelCase)
config:
type: object
description: Configuration for the record manager
DocumentStoreLoaderForRefresh:
type: object
properties:
items:
type: array
items:
$ref: '#/components/schemas/DocumentStoreLoaderForUpsert'
ChatMessageFeedback: ChatMessageFeedback:
type: object type: object
properties: properties:

View File

@ -26,7 +26,7 @@ class UnstructuredFile_DocumentLoaders implements INode {
constructor() { constructor() {
this.label = 'Unstructured File Loader' this.label = 'Unstructured File Loader'
this.name = 'unstructuredFileLoader' this.name = 'unstructuredFileLoader'
this.version = 3.0 this.version = 4.0
this.type = 'Document' this.type = 'Document'
this.icon = 'unstructured-file.svg' this.icon = 'unstructured-file.svg'
this.category = 'Document Loaders' this.category = 'Document Loaders'
@ -40,6 +40,7 @@ class UnstructuredFile_DocumentLoaders implements INode {
optional: true optional: true
} }
this.inputs = [ this.inputs = [
/** Deprecated
{ {
label: 'File Path', label: 'File Path',
name: 'filePath', name: 'filePath',
@ -49,6 +50,7 @@ class UnstructuredFile_DocumentLoaders implements INode {
warning: warning:
'Use the File Upload instead of File path. If file is uploaded, this path is ignored. Path will be deprecated in future releases.' 'Use the File Upload instead of File path. If file is uploaded, this path is ignored. Path will be deprecated in future releases.'
}, },
*/
{ {
label: 'Files Upload', label: 'Files Upload',
name: 'fileObject', name: 'fileObject',
@ -200,7 +202,7 @@ class UnstructuredFile_DocumentLoaders implements INode {
{ {
label: 'Hi-Res Model Name', label: 'Hi-Res Model Name',
name: 'hiResModelName', name: 'hiResModelName',
description: 'The name of the inference model used when strategy is hi_res. Default: detectron2_onnx.', description: 'The name of the inference model used when strategy is hi_res',
type: 'options', type: 'options',
options: [ options: [
{ {
@ -227,8 +229,7 @@ class UnstructuredFile_DocumentLoaders implements INode {
} }
], ],
optional: true, optional: true,
additionalParams: true, additionalParams: true
default: 'detectron2_onnx'
}, },
{ {
label: 'Chunking Strategy', label: 'Chunking Strategy',
@ -241,9 +242,21 @@ class UnstructuredFile_DocumentLoaders implements INode {
label: 'None', label: 'None',
name: 'None' name: 'None'
}, },
{
label: 'Basic',
name: 'basic'
},
{ {
label: 'By Title', label: 'By Title',
name: 'by_title' name: 'by_title'
},
{
label: 'By Page',
name: 'by_page'
},
{
label: 'By Similarity',
name: 'by_similarity'
} }
], ],
optional: true, optional: true,
@ -434,15 +447,15 @@ class UnstructuredFile_DocumentLoaders implements INode {
: ([] as SkipInferTableTypes[]) : ([] as SkipInferTableTypes[])
const hiResModelName = nodeData.inputs?.hiResModelName as HiResModelName const hiResModelName = nodeData.inputs?.hiResModelName as HiResModelName
const includePageBreaks = nodeData.inputs?.includePageBreaks as boolean const includePageBreaks = nodeData.inputs?.includePageBreaks as boolean
const chunkingStrategy = nodeData.inputs?.chunkingStrategy as 'None' | 'by_title' const chunkingStrategy = nodeData.inputs?.chunkingStrategy as string
const metadata = nodeData.inputs?.metadata const metadata = nodeData.inputs?.metadata
const sourceIdKey = (nodeData.inputs?.sourceIdKey as string) || 'source' const sourceIdKey = (nodeData.inputs?.sourceIdKey as string) || 'source'
const ocrLanguages = nodeData.inputs?.ocrLanguages ? JSON.parse(nodeData.inputs?.ocrLanguages as string) : ([] as string[]) const ocrLanguages = nodeData.inputs?.ocrLanguages ? JSON.parse(nodeData.inputs?.ocrLanguages as string) : ([] as string[])
const xmlKeepTags = nodeData.inputs?.xmlKeepTags as boolean const xmlKeepTags = nodeData.inputs?.xmlKeepTags as boolean
const multiPageSections = nodeData.inputs?.multiPageSections as boolean const multiPageSections = nodeData.inputs?.multiPageSections as boolean
const combineUnderNChars = nodeData.inputs?.combineUnderNChars as number const combineUnderNChars = nodeData.inputs?.combineUnderNChars as string
const newAfterNChars = nodeData.inputs?.newAfterNChars as number const newAfterNChars = nodeData.inputs?.newAfterNChars as string
const maxCharacters = nodeData.inputs?.maxCharacters as number const maxCharacters = nodeData.inputs?.maxCharacters as string
const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string
let omitMetadataKeys: string[] = [] let omitMetadataKeys: string[] = []
@ -471,10 +484,19 @@ class UnstructuredFile_DocumentLoaders implements INode {
chunkingStrategy, chunkingStrategy,
ocrLanguages, ocrLanguages,
xmlKeepTags, xmlKeepTags,
multiPageSections, multiPageSections
combineUnderNChars, }
newAfterNChars,
maxCharacters if (combineUnderNChars) {
obj.combineUnderNChars = parseInt(combineUnderNChars, 10)
}
if (newAfterNChars) {
obj.newAfterNChars = parseInt(newAfterNChars, 10)
}
if (maxCharacters) {
obj.maxCharacters = parseInt(maxCharacters, 10)
} }
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)

View File

@ -1,3 +1,4 @@
import { ICommonObject } from 'flowise-components'
import { DocumentStore } from './database/entities/DocumentStore' import { DocumentStore } from './database/entities/DocumentStore'
export enum DocumentStoreStatus { export enum DocumentStoreStatus {
@ -36,23 +37,25 @@ export interface IDocumentStoreFileChunk {
export interface IDocumentStoreFileChunkPagedResponse { export interface IDocumentStoreFileChunkPagedResponse {
chunks: IDocumentStoreFileChunk[] chunks: IDocumentStoreFileChunk[]
count: number count: number
characters: number
file?: IDocumentStoreLoader file?: IDocumentStoreLoader
currentPage: number currentPage: number
storeName: string storeName: string
description: string description: string
docId: string
} }
export interface IDocumentStoreLoader { export interface IDocumentStoreLoader {
id: string id?: string
loaderId: string loaderId?: string
loaderName: string loaderName?: string
loaderConfig: any // JSON string loaderConfig?: any // JSON string
splitterId: string splitterId?: string
splitterName: string splitterName?: string
splitterConfig: any // JSON string splitterConfig?: any // JSON string
totalChunks: number totalChunks?: number
totalChars: number totalChars?: number
status: DocumentStoreStatus status?: DocumentStoreStatus
storeId?: string storeId?: string
files?: IDocumentStoreLoaderFile[] files?: IDocumentStoreLoaderFile[]
source?: string source?: string
@ -60,9 +63,37 @@ export interface IDocumentStoreLoader {
} }
export interface IDocumentStoreLoaderForPreview extends IDocumentStoreLoader { export interface IDocumentStoreLoaderForPreview extends IDocumentStoreLoader {
rehydrated: boolean rehydrated?: boolean
preview: boolean preview?: boolean
previewChunkCount: number previewChunkCount?: number
}
export interface IDocumentStoreUpsertData {
docId: string
loader?: {
name: string
config: ICommonObject
}
splitter?: {
name: string
config: ICommonObject
}
vectorStore?: {
name: string
config: ICommonObject
}
embedding?: {
name: string
config: ICommonObject
}
recordManager?: {
name: string
config: ICommonObject
}
}
export interface IDocumentStoreRefreshData {
items: IDocumentStoreUpsertData[]
} }
export interface IDocumentStoreLoaderFile { export interface IDocumentStoreLoaderFile {
@ -79,6 +110,72 @@ export interface IDocumentStoreWhereUsed {
name: string name: string
} }
const getFileName = (fileBase64: string) => {
let fileNames = []
if (fileBase64.startsWith('FILE-STORAGE::')) {
const names = fileBase64.substring(14)
if (names.includes('[') && names.includes(']')) {
const files = JSON.parse(names)
return files.join(', ')
} else {
return fileBase64.substring(14)
}
}
if (fileBase64.startsWith('[') && fileBase64.endsWith(']')) {
const files = JSON.parse(fileBase64)
for (const file of files) {
const splitDataURI = file.split(',')
const filename = splitDataURI[splitDataURI.length - 1].split(':')[1]
fileNames.push(filename)
}
return fileNames.join(', ')
} else {
const splitDataURI = fileBase64.split(',')
const filename = splitDataURI[splitDataURI.length - 1].split(':')[1]
return filename
}
}
export const addLoaderSource = (loader: IDocumentStoreLoader, isGetFileNameOnly = false) => {
let source = 'None'
const handleUnstructuredFileLoader = (config: any, isGetFileNameOnly: boolean): string => {
if (config.fileObject) {
return isGetFileNameOnly ? getFileName(config.fileObject) : config.fileObject.replace('FILE-STORAGE::', '')
}
return config.filePath || 'None'
}
switch (loader.loaderId) {
case 'pdfFile':
case 'jsonFile':
case 'csvFile':
case 'file':
case 'jsonlinesFile':
case 'txtFile':
source = isGetFileNameOnly
? getFileName(loader.loaderConfig[loader.loaderId])
: loader.loaderConfig[loader.loaderId]?.replace('FILE-STORAGE::', '') || 'None'
break
case 'apiLoader':
source = loader.loaderConfig.url + ' (' + loader.loaderConfig.method + ')'
break
case 'cheerioWebScraper':
case 'playwrightWebScraper':
case 'puppeteerWebScraper':
source = loader.loaderConfig.url || 'None'
break
case 'unstructuredFileLoader':
source = handleUnstructuredFileLoader(loader.loaderConfig, isGetFileNameOnly)
break
default:
source = 'None'
break
}
return source
}
export class DocumentStoreDTO { export class DocumentStoreDTO {
id: string id: string
name: string name: string
@ -130,40 +227,9 @@ export class DocumentStoreDTO {
if (entity.loaders) { if (entity.loaders) {
documentStoreDTO.loaders = JSON.parse(entity.loaders) documentStoreDTO.loaders = JSON.parse(entity.loaders)
documentStoreDTO.loaders.map((loader) => { documentStoreDTO.loaders.map((loader) => {
documentStoreDTO.totalChars += loader.totalChars documentStoreDTO.totalChars += loader.totalChars || 0
documentStoreDTO.totalChunks += loader.totalChunks documentStoreDTO.totalChunks += loader.totalChunks || 0
switch (loader.loaderId) { loader.source = addLoaderSource(loader)
case 'pdfFile':
loader.source = loader.loaderConfig.pdfFile.replace('FILE-STORAGE::', '')
break
case 'apiLoader':
loader.source = loader.loaderConfig.url + ' (' + loader.loaderConfig.method + ')'
break
case 'cheerioWebScraper':
loader.source = loader.loaderConfig.url
break
case 'playwrightWebScraper':
loader.source = loader.loaderConfig.url
break
case 'puppeteerWebScraper':
loader.source = loader.loaderConfig.url
break
case 'jsonFile':
loader.source = loader.loaderConfig.jsonFile.replace('FILE-STORAGE::', '')
break
case 'docxFile':
loader.source = loader.loaderConfig.docxFile.replace('FILE-STORAGE::', '')
break
case 'textFile':
loader.source = loader.loaderConfig.txtFile.replace('FILE-STORAGE::', '')
break
case 'unstructuredFileLoader':
loader.source = loader.loaderConfig.filePath
break
default:
loader.source = 'None'
break
}
if (loader.status !== 'SYNC') { if (loader.status !== 'SYNC') {
documentStoreDTO.status = DocumentStoreStatus.STALE documentStoreDTO.status = DocumentStoreStatus.STALE
} }

View File

@ -4,6 +4,15 @@ import documentStoreService from '../../services/documentstore'
import { DocumentStore } from '../../database/entities/DocumentStore' import { DocumentStore } from '../../database/entities/DocumentStore'
import { InternalFlowiseError } from '../../errors/internalFlowiseError' import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { DocumentStoreDTO } from '../../Interface' import { DocumentStoreDTO } from '../../Interface'
import { getRateLimiter } from '../../utils/rateLimit'
const getRateLimiterMiddleware = async (req: Request, res: Response, next: NextFunction) => {
try {
return getRateLimiter(req, res, next)
} catch (error) {
next(error)
}
}
const createDocumentStore = async (req: Request, res: Response, next: NextFunction) => { const createDocumentStore = async (req: Request, res: Response, next: NextFunction) => {
try { try {
@ -160,16 +169,39 @@ const editDocumentStoreFileChunk = async (req: Request, res: Response, next: Nex
} }
} }
const processFileChunks = async (req: Request, res: Response, next: NextFunction) => { const saveProcessingLoader = async (req: Request, res: Response, next: NextFunction) => {
try { try {
if (typeof req.body === 'undefined') { if (typeof req.body === 'undefined') {
throw new InternalFlowiseError( throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED, StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.processFileChunks - body not provided!` `Error: documentStoreController.saveProcessingLoader - body not provided!`
) )
} }
const body = req.body const body = req.body
const apiResponse = await documentStoreService.processAndSaveChunks(body) const apiResponse = await documentStoreService.saveProcessingLoader(body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const processLoader = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.loaderId === 'undefined' || req.params.loaderId === '') {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.processLoader - loaderId not provided!`
)
}
if (typeof req.body === 'undefined') {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.processLoader - body not provided!`
)
}
const docLoaderId = req.params.loaderId
const body = req.body
const apiResponse = await documentStoreService.processLoader(body, docLoaderId)
return res.json(apiResponse) return res.json(apiResponse)
} catch (error) { } catch (error) {
next(error) next(error)
@ -342,6 +374,42 @@ const getRecordManagerProviders = async (req: Request, res: Response, next: Next
} }
} }
const upsertDocStoreMiddleware = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.upsertDocStoreMiddleware - storeId not provided!`
)
}
if (typeof req.body === 'undefined') {
throw new Error('Error: documentStoreController.upsertDocStoreMiddleware - body not provided!')
}
const body = req.body
const files = (req.files as Express.Multer.File[]) || []
const apiResponse = await documentStoreService.upsertDocStoreMiddleware(req.params.id, body, files)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const refreshDocStoreMiddleware = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new InternalFlowiseError(
StatusCodes.PRECONDITION_FAILED,
`Error: documentStoreController.refreshDocStoreMiddleware - storeId not provided!`
)
}
const body = req.body
const apiResponse = await documentStoreService.refreshDocStoreMiddleware(req.params.id, body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default { export default {
deleteDocumentStore, deleteDocumentStore,
createDocumentStore, createDocumentStore,
@ -350,7 +418,7 @@ export default {
getDocumentStoreById, getDocumentStoreById,
getDocumentStoreFileChunks, getDocumentStoreFileChunks,
updateDocumentStore, updateDocumentStore,
processFileChunks, processLoader,
previewFileChunks, previewFileChunks,
getDocumentLoaders, getDocumentLoaders,
deleteDocumentStoreFileChunk, deleteDocumentStoreFileChunk,
@ -362,5 +430,9 @@ export default {
saveVectorStoreConfig, saveVectorStoreConfig,
queryVectorStore, queryVectorStore,
deleteVectorStoreFromStore, deleteVectorStoreFromStore,
updateVectorStoreConfigOnly updateVectorStoreConfigOnly,
getRateLimiterMiddleware,
upsertDocStoreMiddleware,
refreshDocStoreMiddleware,
saveProcessingLoader
} }

View File

@ -1,6 +1,14 @@
import express from 'express' import express from 'express'
import documentStoreController from '../../controllers/documentstore' import documentStoreController from '../../controllers/documentstore'
import multer from 'multer'
import path from 'path'
const router = express.Router() const router = express.Router()
const upload = multer({ dest: `${path.join(__dirname, '..', '..', '..', 'uploads')}/` })
router.post(['/upsert/', '/upsert/:id'], upload.array('files'), documentStoreController.upsertDocStoreMiddleware)
router.post(['/refresh/', '/refresh/:id'], documentStoreController.refreshDocStoreMiddleware)
/** Document Store Routes */ /** Document Store Routes */
// Create document store // Create document store
@ -22,8 +30,10 @@ router.get('/components/loaders', documentStoreController.getDocumentLoaders)
router.delete('/loader/:id/:loaderId', documentStoreController.deleteLoaderFromDocumentStore) router.delete('/loader/:id/:loaderId', documentStoreController.deleteLoaderFromDocumentStore)
// chunking preview // chunking preview
router.post('/loader/preview', documentStoreController.previewFileChunks) router.post('/loader/preview', documentStoreController.previewFileChunks)
// saving process
router.post('/loader/save', documentStoreController.saveProcessingLoader)
// chunking process // chunking process
router.post('/loader/process', documentStoreController.processFileChunks) router.post('/loader/process/:loaderId', documentStoreController.processLoader)
/** Document Store - Loaders - Chunks */ /** Document Store - Loaders - Chunks */
// delete specific file chunk from the store // delete specific file chunk from the store

View File

@ -1,20 +1,28 @@
import { getRunningExpressApp } from '../../utils/getRunningExpressApp' import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { DocumentStore } from '../../database/entities/DocumentStore' import { DocumentStore } from '../../database/entities/DocumentStore'
import * as fs from 'fs'
import * as path from 'path'
import { import {
addArrayFilesToStorage,
addSingleFileToStorage, addSingleFileToStorage,
getFileFromStorage, getFileFromStorage,
ICommonObject, ICommonObject,
IDocument, IDocument,
mapExtToInputField,
mapMimeTypeToInputField,
removeFilesFromStorage, removeFilesFromStorage,
removeSpecificFileFromStorage removeSpecificFileFromStorage
} from 'flowise-components' } from 'flowise-components'
import { import {
addLoaderSource,
ChatType, ChatType,
DocumentStoreStatus, DocumentStoreStatus,
IDocumentStoreFileChunkPagedResponse, IDocumentStoreFileChunkPagedResponse,
IDocumentStoreLoader, IDocumentStoreLoader,
IDocumentStoreLoaderFile, IDocumentStoreLoaderFile,
IDocumentStoreLoaderForPreview, IDocumentStoreLoaderForPreview,
IDocumentStoreRefreshData,
IDocumentStoreUpsertData,
IDocumentStoreWhereUsed, IDocumentStoreWhereUsed,
INodeData INodeData
} from '../../Interface' } from '../../Interface'
@ -75,7 +83,7 @@ const getAllDocumentFileChunks = async () => {
} }
} }
const deleteLoaderFromDocumentStore = async (storeId: string, loaderId: string) => { const deleteLoaderFromDocumentStore = async (storeId: string, docId: string) => {
try { try {
const appServer = getRunningExpressApp() const appServer = getRunningExpressApp()
const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({ const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({
@ -88,12 +96,16 @@ const deleteLoaderFromDocumentStore = async (storeId: string, loaderId: string)
) )
} }
const existingLoaders = JSON.parse(entity.loaders) const existingLoaders = JSON.parse(entity.loaders)
const found = existingLoaders.find((uFile: IDocumentStoreLoader) => uFile.id === loaderId) const found = existingLoaders.find((loader: IDocumentStoreLoader) => loader.id === docId)
if (found) { if (found) {
if (found.files?.length) { if (found.files?.length) {
for (const file of found.files) { for (const file of found.files) {
if (file.name) { if (file.name) {
try {
await removeSpecificFileFromStorage(DOCUMENT_STORE_BASE_FOLDER, storeId, file.name) await removeSpecificFileFromStorage(DOCUMENT_STORE_BASE_FOLDER, storeId, file.name)
} catch (error) {
console.error(error)
}
} }
} }
} }
@ -169,7 +181,7 @@ const getUsedChatflowNames = async (entity: DocumentStore) => {
} }
// Get chunks for a specific loader or store // Get chunks for a specific loader or store
const getDocumentStoreFileChunks = async (storeId: string, fileId: string, pageNo: number = 1) => { const getDocumentStoreFileChunks = async (storeId: string, docId: string, pageNo: number = 1) => {
try { try {
const appServer = getRunningExpressApp() const appServer = getRunningExpressApp()
const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({ const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({
@ -184,29 +196,34 @@ const getDocumentStoreFileChunks = async (storeId: string, fileId: string, pageN
const loaders = JSON.parse(entity.loaders) const loaders = JSON.parse(entity.loaders)
let found: IDocumentStoreLoader | undefined let found: IDocumentStoreLoader | undefined
if (fileId !== 'all') { if (docId !== 'all') {
found = loaders.find((loader: IDocumentStoreLoader) => loader.id === fileId) found = loaders.find((loader: IDocumentStoreLoader) => loader.id === docId)
if (!found) { if (!found) {
throw new InternalFlowiseError( throw new InternalFlowiseError(
StatusCodes.NOT_FOUND, StatusCodes.NOT_FOUND,
`Error: documentStoreServices.getDocumentStoreById - Document file ${fileId} not found` `Error: documentStoreServices.getDocumentStoreById - Document loader ${docId} not found`
) )
} }
} }
let totalChars = 0
loaders.forEach((loader: IDocumentStoreLoader) => {
totalChars += loader.totalChars
})
if (found) { if (found) {
found.totalChars = totalChars found.id = docId
found.id = fileId
found.status = entity.status found.status = entity.status
} }
let characters = 0
if (docId === 'all') {
loaders.forEach((loader: IDocumentStoreLoader) => {
characters += loader.totalChars || 0
})
} else {
characters = found?.totalChars || 0
}
const PAGE_SIZE = 50 const PAGE_SIZE = 50
const skip = (pageNo - 1) * PAGE_SIZE const skip = (pageNo - 1) * PAGE_SIZE
const take = PAGE_SIZE const take = PAGE_SIZE
let whereCondition: any = { docId: fileId } let whereCondition: any = { docId: docId }
if (fileId === 'all') { if (docId === 'all') {
whereCondition = { storeId: storeId } whereCondition = { storeId: storeId }
} }
const count = await appServer.AppDataSource.getRepository(DocumentStoreFileChunk).count({ const count = await appServer.AppDataSource.getRepository(DocumentStoreFileChunk).count({
@ -222,7 +239,7 @@ const getDocumentStoreFileChunks = async (storeId: string, fileId: string, pageN
}) })
if (!chunksWithCount) { if (!chunksWithCount) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `File ${fileId} not found`) throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chunks with docId: ${docId} not found`)
} }
const response: IDocumentStoreFileChunkPagedResponse = { const response: IDocumentStoreFileChunkPagedResponse = {
@ -231,7 +248,9 @@ const getDocumentStoreFileChunks = async (storeId: string, fileId: string, pageN
file: found, file: found,
currentPage: pageNo, currentPage: pageNo,
storeName: entity.name, storeName: entity.name,
description: entity.description description: entity.description,
docId: docId,
characters
} }
return response return response
} catch (error) { } catch (error) {
@ -465,7 +484,7 @@ const _splitIntoChunks = async (data: IDocumentStoreLoaderForPreview) => {
try { try {
const appServer = getRunningExpressApp() const appServer = getRunningExpressApp()
let splitterInstance = null let splitterInstance = null
if (data.splitterConfig && Object.keys(data.splitterConfig).length > 0) { if (data.splitterId && data.splitterConfig && Object.keys(data.splitterConfig).length > 0) {
const nodeInstanceFilePath = appServer.nodesPool.componentNodes[data.splitterId].filePath as string const nodeInstanceFilePath = appServer.nodesPool.componentNodes[data.splitterId].filePath as string
const nodeModule = await import(nodeInstanceFilePath) const nodeModule = await import(nodeInstanceFilePath)
const newNodeInstance = new nodeModule.nodeClass() const newNodeInstance = new nodeModule.nodeClass()
@ -475,11 +494,12 @@ const _splitIntoChunks = async (data: IDocumentStoreLoaderForPreview) => {
} }
splitterInstance = await newNodeInstance.init(nodeData) splitterInstance = await newNodeInstance.init(nodeData)
} }
if (!data.loaderId) return []
const nodeInstanceFilePath = appServer.nodesPool.componentNodes[data.loaderId].filePath as string const nodeInstanceFilePath = appServer.nodesPool.componentNodes[data.loaderId].filePath as string
const nodeModule = await import(nodeInstanceFilePath) const nodeModule = await import(nodeInstanceFilePath)
// doc loader configs // doc loader configs
const nodeData = { const nodeData = {
credential: data.credential || undefined, credential: data.credential || data.loaderConfig['FLOWISE_CREDENTIAL_ID'] || undefined,
inputs: { ...data.loaderConfig, textSplitter: splitterInstance }, inputs: { ...data.loaderConfig, textSplitter: splitterInstance },
outputs: { output: 'document' } outputs: { output: 'document' }
} }
@ -568,9 +588,9 @@ const previewChunks = async (data: IDocumentStoreLoaderForPreview) => {
// if -1, return all chunks // if -1, return all chunks
if (data.previewChunkCount === -1) data.previewChunkCount = totalChunks if (data.previewChunkCount === -1) data.previewChunkCount = totalChunks
// return all docs if the user ask for more than we have // return all docs if the user ask for more than we have
if (totalChunks <= data.previewChunkCount) data.previewChunkCount = totalChunks if (totalChunks <= (data.previewChunkCount || 0)) data.previewChunkCount = totalChunks
// return only the first n chunks // return only the first n chunks
if (totalChunks > data.previewChunkCount) docs = docs.slice(0, data.previewChunkCount) if (totalChunks > (data.previewChunkCount || 0)) docs = docs.slice(0, data.previewChunkCount)
return { chunks: docs, totalChunks: totalChunks, previewChunkCount: data.previewChunkCount } return { chunks: docs, totalChunks: totalChunks, previewChunkCount: data.previewChunkCount }
} catch (error) { } catch (error) {
@ -581,7 +601,7 @@ const previewChunks = async (data: IDocumentStoreLoaderForPreview) => {
} }
} }
const processAndSaveChunks = async (data: IDocumentStoreLoaderForPreview) => { const saveProcessingLoader = async (data: IDocumentStoreLoaderForPreview): Promise<IDocumentStoreLoader> => {
try { try {
const appServer = getRunningExpressApp() const appServer = getRunningExpressApp()
const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({ const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({
@ -590,14 +610,14 @@ const processAndSaveChunks = async (data: IDocumentStoreLoaderForPreview) => {
if (!entity) { if (!entity) {
throw new InternalFlowiseError( throw new InternalFlowiseError(
StatusCodes.NOT_FOUND, StatusCodes.NOT_FOUND,
`Error: documentStoreServices.processAndSaveChunks - Document store ${data.storeId} not found` `Error: documentStoreServices.saveProcessingLoader - Document store ${data.storeId} not found`
) )
} }
const existingLoaders = JSON.parse(entity.loaders) const existingLoaders = JSON.parse(entity.loaders)
const newLoaderId = data.id ?? uuidv4() const newDocLoaderId = data.id ?? uuidv4()
const found = existingLoaders.find((ldr: IDocumentStoreLoader) => ldr.id === newLoaderId) const found = existingLoaders.find((ldr: IDocumentStoreLoader) => ldr.id === newDocLoaderId)
if (found) { if (found) {
const foundIndex = existingLoaders.findIndex((ldr: IDocumentStoreLoader) => ldr.id === newLoaderId) const foundIndex = existingLoaders.findIndex((ldr: IDocumentStoreLoader) => ldr.id === newDocLoaderId)
if (!data.loaderId) data.loaderId = found.loaderId if (!data.loaderId) data.loaderId = found.loaderId
if (!data.loaderName) data.loaderName = found.loaderName if (!data.loaderName) data.loaderName = found.loaderName
@ -629,7 +649,7 @@ const processAndSaveChunks = async (data: IDocumentStoreLoaderForPreview) => {
entity.loaders = JSON.stringify(existingLoaders) entity.loaders = JSON.stringify(existingLoaders)
} else { } else {
let loader: IDocumentStoreLoader = { let loader: IDocumentStoreLoader = {
id: newLoaderId, id: newDocLoaderId,
loaderId: data.loaderId, loaderId: data.loaderId,
loaderName: data.loaderName, loaderName: data.loaderName,
loaderConfig: data.loaderConfig, loaderConfig: data.loaderConfig,
@ -647,13 +667,40 @@ const processAndSaveChunks = async (data: IDocumentStoreLoaderForPreview) => {
entity.loaders = JSON.stringify(existingLoaders) entity.loaders = JSON.stringify(existingLoaders)
} }
await appServer.AppDataSource.getRepository(DocumentStore).save(entity) await appServer.AppDataSource.getRepository(DocumentStore).save(entity)
// this method will run async, will have to be moved to a worker thread const newLoaders = JSON.parse(entity.loaders)
_saveChunksToStorage(data, entity, newLoaderId).then(() => {}) const newLoader = newLoaders.find((ldr: IDocumentStoreLoader) => ldr.id === newDocLoaderId)
return getDocumentStoreFileChunks(data.storeId as string, newLoaderId) if (!newLoader) {
throw new Error(`Loader ${newDocLoaderId} not found`)
}
newLoader.source = addLoaderSource(newLoader, true)
return newLoader
} catch (error) { } catch (error) {
throw new InternalFlowiseError( throw new InternalFlowiseError(
StatusCodes.INTERNAL_SERVER_ERROR, StatusCodes.INTERNAL_SERVER_ERROR,
`Error: documentStoreServices.processAndSaveChunks - ${getErrorMessage(error)}` `Error: documentStoreServices.saveProcessingLoader - ${getErrorMessage(error)}`
)
}
}
const processLoader = async (data: IDocumentStoreLoaderForPreview, docLoaderId: string) => {
try {
const appServer = getRunningExpressApp()
const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({
id: data.storeId
})
if (!entity) {
throw new InternalFlowiseError(
StatusCodes.NOT_FOUND,
`Error: documentStoreServices.processLoader - Document store ${data.storeId} not found`
)
}
// this method will run async, will have to be moved to a worker thread
await _saveChunksToStorage(data, entity, docLoaderId)
return getDocumentStoreFileChunks(data.storeId as string, docLoaderId)
} catch (error) {
throw new InternalFlowiseError(
StatusCodes.INTERNAL_SERVER_ERROR,
`Error: documentStoreServices.processLoader - ${getErrorMessage(error)}`
) )
} }
} }
@ -665,8 +712,10 @@ const _saveChunksToStorage = async (data: IDocumentStoreLoaderForPreview, entity
const appServer = getRunningExpressApp() const appServer = getRunningExpressApp()
//step 1: restore the full paths, if any //step 1: restore the full paths, if any
await _normalizeFilePaths(data, entity) await _normalizeFilePaths(data, entity)
//step 2: split the file into chunks //step 2: split the file into chunks
previewChunks(data).then(async (response) => { const response = await previewChunks(data)
//step 3: remove all files associated with the loader //step 3: remove all files associated with the loader
const existingLoaders = JSON.parse(entity.loaders) const existingLoaders = JSON.parse(entity.loaders)
const loader = existingLoaders.find((ldr: IDocumentStoreLoader) => ldr.id === newLoaderId) const loader = existingLoaders.find((ldr: IDocumentStoreLoader) => ldr.id === newLoaderId)
@ -677,12 +726,17 @@ const _saveChunksToStorage = async (data: IDocumentStoreLoaderForPreview, entity
if (!data.rehydrated) { if (!data.rehydrated) {
if (loader.files) { if (loader.files) {
loader.files.map(async (file: IDocumentStoreLoaderFile) => { loader.files.map(async (file: IDocumentStoreLoaderFile) => {
try {
await removeSpecificFileFromStorage(DOCUMENT_STORE_BASE_FOLDER, entity.id, file.name) await removeSpecificFileFromStorage(DOCUMENT_STORE_BASE_FOLDER, entity.id, file.name)
} catch (error) {
console.error(error)
}
}) })
} }
} }
} }
} }
//step 4: save new file to storage //step 4: save new file to storage
let filesWithMetadata = [] let filesWithMetadata = []
const keys = Object.getOwnPropertyNames(data.loaderConfig) const keys = Object.getOwnPropertyNames(data.loaderConfig)
@ -715,15 +769,18 @@ const _saveChunksToStorage = async (data: IDocumentStoreLoaderForPreview, entity
break break
} }
} }
//step 5: update with the new files and loaderConfig //step 5: update with the new files and loaderConfig
if (filesWithMetadata.length > 0) { if (filesWithMetadata.length > 0) {
loader.loaderConfig = data.loaderConfig loader.loaderConfig = data.loaderConfig
loader.files = filesWithMetadata loader.files = filesWithMetadata
} }
//step 6: update the loaders with the new loaderConfig //step 6: update the loaders with the new loaderConfig
if (data.id) { if (data.id) {
existingLoaders.push(loader) existingLoaders.push(loader)
} }
//step 7: remove all previous chunks //step 7: remove all previous chunks
await appServer.AppDataSource.getRepository(DocumentStoreFileChunk).delete({ docId: newLoaderId }) await appServer.AppDataSource.getRepository(DocumentStoreFileChunk).delete({ docId: newLoaderId })
if (response.chunks) { if (response.chunks) {
@ -755,10 +812,11 @@ const _saveChunksToStorage = async (data: IDocumentStoreLoaderForPreview, entity
const allSynced = existingLoaders.every((ldr: IDocumentStoreLoader) => ldr.status === 'SYNC') const allSynced = existingLoaders.every((ldr: IDocumentStoreLoader) => ldr.status === 'SYNC')
entity.status = allSynced ? DocumentStoreStatus.SYNC : DocumentStoreStatus.STALE entity.status = allSynced ? DocumentStoreStatus.SYNC : DocumentStoreStatus.STALE
entity.loaders = JSON.stringify(existingLoaders) entity.loaders = JSON.stringify(existingLoaders)
//step 9: update the entity in the database //step 9: update the entity in the database
await appServer.AppDataSource.getRepository(DocumentStore).save(entity) await appServer.AppDataSource.getRepository(DocumentStore).save(entity)
return return
})
} catch (error) { } catch (error) {
throw new InternalFlowiseError( throw new InternalFlowiseError(
StatusCodes.INTERNAL_SERVER_ERROR, StatusCodes.INTERNAL_SERVER_ERROR,
@ -960,11 +1018,16 @@ const _insertIntoVectorStoreWorkerThread = async (data: ICommonObject) => {
// Get Vector Store Node Data // Get Vector Store Node Data
const vStoreNodeData = _createVectorStoreNodeData(appServer, data, embeddingObj, recordManagerObj) const vStoreNodeData = _createVectorStoreNodeData(appServer, data, embeddingObj, recordManagerObj)
// Prepare docs for upserting // Prepare docs for upserting
const chunks = await appServer.AppDataSource.getRepository(DocumentStoreFileChunk).find({ const filterOptions: ICommonObject = {
where: {
storeId: data.storeId storeId: data.storeId
} }
if (data.docId) {
filterOptions['docId'] = data.docId
}
const chunks = await appServer.AppDataSource.getRepository(DocumentStoreFileChunk).find({
where: filterOptions
}) })
const docs: Document[] = chunks.map((chunk: DocumentStoreFileChunk) => { const docs: Document[] = chunks.map((chunk: DocumentStoreFileChunk) => {
return new Document({ return new Document({
@ -1248,6 +1311,263 @@ const _createVectorStoreObject = async (
return vStoreNodeInstance return vStoreNodeInstance
} }
const upsertDocStoreMiddleware = async (
storeId: string,
data: IDocumentStoreUpsertData,
files: Express.Multer.File[] = [],
isRefreshExisting = false
) => {
const appServer = getRunningExpressApp()
const docId = data.docId
const newLoader = typeof data.loader === 'string' ? JSON.parse(data.loader) : data.loader
const newSplitter = typeof data.splitter === 'string' ? JSON.parse(data.splitter) : data.splitter
const newVectorStore = typeof data.vectorStore === 'string' ? JSON.parse(data.vectorStore) : data.vectorStore
const newEmbedding = typeof data.embedding === 'string' ? JSON.parse(data.embedding) : data.embedding
const newRecordManager = typeof data.recordManager === 'string' ? JSON.parse(data.recordManager) : data.recordManager
const getComponentLabelFromName = (nodeName: string) => {
const component = Object.values(appServer.nodesPool.componentNodes).find((node) => node.name === nodeName)
return component?.label || ''
}
let loaderName = ''
let loaderId = ''
let loaderConfig: ICommonObject = {}
let splitterName = ''
let splitterId = ''
let splitterConfig: ICommonObject = {}
let vectorStoreName = ''
let vectorStoreConfig: ICommonObject = {}
let embeddingName = ''
let embeddingConfig: ICommonObject = {}
let recordManagerName = ''
let recordManagerConfig: ICommonObject = {}
// Step 1: Get existing loader
if (docId) {
const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({ id: storeId })
if (!entity) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Document store ${storeId} not found`)
}
const loaders = JSON.parse(entity.loaders)
const loader = loaders.find((ldr: IDocumentStoreLoader) => ldr.id === docId)
if (!loader) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Document loader ${docId} not found`)
}
// Loader
loaderName = loader.loaderName
loaderId = loader.loaderId
loaderConfig = {
...loaderConfig,
...loader?.loaderConfig
}
// Splitter
splitterName = loader.splitterName
splitterId = loader.splitterId
splitterConfig = {
...splitterConfig,
...loader?.splitterConfig
}
// Vector Store
vectorStoreName = JSON.parse(entity.vectorStoreConfig || '{}')?.name
vectorStoreConfig = JSON.parse(entity.vectorStoreConfig || '{}')?.config
// Embedding
embeddingName = JSON.parse(entity.embeddingConfig || '{}')?.name
embeddingConfig = JSON.parse(entity.embeddingConfig || '{}')?.config
// Record Manager
recordManagerName = JSON.parse(entity.recordManagerConfig || '{}')?.name
recordManagerConfig = JSON.parse(entity.recordManagerConfig || '{}')?.config
}
// Step 2: Replace with new values
loaderName = newLoader?.name ? getComponentLabelFromName(newLoader?.name) : loaderName
loaderId = newLoader?.name || loaderId
loaderConfig = {
...loaderConfig,
...newLoader?.config
}
splitterName = newSplitter?.name ? getComponentLabelFromName(newSplitter?.name) : splitterName
splitterId = newSplitter?.name || splitterId
splitterConfig = {
...splitterConfig,
...newSplitter?.config
}
vectorStoreName = newVectorStore?.name || vectorStoreName
vectorStoreConfig = {
...vectorStoreConfig,
...newVectorStore?.config
}
embeddingName = newEmbedding?.name || embeddingName
embeddingConfig = {
...embeddingConfig,
...newEmbedding?.config
}
recordManagerName = newRecordManager?.name || recordManagerName
recordManagerConfig = {
recordManagerConfig,
...newRecordManager?.config
}
// Step 3: Replace with files
if (files.length) {
const filesLoaderConfig: ICommonObject = {}
for (const file of files) {
const fileNames: string[] = []
const fileBuffer = fs.readFileSync(file.path)
// Address file name with special characters: https://github.com/expressjs/multer/issues/1104
file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8')
try {
await addArrayFilesToStorage(file.mimetype, fileBuffer, file.originalname, fileNames, DOCUMENT_STORE_BASE_FOLDER, storeId)
} catch (error) {
continue
}
const mimePrefix = 'data:' + file.mimetype + ';base64'
const storagePath = mimePrefix + ',' + fileBuffer.toString('base64') + `,filename:${file.originalname}`
const fileInputFieldFromMimeType = mapMimeTypeToInputField(file.mimetype)
const fileExtension = path.extname(file.originalname)
const fileInputFieldFromExt = mapExtToInputField(fileExtension)
let fileInputField = 'txtFile'
if (fileInputFieldFromExt !== 'txtFile') {
fileInputField = fileInputFieldFromExt
} else if (fileInputFieldFromMimeType !== 'txtFile') {
fileInputField = fileInputFieldFromExt
}
if (loaderId === 'unstructuredFileLoader') {
fileInputField = 'fileObject'
}
if (filesLoaderConfig[fileInputField]) {
const existingFileInputFieldArray = JSON.parse(filesLoaderConfig[fileInputField])
const newFileInputFieldArray = [storagePath]
const updatedFieldArray = existingFileInputFieldArray.concat(newFileInputFieldArray)
filesLoaderConfig[fileInputField] = JSON.stringify(updatedFieldArray)
} else {
filesLoaderConfig[fileInputField] = JSON.stringify([storagePath])
}
fs.unlinkSync(file.path)
}
loaderConfig = {
...loaderConfig,
...filesLoaderConfig
}
}
// Step 4: Verification for must have components
if (!loaderName || !loaderId || !loaderConfig) {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Loader not configured`)
}
if (!vectorStoreName || !vectorStoreConfig) {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Vector store not configured`)
}
if (!embeddingName || !embeddingConfig) {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Embedding not configured`)
}
// Step 5: Process & Upsert
const processData: IDocumentStoreLoaderForPreview = {
storeId,
loaderId,
loaderName,
loaderConfig,
splitterId,
splitterName,
splitterConfig
}
if (isRefreshExisting) {
processData.id = docId
}
try {
const newLoader = await saveProcessingLoader(processData)
const result = await processLoader(processData, newLoader.id || '')
const newDocId = result.docId
const insertData = {
storeId,
docId: newDocId,
vectorStoreName,
vectorStoreConfig,
embeddingName,
embeddingConfig,
recordManagerName,
recordManagerConfig
}
const res = await insertIntoVectorStore(insertData)
res.docId = newDocId
return res
} catch (error) {
throw new InternalFlowiseError(
StatusCodes.INTERNAL_SERVER_ERROR,
`Error: documentStoreServices.upsertDocStoreMiddleware - ${getErrorMessage(error)}`
)
}
}
const refreshDocStoreMiddleware = async (storeId: string, data?: IDocumentStoreRefreshData) => {
const appServer = getRunningExpressApp()
try {
const results = []
let totalItems: IDocumentStoreUpsertData[] = []
if (!data || !data.items || data.items.length === 0) {
const entity = await appServer.AppDataSource.getRepository(DocumentStore).findOneBy({ id: storeId })
if (!entity) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Document store ${storeId} not found`)
}
const loaders = JSON.parse(entity.loaders)
totalItems = loaders.map((ldr: IDocumentStoreLoader) => {
return {
docId: ldr.id
}
})
} else {
totalItems = data.items
}
for (const item of totalItems) {
const res = await upsertDocStoreMiddleware(storeId, item, [], true)
results.push(res)
}
return results
} catch (error) {
throw new InternalFlowiseError(
StatusCodes.INTERNAL_SERVER_ERROR,
`Error: documentStoreServices.refreshDocStoreMiddleware - ${getErrorMessage(error)}`
)
}
}
export default { export default {
updateDocumentStoreUsage, updateDocumentStoreUsage,
deleteDocumentStore, deleteDocumentStore,
@ -1260,7 +1580,8 @@ export default {
getDocumentStoreFileChunks, getDocumentStoreFileChunks,
updateDocumentStore, updateDocumentStore,
previewChunks, previewChunks,
processAndSaveChunks, saveProcessingLoader,
processLoader,
deleteDocumentStoreFileChunk, deleteDocumentStoreFileChunk,
editDocumentStoreFileChunk, editDocumentStoreFileChunk,
getDocumentLoaders, getDocumentLoaders,
@ -1271,5 +1592,7 @@ export default {
saveVectorStoreConfig, saveVectorStoreConfig,
queryVectorStore, queryVectorStore,
deleteVectorStoreFromStore, deleteVectorStoreFromStore,
updateVectorStoreConfigOnly updateVectorStoreConfigOnly,
upsertDocStoreMiddleware,
refreshDocStoreMiddleware
} }

View File

@ -39,6 +39,7 @@ export const createFileAttachment = async (req: Request) => {
const files = (req.files as Express.Multer.File[]) || [] const files = (req.files as Express.Multer.File[]) || []
const fileAttachments = [] const fileAttachments = []
if (files.length) { if (files.length) {
const isBase64 = req.body.base64
for (const file of files) { for (const file of files) {
const fileBuffer = fs.readFileSync(file.path) const fileBuffer = fs.readFileSync(file.path)
const fileNames: string[] = [] const fileNames: string[] = []
@ -70,13 +71,21 @@ export const createFileAttachment = async (req: Request) => {
[fileInputField]: storagePath [fileInputField]: storagePath
} }
} }
let content = ''
if (isBase64) {
content = fileBuffer.toString('base64')
} else {
const documents: IDocument[] = await fileLoaderNodeInstance.init(nodeData, '', options) const documents: IDocument[] = await fileLoaderNodeInstance.init(nodeData, '', options)
const pageContents = documents.map((doc) => doc.pageContent).join('\n') content = documents.map((doc) => doc.pageContent).join('\n')
}
fileAttachments.push({ fileAttachments.push({
name: file.originalname, name: file.originalname,
mimeType: file.mimetype, mimeType: file.mimetype,
size: file.size, size: file.size,
content: pageContents content
}) })
} catch (error) { } catch (error) {
throw new Error(`Failed operation: createFileAttachment - ${getErrorMessage(error)}`) throw new Error(`Failed operation: createFileAttachment - ${getErrorMessage(error)}`)

View File

@ -14,7 +14,9 @@ const editChunkFromStore = (storeId, loaderId, chunkId, body) =>
const getFileChunks = (storeId, fileId, pageNo) => client.get(`/document-store/chunks/${storeId}/${fileId}/${pageNo}`) const getFileChunks = (storeId, fileId, pageNo) => client.get(`/document-store/chunks/${storeId}/${fileId}/${pageNo}`)
const previewChunks = (body) => client.post('/document-store/loader/preview', body) const previewChunks = (body) => client.post('/document-store/loader/preview', body)
const processChunks = (body) => client.post(`/document-store/loader/process`, body) const processLoader = (body, loaderId) => client.post(`/document-store/loader/process/${loaderId}`, body)
const saveProcessingLoader = (body) => client.post(`/document-store/loader/save`, body)
const refreshLoader = (storeId) => client.post(`/document-store/refresh/${storeId}`)
const insertIntoVectorStore = (body) => client.post(`/document-store/vectorstore/insert`, body) const insertIntoVectorStore = (body) => client.post(`/document-store/vectorstore/insert`, body)
const saveVectorStoreConfig = (body) => client.post(`/document-store/vectorstore/save`, body) const saveVectorStoreConfig = (body) => client.post(`/document-store/vectorstore/save`, body)
@ -33,7 +35,7 @@ export default {
getFileChunks, getFileChunks,
updateDocumentStore, updateDocumentStore,
previewChunks, previewChunks,
processChunks, processLoader,
getDocumentLoaders, getDocumentLoaders,
deleteChunkFromStore, deleteChunkFromStore,
editChunkFromStore, editChunkFromStore,
@ -45,5 +47,7 @@ export default {
saveVectorStoreConfig, saveVectorStoreConfig,
queryVectorStore, queryVectorStore,
deleteVectorStoreDataFromStore, deleteVectorStoreDataFromStore,
updateVectorStoreConfig updateVectorStoreConfig,
saveProcessingLoader,
refreshLoader
} }

View File

@ -98,6 +98,10 @@ const MainRoutes = {
path: '/document-stores/vector/:id', path: '/document-stores/vector/:id',
element: <VectorStoreConfigure /> element: <VectorStoreConfigure />
}, },
{
path: '/document-stores/vector/:id/:docId',
element: <VectorStoreConfigure />
},
{ {
path: '/document-stores/query/:id', path: '/document-stores/query/:id',
element: <VectorStoreQuery /> element: <VectorStoreQuery />

View File

@ -60,7 +60,8 @@ const DocumentStoreCard = ({ data, images, onClick }) => {
WebkitBoxOrient: 'vertical', WebkitBoxOrient: 'vertical',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
overflow: 'hidden', overflow: 'hidden',
flex: 1 flex: 1,
mr: 1
}} }}
> >
{data.name} {data.name}

View File

@ -35,6 +35,8 @@ import ErrorBoundary from '@/ErrorBoundary'
import { StyledButton } from '@/ui-component/button/StyledButton' import { StyledButton } from '@/ui-component/button/StyledButton'
import ViewHeader from '@/layout/MainLayout/ViewHeader' import ViewHeader from '@/layout/MainLayout/ViewHeader'
import DeleteDocStoreDialog from './DeleteDocStoreDialog' import DeleteDocStoreDialog from './DeleteDocStoreDialog'
import DocumentStoreStatus from '@/views/docstore/DocumentStoreStatus'
import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog'
// API // API
import documentsApi from '@/api/documentstore' import documentsApi from '@/api/documentstore'
@ -42,22 +44,18 @@ import documentsApi from '@/api/documentstore'
// Hooks // Hooks
import useApi from '@/hooks/useApi' import useApi from '@/hooks/useApi'
import useNotifier from '@/utils/useNotifier' import useNotifier from '@/utils/useNotifier'
import { getFileName } from '@/utils/genericHelper'
import useConfirm from '@/hooks/useConfirm'
// icons // icons
import { import { IconPlus, IconRefresh, IconX, IconVectorBezier2 } from '@tabler/icons-react'
IconPlus,
IconRefresh,
IconListDetails,
IconTrash,
IconX,
IconVectorBezier2,
IconRowInsertTop,
IconZoomScan
} from '@tabler/icons-react'
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
import FileDeleteIcon from '@mui/icons-material/Delete' import FileDeleteIcon from '@mui/icons-material/Delete'
import FileEditIcon from '@mui/icons-material/Edit' import FileEditIcon from '@mui/icons-material/Edit'
import FileChunksIcon from '@mui/icons-material/AppRegistration' 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 doc_store_details_emptySVG from '@/assets/images/doc_store_details_empty.svg' import doc_store_details_emptySVG from '@/assets/images/doc_store_details_empty.svg'
// store // store
@ -127,6 +125,7 @@ const DocumentStoreDetails = () => {
const navigate = useNavigate() const navigate = useNavigate()
const dispatch = useDispatch() const dispatch = useDispatch()
useNotifier() useNotifier()
const { confirm } = useConfirm()
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
@ -144,6 +143,9 @@ const DocumentStoreDetails = () => {
const [showDeleteDocStoreDialog, setShowDeleteDocStoreDialog] = useState(false) const [showDeleteDocStoreDialog, setShowDeleteDocStoreDialog] = useState(false)
const [deleteDocStoreDialogProps, setDeleteDocStoreDialogProps] = useState({}) const [deleteDocStoreDialogProps, setDeleteDocStoreDialogProps] = useState({})
const [anchorEl, setAnchorEl] = useState(null)
const open = Boolean(anchorEl)
const URLpath = document.location.pathname.toString().split('/') const URLpath = document.location.pathname.toString().split('/')
const storeId = URLpath[URLpath.length - 1] === 'document-stores' ? '' : URLpath[URLpath.length - 1] const storeId = URLpath[URLpath.length - 1] === 'document-stores' ? '' : URLpath[URLpath.length - 1]
@ -212,9 +214,10 @@ const DocumentStoreDetails = () => {
} catch (error) { } catch (error) {
setBackdropLoading(false) setBackdropLoading(false)
setError(error) setError(error)
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
enqueueSnackbar({ enqueueSnackbar({
message: `Failed to delete loader: ${errorData}`, message: `Failed to delete Document Store: ${
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
}`,
options: { options: {
key: new Date().getTime() + Math.random(), key: new Date().getTime() + Math.random(),
variant: 'error', variant: 'error',
@ -249,9 +252,10 @@ const DocumentStoreDetails = () => {
} catch (error) { } catch (error) {
setError(error) setError(error)
setBackdropLoading(false) setBackdropLoading(false)
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
enqueueSnackbar({ enqueueSnackbar({
message: `Failed to delete loader: ${errorData}`, message: `Failed to delete Document Loader: ${
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
}`,
options: { options: {
key: new Date().getTime() + Math.random(), key: new Date().getTime() + Math.random(),
variant: 'error', variant: 'error',
@ -294,6 +298,55 @@ const DocumentStoreDetails = () => {
setShowDeleteDocStoreDialog(true) 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 onEditClicked = () => {
const data = { const data = {
name: documentStore.name, name: documentStore.name,
@ -316,6 +369,16 @@ const DocumentStoreDetails = () => {
getSpecificDocumentStore.request(storeId) getSpecificDocumentStore.request(storeId)
} }
const handleClick = (event) => {
event.preventDefault()
event.stopPropagation()
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
useEffect(() => { useEffect(() => {
getSpecificDocumentStore.request(storeId) getSpecificDocumentStore.request(storeId)
@ -358,85 +421,86 @@ const DocumentStoreDetails = () => {
onBack={() => navigate('/document-stores')} onBack={() => navigate('/document-stores')}
onEdit={() => onEditClicked()} onEdit={() => onEditClicked()}
> >
<IconButton {(documentStore?.status === 'STALE' || documentStore?.status === 'UPSERTING') && (
onClick={() => onStoreDelete(documentStore.vectorStoreConfig, documentStore.recordManagerConfig)} <IconButton onClick={onConfirm} size='small' color='primary' title='Refresh Document Store'>
size='small' <IconRefresh />
color='error'
title='Delete Document Store'
sx={{ mr: 2 }}
>
<IconTrash />
</IconButton> </IconButton>
)}
<StyledButton <StyledButton
variant='contained' variant='contained'
sx={{ borderRadius: 2, height: '100%', color: 'white' }} sx={{ ml: 2, minWidth: 200, borderRadius: 2, height: '100%', color: 'white' }}
startIcon={<IconPlus />} startIcon={<IconPlus />}
onClick={listLoaders} onClick={listLoaders}
> >
Add Document Loader Add Document Loader
</StyledButton> </StyledButton>
{(documentStore?.status === 'STALE' || documentStore?.status === 'UPSERTING') && (
<Button variant='outlined' sx={{ mr: 2 }} startIcon={<IconRefresh />} onClick={onConfirm}>
Refresh
</Button>
)}
{documentStore?.status === 'UPSERTING' && (
<Chip
variant='raised'
label='Upserting to Vector Store'
color='warning'
sx={{ borderRadius: 2, height: '100%' }}
/>
)}
{documentStore?.totalChunks > 0 && documentStore?.status !== 'UPSERTING' && (
<>
<Button <Button
variant='contained' id='document-store-header-action-button'
sx={{ aria-controls={open ? 'document-store-header-menu' : undefined}
borderRadius: 2, aria-haspopup='true'
height: '100%' aria-expanded={open ? 'true' : undefined}
}} variant='outlined'
disableElevation
color='secondary' color='secondary'
startIcon={<IconListDetails />} 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')} onClick={() => showStoredChunks('all')}
disableRipple
> >
View Chunks <FileChunksIcon />
</Button> View & Edit Chunks
<Button </MenuItem>
variant='contained' <MenuItem
sx={{ disabled={documentStore?.totalChunks <= 0 || documentStore?.status === 'UPSERTING'}
borderRadius: 2,
height: '100%',
backgroundImage: `linear-gradient(to right, #13547a, #2f9e91)`,
'&:hover': {
backgroundImage: `linear-gradient(to right, #0b3d5b, #1a8377)`
}
}}
startIcon={<IconRowInsertTop />}
onClick={() => showVectorStore(documentStore.id)} onClick={() => showVectorStore(documentStore.id)}
disableRipple
> >
Upsert Config <NoteAddIcon />
</Button> Upsert All Chunks
</> </MenuItem>
)} <MenuItem
{documentStore?.totalChunks > 0 && documentStore?.status === 'UPSERTED' && ( disabled={documentStore?.totalChunks <= 0 || documentStore?.status !== 'UPSERTED'}
<Button
variant='contained'
sx={{
borderRadius: 2,
height: '100%',
backgroundImage: `linear-gradient(to right, #3f5efb, #fc466b)`,
'&:hover': {
backgroundImage: `linear-gradient(to right, #2b4efb, #fe2752)`
}
}}
startIcon={<IconZoomScan />}
onClick={() => showVectorStoreQuery(documentStore.id)} onClick={() => showVectorStoreQuery(documentStore.id)}
disableRipple
> >
<SearchIcon />
Retrieval Query Retrieval Query
</Button> </MenuItem>
)} <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>
<Divider sx={{ my: 0.5 }} />
<MenuItem
onClick={() => onStoreDelete(documentStore.vectorStoreConfig, documentStore.recordManagerConfig)}
disableRipple
>
<FileDeleteIcon />
Delete
</MenuItem>
</StyledMenu>
</ViewHeader> </ViewHeader>
<DocumentStoreStatus status={documentStore?.status} />
{getSpecificDocumentStore.data?.whereUsed?.length > 0 && ( {getSpecificDocumentStore.data?.whereUsed?.length > 0 && (
<Stack flexDirection='row' sx={{ gap: 2, alignItems: 'center', flexWrap: 'wrap' }}> <Stack flexDirection='row' sx={{ gap: 2, alignItems: 'center', flexWrap: 'wrap' }}>
<div <div
@ -584,6 +648,9 @@ const DocumentStoreDetails = () => {
documentStore?.recordManagerConfig documentStore?.recordManagerConfig
) )
} }
onChunkUpsert={() =>
navigate(`/document-stores/vector/${documentStore.id}/${loader.id}`)
}
/> />
))} ))}
</> </>
@ -630,6 +697,7 @@ const DocumentStoreDetails = () => {
/> />
)} )}
{isBackdropLoading && <BackdropLoader open={isBackdropLoading} />} {isBackdropLoading && <BackdropLoader open={isBackdropLoading} />}
<ConfirmDialog />
</> </>
) )
} }
@ -649,6 +717,9 @@ function LoaderRow(props) {
} }
const formatSources = (source) => { const formatSources = (source) => {
if (source && typeof source === 'string' && source.includes('base64')) {
return getFileName(source)
}
if (source && typeof source === 'string' && source.startsWith('[') && source.endsWith(']')) { if (source && typeof source === 'string' && source.startsWith('[') && source.endsWith(']')) {
return JSON.parse(source).join(', ') return JSON.parse(source).join(', ')
} }
@ -710,6 +781,10 @@ function LoaderRow(props) {
<FileChunksIcon /> <FileChunksIcon />
View & Edit Chunks View & Edit Chunks
</MenuItem> </MenuItem>
<MenuItem onClick={props.onChunkUpsert} disableRipple>
<NoteAddIcon />
Upsert Chunks
</MenuItem>
<Divider sx={{ my: 0.5 }} /> <Divider sx={{ my: 0.5 }} />
<MenuItem onClick={props.onDeleteClick} disableRipple> <MenuItem onClick={props.onDeleteClick} disableRipple>
<FileDeleteIcon /> <FileDeleteIcon />
@ -730,6 +805,7 @@ LoaderRow.propTypes = {
theme: PropTypes.any, theme: PropTypes.any,
onViewChunksClick: PropTypes.func, onViewChunksClick: PropTypes.func,
onEditClick: PropTypes.func, onEditClick: PropTypes.func,
onDeleteClick: PropTypes.func onDeleteClick: PropTypes.func,
onChunkUpsert: PropTypes.func
} }
export default DocumentStoreDetails export default DocumentStoreDetails

View File

@ -10,22 +10,36 @@ const DocumentStoreStatus = ({ status, isTableView }) => {
switch (status) { switch (status) {
case 'STALE': case 'STALE':
return customization.isDarkMode return customization.isDarkMode
? [theme.palette.grey[400], theme.palette.grey[600], theme.palette.grey[700]] ? [theme.palette.grey[400], theme.palette.grey[600], theme.palette.grey[800]]
: [theme.palette.grey[300], theme.palette.grey[500], theme.palette.grey[700]] : [theme.palette.grey[300], theme.palette.grey[500], theme.palette.grey[700]]
case 'EMPTY': case 'EMPTY':
return ['#673ab7', '#673ab7', '#673ab7'] return customization.isDarkMode
? ['#4a148c', '#6a1b9a', '#ffffff'] // Deep Purple
: ['#d1c4e9', '#9575cd', '#673ab7']
case 'SYNCING': case 'SYNCING':
return customization.isDarkMode
? ['#ff6f00', '#ff8f00', '#ffffff'] // Amber
: ['#fff8e1', '#ffe57f', '#ffc107']
case 'UPSERTING': case 'UPSERTING':
return ['#fff8e1', '#ffe57f', '#ffc107'] return customization.isDarkMode
? ['#01579b', '#0277bd', '#ffffff'] // Light Blue
: ['#e1f5fe', '#4fc3f7', '#0288d1']
case 'SYNC': case 'SYNC':
return customization.isDarkMode
? ['#1b5e20', '#2e7d32', '#ffffff'] // Green
: ['#e8f5e9', '#81c784', '#43a047']
case 'UPSERTED': case 'UPSERTED':
return ['#cdf5d8', '#00e676', '#00c853'] return customization.isDarkMode
? ['#004d40', '#00695c', '#ffffff'] // Teal
: ['#e0f2f1', '#4db6ac', '#00897b']
case 'NEW': case 'NEW':
return ['#e3f2fd', '#2196f3', '#1e88e5'] return customization.isDarkMode
? ['#0d47a1', '#1565c0', '#ffffff'] // Blue
: ['#e3f2fd', '#64b5f6', '#1e88e5']
default: default:
return customization.isDarkMode return customization.isDarkMode
? [theme.palette.grey[300], theme.palette.grey[500], theme.palette.grey[700]] ? [theme.palette.grey[300], theme.palette.grey[500], theme.palette.grey[700]]
: [theme.palette.grey[300], theme.palette.grey[500], theme.palette.grey[700]] : [theme.palette.grey[200], theme.palette.grey[400], theme.palette.grey[600]]
} }
} }
@ -45,7 +59,8 @@ const DocumentStoreStatus = ({ status, isTableView }) => {
paddingTop: '3px', paddingTop: '3px',
paddingBottom: '3px', paddingBottom: '3px',
paddingLeft: '10px', paddingLeft: '10px',
paddingRight: '10px' paddingRight: '10px',
width: 'fit-content'
}} }}
> >
<div <div

View File

@ -186,9 +186,9 @@ const LoaderConfigPreviewChunks = () => {
setLoading(true) setLoading(true)
const config = prepareConfig() const config = prepareConfig()
try { try {
const processResp = await documentStoreApi.processChunks(config) const saveResp = await documentStoreApi.saveProcessingLoader(config)
setLoading(false) setLoading(false)
if (processResp.data) { if (saveResp.data) {
enqueueSnackbar({ enqueueSnackbar({
message: 'File submitted for processing. Redirecting to Document Store..', message: 'File submitted for processing. Redirecting to Document Store..',
options: { options: {
@ -201,6 +201,8 @@ const LoaderConfigPreviewChunks = () => {
) )
} }
}) })
// don't wait for the process to complete, redirect to document store
documentStoreApi.processLoader(config, saveResp.data?.id)
navigate('/document-stores/' + storeId) navigate('/document-stores/' + storeId)
} }
} catch (error) { } catch (error) {

View File

@ -310,7 +310,7 @@ const ShowStoredChunks = () => {
</div> </div>
<div style={{ marginRight: 20, display: 'flex', flexDirection: 'row', alignItems: 'center' }}> <div style={{ marginRight: 20, display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<IconLanguage style={{ marginRight: 10 }} size={20} /> <IconLanguage style={{ marginRight: 10 }} size={20} />
{getChunksApi.data?.file?.totalChars?.toLocaleString()} characters {getChunksApi.data?.characters?.toLocaleString()} characters
</div> </div>
</div> </div>
</div> </div>

View File

@ -50,6 +50,10 @@ const VectorStoreConfigure = () => {
useNotifier() useNotifier()
const customization = useSelector((state) => state.customization) const customization = useSelector((state) => state.customization)
const pathSegments = document.location.pathname.toString().split('/')
const storeId = pathSegments[3] || null
const docId = pathSegments[4] || null
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
@ -213,7 +217,8 @@ const VectorStoreConfigure = () => {
const prepareConfigData = () => { const prepareConfigData = () => {
const data = { const data = {
storeId: storeId storeId: storeId,
docId: docId
} }
// Set embedding config // Set embedding config
if (selectedEmbeddingsProvider.inputs) { if (selectedEmbeddingsProvider.inputs) {
@ -365,8 +370,6 @@ const VectorStoreConfigure = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [saveVectorStoreConfigApi.error]) }, [saveVectorStoreConfigApi.error])
const URLpath = document.location.pathname.toString().split('/')
const storeId = URLpath[URLpath.length - 1] === 'document-stores' ? '' : URLpath[URLpath.length - 1]
useEffect(() => { useEffect(() => {
getSpecificDocumentStoreApi.request(storeId) getSpecificDocumentStoreApi.request(storeId)
@ -432,6 +435,9 @@ const VectorStoreConfigure = () => {
{error ? ( {error ? (
<ErrorBoundary error={error} /> <ErrorBoundary error={error} />
) : ( ) : (
<>
{!storeId && <div></div>}
{storeId && (
<Stack flexDirection='column' sx={{ gap: 3 }}> <Stack flexDirection='column' sx={{ gap: 3 }}>
<ViewHeader <ViewHeader
isBackButton={true} isBackButton={true}
@ -470,7 +476,8 @@ const VectorStoreConfigure = () => {
Save Config Save Config
</Button> </Button>
)} )}
{Object.keys(selectedEmbeddingsProvider).length > 0 && Object.keys(selectedVectorStoreProvider).length > 0 && ( {Object.keys(selectedEmbeddingsProvider).length > 0 &&
Object.keys(selectedVectorStoreProvider).length > 0 && (
<Button <Button
variant='contained' variant='contained'
sx={{ sx={{
@ -847,6 +854,8 @@ const VectorStoreConfigure = () => {
</Grid> </Grid>
</Stack> </Stack>
)} )}
</>
)}
</MainCard> </MainCard>
{showEmbeddingsListDialog && ( {showEmbeddingsListDialog && (