mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 13:00:56 +03:00
Chore/refractor (#4454)
* markdown files and env examples cleanup * components update * update jsonlines description * server refractor * update telemetry * add execute custom node * add ui refractor * add username and password authenticate * correctly retrieve past images in agentflowv2 * disable e2e temporarily * add existing username and password authenticate * update migration to default workspace * update todo * blob storage migrating * throw error on agent tool call error * add missing execution import * add referral * chore: add error message when importData is undefined * migrate api keys to db * fix: data too long for column executionData * migrate api keys from json to db at init * add info on account setup * update docstore missing fields --------- Co-authored-by: chungyau97 <chungyau97@gmail.com>
This commit is contained in:
@@ -15,6 +15,7 @@ export const addChatflowsCount = async (keys: any) => {
|
||||
const chatflows = await appServer.AppDataSource.getRepository(ChatFlow)
|
||||
.createQueryBuilder('cf')
|
||||
.where('cf.apikeyid = :apikeyid', { apikeyid: key.id })
|
||||
.andWhere('cf.workspaceId = :workspaceId', { workspaceId: key.workspaceId })
|
||||
.getMany()
|
||||
const linkedChatFlows: any[] = []
|
||||
chatflows.map((cf) => {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { randomBytes, scryptSync, timingSafeEqual } from 'crypto'
|
||||
import { ICommonObject } from 'flowise-components'
|
||||
import moment from 'moment'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import logger from './logger'
|
||||
import { appConfig } from '../AppConfig'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { ApiKey } from '../database/entities/ApiKey'
|
||||
import { Workspace } from '../enterprise/database/entities/workspace.entity'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||
import { addChatflowsCount } from './addChatflowsCount'
|
||||
import { Platform } from '../Interface'
|
||||
|
||||
/**
|
||||
* Returns the api key path
|
||||
@@ -51,94 +55,14 @@ export const compareKeys = (storedKey: string, suppliedKey: string): boolean =>
|
||||
* @returns {Promise<ICommonObject[]>}
|
||||
*/
|
||||
export const getAPIKeys = async (): Promise<ICommonObject[]> => {
|
||||
if (appConfig.apiKeys.storageType !== 'json') {
|
||||
return []
|
||||
}
|
||||
try {
|
||||
const content = await fs.promises.readFile(getAPIKeyPath(), 'utf8')
|
||||
return JSON.parse(content)
|
||||
} catch (error) {
|
||||
const keyName = 'DefaultKey'
|
||||
const apiKey = generateAPIKey()
|
||||
const apiSecret = generateSecretHash(apiKey)
|
||||
const content = [
|
||||
{
|
||||
keyName,
|
||||
apiKey,
|
||||
apiSecret,
|
||||
createdAt: moment().format('DD-MMM-YY'),
|
||||
id: randomBytes(16).toString('hex')
|
||||
}
|
||||
]
|
||||
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8')
|
||||
return content
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new API key
|
||||
* @param {string} keyName
|
||||
* @returns {Promise<ICommonObject[]>}
|
||||
*/
|
||||
export const addAPIKey = async (keyName: string): Promise<ICommonObject[]> => {
|
||||
const existingAPIKeys = await getAPIKeys()
|
||||
const apiKey = generateAPIKey()
|
||||
const apiSecret = generateSecretHash(apiKey)
|
||||
const content = [
|
||||
...existingAPIKeys,
|
||||
{
|
||||
keyName,
|
||||
apiKey,
|
||||
apiSecret,
|
||||
createdAt: moment().format('DD-MMM-YY'),
|
||||
id: randomBytes(16).toString('hex')
|
||||
}
|
||||
]
|
||||
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8')
|
||||
return content
|
||||
}
|
||||
|
||||
/**
|
||||
* import API keys
|
||||
* @param {[]} keys
|
||||
* @returns {Promise<ICommonObject[]>}
|
||||
*/
|
||||
export const importKeys = async (keys: any[], importMode: string): Promise<ICommonObject[]> => {
|
||||
const allApiKeys = await getAPIKeys()
|
||||
// if importMode is errorIfExist, check for existing keys and raise error before any modification to the file
|
||||
if (importMode === 'errorIfExist') {
|
||||
for (const key of keys) {
|
||||
const keyNameExists = allApiKeys.find((k) => k.keyName === key.keyName)
|
||||
if (keyNameExists) {
|
||||
throw new Error(`Key with name ${key.keyName} already exists`)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const key of keys) {
|
||||
// Check if keyName already exists, if overwrite is false, raise an error else overwrite the key
|
||||
const keyNameExists = allApiKeys.find((k) => k.keyName === key.keyName)
|
||||
if (keyNameExists) {
|
||||
const keyIndex = allApiKeys.findIndex((k) => k.keyName === key.keyName)
|
||||
switch (importMode) {
|
||||
case 'overwriteIfExist':
|
||||
allApiKeys[keyIndex] = key
|
||||
continue
|
||||
case 'ignoreIfExist':
|
||||
// ignore this key and continue
|
||||
continue
|
||||
case 'errorIfExist':
|
||||
// should not reach here as we have already checked for existing keys
|
||||
throw new Error(`Key with name ${key.keyName} already exists`)
|
||||
default:
|
||||
throw new Error(`Unknown overwrite option ${importMode}`)
|
||||
}
|
||||
}
|
||||
allApiKeys.push(key)
|
||||
}
|
||||
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(allApiKeys), 'utf8')
|
||||
return allApiKeys
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API Key details
|
||||
* @param {string} apiKey
|
||||
@@ -151,42 +75,82 @@ export const getApiKey = async (apiKey: string) => {
|
||||
return existingAPIKeys[keyIndex]
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing API key
|
||||
* @param {string} keyIdToUpdate
|
||||
* @param {string} newKeyName
|
||||
* @returns {Promise<ICommonObject[]>}
|
||||
*/
|
||||
export const updateAPIKey = async (keyIdToUpdate: string, newKeyName: string): Promise<ICommonObject[]> => {
|
||||
const existingAPIKeys = await getAPIKeys()
|
||||
const keyIndex = existingAPIKeys.findIndex((key) => key.id === keyIdToUpdate)
|
||||
if (keyIndex < 0) return []
|
||||
existingAPIKeys[keyIndex].keyName = newKeyName
|
||||
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(existingAPIKeys), 'utf8')
|
||||
return existingAPIKeys
|
||||
}
|
||||
export const migrateApiKeysFromJsonToDb = async (appDataSource: DataSource, platformType: Platform) => {
|
||||
if (platformType === Platform.CLOUD) {
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete API key
|
||||
* @param {string} keyIdToDelete
|
||||
* @returns {Promise<ICommonObject[]>}
|
||||
*/
|
||||
export const deleteAPIKey = async (keyIdToDelete: string): Promise<ICommonObject[]> => {
|
||||
const existingAPIKeys = await getAPIKeys()
|
||||
const result = existingAPIKeys.filter((key) => key.id !== keyIdToDelete)
|
||||
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(result), 'utf8')
|
||||
return result
|
||||
}
|
||||
if (!process.env.APIKEY_STORAGE_TYPE || process.env.APIKEY_STORAGE_TYPE === 'json') {
|
||||
const keys = await getAPIKeys()
|
||||
if (keys.length > 0) {
|
||||
try {
|
||||
// Get all available workspaces
|
||||
const workspaces = await appDataSource.getRepository(Workspace).find()
|
||||
|
||||
/**
|
||||
* Replace all api keys
|
||||
* @param {ICommonObject[]} content
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const replaceAllAPIKeys = async (content: ICommonObject[]): Promise<void> => {
|
||||
try {
|
||||
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8')
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
for (const key of keys) {
|
||||
const existingKey = await appDataSource.getRepository(ApiKey).findOneBy({
|
||||
apiKey: key.apiKey
|
||||
})
|
||||
|
||||
// Only add if key doesn't already exist in DB
|
||||
if (!existingKey) {
|
||||
// Create a new API key for each workspace
|
||||
if (workspaces.length > 0) {
|
||||
for (const workspace of workspaces) {
|
||||
const newKey = new ApiKey()
|
||||
newKey.id = uuidv4()
|
||||
newKey.apiKey = key.apiKey
|
||||
newKey.apiSecret = key.apiSecret
|
||||
newKey.keyName = key.keyName
|
||||
newKey.workspaceId = workspace.id
|
||||
|
||||
const keyEntity = appDataSource.getRepository(ApiKey).create(newKey)
|
||||
await appDataSource.getRepository(ApiKey).save(keyEntity)
|
||||
|
||||
const chatflows = await appDataSource.getRepository(ChatFlow).findBy({
|
||||
apikeyid: key.id,
|
||||
workspaceId: workspace.id
|
||||
})
|
||||
|
||||
for (const chatflow of chatflows) {
|
||||
chatflow.apikeyid = newKey.id
|
||||
await appDataSource.getRepository(ChatFlow).save(chatflow)
|
||||
}
|
||||
|
||||
await addChatflowsCount(chatflows)
|
||||
}
|
||||
} else {
|
||||
// If no workspaces exist, create the key without a workspace ID and later will be updated by setNullWorkspaceId
|
||||
const newKey = new ApiKey()
|
||||
newKey.id = uuidv4()
|
||||
newKey.apiKey = key.apiKey
|
||||
newKey.apiSecret = key.apiSecret
|
||||
newKey.keyName = key.keyName
|
||||
|
||||
const keyEntity = appDataSource.getRepository(ApiKey).create(newKey)
|
||||
await appDataSource.getRepository(ApiKey).save(keyEntity)
|
||||
|
||||
const chatflows = await appDataSource.getRepository(ChatFlow).findBy({
|
||||
apikeyid: key.id
|
||||
})
|
||||
|
||||
for (const chatflow of chatflows) {
|
||||
chatflow.apikeyid = newKey.id
|
||||
await appDataSource.getRepository(ChatFlow).save(chatflow)
|
||||
}
|
||||
|
||||
await addChatflowsCount(chatflows)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the JSON file
|
||||
if (fs.existsSync(getAPIKeyPath())) {
|
||||
fs.unlinkSync(getAPIKeyPath())
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error migrating API keys from JSON to DB', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import { InternalFlowiseError } from '../errors/internalFlowiseError'
|
||||
import { getErrorMessage } from '../errors/utils'
|
||||
import logger from './logger'
|
||||
import { Variable } from '../database/entities/Variable'
|
||||
import { getWorkspaceSearchOptions } from '../enterprise/utils/ControllerServiceUtils'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { CachePool } from '../CachePool'
|
||||
|
||||
@@ -50,7 +51,9 @@ export const buildAgentGraph = async ({
|
||||
shouldStreamResponse,
|
||||
cachePool,
|
||||
baseURL,
|
||||
signal
|
||||
signal,
|
||||
orgId,
|
||||
workspaceId
|
||||
}: {
|
||||
agentflow: IChatFlow
|
||||
flowConfig: IFlowConfig
|
||||
@@ -70,6 +73,8 @@ export const buildAgentGraph = async ({
|
||||
cachePool: CachePool
|
||||
baseURL: string
|
||||
signal?: AbortController
|
||||
orgId: string
|
||||
workspaceId?: string
|
||||
}): Promise<any> => {
|
||||
try {
|
||||
const chatflowid = flowConfig.chatflowid
|
||||
@@ -79,6 +84,8 @@ export const buildAgentGraph = async ({
|
||||
const uploads = incomingInput.uploads
|
||||
|
||||
const options = {
|
||||
orgId,
|
||||
workspaceId,
|
||||
chatId,
|
||||
sessionId,
|
||||
chatflowid,
|
||||
@@ -384,7 +391,7 @@ export const buildAgentGraph = async ({
|
||||
}
|
||||
} catch (e) {
|
||||
// clear agent memory because checkpoints were saved during runtime
|
||||
await clearSessionMemory(nodes, componentNodes, chatId, appDataSource, sessionId)
|
||||
await clearSessionMemory(nodes, componentNodes, chatId, appDataSource, orgId, sessionId)
|
||||
if (getErrorMessage(e).includes('Aborted')) {
|
||||
if (shouldStreamResponse && sseStreamer) {
|
||||
sseStreamer.streamAbortEvent(chatId)
|
||||
@@ -395,7 +402,7 @@ export const buildAgentGraph = async ({
|
||||
}
|
||||
return streamResults
|
||||
} catch (e) {
|
||||
logger.error('[server]: Error:', e)
|
||||
logger.error(`[server]: [${orgId}]: Error:`, e)
|
||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error buildAgentGraph - ${getErrorMessage(e)}`)
|
||||
}
|
||||
}
|
||||
@@ -457,7 +464,7 @@ const compileMultiAgentsGraph = async (params: MultiAgentsGraphParams) => {
|
||||
const workerNodes = reactFlowNodes.filter((node) => workerNodeIds.includes(node.data.id))
|
||||
|
||||
/*** Get API Config ***/
|
||||
const availableVariables = await appDataSource.getRepository(Variable).find()
|
||||
const availableVariables = await appDataSource.getRepository(Variable).findBy(getWorkspaceSearchOptions(agentflow.workspaceId))
|
||||
const { nodeOverrides, variableOverrides, apiOverrideStatus } = getAPIOverrideConfig(agentflow)
|
||||
|
||||
let supervisorWorkers: { [key: string]: IMultiAgentNode[] } = {}
|
||||
@@ -566,7 +573,7 @@ const compileMultiAgentsGraph = async (params: MultiAgentsGraphParams) => {
|
||||
|
||||
const graph = workflowGraph.compile({ checkpointer: memory })
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(logger)
|
||||
const loggerHandler = new ConsoleCallbackHandler(logger, options?.orgId)
|
||||
const callbacks = await additionalCallbacks(flowNodeData, options)
|
||||
const config = { configurable: { thread_id: threadId } }
|
||||
|
||||
@@ -686,7 +693,7 @@ const compileSeqAgentsGraph = async (params: SeqAgentsGraphParams) => {
|
||||
let interruptToolNodeNames = []
|
||||
|
||||
/*** Get API Config ***/
|
||||
const availableVariables = await appDataSource.getRepository(Variable).find()
|
||||
const availableVariables = await appDataSource.getRepository(Variable).findBy(getWorkspaceSearchOptions(agentflow.workspaceId))
|
||||
const { nodeOverrides, variableOverrides, apiOverrideStatus } = getAPIOverrideConfig(agentflow)
|
||||
|
||||
const initiateNode = async (node: IReactFlowNode) => {
|
||||
@@ -996,7 +1003,7 @@ const compileSeqAgentsGraph = async (params: SeqAgentsGraphParams) => {
|
||||
interruptBefore: interruptToolNodeNames as any
|
||||
})
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(logger)
|
||||
const loggerHandler = new ConsoleCallbackHandler(logger, options?.orgId)
|
||||
const callbacks = await additionalCallbacks(flowNodeData as any, options)
|
||||
const config = { configurable: { thread_id: threadId }, bindModel }
|
||||
|
||||
@@ -1044,7 +1051,7 @@ const compileSeqAgentsGraph = async (params: SeqAgentsGraphParams) => {
|
||||
configurable: config
|
||||
})
|
||||
} catch (e) {
|
||||
logger.error('Error compile graph', e)
|
||||
logger.error(`[${options.orgId}]: Error compile graph`, e)
|
||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error compile graph - ${getErrorMessage(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ import { utilAddChatMessage } from './addChatMesage'
|
||||
import { CachePool } from '../CachePool'
|
||||
import { ChatMessage } from '../database/entities/ChatMessage'
|
||||
import { Telemetry } from './telemetry'
|
||||
import { getWorkspaceSearchOptions } from '../enterprise/utils/ControllerServiceUtils'
|
||||
import { UsageCacheManager } from '../UsageCacheManager'
|
||||
|
||||
interface IWaitingNode {
|
||||
nodeId: string
|
||||
@@ -99,9 +101,11 @@ interface IExecuteNodeParams {
|
||||
chatId: string
|
||||
sessionId: string
|
||||
apiMessageId: string
|
||||
evaluationRunId?: string
|
||||
isInternal: boolean
|
||||
pastChatHistory: IMessage[]
|
||||
appDataSource: DataSource
|
||||
usageCacheManager: UsageCacheManager
|
||||
telemetry: Telemetry
|
||||
componentNodes: IComponentNodes
|
||||
cachePool: CachePool
|
||||
@@ -122,6 +126,9 @@ interface IExecuteNodeParams {
|
||||
parentExecutionId?: string
|
||||
isRecursive?: boolean
|
||||
iterationContext?: ICommonObject
|
||||
orgId: string
|
||||
workspaceId: string
|
||||
subscriptionId: string
|
||||
}
|
||||
|
||||
interface IExecuteAgentFlowParams extends Omit<IExecuteFlowParams, 'incomingInput'> {
|
||||
@@ -142,13 +149,15 @@ const addExecution = async (
|
||||
appDataSource: DataSource,
|
||||
agentflowId: string,
|
||||
agentFlowExecutedData: IAgentflowExecutedData[],
|
||||
sessionId: string
|
||||
sessionId: string,
|
||||
workspaceId: string
|
||||
) => {
|
||||
const newExecution = new Execution()
|
||||
const bodyExecution = {
|
||||
agentflowId,
|
||||
state: 'INPROGRESS',
|
||||
sessionId,
|
||||
workspaceId,
|
||||
executionData: JSON.stringify(agentFlowExecutedData)
|
||||
}
|
||||
Object.assign(newExecution, bodyExecution)
|
||||
@@ -164,9 +173,10 @@ const addExecution = async (
|
||||
* @param {Partial<IExecution>} data
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const updateExecution = async (appDataSource: DataSource, executionId: string, data?: Partial<IExecution>) => {
|
||||
const updateExecution = async (appDataSource: DataSource, executionId: string, workspaceId: string, data?: Partial<IExecution>) => {
|
||||
const execution = await appDataSource.getRepository(Execution).findOneBy({
|
||||
id: executionId
|
||||
id: executionId,
|
||||
workspaceId
|
||||
})
|
||||
|
||||
if (!execution) {
|
||||
@@ -770,9 +780,11 @@ const executeNode = async ({
|
||||
chatId,
|
||||
sessionId,
|
||||
apiMessageId,
|
||||
evaluationRunId,
|
||||
parentExecutionId,
|
||||
pastChatHistory,
|
||||
appDataSource,
|
||||
usageCacheManager,
|
||||
telemetry,
|
||||
componentNodes,
|
||||
cachePool,
|
||||
@@ -792,7 +804,10 @@ const executeNode = async ({
|
||||
analyticHandlers,
|
||||
isInternal,
|
||||
isRecursive,
|
||||
iterationContext
|
||||
iterationContext,
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId
|
||||
}: IExecuteNodeParams): Promise<{
|
||||
result: any
|
||||
shouldStop?: boolean
|
||||
@@ -824,7 +839,7 @@ const executeNode = async ({
|
||||
}
|
||||
|
||||
// Get available variables and resolve them
|
||||
const availableVariables = await appDataSource.getRepository(Variable).find()
|
||||
const availableVariables = await appDataSource.getRepository(Variable).findBy(getWorkspaceSearchOptions(workspaceId))
|
||||
|
||||
// Prepare flow config
|
||||
let updatedState = cloneDeep(agentflowRuntime.state)
|
||||
@@ -902,6 +917,9 @@ const executeNode = async ({
|
||||
|
||||
// Prepare run parameters
|
||||
const runParams = {
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId,
|
||||
chatId,
|
||||
sessionId,
|
||||
chatflowid: chatflow.id,
|
||||
@@ -909,6 +927,7 @@ const executeNode = async ({
|
||||
logger,
|
||||
appDataSource,
|
||||
databaseEntities,
|
||||
usageCacheManager,
|
||||
componentNodes,
|
||||
cachePool,
|
||||
analytic: chatflow.analytic,
|
||||
@@ -922,7 +941,8 @@ const executeNode = async ({
|
||||
analyticHandlers,
|
||||
parentTraceIds,
|
||||
humanInputAction,
|
||||
iterationContext
|
||||
iterationContext,
|
||||
evaluationRunId
|
||||
}
|
||||
|
||||
// Execute node
|
||||
@@ -982,7 +1002,9 @@ const executeNode = async ({
|
||||
incomingInput,
|
||||
chatflow: iterationChatflow,
|
||||
chatId,
|
||||
evaluationRunId,
|
||||
appDataSource,
|
||||
usageCacheManager,
|
||||
telemetry,
|
||||
cachePool,
|
||||
sseStreamer,
|
||||
@@ -996,7 +1018,10 @@ const executeNode = async ({
|
||||
iterationContext: {
|
||||
...iterationContext,
|
||||
agentflowRuntime
|
||||
}
|
||||
},
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId
|
||||
})
|
||||
|
||||
// Store the result
|
||||
@@ -1023,7 +1048,7 @@ const executeNode = async ({
|
||||
if (parentExecutionId) {
|
||||
try {
|
||||
logger.debug(` 📝 Updating parent execution ${parentExecutionId} with iteration ${i + 1} data`)
|
||||
await updateExecution(appDataSource, parentExecutionId, {
|
||||
await updateExecution(appDataSource, parentExecutionId, workspaceId, {
|
||||
executionData: JSON.stringify(agentFlowExecutedData)
|
||||
})
|
||||
} catch (error) {
|
||||
@@ -1192,8 +1217,10 @@ export const executeAgentFlow = async ({
|
||||
incomingInput,
|
||||
chatflow,
|
||||
chatId,
|
||||
evaluationRunId,
|
||||
appDataSource,
|
||||
telemetry,
|
||||
usageCacheManager,
|
||||
cachePool,
|
||||
sseStreamer,
|
||||
baseURL,
|
||||
@@ -1204,7 +1231,10 @@ export const executeAgentFlow = async ({
|
||||
isRecursive = false,
|
||||
parentExecutionId,
|
||||
iterationContext,
|
||||
isTool = false
|
||||
isTool = false,
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId
|
||||
}: IExecuteAgentFlowParams) => {
|
||||
logger.debug('\n🚀 Starting flow execution')
|
||||
|
||||
@@ -1281,7 +1311,8 @@ export const executeAgentFlow = async ({
|
||||
const previousExecutions = await appDataSource.getRepository(Execution).find({
|
||||
where: {
|
||||
sessionId,
|
||||
agentflowId: chatflowid
|
||||
agentflowId: chatflowid,
|
||||
workspaceId
|
||||
},
|
||||
order: {
|
||||
createdDate: 'DESC'
|
||||
@@ -1344,7 +1375,7 @@ export const executeAgentFlow = async ({
|
||||
agentflowRuntime.state = (lastState as ICommonObject) ?? {}
|
||||
|
||||
// Update execution state to INPROGRESS
|
||||
await updateExecution(appDataSource, previousExecution.id, {
|
||||
await updateExecution(appDataSource, previousExecution.id, workspaceId, {
|
||||
state: 'INPROGRESS'
|
||||
})
|
||||
newExecution = previousExecution
|
||||
@@ -1357,7 +1388,7 @@ export const executeAgentFlow = async ({
|
||||
// For recursive calls with a valid parent execution ID, don't create a new execution
|
||||
// Instead, fetch the parent execution to use it
|
||||
const parentExecution = await appDataSource.getRepository(Execution).findOne({
|
||||
where: { id: parentExecutionId }
|
||||
where: { id: parentExecutionId, workspaceId }
|
||||
})
|
||||
|
||||
if (parentExecution) {
|
||||
@@ -1365,7 +1396,7 @@ export const executeAgentFlow = async ({
|
||||
newExecution = parentExecution
|
||||
} else {
|
||||
console.warn(` ⚠️ Parent execution ID ${parentExecutionId} not found, will create new execution`)
|
||||
newExecution = await addExecution(appDataSource, chatflowid, agentFlowExecutedData, sessionId)
|
||||
newExecution = await addExecution(appDataSource, chatflowid, agentFlowExecutedData, sessionId, workspaceId)
|
||||
parentExecutionId = newExecution.id
|
||||
}
|
||||
} else {
|
||||
@@ -1374,7 +1405,7 @@ export const executeAgentFlow = async ({
|
||||
checkForMultipleStartNodes(startingNodeIds, isRecursive, nodes)
|
||||
|
||||
// Only create a new execution if this is not a recursive call
|
||||
newExecution = await addExecution(appDataSource, chatflowid, agentFlowExecutedData, sessionId)
|
||||
newExecution = await addExecution(appDataSource, chatflowid, agentFlowExecutedData, sessionId, workspaceId)
|
||||
parentExecutionId = newExecution.id
|
||||
}
|
||||
|
||||
@@ -1430,6 +1461,8 @@ export const executeAgentFlow = async ({
|
||||
try {
|
||||
if (chatflow.analytic) {
|
||||
analyticHandlers = AnalyticHandler.getInstance({ inputs: {} } as any, {
|
||||
orgId,
|
||||
workspaceId,
|
||||
appDataSource,
|
||||
databaseEntities,
|
||||
componentNodes,
|
||||
@@ -1486,10 +1519,12 @@ export const executeAgentFlow = async ({
|
||||
chatId,
|
||||
sessionId,
|
||||
apiMessageId,
|
||||
evaluationRunId,
|
||||
parentExecutionId,
|
||||
isInternal,
|
||||
pastChatHistory,
|
||||
appDataSource,
|
||||
usageCacheManager,
|
||||
telemetry,
|
||||
componentNodes,
|
||||
cachePool,
|
||||
@@ -1508,7 +1543,10 @@ export const executeAgentFlow = async ({
|
||||
parentTraceIds,
|
||||
analyticHandlers,
|
||||
isRecursive,
|
||||
iterationContext
|
||||
iterationContext,
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId
|
||||
})
|
||||
|
||||
if (executionResult.agentFlowExecutedData) {
|
||||
@@ -1607,7 +1645,7 @@ export const executeAgentFlow = async ({
|
||||
if (!isRecursive) {
|
||||
sseStreamer?.streamAgentFlowExecutedDataEvent(chatId, agentFlowExecutedData)
|
||||
|
||||
await updateExecution(appDataSource, newExecution.id, {
|
||||
await updateExecution(appDataSource, newExecution.id, workspaceId, {
|
||||
executionData: JSON.stringify(agentFlowExecutedData),
|
||||
state: errorStatus
|
||||
})
|
||||
@@ -1642,7 +1680,7 @@ export const executeAgentFlow = async ({
|
||||
|
||||
// Only update execution record if this is not a recursive call
|
||||
if (!isRecursive) {
|
||||
await updateExecution(appDataSource, newExecution.id, {
|
||||
await updateExecution(appDataSource, newExecution.id, workspaceId, {
|
||||
executionData: JSON.stringify(agentFlowExecutedData),
|
||||
state: status
|
||||
})
|
||||
@@ -1744,6 +1782,8 @@ export const executeAgentFlow = async ({
|
||||
if (chatflow.followUpPrompts) {
|
||||
const followUpPromptsConfig = JSON.parse(chatflow.followUpPrompts)
|
||||
const followUpPrompts = await generateFollowUpPrompts(followUpPromptsConfig, apiMessage.content, {
|
||||
orgId,
|
||||
workspaceId,
|
||||
chatId,
|
||||
chatflowid,
|
||||
appDataSource,
|
||||
@@ -1760,13 +1800,17 @@ export const executeAgentFlow = async ({
|
||||
|
||||
logger.debug(`[server]: Finished running agentflow ${chatflowid}`)
|
||||
|
||||
await telemetry.sendTelemetry('prediction_sent', {
|
||||
version: await getAppVersion(),
|
||||
chatflowId: chatflowid,
|
||||
chatId,
|
||||
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||
flowGraph: getTelemetryFlowObj(nodes, edges)
|
||||
})
|
||||
await telemetry.sendTelemetry(
|
||||
'prediction_sent',
|
||||
{
|
||||
version: await getAppVersion(),
|
||||
chatflowId: chatflowid,
|
||||
chatId,
|
||||
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||
flowGraph: getTelemetryFlowObj(nodes, edges)
|
||||
},
|
||||
orgId
|
||||
)
|
||||
|
||||
/*** Prepare response ***/
|
||||
let result: ICommonObject = {}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
mapExtToInputField,
|
||||
getFileFromUpload,
|
||||
removeSpecificFileFromUpload,
|
||||
EvaluationRunner,
|
||||
handleEscapeCharacters
|
||||
} from 'flowise-components'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
@@ -22,8 +23,8 @@ import {
|
||||
IncomingInput,
|
||||
IMessage,
|
||||
INodeData,
|
||||
IReactFlowObject,
|
||||
IReactFlowNode,
|
||||
IReactFlowObject,
|
||||
IDepthQueue,
|
||||
ChatType,
|
||||
IChatMessage,
|
||||
@@ -59,11 +60,15 @@ import {
|
||||
import { validateChatflowAPIKey } from './validateKey'
|
||||
import logger from './logger'
|
||||
import { utilAddChatMessage } from './addChatMesage'
|
||||
import { checkPredictions, checkStorage, updatePredictionsUsage, updateStorageUsage } from './quotaUsage'
|
||||
import { buildAgentGraph } from './buildAgentGraph'
|
||||
import { getErrorMessage } from '../errors/utils'
|
||||
import { FLOWISE_METRIC_COUNTERS, FLOWISE_COUNTER_STATUS, IMetricsProvider } from '../Interface.Metrics'
|
||||
import { getWorkspaceSearchOptions } from '../enterprise/utils/ControllerServiceUtils'
|
||||
import { OMIT_QUEUE_JOB_DATA } from './constants'
|
||||
import { executeAgentFlow } from './buildAgentflow'
|
||||
import { Workspace } from '../enterprise/database/entities/workspace.entity'
|
||||
import { Organization } from '../enterprise/database/entities/organization.entity'
|
||||
|
||||
/*
|
||||
* Initialize the ending node to be executed
|
||||
@@ -230,15 +235,21 @@ export const executeFlow = async ({
|
||||
incomingInput,
|
||||
chatflow,
|
||||
chatId,
|
||||
isEvaluation,
|
||||
evaluationRunId,
|
||||
appDataSource,
|
||||
telemetry,
|
||||
cachePool,
|
||||
usageCacheManager,
|
||||
sseStreamer,
|
||||
baseURL,
|
||||
isInternal,
|
||||
files,
|
||||
signal,
|
||||
isTool
|
||||
isTool,
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId
|
||||
}: IExecuteFlowParams) => {
|
||||
// Ensure incomingInput has all required properties with default values
|
||||
incomingInput = {
|
||||
@@ -265,6 +276,8 @@ export const executeFlow = async ({
|
||||
if (uploads) {
|
||||
fileUploads = uploads
|
||||
for (let i = 0; i < fileUploads.length; i += 1) {
|
||||
await checkStorage(orgId, subscriptionId, usageCacheManager)
|
||||
|
||||
const upload = fileUploads[i]
|
||||
|
||||
// if upload in an image, a rag file, or audio
|
||||
@@ -273,7 +286,8 @@ export const executeFlow = async ({
|
||||
const splitDataURI = upload.data.split(',')
|
||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||
const mime = splitDataURI[0].split(':')[1].split(';')[0]
|
||||
await addSingleFileToStorage(mime, bf, filename, chatflowid, chatId)
|
||||
const { totalSize } = await addSingleFileToStorage(mime, bf, filename, orgId, chatflowid, chatId)
|
||||
await updateStorageUsage(orgId, workspaceId, totalSize, usageCacheManager)
|
||||
upload.type = 'stored-file'
|
||||
// Omit upload.data since we don't store the content in database
|
||||
fileUploads[i] = omit(upload, ['data'])
|
||||
@@ -287,7 +301,7 @@ export const executeFlow = async ({
|
||||
|
||||
// Run Speech to Text conversion
|
||||
if (upload.mime === 'audio/webm' || upload.mime === 'audio/mp4' || upload.mime === 'audio/ogg') {
|
||||
logger.debug(`Attempting a speech to text conversion...`)
|
||||
logger.debug(`[server]: [${orgId}]: Attempting a speech to text conversion...`)
|
||||
let speechToTextConfig: ICommonObject = {}
|
||||
if (chatflow.speechToText) {
|
||||
const speechToTextProviders = JSON.parse(chatflow.speechToText)
|
||||
@@ -302,13 +316,14 @@ export const executeFlow = async ({
|
||||
}
|
||||
if (speechToTextConfig) {
|
||||
const options: ICommonObject = {
|
||||
orgId,
|
||||
chatId,
|
||||
chatflowid,
|
||||
appDataSource,
|
||||
databaseEntities: databaseEntities
|
||||
}
|
||||
const speechToTextResult = await convertSpeechToText(upload, speechToTextConfig, options)
|
||||
logger.debug(`Speech to text result: ${speechToTextResult}`)
|
||||
logger.debug(`[server]: [${orgId}]: Speech to text result: ${speechToTextResult}`)
|
||||
if (speechToTextResult) {
|
||||
incomingInput.question = speechToTextResult
|
||||
question = speechToTextResult
|
||||
@@ -329,11 +344,21 @@ export const executeFlow = async ({
|
||||
if (files?.length) {
|
||||
overrideConfig = { ...incomingInput }
|
||||
for (const file of files) {
|
||||
await checkStorage(orgId, subscriptionId, usageCacheManager)
|
||||
|
||||
const fileNames: string[] = []
|
||||
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)
|
||||
const { path: storagePath, totalSize } = await addArrayFilesToStorage(
|
||||
file.mimetype,
|
||||
fileBuffer,
|
||||
file.originalname,
|
||||
fileNames,
|
||||
orgId,
|
||||
chatflowid
|
||||
)
|
||||
await updateStorageUsage(orgId, workspaceId, totalSize, usageCacheManager)
|
||||
|
||||
const fileInputFieldFromMimeType = mapMimeTypeToInputField(file.mimetype)
|
||||
|
||||
@@ -382,16 +407,21 @@ export const executeFlow = async ({
|
||||
incomingInput,
|
||||
chatflow,
|
||||
chatId,
|
||||
evaluationRunId,
|
||||
appDataSource,
|
||||
telemetry,
|
||||
cachePool,
|
||||
usageCacheManager,
|
||||
sseStreamer,
|
||||
baseURL,
|
||||
isInternal,
|
||||
uploadedFilesContent,
|
||||
fileUploads,
|
||||
signal,
|
||||
isTool
|
||||
isTool,
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId
|
||||
})
|
||||
}
|
||||
|
||||
@@ -443,7 +473,7 @@ export const executeFlow = async ({
|
||||
})
|
||||
|
||||
/*** Get API Config ***/
|
||||
const availableVariables = await appDataSource.getRepository(Variable).find()
|
||||
const availableVariables = await appDataSource.getRepository(Variable).findBy(getWorkspaceSearchOptions(workspaceId))
|
||||
const { nodeOverrides, variableOverrides, apiOverrideStatus } = getAPIOverrideConfig(chatflow)
|
||||
|
||||
const flowConfig: IFlowConfig = {
|
||||
@@ -455,7 +485,7 @@ export const executeFlow = async ({
|
||||
...incomingInput.overrideConfig
|
||||
}
|
||||
|
||||
logger.debug(`[server]: Start building flow ${chatflowid}`)
|
||||
logger.debug(`[server]: [${orgId}]: Start building flow ${chatflowid}`)
|
||||
|
||||
/*** BFS to traverse from Starting Nodes to Ending Node ***/
|
||||
const reactFlowNodes = await buildFlow({
|
||||
@@ -479,9 +509,13 @@ export const executeFlow = async ({
|
||||
availableVariables,
|
||||
variableOverrides,
|
||||
cachePool,
|
||||
usageCacheManager,
|
||||
isUpsert: false,
|
||||
uploads,
|
||||
baseURL
|
||||
baseURL,
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId
|
||||
})
|
||||
|
||||
const setVariableNodesOutput = getSetVariableNodesOutput(reactFlowNodes)
|
||||
@@ -506,7 +540,9 @@ export const executeFlow = async ({
|
||||
shouldStreamResponse: true, // agentflow is always streamed
|
||||
cachePool,
|
||||
baseURL,
|
||||
signal
|
||||
signal,
|
||||
orgId,
|
||||
workspaceId
|
||||
})
|
||||
|
||||
if (streamResults) {
|
||||
@@ -556,13 +592,17 @@ export const executeFlow = async ({
|
||||
}
|
||||
const chatMessage = await utilAddChatMessage(apiMessage, appDataSource)
|
||||
|
||||
await telemetry.sendTelemetry('agentflow_prediction_sent', {
|
||||
version: await getAppVersion(),
|
||||
agentflowId: agentflow.id,
|
||||
chatId,
|
||||
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||
flowGraph: getTelemetryFlowObj(nodes, edges)
|
||||
})
|
||||
await telemetry.sendTelemetry(
|
||||
'agentflow_prediction_sent',
|
||||
{
|
||||
version: await getAppVersion(),
|
||||
agentflowId: agentflow.id,
|
||||
chatId,
|
||||
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||
flowGraph: getTelemetryFlowObj(nodes, edges)
|
||||
},
|
||||
orgId
|
||||
)
|
||||
|
||||
// Find the previous chat message with the same action id and remove the action
|
||||
if (incomingInput.action && Object.keys(incomingInput.action).length) {
|
||||
@@ -596,6 +636,7 @@ export const executeFlow = async ({
|
||||
// Prepare response
|
||||
let result: ICommonObject = {}
|
||||
result.text = finalResult
|
||||
|
||||
result.question = incomingInput.question
|
||||
result.chatId = chatId
|
||||
result.chatMessageId = chatMessage?.id
|
||||
@@ -605,7 +646,6 @@ export const executeFlow = async ({
|
||||
if (finalAction && Object.keys(finalAction).length) result.action = finalAction
|
||||
if (Object.keys(setVariableNodesOutput).length) result.flowVariables = setVariableNodesOutput
|
||||
result.followUpPrompts = JSON.stringify(apiMessage.followUpPrompts)
|
||||
|
||||
return result
|
||||
}
|
||||
return undefined
|
||||
@@ -643,16 +683,23 @@ export const executeFlow = async ({
|
||||
|
||||
/*** Prepare run params ***/
|
||||
const runParams = {
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId,
|
||||
chatId,
|
||||
chatflowid,
|
||||
apiMessageId,
|
||||
logger,
|
||||
appDataSource,
|
||||
databaseEntities,
|
||||
usageCacheManager,
|
||||
analytic: chatflow.analytic,
|
||||
uploads,
|
||||
prependMessages,
|
||||
...(isStreamValid && { sseStreamer, shouldStreamResponse: isStreamValid })
|
||||
...(isStreamValid && { sseStreamer, shouldStreamResponse: isStreamValid }),
|
||||
evaluationRunId,
|
||||
updateStorageUsage,
|
||||
checkStorage
|
||||
}
|
||||
|
||||
/*** Run the ending node ***/
|
||||
@@ -669,7 +716,7 @@ export const executeFlow = async ({
|
||||
role: 'userMessage',
|
||||
content: question,
|
||||
chatflowid,
|
||||
chatType: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||
chatType: isEvaluation ? ChatType.EVALUATION : isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||
chatId,
|
||||
memoryType,
|
||||
sessionId,
|
||||
@@ -725,7 +772,7 @@ export const executeFlow = async ({
|
||||
role: 'apiMessage',
|
||||
content: resultText,
|
||||
chatflowid,
|
||||
chatType: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||
chatType: isEvaluation ? ChatType.EVALUATION : isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||
chatId,
|
||||
memoryType,
|
||||
sessionId
|
||||
@@ -749,15 +796,22 @@ export const executeFlow = async ({
|
||||
|
||||
const chatMessage = await utilAddChatMessage(apiMessage, appDataSource)
|
||||
|
||||
logger.debug(`[server]: Finished running ${endingNodeData.label} (${endingNodeData.id})`)
|
||||
|
||||
await telemetry.sendTelemetry('prediction_sent', {
|
||||
version: await getAppVersion(),
|
||||
chatflowId: chatflowid,
|
||||
chatId,
|
||||
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||
flowGraph: getTelemetryFlowObj(nodes, edges)
|
||||
})
|
||||
logger.debug(`[server]: [${orgId}]: Finished running ${endingNodeData.label} (${endingNodeData.id})`)
|
||||
if (evaluationRunId) {
|
||||
const metrics = await EvaluationRunner.getAndDeleteMetrics(evaluationRunId)
|
||||
result.metrics = metrics
|
||||
}
|
||||
await telemetry.sendTelemetry(
|
||||
'prediction_sent',
|
||||
{
|
||||
version: await getAppVersion(),
|
||||
chatflowId: chatflowid,
|
||||
chatId,
|
||||
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||
flowGraph: getTelemetryFlowObj(nodes, edges)
|
||||
},
|
||||
orgId
|
||||
)
|
||||
|
||||
/*** Prepare response ***/
|
||||
result.question = incomingInput.question // return the question in the response, this is used when input text is empty but question is in audio format
|
||||
@@ -830,6 +884,7 @@ const checkIfStreamValid = async (
|
||||
*/
|
||||
export const utilBuildChatflow = async (req: Request, isInternal: boolean = false): Promise<any> => {
|
||||
const appServer = getRunningExpressApp()
|
||||
|
||||
const chatflowid = req.params.id
|
||||
|
||||
// Check if chatflow exists
|
||||
@@ -841,7 +896,6 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||
}
|
||||
|
||||
const isAgentFlow = chatflow.type === 'MULTIAGENT'
|
||||
|
||||
const httpProtocol = req.get('x-forwarded-proto') || req.protocol
|
||||
const baseURL = `${httpProtocol}://${req.get('host')}`
|
||||
const incomingInput: IncomingInput = req.body || {} // Ensure incomingInput is never undefined
|
||||
@@ -849,6 +903,20 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||
const files = (req.files as Express.Multer.File[]) || []
|
||||
const abortControllerId = `${chatflow.id}_${chatId}`
|
||||
const isTool = req.get('flowise-tool') === 'true'
|
||||
const isEvaluation: boolean = req.headers['X-Flowise-Evaluation'] || req.body.evaluation
|
||||
let evaluationRunId = ''
|
||||
if (isEvaluation) {
|
||||
evaluationRunId = req.body.evaluationRunId
|
||||
if (evaluationRunId) {
|
||||
const newEval = {
|
||||
evaluation: {
|
||||
status: true,
|
||||
evaluationRunId
|
||||
}
|
||||
}
|
||||
chatflow.analytic = JSON.stringify(newEval)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Validate API Key if its external API request
|
||||
@@ -859,6 +927,28 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||
}
|
||||
}
|
||||
|
||||
// This can be public API, so we can only get orgId from the chatflow
|
||||
const chatflowWorkspaceId = chatflow.workspaceId
|
||||
const workspace = await appServer.AppDataSource.getRepository(Workspace).findOneBy({
|
||||
id: chatflowWorkspaceId
|
||||
})
|
||||
if (!workspace) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Workspace ${chatflowWorkspaceId} not found`)
|
||||
}
|
||||
const workspaceId = workspace.id
|
||||
|
||||
const org = await appServer.AppDataSource.getRepository(Organization).findOneBy({
|
||||
id: workspace.organizationId
|
||||
})
|
||||
if (!org) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Organization ${workspace.organizationId} not found`)
|
||||
}
|
||||
|
||||
const orgId = org.id
|
||||
const subscriptionId = org.subscriptionId as string
|
||||
|
||||
await checkPredictions(orgId, subscriptionId, appServer.usageCacheManager)
|
||||
|
||||
const executeData: IExecuteFlowParams = {
|
||||
incomingInput, // Use the defensively created incomingInput variable
|
||||
chatflow,
|
||||
@@ -866,18 +956,24 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||
baseURL,
|
||||
isInternal,
|
||||
files,
|
||||
isEvaluation,
|
||||
evaluationRunId,
|
||||
appDataSource: appServer.AppDataSource,
|
||||
sseStreamer: appServer.sseStreamer,
|
||||
telemetry: appServer.telemetry,
|
||||
cachePool: appServer.cachePool,
|
||||
componentNodes: appServer.nodesPool.componentNodes,
|
||||
isTool // used to disable streaming if incoming request its from ChatflowTool
|
||||
isTool, // used to disable streaming if incoming request its from ChatflowTool
|
||||
usageCacheManager: appServer.usageCacheManager,
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId
|
||||
}
|
||||
|
||||
if (process.env.MODE === MODE.QUEUE) {
|
||||
const predictionQueue = appServer.queueManager.getQueue('prediction')
|
||||
const job = await predictionQueue.addJob(omit(executeData, OMIT_QUEUE_JOB_DATA))
|
||||
logger.debug(`[server]: Job added to queue: ${job.id}`)
|
||||
logger.debug(`[server]: [${orgId}]: Job added to queue: ${job.id}`)
|
||||
|
||||
const queueEvents = predictionQueue.getQueueEvents()
|
||||
const result = await job.waitUntilFinished(queueEvents)
|
||||
@@ -885,7 +981,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||
if (!result) {
|
||||
throw new Error('Job execution failed')
|
||||
}
|
||||
|
||||
await updatePredictionsUsage(orgId, subscriptionId, workspaceId, appServer.usageCacheManager)
|
||||
incrementSuccessMetricCounter(appServer.metricsProvider, isInternal, isAgentFlow)
|
||||
return result
|
||||
} else {
|
||||
@@ -893,9 +989,11 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||
const signal = new AbortController()
|
||||
appServer.abortControllerPool.add(abortControllerId, signal)
|
||||
executeData.signal = signal
|
||||
|
||||
const result = await executeFlow(executeData)
|
||||
|
||||
appServer.abortControllerPool.remove(abortControllerId)
|
||||
await updatePredictionsUsage(orgId, subscriptionId, workspaceId, appServer.usageCacheManager)
|
||||
incrementSuccessMetricCounter(appServer.metricsProvider, isInternal, isAgentFlow)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import Auth0SSO from '../enterprise/sso/Auth0SSO'
|
||||
import AzureSSO from '../enterprise/sso/AzureSSO'
|
||||
import GithubSSO from '../enterprise/sso/GithubSSO'
|
||||
import GoogleSSO from '../enterprise/sso/GoogleSSO'
|
||||
|
||||
export const WHITELIST_URLS = [
|
||||
'/api/v1/verify/apikey/',
|
||||
'/api/v1/chatflows/apikey/',
|
||||
@@ -19,10 +24,63 @@ export const WHITELIST_URLS = [
|
||||
'/api/v1/version',
|
||||
'/api/v1/attachments',
|
||||
'/api/v1/metrics',
|
||||
'/api/v1/nvidia-nim'
|
||||
'/api/v1/nvidia-nim',
|
||||
'/api/v1/auth/resolve',
|
||||
'/api/v1/auth/login',
|
||||
'/api/v1/auth/refreshToken',
|
||||
'/api/v1/settings',
|
||||
'/api/v1/account/logout',
|
||||
'/api/v1/account/verify',
|
||||
'/api/v1/account/register',
|
||||
'/api/v1/account/resend-verification',
|
||||
'/api/v1/account/forgot-password',
|
||||
'/api/v1/account/reset-password',
|
||||
'/api/v1/account/basic-auth',
|
||||
'/api/v1/loginmethod',
|
||||
'/api/v1/pricing',
|
||||
'/api/v1/user/test',
|
||||
AzureSSO.LOGIN_URI,
|
||||
AzureSSO.LOGOUT_URI,
|
||||
AzureSSO.CALLBACK_URI,
|
||||
GoogleSSO.LOGIN_URI,
|
||||
GoogleSSO.LOGOUT_URI,
|
||||
GoogleSSO.CALLBACK_URI,
|
||||
Auth0SSO.LOGIN_URI,
|
||||
Auth0SSO.LOGOUT_URI,
|
||||
Auth0SSO.CALLBACK_URI,
|
||||
GithubSSO.LOGIN_URI,
|
||||
GithubSSO.LOGOUT_URI,
|
||||
GithubSSO.CALLBACK_URI
|
||||
]
|
||||
|
||||
export const OMIT_QUEUE_JOB_DATA = ['componentNodes', 'appDataSource', 'sseStreamer', 'telemetry', 'cachePool']
|
||||
export const enum GeneralErrorMessage {
|
||||
UNAUTHORIZED = 'Unauthorized',
|
||||
UNHANDLED_EDGE_CASE = 'Unhandled Edge Case',
|
||||
INVALID_PASSWORD = 'Invalid Password',
|
||||
NOT_ALLOWED_TO_DELETE_OWNER = 'Not Allowed To Delete Owner',
|
||||
INTERNAL_SERVER_ERROR = 'Internal Server Error'
|
||||
}
|
||||
|
||||
export const enum GeneralSuccessMessage {
|
||||
CREATED = 'Resource Created Successful',
|
||||
UPDATED = 'Resource Updated Successful',
|
||||
DELETED = 'Resource Deleted Successful',
|
||||
FETCHED = 'Resource Fetched Successful',
|
||||
LOGGED_IN = 'Login Successful',
|
||||
LOGGED_OUT = 'Logout Successful'
|
||||
}
|
||||
|
||||
export const DOCUMENT_STORE_BASE_FOLDER = 'docustore'
|
||||
|
||||
export const OMIT_QUEUE_JOB_DATA = [
|
||||
'componentNodes',
|
||||
'appDataSource',
|
||||
'sseStreamer',
|
||||
'telemetry',
|
||||
'cachePool',
|
||||
'usageCacheManager',
|
||||
'abortControllerPool'
|
||||
]
|
||||
|
||||
export const INPUT_PARAMS_TYPE = [
|
||||
'asyncOptions',
|
||||
@@ -42,3 +100,13 @@ export const INPUT_PARAMS_TYPE = [
|
||||
'folder',
|
||||
'tabs'
|
||||
]
|
||||
|
||||
export const LICENSE_QUOTAS = {
|
||||
// Renew per month
|
||||
PREDICTIONS_LIMIT: 'quota:predictions',
|
||||
// Static
|
||||
FLOWS_LIMIT: 'quota:flows',
|
||||
USERS_LIMIT: 'quota:users',
|
||||
STORAGE_LIMIT: 'quota:storage',
|
||||
ADDITIONAL_SEATS_LIMIT: 'quota:additionalSeats'
|
||||
} as const
|
||||
|
||||
@@ -12,9 +12,12 @@ import {
|
||||
} from 'flowise-components'
|
||||
import { getRunningExpressApp } from './getRunningExpressApp'
|
||||
import { getErrorMessage } from '../errors/utils'
|
||||
import { checkStorage, updateStorageUsage } from './quotaUsage'
|
||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||
import { Workspace } from '../enterprise/database/entities/workspace.entity'
|
||||
import { Organization } from '../enterprise/database/entities/organization.entity'
|
||||
import { InternalFlowiseError } from '../errors/internalFlowiseError'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||
|
||||
/**
|
||||
* Create attachment
|
||||
@@ -46,6 +49,32 @@ export const createFileAttachment = async (req: Request) => {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowid} not found`)
|
||||
}
|
||||
|
||||
let orgId = req.user?.activeOrganizationId || ''
|
||||
let workspaceId = req.user?.activeWorkspaceId || ''
|
||||
let subscriptionId = req.user?.activeOrganizationSubscriptionId || ''
|
||||
|
||||
// This is one of the WHITELIST_URLS, API can be public and there might be no req.user
|
||||
if (!orgId || !workspaceId) {
|
||||
const chatflowWorkspaceId = chatflow.workspaceId
|
||||
const workspace = await appServer.AppDataSource.getRepository(Workspace).findOneBy({
|
||||
id: chatflowWorkspaceId
|
||||
})
|
||||
if (!workspace) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Workspace ${chatflowWorkspaceId} not found`)
|
||||
}
|
||||
workspaceId = workspace.id
|
||||
|
||||
const org = await appServer.AppDataSource.getRepository(Organization).findOneBy({
|
||||
id: workspace.organizationId
|
||||
})
|
||||
if (!org) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Organization ${workspace.organizationId} not found`)
|
||||
}
|
||||
|
||||
orgId = org.id
|
||||
subscriptionId = org.subscriptionId as string
|
||||
}
|
||||
|
||||
// Parse chatbot configuration to get file upload settings
|
||||
let pdfConfig = {
|
||||
usage: 'perPage',
|
||||
@@ -75,6 +104,7 @@ export const createFileAttachment = async (req: Request) => {
|
||||
const fileLoaderNodeInstance = new fileLoaderNodeModule.nodeClass()
|
||||
const options = {
|
||||
retrieveAttachmentChatId: true,
|
||||
orgId,
|
||||
chatflowid,
|
||||
chatId
|
||||
}
|
||||
@@ -83,13 +113,22 @@ export const createFileAttachment = async (req: Request) => {
|
||||
if (files.length) {
|
||||
const isBase64 = req.body.base64
|
||||
for (const file of files) {
|
||||
await checkStorage(orgId, subscriptionId, appServer.usageCacheManager)
|
||||
|
||||
const fileBuffer = await getFileFromUpload(file.path ?? file.key)
|
||||
const fileNames: string[] = []
|
||||
|
||||
// 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, chatId)
|
||||
const { path: storagePath, totalSize } = await addArrayFilesToStorage(
|
||||
file.mimetype,
|
||||
fileBuffer,
|
||||
file.originalname,
|
||||
fileNames,
|
||||
orgId,
|
||||
chatflowid,
|
||||
chatId
|
||||
)
|
||||
await updateStorageUsage(orgId, workspaceId, totalSize, appServer.usageCacheManager)
|
||||
|
||||
const fileInputFieldFromMimeType = mapMimeTypeToInputField(file.mimetype)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||
import { IReactFlowObject } from '../Interface'
|
||||
import { addBase64FilesToStorage } from 'flowise-components'
|
||||
import { checkStorage, updateStorageUsage } from './quotaUsage'
|
||||
import { UsageCacheManager } from '../UsageCacheManager'
|
||||
|
||||
export const containsBase64File = (chatflow: ChatFlow) => {
|
||||
const parsedFlowData: IReactFlowObject = JSON.parse(chatflow.flowData)
|
||||
@@ -46,11 +48,19 @@ export const containsBase64File = (chatflow: ChatFlow) => {
|
||||
return found
|
||||
}
|
||||
|
||||
export const updateFlowDataWithFilePaths = async (chatflowid: string, flowData: string) => {
|
||||
export const updateFlowDataWithFilePaths = async (
|
||||
chatflowid: string,
|
||||
flowData: string,
|
||||
orgId: string,
|
||||
workspaceId: string,
|
||||
subscriptionId: string,
|
||||
usageCacheManager: UsageCacheManager
|
||||
) => {
|
||||
try {
|
||||
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
|
||||
const re = new RegExp('^data.*;base64', 'i')
|
||||
const nodes = parsedFlowData.nodes
|
||||
|
||||
for (let j = 0; j < nodes.length; j++) {
|
||||
const node = nodes[j]
|
||||
if (node.data.category !== 'Document Loaders') {
|
||||
@@ -75,21 +85,26 @@ export const updateFlowDataWithFilePaths = async (chatflowid: string, flowData:
|
||||
for (let j = 0; j < files.length; j++) {
|
||||
const file = files[j]
|
||||
if (re.test(file)) {
|
||||
node.data.inputs[key] = await addBase64FilesToStorage(file, chatflowid, fileNames)
|
||||
await checkStorage(orgId, subscriptionId, usageCacheManager)
|
||||
const { path, totalSize } = await addBase64FilesToStorage(file, chatflowid, fileNames, orgId)
|
||||
node.data.inputs[key] = path
|
||||
await updateStorageUsage(orgId, workspaceId, totalSize, usageCacheManager)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
continue
|
||||
}
|
||||
} else if (re.test(input)) {
|
||||
node.data.inputs[key] = await addBase64FilesToStorage(input, chatflowid, fileNames)
|
||||
await checkStorage(orgId, subscriptionId, usageCacheManager)
|
||||
const { path, totalSize } = await addBase64FilesToStorage(input, chatflowid, fileNames, orgId)
|
||||
node.data.inputs[key] = path
|
||||
await updateStorageUsage(orgId, workspaceId, totalSize, usageCacheManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(parsedFlowData)
|
||||
} catch (e) {
|
||||
return ''
|
||||
} catch (e: any) {
|
||||
throw new Error(`Error updating flow data with file paths: ${e.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { MoreThanOrEqual, LessThanOrEqual, Between, In } from 'typeorm'
|
||||
import { ChatMessageRatingType, ChatType } from '../Interface'
|
||||
import { ChatMessage } from '../database/entities/ChatMessage'
|
||||
import { ChatMessageFeedback } from '../database/entities/ChatMessageFeedback'
|
||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
||||
import { aMonthAgo } from '.'
|
||||
|
||||
@@ -30,6 +31,7 @@ interface GetChatMessageParams {
|
||||
messageId?: string
|
||||
feedback?: boolean
|
||||
feedbackTypes?: ChatMessageRatingType[]
|
||||
activeWorkspaceId?: string
|
||||
}
|
||||
|
||||
export const utilGetChatMessage = async ({
|
||||
@@ -43,10 +45,21 @@ export const utilGetChatMessage = async ({
|
||||
endDate,
|
||||
messageId,
|
||||
feedback,
|
||||
feedbackTypes
|
||||
feedbackTypes,
|
||||
activeWorkspaceId
|
||||
}: GetChatMessageParams): Promise<ChatMessage[]> => {
|
||||
const appServer = getRunningExpressApp()
|
||||
|
||||
// Check if chatflow workspaceId is same as activeWorkspaceId
|
||||
if (activeWorkspaceId) {
|
||||
const chatflow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
|
||||
id: chatflowid
|
||||
})
|
||||
if (chatflow?.workspaceId !== activeWorkspaceId) {
|
||||
throw new Error('Unauthorized access')
|
||||
}
|
||||
}
|
||||
|
||||
if (feedback) {
|
||||
const query = await appServer.AppDataSource.getRepository(ChatMessage).createQueryBuilder('chat_message')
|
||||
|
||||
@@ -102,6 +115,7 @@ export const utilGetChatMessage = async ({
|
||||
}
|
||||
|
||||
let createdDateQuery
|
||||
|
||||
if (startDate || endDate) {
|
||||
if (startDate && endDate) {
|
||||
createdDateQuery = Between(new Date(startDate), new Date(endDate))
|
||||
@@ -112,7 +126,7 @@ export const utilGetChatMessage = async ({
|
||||
}
|
||||
}
|
||||
|
||||
return await appServer.AppDataSource.getRepository(ChatMessage).find({
|
||||
const messages = await appServer.AppDataSource.getRepository(ChatMessage).find({
|
||||
where: {
|
||||
chatflowid,
|
||||
chatType: chatTypes?.length ? In(chatTypes) : undefined,
|
||||
@@ -129,4 +143,6 @@ export const utilGetChatMessage = async ({
|
||||
createdDate: sortOrder === 'DESC' ? 'DESC' : 'ASC'
|
||||
}
|
||||
})
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import logger from './logger'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import {
|
||||
IChatFlow,
|
||||
IComponentCredentials,
|
||||
@@ -63,6 +64,8 @@ import {
|
||||
SecretsManagerClient,
|
||||
SecretsManagerClientConfig
|
||||
} from '@aws-sdk/client-secrets-manager'
|
||||
import { checkStorage, updateStorageUsage } from './quotaUsage'
|
||||
import { UsageCacheManager } from '../UsageCacheManager'
|
||||
|
||||
export const QUESTION_VAR_PREFIX = 'question'
|
||||
export const FILE_ATTACHMENT_PREFIX = 'file_attachment'
|
||||
@@ -203,6 +206,22 @@ export const constructGraphs = (
|
||||
return { graph, nodeDependencies }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get starting node and check if flow is valid
|
||||
* @param {INodeDependencies} nodeDependencies
|
||||
*/
|
||||
export const getStartingNode = (nodeDependencies: INodeDependencies) => {
|
||||
// Find starting node
|
||||
const startingNodeIds = [] as string[]
|
||||
Object.keys(nodeDependencies).forEach((nodeId) => {
|
||||
if (nodeDependencies[nodeId] === 0) {
|
||||
startingNodeIds.push(nodeId)
|
||||
}
|
||||
})
|
||||
|
||||
return { startingNodeIds }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get starting nodes and check if flow is valid
|
||||
* @param {INodeDependencies} graph
|
||||
@@ -239,22 +258,6 @@ export const getStartingNodes = (graph: INodeDirectedGraph, endNodeId: string) =
|
||||
return { startingNodeIds, depthQueue: depthQueueReversed }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get starting node and check if flow is valid
|
||||
* @param {INodeDependencies} nodeDependencies
|
||||
*/
|
||||
export const getStartingNode = (nodeDependencies: INodeDependencies) => {
|
||||
// Find starting node
|
||||
const startingNodeIds = [] as string[]
|
||||
Object.keys(nodeDependencies).forEach((nodeId) => {
|
||||
if (nodeDependencies[nodeId] === 0) {
|
||||
startingNodeIds.push(nodeId)
|
||||
}
|
||||
})
|
||||
|
||||
return { startingNodeIds }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all connected nodes from startnode
|
||||
* @param {INodeDependencies} graph
|
||||
@@ -497,6 +500,10 @@ type BuildFlowParams = {
|
||||
stopNodeId?: string
|
||||
uploads?: IFileUpload[]
|
||||
baseURL?: string
|
||||
orgId?: string
|
||||
workspaceId?: string
|
||||
subscriptionId?: string
|
||||
usageCacheManager?: UsageCacheManager
|
||||
uploadedFilesContent?: string
|
||||
}
|
||||
|
||||
@@ -528,7 +535,11 @@ export const buildFlow = async ({
|
||||
isUpsert,
|
||||
stopNodeId,
|
||||
uploads,
|
||||
baseURL
|
||||
baseURL,
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId,
|
||||
usageCacheManager
|
||||
}: BuildFlowParams) => {
|
||||
const flowNodes = cloneDeep(reactFlowNodes)
|
||||
|
||||
@@ -591,8 +602,11 @@ export const buildFlow = async ({
|
||||
)
|
||||
|
||||
if (isUpsert && stopNodeId && nodeId === stopNodeId) {
|
||||
logger.debug(`[server]: Upserting ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
|
||||
logger.debug(`[server]: [${orgId}]: Upserting ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
|
||||
const indexResult = await newNodeInstance.vectorStoreMethods!['upsert']!.call(newNodeInstance, reactFlowNodeData, {
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId,
|
||||
chatId,
|
||||
sessionId,
|
||||
chatflowid,
|
||||
@@ -602,12 +616,13 @@ export const buildFlow = async ({
|
||||
appDataSource,
|
||||
databaseEntities,
|
||||
cachePool,
|
||||
usageCacheManager,
|
||||
dynamicVariables,
|
||||
uploads,
|
||||
baseURL
|
||||
})
|
||||
if (indexResult) upsertHistory['result'] = indexResult
|
||||
logger.debug(`[server]: Finished upserting ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
|
||||
logger.debug(`[server]: [${orgId}]: Finished upserting ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
|
||||
break
|
||||
} else if (
|
||||
!isUpsert &&
|
||||
@@ -616,9 +631,12 @@ export const buildFlow = async ({
|
||||
) {
|
||||
initializedNodes.add(nodeId)
|
||||
} else {
|
||||
logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
|
||||
logger.debug(`[server]: [${orgId}]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
|
||||
const finalQuestion = uploadedFilesContent ? `${uploadedFilesContent}\n\n${question}` : question
|
||||
let outputResult = await newNodeInstance.init(reactFlowNodeData, finalQuestion, {
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId,
|
||||
chatId,
|
||||
sessionId,
|
||||
chatflowid,
|
||||
@@ -627,11 +645,14 @@ export const buildFlow = async ({
|
||||
appDataSource,
|
||||
databaseEntities,
|
||||
cachePool,
|
||||
usageCacheManager,
|
||||
isUpsert,
|
||||
dynamicVariables,
|
||||
uploads,
|
||||
baseURL,
|
||||
componentNodes: componentNodes as ICommonObject
|
||||
componentNodes,
|
||||
updateStorageUsage,
|
||||
checkStorage
|
||||
})
|
||||
|
||||
// Save dynamic variables
|
||||
@@ -676,11 +697,11 @@ export const buildFlow = async ({
|
||||
|
||||
flowNodes[nodeIndex].data.instance = outputResult
|
||||
|
||||
logger.debug(`[server]: Finished initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
|
||||
logger.debug(`[server]: [${orgId}]: Finished initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
|
||||
initializedNodes.add(reactFlowNode.data.id)
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error(e)
|
||||
logger.error(`[server]: [${orgId}]:`, e)
|
||||
throw new Error(e)
|
||||
}
|
||||
|
||||
@@ -744,6 +765,7 @@ export const clearSessionMemory = async (
|
||||
componentNodes: IComponentNodes,
|
||||
chatId: string,
|
||||
appDataSource: DataSource,
|
||||
orgId?: string,
|
||||
sessionId?: string,
|
||||
memoryType?: string,
|
||||
isClearFromViewMessageDialog?: string
|
||||
@@ -757,7 +779,7 @@ export const clearSessionMemory = async (
|
||||
const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string
|
||||
const nodeModule = await import(nodeInstanceFilePath)
|
||||
const newNodeInstance = new nodeModule.nodeClass()
|
||||
const options: ICommonObject = { chatId, appDataSource, databaseEntities, logger }
|
||||
const options: ICommonObject = { orgId, chatId, appDataSource, databaseEntities, logger }
|
||||
|
||||
// SessionId always take priority first because it is the sessionId used for 3rd party memory node
|
||||
if (sessionId && node.data.inputs) {
|
||||
@@ -1261,7 +1283,6 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component
|
||||
for (const flowNode of reactFlowNodes) {
|
||||
for (const inputParam of flowNode.data.inputParams) {
|
||||
let obj: IOverrideConfig | undefined
|
||||
|
||||
if (inputParam.type === 'file') {
|
||||
obj = {
|
||||
node: flowNode.data.label,
|
||||
@@ -1500,7 +1521,6 @@ export const decryptCredentialData = async (
|
||||
|
||||
if (USE_AWS_SECRETS_MANAGER && secretsManagerClient) {
|
||||
try {
|
||||
logger.info(`[server]: Reading AWS Secret: ${encryptedData}`)
|
||||
if (encryptedData.startsWith('FlowiseCredential_')) {
|
||||
const command = new GetSecretValueCommand({ SecretId: encryptedData })
|
||||
const response = await secretsManagerClient.send(command)
|
||||
@@ -1567,6 +1587,10 @@ export const transformToCredentialEntity = async (body: ICredentialReqBody): Pro
|
||||
const newCredential = new Credential()
|
||||
Object.assign(newCredential, credentialBody)
|
||||
|
||||
if (body.workspaceId) {
|
||||
newCredential.workspaceId = body.workspaceId
|
||||
}
|
||||
|
||||
return newCredential
|
||||
}
|
||||
|
||||
@@ -1734,21 +1758,6 @@ export const getTelemetryFlowObj = (nodes: IReactFlowNode[], edges: IReactFlowEd
|
||||
return { nodes: nodeData, edges: edgeData }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user settings file
|
||||
* TODO: move env variables to settings json file, easier configuration
|
||||
*/
|
||||
export const getUserSettingsFilePath = () => {
|
||||
if (process.env.SECRETKEY_PATH) return path.join(process.env.SECRETKEY_PATH, 'settings.json')
|
||||
const checkPaths = [path.join(getUserHome(), '.flowise', 'settings.json')]
|
||||
for (const checkPath of checkPaths) {
|
||||
if (fs.existsSync(checkPath)) {
|
||||
return checkPath
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Get app current version
|
||||
*/
|
||||
@@ -1815,14 +1824,8 @@ export const getUploadPath = (): string => {
|
||||
: 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 function generateId() {
|
||||
return uuidv4()
|
||||
}
|
||||
|
||||
export const getMulterStorage = () => {
|
||||
@@ -1837,10 +1840,10 @@ export const getMulterStorage = () => {
|
||||
s3: s3Client,
|
||||
bucket: Bucket,
|
||||
metadata: function (req, file, cb) {
|
||||
cb(null, { fieldName: file.fieldname, originalName: file.originalname, orgId: getOrgId() })
|
||||
cb(null, { fieldName: file.fieldname, originalName: file.originalname })
|
||||
},
|
||||
key: function (req, file, cb) {
|
||||
cb(null, `${getOrgId()}/${Date.now().toString()}`)
|
||||
cb(null, `${generateId()}`)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1852,7 +1855,7 @@ export const getMulterStorage = () => {
|
||||
bucket: process.env.GOOGLE_CLOUD_STORAGE_BUCKET_NAME,
|
||||
keyFilename: process.env.GOOGLE_CLOUD_STORAGE_CREDENTIAL,
|
||||
uniformBucketLevelAccess: Boolean(process.env.GOOGLE_CLOUD_UNIFORM_BUCKET_ACCESS) ?? true,
|
||||
destination: `uploads/${getOrgId()}`
|
||||
destination: `uploads/${generateId()}`
|
||||
})
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { hostname } from 'node:os'
|
||||
import config from './config' // should be replaced by node-config or similar
|
||||
import { createLogger, transports, format } from 'winston'
|
||||
import { NextFunction, Request, Response } from 'express'
|
||||
import DailyRotateFile from 'winston-daily-rotate-file'
|
||||
import { S3ClientConfig } from '@aws-sdk/client-s3'
|
||||
import { LoggingWinston } from '@google-cloud/logging-winston'
|
||||
|
||||
@@ -114,13 +115,11 @@ const logger = createLogger({
|
||||
new transports.Console(),
|
||||
...(!process.env.STORAGE_TYPE || process.env.STORAGE_TYPE === 'local'
|
||||
? [
|
||||
new transports.File({
|
||||
filename: path.join(logDir, config.logging.server.filename ?? 'server.log'),
|
||||
new DailyRotateFile({
|
||||
filename: path.join(logDir, config.logging.server.filename ?? 'server-%DATE%.log'),
|
||||
datePattern: 'YYYY-MM-DD-HH',
|
||||
maxSize: '20m',
|
||||
level: config.logging.server.level ?? 'info'
|
||||
}),
|
||||
new transports.File({
|
||||
filename: path.join(logDir, config.logging.server.errorFilename ?? 'server-error.log'),
|
||||
level: 'error' // Log only errors to this file
|
||||
})
|
||||
]
|
||||
: []),
|
||||
@@ -134,13 +133,7 @@ const logger = createLogger({
|
||||
...(process.env.STORAGE_TYPE === 'gcs' ? [gcsServerStream] : [])
|
||||
],
|
||||
exceptionHandlers: [
|
||||
...(!process.env.STORAGE_TYPE || process.env.STORAGE_TYPE === 'local'
|
||||
? [
|
||||
new transports.File({
|
||||
filename: path.join(logDir, config.logging.server.errorFilename ?? 'server-error.log')
|
||||
})
|
||||
]
|
||||
: []),
|
||||
...(process.env.DEBUG && process.env.DEBUG === 'true' ? [new transports.Console()] : []),
|
||||
...(process.env.STORAGE_TYPE === 's3'
|
||||
? [
|
||||
new transports.Stream({
|
||||
@@ -151,13 +144,7 @@ const logger = createLogger({
|
||||
...(process.env.STORAGE_TYPE === 'gcs' ? [gcsErrorStream] : [])
|
||||
],
|
||||
rejectionHandlers: [
|
||||
...(!process.env.STORAGE_TYPE || process.env.STORAGE_TYPE === 'local'
|
||||
? [
|
||||
new transports.File({
|
||||
filename: path.join(logDir, config.logging.server.errorFilename ?? 'server-error.log')
|
||||
})
|
||||
]
|
||||
: []),
|
||||
...(process.env.DEBUG && process.env.DEBUG === 'true' ? [new transports.Console()] : []),
|
||||
...(process.env.STORAGE_TYPE === 's3'
|
||||
? [
|
||||
new transports.Stream({
|
||||
@@ -171,7 +158,14 @@ const logger = createLogger({
|
||||
|
||||
export function expressRequestLogger(req: Request, res: Response, next: NextFunction): void {
|
||||
const unwantedLogURLs = ['/api/v1/node-icon/', '/api/v1/components-credentials-icon/', '/api/v1/ping']
|
||||
|
||||
if (/\/api\/v1\//i.test(req.url) && !unwantedLogURLs.some((url) => new RegExp(url, 'i').test(req.url))) {
|
||||
// Create a sanitized copy of the request body
|
||||
const sanitizedBody = { ...req.body }
|
||||
if (sanitizedBody.password) {
|
||||
sanitizedBody.password = '********'
|
||||
}
|
||||
|
||||
const fileLogger = createLogger({
|
||||
format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.json(), errors({ stack: true })),
|
||||
defaultMeta: {
|
||||
@@ -179,13 +173,14 @@ export function expressRequestLogger(req: Request, res: Response, next: NextFunc
|
||||
request: {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
body: req.body,
|
||||
body: sanitizedBody, // Use sanitized body instead of raw body
|
||||
query: req.query,
|
||||
params: req.params,
|
||||
headers: req.headers
|
||||
}
|
||||
},
|
||||
transports: [
|
||||
...(process.env.DEBUG && process.env.DEBUG === 'true' ? [new transports.Console()] : []),
|
||||
...(!process.env.STORAGE_TYPE || process.env.STORAGE_TYPE === 'local'
|
||||
? [
|
||||
new transports.File({
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { InternalFlowiseError } from '../errors/internalFlowiseError'
|
||||
import { UsageCacheManager } from '../UsageCacheManager'
|
||||
import { LICENSE_QUOTAS } from './constants'
|
||||
import logger from './logger'
|
||||
|
||||
type UsageType = 'flows' | 'users'
|
||||
export const ENTERPRISE_FEATURE_FLAGS = [
|
||||
//'feat:account', // Only for Cloud
|
||||
'feat:datasets',
|
||||
'feat:evaluations',
|
||||
'feat:evaluators',
|
||||
'feat:files',
|
||||
'feat:login-activity',
|
||||
'feat:users',
|
||||
'feat:workspaces',
|
||||
'feat:logs',
|
||||
'feat:roles',
|
||||
'feat:sso-config'
|
||||
]
|
||||
|
||||
export const getCurrentUsage = async (orgId: string, subscriptionId: string, usageCacheManager: UsageCacheManager) => {
|
||||
try {
|
||||
if (!usageCacheManager || !subscriptionId || !orgId) return
|
||||
|
||||
const currentStorageUsage = (await usageCacheManager.get(`storage:${orgId}`)) || 0
|
||||
const currentPredictionsUsage = (await usageCacheManager.get(`predictions:${orgId}`)) || 0
|
||||
|
||||
const quotas = await usageCacheManager.getQuotas(subscriptionId)
|
||||
const storageLimit = quotas[LICENSE_QUOTAS.STORAGE_LIMIT]
|
||||
const predLimit = quotas[LICENSE_QUOTAS.PREDICTIONS_LIMIT]
|
||||
|
||||
return {
|
||||
predictions: {
|
||||
usage: currentPredictionsUsage,
|
||||
limit: predLimit
|
||||
},
|
||||
storage: {
|
||||
usage: currentStorageUsage,
|
||||
limit: storageLimit
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[getCurrentUsage] Error getting usage: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// For usage that doesn't renew per month, we just get the count from database and check
|
||||
export const checkUsageLimit = async (
|
||||
type: UsageType,
|
||||
subscriptionId: string,
|
||||
usageCacheManager: UsageCacheManager,
|
||||
currentUsage: number
|
||||
) => {
|
||||
if (!usageCacheManager || !subscriptionId) return
|
||||
|
||||
const quotas = await usageCacheManager.getQuotas(subscriptionId)
|
||||
|
||||
let limit = -1
|
||||
switch (type) {
|
||||
case 'flows':
|
||||
limit = quotas[LICENSE_QUOTAS.FLOWS_LIMIT]
|
||||
break
|
||||
case 'users':
|
||||
limit = quotas[LICENSE_QUOTAS.USERS_LIMIT] + (Math.max(quotas[LICENSE_QUOTAS.ADDITIONAL_SEATS_LIMIT], 0) || 0)
|
||||
break
|
||||
}
|
||||
|
||||
if (limit === -1) return
|
||||
|
||||
if (currentUsage > limit) {
|
||||
throw new InternalFlowiseError(StatusCodes.TOO_MANY_REQUESTS, `Limit exceeded: ${type}`)
|
||||
}
|
||||
}
|
||||
|
||||
// As predictions limit renew per month, we set to cache with 1 month TTL
|
||||
export const updatePredictionsUsage = async (
|
||||
orgId: string,
|
||||
subscriptionId: string,
|
||||
_: string = '',
|
||||
usageCacheManager?: UsageCacheManager
|
||||
) => {
|
||||
if (!usageCacheManager) return
|
||||
|
||||
const quotas = await usageCacheManager.getQuotas(subscriptionId)
|
||||
const predictionsLimit = quotas[LICENSE_QUOTAS.PREDICTIONS_LIMIT]
|
||||
|
||||
let currentPredictions = 0
|
||||
const existingPredictions = await usageCacheManager.get(`predictions:${orgId}`)
|
||||
if (existingPredictions) {
|
||||
currentPredictions = 1 + (existingPredictions as number) > predictionsLimit ? predictionsLimit : 1 + (existingPredictions as number)
|
||||
} else {
|
||||
currentPredictions = 1
|
||||
}
|
||||
|
||||
const currentTTL = await usageCacheManager.getTTL(`predictions:${orgId}`)
|
||||
if (currentTTL) {
|
||||
const currentTimestamp = Date.now()
|
||||
const timeLeft = currentTTL - currentTimestamp
|
||||
usageCacheManager.set(`predictions:${orgId}`, currentPredictions, timeLeft)
|
||||
} else {
|
||||
const subscriptionDetails = await usageCacheManager.getSubscriptionDetails(subscriptionId)
|
||||
if (subscriptionDetails && subscriptionDetails.created) {
|
||||
const MS_PER_DAY = 24 * 60 * 60 * 1000
|
||||
const DAYS = 30
|
||||
const approximateMonthMs = DAYS * MS_PER_DAY
|
||||
|
||||
// Calculate time elapsed since subscription creation
|
||||
const createdTimestamp = subscriptionDetails.created * 1000 // Convert to milliseconds if timestamp is in seconds
|
||||
const currentTimestamp = Date.now()
|
||||
const timeElapsed = currentTimestamp - createdTimestamp
|
||||
|
||||
// Calculate remaining time in the current month period
|
||||
const timeLeft = approximateMonthMs - (timeElapsed % approximateMonthMs)
|
||||
|
||||
usageCacheManager.set(`predictions:${orgId}`, currentPredictions, timeLeft)
|
||||
} else {
|
||||
// Fallback to default 30 days if no creation date
|
||||
const MS_PER_DAY = 24 * 60 * 60 * 1000
|
||||
const DAYS = 30
|
||||
const approximateMonthMs = DAYS * MS_PER_DAY
|
||||
usageCacheManager.set(`predictions:${orgId}`, currentPredictions, approximateMonthMs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const checkPredictions = async (orgId: string, subscriptionId: string, usageCacheManager: UsageCacheManager) => {
|
||||
if (!usageCacheManager || !subscriptionId) return
|
||||
|
||||
const currentPredictions: number = (await usageCacheManager.get(`predictions:${orgId}`)) || 0
|
||||
|
||||
const quotas = await usageCacheManager.getQuotas(subscriptionId)
|
||||
const predictionsLimit = quotas[LICENSE_QUOTAS.PREDICTIONS_LIMIT]
|
||||
if (predictionsLimit === -1) return
|
||||
|
||||
if (currentPredictions >= predictionsLimit) {
|
||||
throw new InternalFlowiseError(StatusCodes.TOO_MANY_REQUESTS, 'Predictions limit exceeded')
|
||||
}
|
||||
|
||||
return {
|
||||
usage: currentPredictions,
|
||||
limit: predictionsLimit
|
||||
}
|
||||
}
|
||||
|
||||
// Storage does not renew per month nor do we store the total size in database, so we just store the total size in cache
|
||||
export const updateStorageUsage = (orgId: string, _: string = '', totalSize: number, usageCacheManager?: UsageCacheManager) => {
|
||||
if (!usageCacheManager) return
|
||||
usageCacheManager.set(`storage:${orgId}`, totalSize)
|
||||
}
|
||||
|
||||
export const checkStorage = async (orgId: string, subscriptionId: string, usageCacheManager: UsageCacheManager) => {
|
||||
if (!usageCacheManager || !subscriptionId) return
|
||||
|
||||
let currentStorageUsage = 0
|
||||
currentStorageUsage = (await usageCacheManager.get(`storage:${orgId}`)) || 0
|
||||
|
||||
const quotas = await usageCacheManager.getQuotas(subscriptionId)
|
||||
const storageLimit = quotas[LICENSE_QUOTAS.STORAGE_LIMIT]
|
||||
if (storageLimit === -1) return
|
||||
|
||||
if (currentStorageUsage >= storageLimit) {
|
||||
throw new InternalFlowiseError(StatusCodes.TOO_MANY_REQUESTS, 'Storage limit exceeded')
|
||||
}
|
||||
|
||||
return {
|
||||
usage: currentStorageUsage,
|
||||
limit: storageLimit
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { PostHog } from 'posthog-node'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { getUserHome, getUserSettingsFilePath } from '.'
|
||||
import { getAppVersion } from '../utils'
|
||||
|
||||
export enum TelemetryEventType {
|
||||
'USER_CREATED' = 'user_created',
|
||||
'ORGANIZATION_CREATED' = 'organization_created'
|
||||
}
|
||||
|
||||
export class Telemetry {
|
||||
postHog?: PostHog
|
||||
@@ -15,27 +18,10 @@ export class Telemetry {
|
||||
}
|
||||
}
|
||||
|
||||
async id(): Promise<string> {
|
||||
try {
|
||||
const settingsContent = await fs.promises.readFile(getUserSettingsFilePath(), 'utf8')
|
||||
const settings = JSON.parse(settingsContent)
|
||||
return settings.instanceId
|
||||
} catch (error) {
|
||||
const instanceId = uuidv4()
|
||||
const settings = {
|
||||
instanceId
|
||||
}
|
||||
const defaultLocation = process.env.SECRETKEY_PATH
|
||||
? path.join(process.env.SECRETKEY_PATH, 'settings.json')
|
||||
: path.join(getUserHome(), '.flowise', 'settings.json')
|
||||
await fs.promises.writeFile(defaultLocation, JSON.stringify(settings, null, 2))
|
||||
return instanceId
|
||||
}
|
||||
}
|
||||
|
||||
async sendTelemetry(event: string, properties = {}): Promise<void> {
|
||||
async sendTelemetry(event: string, properties: Record<string, any> = {}, orgId = ''): Promise<void> {
|
||||
properties.version = await getAppVersion()
|
||||
if (this.postHog) {
|
||||
const distinctId = await this.id()
|
||||
const distinctId = orgId || uuidv4()
|
||||
this.postHog.capture({
|
||||
event,
|
||||
distinctId,
|
||||
|
||||
@@ -28,11 +28,15 @@ import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
||||
import { UpsertHistory } from '../database/entities/UpsertHistory'
|
||||
import { InternalFlowiseError } from '../errors/internalFlowiseError'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { checkStorage, updateStorageUsage } from './quotaUsage'
|
||||
import { getErrorMessage } from '../errors/utils'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { FLOWISE_COUNTER_STATUS, FLOWISE_METRIC_COUNTERS } from '../Interface.Metrics'
|
||||
import { Variable } from '../database/entities/Variable'
|
||||
import { getWorkspaceSearchOptions } from '../enterprise/utils/ControllerServiceUtils'
|
||||
import { OMIT_QUEUE_JOB_DATA } from './constants'
|
||||
import { Workspace } from '../enterprise/database/entities/workspace.entity'
|
||||
import { Organization } from '../enterprise/database/entities/organization.entity'
|
||||
|
||||
export const executeUpsert = async ({
|
||||
componentNodes,
|
||||
@@ -43,7 +47,11 @@ export const executeUpsert = async ({
|
||||
telemetry,
|
||||
cachePool,
|
||||
isInternal,
|
||||
files
|
||||
files,
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId,
|
||||
usageCacheManager
|
||||
}: IExecuteFlowParams) => {
|
||||
const question = incomingInput.question
|
||||
let overrideConfig = incomingInput.overrideConfig ?? {}
|
||||
@@ -56,11 +64,21 @@ export const executeUpsert = async ({
|
||||
if (files?.length) {
|
||||
overrideConfig = { ...incomingInput }
|
||||
for (const file of files) {
|
||||
await checkStorage(orgId, subscriptionId, usageCacheManager)
|
||||
|
||||
const fileNames: string[] = []
|
||||
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)
|
||||
const { path: storagePath, totalSize } = await addArrayFilesToStorage(
|
||||
file.mimetype,
|
||||
fileBuffer,
|
||||
file.originalname,
|
||||
fileNames,
|
||||
orgId,
|
||||
chatflowid
|
||||
)
|
||||
await updateStorageUsage(orgId, workspaceId, totalSize, usageCacheManager)
|
||||
|
||||
const fileInputFieldFromMimeType = mapMimeTypeToInputField(file.mimetype)
|
||||
|
||||
@@ -147,7 +165,7 @@ export const executeUpsert = async ({
|
||||
const { startingNodeIds, depthQueue } = getStartingNodes(filteredGraph, stopNodeId)
|
||||
|
||||
/*** Get API Config ***/
|
||||
const availableVariables = await appDataSource.getRepository(Variable).find()
|
||||
const availableVariables = await appDataSource.getRepository(Variable).findBy(getWorkspaceSearchOptions(chatflow.workspaceId))
|
||||
const { nodeOverrides, variableOverrides, apiOverrideStatus } = getAPIOverrideConfig(chatflow)
|
||||
|
||||
const upsertedResult = await buildFlow({
|
||||
@@ -164,14 +182,18 @@ export const executeUpsert = async ({
|
||||
sessionId,
|
||||
chatflowid,
|
||||
appDataSource,
|
||||
usageCacheManager,
|
||||
cachePool,
|
||||
isUpsert,
|
||||
stopNodeId,
|
||||
overrideConfig,
|
||||
apiOverrideStatus,
|
||||
nodeOverrides,
|
||||
availableVariables,
|
||||
variableOverrides,
|
||||
cachePool,
|
||||
isUpsert,
|
||||
stopNodeId
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId
|
||||
})
|
||||
|
||||
// Save to DB
|
||||
@@ -186,13 +208,17 @@ export const executeUpsert = async ({
|
||||
await appDataSource.getRepository(UpsertHistory).save(upsertHistory)
|
||||
}
|
||||
|
||||
await telemetry.sendTelemetry('vector_upserted', {
|
||||
version: await getAppVersion(),
|
||||
chatlowId: chatflowid,
|
||||
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||
flowGraph: getTelemetryFlowObj(nodes, edges),
|
||||
stopNodeId
|
||||
})
|
||||
await telemetry.sendTelemetry(
|
||||
'vector_upserted',
|
||||
{
|
||||
version: await getAppVersion(),
|
||||
chatlowId: chatflowid,
|
||||
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||
flowGraph: getTelemetryFlowObj(nodes, edges),
|
||||
stopNodeId
|
||||
},
|
||||
orgId
|
||||
)
|
||||
|
||||
return upsertedResult['result'] ?? { result: 'Successfully Upserted' }
|
||||
}
|
||||
@@ -204,6 +230,7 @@ export const executeUpsert = async ({
|
||||
*/
|
||||
export const upsertVector = async (req: Request, isInternal: boolean = false) => {
|
||||
const appServer = getRunningExpressApp()
|
||||
|
||||
try {
|
||||
const chatflowid = req.params.id
|
||||
|
||||
@@ -228,6 +255,26 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
|
||||
}
|
||||
}
|
||||
|
||||
// This can be public API, so we can only get orgId from the chatflow
|
||||
const chatflowWorkspaceId = chatflow.workspaceId
|
||||
const workspace = await appServer.AppDataSource.getRepository(Workspace).findOneBy({
|
||||
id: chatflowWorkspaceId
|
||||
})
|
||||
if (!workspace) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Workspace ${chatflowWorkspaceId} not found`)
|
||||
}
|
||||
const workspaceId = workspace.id
|
||||
|
||||
const org = await appServer.AppDataSource.getRepository(Organization).findOneBy({
|
||||
id: workspace.organizationId
|
||||
})
|
||||
if (!org) {
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Organization ${workspace.organizationId} not found`)
|
||||
}
|
||||
|
||||
const orgId = org.id
|
||||
const subscriptionId = org.subscriptionId as string
|
||||
|
||||
const executeData: IExecuteFlowParams = {
|
||||
componentNodes: appServer.nodesPool.componentNodes,
|
||||
incomingInput,
|
||||
@@ -237,17 +284,21 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
|
||||
telemetry: appServer.telemetry,
|
||||
cachePool: appServer.cachePool,
|
||||
sseStreamer: appServer.sseStreamer,
|
||||
usageCacheManager: appServer.usageCacheManager,
|
||||
baseURL,
|
||||
isInternal,
|
||||
files,
|
||||
isUpsert: true
|
||||
isUpsert: true,
|
||||
orgId,
|
||||
workspaceId,
|
||||
subscriptionId
|
||||
}
|
||||
|
||||
if (process.env.MODE === MODE.QUEUE) {
|
||||
const upsertQueue = appServer.queueManager.getQueue('upsert')
|
||||
|
||||
const job = await upsertQueue.addJob(omit(executeData, OMIT_QUEUE_JOB_DATA))
|
||||
logger.debug(`[server]: Job added to queue: ${job.id}`)
|
||||
logger.debug(`[server]: [${orgId}]: Job added to queue: ${job.id}`)
|
||||
|
||||
const queueEvents = upsertQueue.getQueueEvents()
|
||||
const result = await job.waitUntilFinished(queueEvents)
|
||||
|
||||
@@ -34,6 +34,7 @@ export const validateAPIKey = async (req: Request) => {
|
||||
if (!authorizationHeader) return false
|
||||
|
||||
const suppliedKey = authorizationHeader.split(`Bearer `).pop()
|
||||
|
||||
if (suppliedKey) {
|
||||
const keys = await apikeyService.getAllApiKeys()
|
||||
const apiSecret = keys.find((key: any) => key.apiKey === suppliedKey)?.apiSecret
|
||||
@@ -43,3 +44,19 @@ export const validateAPIKey = async (req: Request) => {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API Key WorkspaceID
|
||||
* @param {Request} req
|
||||
*/
|
||||
export const getAPIKeyWorkspaceID = async (req: Request) => {
|
||||
const authorizationHeader = (req.headers['Authorization'] as string) ?? (req.headers['authorization'] as string) ?? ''
|
||||
if (!authorizationHeader) return false
|
||||
|
||||
const suppliedKey = authorizationHeader.split(`Bearer `).pop()
|
||||
if (suppliedKey) {
|
||||
const key = await apikeyService.getApiKey(suppliedKey)
|
||||
return key?.workspaceId
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user