mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 17:01:00 +03:00
Feat/add multer s3 (#3854)
* add multer s3 * add types multer s3 * update multer s3 implementation * Revert "update multer s3 implementation" This reverts commit 9a25bf57a93f77074bdb084921f0591dbe0b44e1. * update storage utils * update multer storage type on routes * revert getMulterStorage * revert getMulterStorage * update getmulterstorage * update getmulterstorage * update getmulterstorage
This commit is contained in:
@@ -91,6 +91,7 @@
|
||||
"moment": "^2.29.3",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"multer-s3": "^3.0.1",
|
||||
"mysql2": "^3.11.3",
|
||||
"openai": "^4.57.3",
|
||||
"pg": "^8.11.1",
|
||||
@@ -110,6 +111,7 @@
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/multer-s3": "^3.0.3",
|
||||
"@types/sanitize-html": "^2.9.5",
|
||||
"concurrently": "^7.1.0",
|
||||
"cypress": "^13.13.0",
|
||||
|
||||
@@ -143,7 +143,7 @@ const uploadFilesToAssistantVectorStore = async (req: Request, res: Response, ne
|
||||
// Address file name with special characters: https://github.com/expressjs/multer/issues/1104
|
||||
file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8')
|
||||
uploadFiles.push({
|
||||
filePath: file.path,
|
||||
filePath: file.path ?? file.key,
|
||||
fileName: file.originalname
|
||||
})
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ const uploadAssistantFiles = async (req: Request, res: Response, next: NextFunct
|
||||
// Address file name with special characters: https://github.com/expressjs/multer/issues/1104
|
||||
file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8')
|
||||
uploadFiles.push({
|
||||
filePath: file.path,
|
||||
filePath: file.path ?? file.key,
|
||||
fileName: file.originalname
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,6 +33,20 @@ declare global {
|
||||
interface Request {
|
||||
io?: Server
|
||||
}
|
||||
namespace Multer {
|
||||
interface File {
|
||||
bucket: string
|
||||
key: string
|
||||
acl: string
|
||||
contentType: string
|
||||
contentDisposition: null
|
||||
storageClass: string
|
||||
serverSideEncryption: null
|
||||
metadata: any
|
||||
location: string
|
||||
etag: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import express from 'express'
|
||||
import multer from 'multer'
|
||||
import attachmentsController from '../../controllers/attachments'
|
||||
import { getUploadPath } from '../../utils'
|
||||
import { getMulterStorage } from '../../utils'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
const upload = multer({ dest: getUploadPath() })
|
||||
|
||||
// CREATE
|
||||
router.post('/:chatflowId/:chatId', upload.array('files'), attachmentsController.createAttachment)
|
||||
router.post('/:chatflowId/:chatId', getMulterStorage().array('files'), attachmentsController.createAttachment)
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import express from 'express'
|
||||
import multer from 'multer'
|
||||
import { getUploadPath } from '../../utils'
|
||||
import documentStoreController from '../../controllers/documentstore'
|
||||
import { getMulterStorage } from '../../utils'
|
||||
|
||||
const router = express.Router()
|
||||
const upload = multer({ dest: getUploadPath() })
|
||||
|
||||
router.post(['/upsert/', '/upsert/:id'], upload.array('files'), documentStoreController.upsertDocStoreMiddleware)
|
||||
router.post(['/upsert/', '/upsert/:id'], getMulterStorage().array('files'), documentStoreController.upsertDocStoreMiddleware)
|
||||
|
||||
router.post(['/refresh/', '/refresh/:id'], documentStoreController.refreshDocStoreMiddleware)
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import express from 'express'
|
||||
import multer from 'multer'
|
||||
import openaiAssistantsController from '../../controllers/openai-assistants'
|
||||
import { getUploadPath } from '../../utils'
|
||||
import { getMulterStorage } from '../../utils'
|
||||
|
||||
const router = express.Router()
|
||||
const upload = multer({ dest: getUploadPath() })
|
||||
|
||||
router.post('/download/', openaiAssistantsController.getFileFromAssistant)
|
||||
router.post('/upload/', upload.array('files'), openaiAssistantsController.uploadAssistantFiles)
|
||||
router.post('/upload/', getMulterStorage().array('files'), openaiAssistantsController.uploadAssistantFiles)
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import express from 'express'
|
||||
import multer from 'multer'
|
||||
import openaiAssistantsVectorStoreController from '../../controllers/openai-assistants-vector-store'
|
||||
import { getUploadPath } from '../../utils'
|
||||
import { getMulterStorage } from '../../utils'
|
||||
|
||||
const router = express.Router()
|
||||
const upload = multer({ dest: getUploadPath() })
|
||||
|
||||
// CREATE
|
||||
router.post('/', openaiAssistantsVectorStoreController.createAssistantVectorStore)
|
||||
@@ -22,7 +20,7 @@ router.put(['/', '/:id'], openaiAssistantsVectorStoreController.updateAssistantV
|
||||
router.delete(['/', '/:id'], openaiAssistantsVectorStoreController.deleteAssistantVectorStore)
|
||||
|
||||
// POST
|
||||
router.post('/:id', upload.array('files'), openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore)
|
||||
router.post('/:id', getMulterStorage().array('files'), openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore)
|
||||
|
||||
// DELETE
|
||||
router.patch(['/', '/:id'], openaiAssistantsVectorStoreController.deleteFilesFromAssistantVectorStore)
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import express from 'express'
|
||||
import multer from 'multer'
|
||||
import predictionsController from '../../controllers/predictions'
|
||||
import { getUploadPath } from '../../utils'
|
||||
import { getMulterStorage } from '../../utils'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
const upload = multer({ dest: getUploadPath() })
|
||||
|
||||
// CREATE
|
||||
router.post(['/', '/:id'], upload.array('files'), predictionsController.getRateLimiterMiddleware, predictionsController.createPrediction)
|
||||
router.post(
|
||||
['/', '/:id'],
|
||||
getMulterStorage().array('files'),
|
||||
predictionsController.getRateLimiterMiddleware,
|
||||
predictionsController.createPrediction
|
||||
)
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import express from 'express'
|
||||
import multer from 'multer'
|
||||
import vectorsController from '../../controllers/vectors'
|
||||
import { getUploadPath } from '../../utils'
|
||||
import { getMulterStorage } from '../../utils'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
const upload = multer({ dest: getUploadPath() })
|
||||
|
||||
// CREATE
|
||||
router.post(
|
||||
['/upsert/', '/upsert/:id'],
|
||||
upload.array('files'),
|
||||
getMulterStorage().array('files'),
|
||||
vectorsController.getRateLimiterMiddleware,
|
||||
vectorsController.upsertVectorMiddleware
|
||||
)
|
||||
router.post(['/internal-upsert/', '/internal-upsert/:id'], upload.array('files'), vectorsController.createInternalUpsert)
|
||||
router.post(['/internal-upsert/', '/internal-upsert/:id'], getMulterStorage().array('files'), vectorsController.createInternalUpsert)
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||
import { DocumentStore } from '../../database/entities/DocumentStore'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import {
|
||||
addArrayFilesToStorage,
|
||||
addSingleFileToStorage,
|
||||
getFileFromStorage,
|
||||
getFileFromUpload,
|
||||
ICommonObject,
|
||||
IDocument,
|
||||
mapExtToInputField,
|
||||
mapMimeTypeToInputField,
|
||||
removeFilesFromStorage,
|
||||
removeSpecificFileFromStorage
|
||||
removeSpecificFileFromStorage,
|
||||
removeSpecificFileFromUpload
|
||||
} from 'flowise-components'
|
||||
import {
|
||||
addLoaderSource,
|
||||
@@ -1441,7 +1442,7 @@ const upsertDocStoreMiddleware = async (
|
||||
const filesLoaderConfig: ICommonObject = {}
|
||||
for (const file of files) {
|
||||
const fileNames: string[] = []
|
||||
const fileBuffer = fs.readFileSync(file.path)
|
||||
const fileBuffer = await getFileFromUpload(file.path ?? file.key)
|
||||
// Address file name with special characters: https://github.com/expressjs/multer/issues/1104
|
||||
file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8')
|
||||
|
||||
@@ -1481,7 +1482,7 @@ const upsertDocStoreMiddleware = async (
|
||||
filesLoaderConfig[fileInputField] = JSON.stringify([storagePath])
|
||||
}
|
||||
|
||||
fs.unlinkSync(file.path)
|
||||
await removeSpecificFileFromUpload(file.path ?? file.key)
|
||||
}
|
||||
|
||||
loaderConfig = {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import OpenAI from 'openai'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import fs from 'fs'
|
||||
import { Credential } from '../../database/entities/Credential'
|
||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { getErrorMessage } from '../../errors/utils'
|
||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||
import { decryptCredentialData } from '../../utils'
|
||||
import { getFileFromUpload, removeSpecificFileFromUpload } from 'flowise-components'
|
||||
|
||||
const getAssistantVectorStore = async (credentialId: string, vectorStoreId: string) => {
|
||||
try {
|
||||
@@ -178,13 +178,14 @@ const uploadFilesToAssistantVectorStore = async (
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
const uploadedFiles = []
|
||||
for (const file of files) {
|
||||
const toFile = await OpenAI.toFile(fs.readFileSync(file.filePath), file.fileName)
|
||||
const fileBuffer = await getFileFromUpload(file.filePath)
|
||||
const toFile = await OpenAI.toFile(fileBuffer, file.fileName)
|
||||
const createdFile = await openai.files.create({
|
||||
file: toFile,
|
||||
purpose: 'assistants'
|
||||
})
|
||||
uploadedFiles.push(createdFile)
|
||||
fs.unlinkSync(file.filePath)
|
||||
await removeSpecificFileFromUpload(file.filePath)
|
||||
}
|
||||
|
||||
const file_ids = [...uploadedFiles.map((file) => file.id)]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import OpenAI from 'openai'
|
||||
import fs from 'fs'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { decryptCredentialData } from '../../utils'
|
||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||
import { Credential } from '../../database/entities/Credential'
|
||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { getErrorMessage } from '../../errors/utils'
|
||||
import { getFileFromUpload, removeSpecificFileFromUpload } from 'flowise-components'
|
||||
|
||||
// ----------------------------------------
|
||||
// Assistants
|
||||
@@ -101,13 +101,14 @@ const uploadFilesToAssistant = async (credentialId: string, files: { filePath: s
|
||||
const uploadedFiles = []
|
||||
|
||||
for (const file of files) {
|
||||
const toFile = await OpenAI.toFile(fs.readFileSync(file.filePath), file.fileName)
|
||||
const fileBuffer = await getFileFromUpload(file.filePath)
|
||||
const toFile = await OpenAI.toFile(fileBuffer, file.fileName)
|
||||
const createdFile = await openai.files.create({
|
||||
file: toFile,
|
||||
purpose: 'assistants'
|
||||
})
|
||||
uploadedFiles.push(createdFile)
|
||||
fs.unlinkSync(file.filePath)
|
||||
await removeSpecificFileFromUpload(file.filePath)
|
||||
}
|
||||
|
||||
return uploadedFiles
|
||||
|
||||
@@ -9,7 +9,9 @@ import {
|
||||
mapMimeTypeToInputField,
|
||||
mapExtToInputField,
|
||||
generateFollowUpPrompts,
|
||||
IServerSideEventStreamer
|
||||
IServerSideEventStreamer,
|
||||
getFileFromUpload,
|
||||
removeSpecificFileFromUpload
|
||||
} from 'flowise-components'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import {
|
||||
@@ -49,7 +51,6 @@ import { validateChatflowAPIKey } from './validateKey'
|
||||
import { databaseEntities } from '.'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { omit } from 'lodash'
|
||||
import * as fs from 'fs'
|
||||
import logger from './logger'
|
||||
import { utilAddChatMessage } from './addChatMesage'
|
||||
import { buildAgentGraph } from './buildAgentGraph'
|
||||
@@ -162,7 +163,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||
const overrideConfig: ICommonObject = { ...req.body }
|
||||
const fileNames: string[] = []
|
||||
for (const file of files) {
|
||||
const fileBuffer = fs.readFileSync(file.path)
|
||||
const fileBuffer = await getFileFromUpload(file.path ?? file.key)
|
||||
// Address file name with special characters: https://github.com/expressjs/multer/issues/1104
|
||||
file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8')
|
||||
const storagePath = await addArrayFilesToStorage(file.mimetype, fileBuffer, file.originalname, fileNames, chatflowid)
|
||||
@@ -195,7 +196,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||
overrideConfig[fileInputField] = storagePath
|
||||
}
|
||||
|
||||
fs.unlinkSync(file.path)
|
||||
await removeSpecificFileFromUpload(file.path ?? file.key)
|
||||
}
|
||||
if (overrideConfig.vars && typeof overrideConfig.vars === 'string') {
|
||||
overrideConfig.vars = JSON.parse(overrideConfig.vars)
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { Request } from 'express'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import { addArrayFilesToStorage, IDocument, mapExtToInputField, mapMimeTypeToInputField } from 'flowise-components'
|
||||
import {
|
||||
addArrayFilesToStorage,
|
||||
getFileFromUpload,
|
||||
IDocument,
|
||||
mapExtToInputField,
|
||||
mapMimeTypeToInputField,
|
||||
removeSpecificFileFromUpload
|
||||
} from 'flowise-components'
|
||||
import { getRunningExpressApp } from './getRunningExpressApp'
|
||||
import { getErrorMessage } from '../errors/utils'
|
||||
|
||||
@@ -41,7 +47,7 @@ export const createFileAttachment = async (req: Request) => {
|
||||
if (files.length) {
|
||||
const isBase64 = req.body.base64
|
||||
for (const file of files) {
|
||||
const fileBuffer = fs.readFileSync(file.path)
|
||||
const fileBuffer = await getFileFromUpload(file.path ?? file.key)
|
||||
const fileNames: string[] = []
|
||||
|
||||
// Address file name with special characters: https://github.com/expressjs/multer/issues/1104
|
||||
@@ -63,7 +69,7 @@ export const createFileAttachment = async (req: Request) => {
|
||||
fileInputField = fileInputFieldFromExt
|
||||
}
|
||||
|
||||
fs.unlinkSync(file.path)
|
||||
await removeSpecificFileFromUpload(file.path ?? file.key)
|
||||
|
||||
try {
|
||||
const nodeData = {
|
||||
|
||||
@@ -36,11 +36,13 @@ import {
|
||||
IDatabaseEntity,
|
||||
IMessage,
|
||||
FlowiseMemory,
|
||||
IFileUpload
|
||||
IFileUpload,
|
||||
getS3Config
|
||||
} from 'flowise-components'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { AES, enc } from 'crypto-js'
|
||||
|
||||
import multer from 'multer'
|
||||
import multerS3 from 'multer-s3'
|
||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||
import { ChatMessage } from '../database/entities/ChatMessage'
|
||||
import { Credential } from '../database/entities/Credential'
|
||||
@@ -1779,3 +1781,38 @@ export const getUploadPath = (): string => {
|
||||
? path.join(process.env.BLOB_STORAGE_PATH, 'uploads')
|
||||
: path.join(getUserHome(), '.flowise', 'uploads')
|
||||
}
|
||||
|
||||
const getOrgId = () => {
|
||||
const settingsContent = fs.readFileSync(getUserSettingsFilePath(), 'utf8')
|
||||
try {
|
||||
const settings = JSON.parse(settingsContent)
|
||||
return settings.instanceId
|
||||
} catch (error) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export const getMulterStorage = () => {
|
||||
const storageType = process.env.STORAGE_TYPE ? process.env.STORAGE_TYPE : 'local'
|
||||
|
||||
if (storageType === 's3') {
|
||||
const s3Client = getS3Config().s3Client
|
||||
const Bucket = getS3Config().Bucket
|
||||
|
||||
const upload = multer({
|
||||
storage: multerS3({
|
||||
s3: s3Client,
|
||||
bucket: Bucket,
|
||||
metadata: function (req, file, cb) {
|
||||
cb(null, { fieldName: file.fieldname, originalName: file.originalname, orgId: getOrgId() })
|
||||
},
|
||||
key: function (req, file, cb) {
|
||||
cb(null, `${getOrgId()}/${Date.now().toString()}`)
|
||||
}
|
||||
})
|
||||
})
|
||||
return upload
|
||||
} else {
|
||||
return multer({ dest: getUploadPath() })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { Request } from 'express'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import { cloneDeep, omit } from 'lodash'
|
||||
import { ICommonObject, IMessage, addArrayFilesToStorage, mapMimeTypeToInputField, mapExtToInputField } from 'flowise-components'
|
||||
import {
|
||||
ICommonObject,
|
||||
IMessage,
|
||||
addArrayFilesToStorage,
|
||||
mapMimeTypeToInputField,
|
||||
mapExtToInputField,
|
||||
getFileFromUpload,
|
||||
removeSpecificFileFromUpload
|
||||
} from 'flowise-components'
|
||||
import logger from '../utils/logger'
|
||||
import {
|
||||
buildFlow,
|
||||
@@ -57,7 +64,7 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
|
||||
const overrideConfig: ICommonObject = { ...req.body }
|
||||
for (const file of files) {
|
||||
const fileNames: string[] = []
|
||||
const fileBuffer = fs.readFileSync(file.path)
|
||||
const fileBuffer = await getFileFromUpload(file.path ?? file.key)
|
||||
// Address file name with special characters: https://github.com/expressjs/multer/issues/1104
|
||||
file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8')
|
||||
const storagePath = await addArrayFilesToStorage(file.mimetype, fileBuffer, file.originalname, fileNames, chatflowid)
|
||||
@@ -90,7 +97,7 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
|
||||
overrideConfig[fileInputField] = storagePath
|
||||
}
|
||||
|
||||
fs.unlinkSync(file.path)
|
||||
await removeSpecificFileFromUpload(file.path ?? file.key)
|
||||
}
|
||||
if (overrideConfig.vars && typeof overrideConfig.vars === 'string') {
|
||||
overrideConfig.vars = JSON.parse(overrideConfig.vars)
|
||||
|
||||
Reference in New Issue
Block a user