Feature/Full File Uploads & Message Delete API (#3314)

* add functionality for full file uploads, add remove messages from view dialog and API

* add attachments swagger

* update question to include uploadedFilesContent

* make config dialog modal lg size
This commit is contained in:
Henry Heng
2024-10-23 11:00:46 +01:00
committed by GitHub
parent 116d02d0bc
commit 53e504c32f
31 changed files with 1012 additions and 193 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ export type MessageType = 'apiMessage' | 'userMessage'
export type ChatflowType = 'CHATFLOW' | 'MULTIAGENT'
export enum chatType {
export enum ChatType {
INTERNAL = 'INTERNAL',
EXTERNAL = 'EXTERNAL'
}
@@ -0,0 +1,15 @@
import { Request, Response, NextFunction } from 'express'
import attachmentsService from '../../services/attachments'
const createAttachment = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await attachmentsService.createAttachment(req)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
createAttachment
}
@@ -1,13 +1,36 @@
import { Request, Response, NextFunction } from 'express'
import { ChatMessageRatingType, chatType, IReactFlowObject } from '../../Interface'
import { ChatMessageRatingType, ChatType, IReactFlowObject } from '../../Interface'
import chatflowsService from '../../services/chatflows'
import chatMessagesService from '../../services/chat-messages'
import { clearSessionMemory } from '../../utils'
import { aMonthAgo, clearSessionMemory, setDateToStartOrEndOfDay } from '../../utils'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { FindOptionsWhere } from 'typeorm'
import { Between, FindOptionsWhere } from 'typeorm'
import { ChatMessage } from '../../database/entities/ChatMessage'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { StatusCodes } from 'http-status-codes'
import { utilGetChatMessage } from '../../utils/getChatMessage'
const getFeedbackTypeFilters = (_feedbackTypeFilters: ChatMessageRatingType[]): ChatMessageRatingType[] | undefined => {
try {
let feedbackTypeFilters
const feedbackTypeFilterArray = JSON.parse(JSON.stringify(_feedbackTypeFilters))
if (
feedbackTypeFilterArray.includes(ChatMessageRatingType.THUMBS_UP) &&
feedbackTypeFilterArray.includes(ChatMessageRatingType.THUMBS_DOWN)
) {
feedbackTypeFilters = [ChatMessageRatingType.THUMBS_UP, ChatMessageRatingType.THUMBS_DOWN]
} else if (feedbackTypeFilterArray.includes(ChatMessageRatingType.THUMBS_UP)) {
feedbackTypeFilters = [ChatMessageRatingType.THUMBS_UP]
} else if (feedbackTypeFilterArray.includes(ChatMessageRatingType.THUMBS_DOWN)) {
feedbackTypeFilters = [ChatMessageRatingType.THUMBS_DOWN]
} else {
feedbackTypeFilters = undefined
}
return feedbackTypeFilters
} catch (e) {
return _feedbackTypeFilters
}
}
const createChatMessage = async (req: Request, res: Response, next: NextFunction) => {
try {
@@ -26,16 +49,16 @@ const createChatMessage = async (req: Request, res: Response, next: NextFunction
const getAllChatMessages = async (req: Request, res: Response, next: NextFunction) => {
try {
let chatTypeFilter = req.query?.chatType as chatType | undefined
let chatTypeFilter = req.query?.chatType as ChatType | undefined
if (chatTypeFilter) {
try {
const chatTypeFilterArray = JSON.parse(chatTypeFilter)
if (chatTypeFilterArray.includes(chatType.EXTERNAL) && chatTypeFilterArray.includes(chatType.INTERNAL)) {
if (chatTypeFilterArray.includes(ChatType.EXTERNAL) && chatTypeFilterArray.includes(ChatType.INTERNAL)) {
chatTypeFilter = undefined
} else if (chatTypeFilterArray.includes(chatType.EXTERNAL)) {
chatTypeFilter = chatType.EXTERNAL
} else if (chatTypeFilterArray.includes(chatType.INTERNAL)) {
chatTypeFilter = chatType.INTERNAL
} else if (chatTypeFilterArray.includes(ChatType.EXTERNAL)) {
chatTypeFilter = ChatType.EXTERNAL
} else if (chatTypeFilterArray.includes(ChatType.INTERNAL)) {
chatTypeFilter = ChatType.INTERNAL
}
} catch (e) {
return res.status(500).send(e)
@@ -51,23 +74,7 @@ const getAllChatMessages = async (req: Request, res: Response, next: NextFunctio
const feedback = req.query?.feedback as boolean | undefined
let feedbackTypeFilters = req.query?.feedbackType as ChatMessageRatingType[] | undefined
if (feedbackTypeFilters) {
try {
const feedbackTypeFilterArray = JSON.parse(JSON.stringify(feedbackTypeFilters))
if (
feedbackTypeFilterArray.includes(ChatMessageRatingType.THUMBS_UP) &&
feedbackTypeFilterArray.includes(ChatMessageRatingType.THUMBS_DOWN)
) {
feedbackTypeFilters = [ChatMessageRatingType.THUMBS_UP, ChatMessageRatingType.THUMBS_DOWN]
} else if (feedbackTypeFilterArray.includes(ChatMessageRatingType.THUMBS_UP)) {
feedbackTypeFilters = [ChatMessageRatingType.THUMBS_UP]
} else if (feedbackTypeFilterArray.includes(ChatMessageRatingType.THUMBS_DOWN)) {
feedbackTypeFilters = [ChatMessageRatingType.THUMBS_DOWN]
} else {
feedbackTypeFilters = undefined
}
} catch (e) {
return res.status(500).send(e)
}
feedbackTypeFilters = getFeedbackTypeFilters(feedbackTypeFilters)
}
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(
@@ -105,9 +112,13 @@ const getAllInternalChatMessages = async (req: Request, res: Response, next: Nex
const startDate = req.query?.startDate as string | undefined
const endDate = req.query?.endDate as string | undefined
const feedback = req.query?.feedback as boolean | undefined
let feedbackTypeFilters = req.query?.feedbackType as ChatMessageRatingType[] | undefined
if (feedbackTypeFilters) {
feedbackTypeFilters = getFeedbackTypeFilters(feedbackTypeFilters)
}
const apiResponse = await chatMessagesService.getAllInternalChatMessages(
req.params.id,
chatType.INTERNAL,
ChatType.INTERNAL,
sortOrder,
chatId,
memoryType,
@@ -115,7 +126,8 @@ const getAllInternalChatMessages = async (req: Request, res: Response, next: Nex
startDate,
endDate,
messageId,
feedback
feedback,
feedbackTypeFilters
)
return res.json(parseAPIResponse(apiResponse))
} catch (error) {
@@ -123,7 +135,6 @@ const getAllInternalChatMessages = async (req: Request, res: Response, next: Nex
}
}
//Delete all chatmessages from chatId
const removeAllChatMessages = async (req: Request, res: Response, next: NextFunction) => {
try {
const appServer = getRunningExpressApp()
@@ -138,35 +149,102 @@ const removeAllChatMessages = async (req: Request, res: Response, next: NextFunc
if (!chatflow) {
return res.status(404).send(`Chatflow ${req.params.id} not found`)
}
const chatId = req.query?.chatId as string
const memoryType = req.query?.memoryType as string | undefined
const sessionId = req.query?.sessionId as string | undefined
const chatType = req.query?.chatType as string | undefined
const isClearFromViewMessageDialog = req.query?.isClearFromViewMessageDialog as string | undefined
const flowData = chatflow.flowData
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
const nodes = parsedFlowData.nodes
try {
await clearSessionMemory(
nodes,
appServer.nodesPool.componentNodes,
chatId,
appServer.AppDataSource,
sessionId,
memoryType,
isClearFromViewMessageDialog
)
} catch (e) {
return res.status(500).send('Error clearing chat messages')
const chatId = req.query?.chatId as string
const memoryType = req.query?.memoryType as string | undefined
const sessionId = req.query?.sessionId as string | undefined
const _chatType = req.query?.chatType as string | undefined
const startDate = req.query?.startDate as string | undefined
const endDate = req.query?.endDate as string | undefined
const isClearFromViewMessageDialog = req.query?.isClearFromViewMessageDialog as string | undefined
let feedbackTypeFilters = req.query?.feedbackType as ChatMessageRatingType[] | undefined
if (feedbackTypeFilters) {
feedbackTypeFilters = getFeedbackTypeFilters(feedbackTypeFilters)
}
const deleteOptions: FindOptionsWhere<ChatMessage> = { chatflowid }
if (chatId) deleteOptions.chatId = chatId
if (memoryType) deleteOptions.memoryType = memoryType
if (sessionId) deleteOptions.sessionId = sessionId
if (chatType) deleteOptions.chatType = chatType
const apiResponse = await chatMessagesService.removeAllChatMessages(chatId, chatflowid, deleteOptions)
return res.json(apiResponse)
if (!chatId) {
const isFeedback = feedbackTypeFilters?.length ? true : false
const hardDelete = req.query?.hardDelete as boolean | undefined
const messages = await utilGetChatMessage(
chatflowid,
_chatType as ChatType | undefined,
undefined,
undefined,
undefined,
undefined,
startDate,
endDate,
undefined,
isFeedback,
feedbackTypeFilters
)
const messageIds = messages.map((message) => message.id)
// Categorize by chatId_memoryType_sessionId
const chatIdMap = new Map<string, ChatMessage[]>()
messages.forEach((message) => {
const chatId = message.chatId
const memoryType = message.memoryType
const sessionId = message.sessionId
const composite_key = `${chatId}_${memoryType}_${sessionId}`
if (!chatIdMap.has(composite_key)) {
chatIdMap.set(composite_key, [])
}
chatIdMap.get(composite_key)?.push(message)
})
// If hardDelete is ON, we clearSessionMemory from third party integrations
if (hardDelete) {
for (const [composite_key] of chatIdMap) {
const [chatId, memoryType, sessionId] = composite_key.split('_')
try {
await clearSessionMemory(
nodes,
appServer.nodesPool.componentNodes,
chatId,
appServer.AppDataSource,
sessionId,
memoryType,
isClearFromViewMessageDialog
)
} catch (e) {
console.error('Error clearing chat messages')
}
}
}
const apiResponse = await chatMessagesService.removeChatMessagesByMessageIds(chatflowid, chatIdMap, messageIds)
return res.json(apiResponse)
} else {
try {
await clearSessionMemory(
nodes,
appServer.nodesPool.componentNodes,
chatId,
appServer.AppDataSource,
sessionId,
memoryType,
isClearFromViewMessageDialog
)
} catch (e) {
return res.status(500).send('Error clearing chat messages')
}
const deleteOptions: FindOptionsWhere<ChatMessage> = { chatflowid }
if (chatId) deleteOptions.chatId = chatId
if (memoryType) deleteOptions.memoryType = memoryType
if (sessionId) deleteOptions.sessionId = sessionId
if (_chatType) deleteOptions.chatType = _chatType
if (startDate && endDate) {
const fromDate = setDateToStartOrEndOfDay(startDate, 'start')
const toDate = setDateToStartOrEndOfDay(endDate, 'end')
deleteOptions.createdDate = Between(fromDate ?? aMonthAgo(), toDate ?? new Date())
}
const apiResponse = await chatMessagesService.removeAllChatMessages(chatId, chatflowid, deleteOptions)
return res.json(apiResponse)
}
} catch (error) {
next(error)
}
@@ -1,7 +1,7 @@
import { StatusCodes } from 'http-status-codes'
import { Request, Response, NextFunction } from 'express'
import statsService from '../../services/stats'
import { ChatMessageRatingType, chatType } from '../../Interface'
import { ChatMessageRatingType, ChatType } from '../../Interface'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { getErrorMessage } from '../../errors/utils'
@@ -11,19 +11,19 @@ const getChatflowStats = async (req: Request, res: Response, next: NextFunction)
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: statsController.getChatflowStats - id not provided!`)
}
const chatflowid = req.params.id
let chatTypeFilter = req.query?.chatType as chatType | undefined
let chatTypeFilter = req.query?.chatType as ChatType | undefined
const startDate = req.query?.startDate as string | undefined
const endDate = req.query?.endDate as string | undefined
let feedbackTypeFilters = req.query?.feedbackType as ChatMessageRatingType[] | undefined
if (chatTypeFilter) {
try {
const chatTypeFilterArray = JSON.parse(chatTypeFilter)
if (chatTypeFilterArray.includes(chatType.EXTERNAL) && chatTypeFilterArray.includes(chatType.INTERNAL)) {
if (chatTypeFilterArray.includes(ChatType.EXTERNAL) && chatTypeFilterArray.includes(ChatType.INTERNAL)) {
chatTypeFilter = undefined
} else if (chatTypeFilterArray.includes(chatType.EXTERNAL)) {
chatTypeFilter = chatType.EXTERNAL
} else if (chatTypeFilterArray.includes(chatType.INTERNAL)) {
chatTypeFilter = chatType.INTERNAL
} else if (chatTypeFilterArray.includes(ChatType.EXTERNAL)) {
chatTypeFilter = ChatType.EXTERNAL
} else if (chatTypeFilterArray.includes(ChatType.INTERNAL)) {
chatTypeFilter = ChatType.INTERNAL
}
} catch (e) {
throw new InternalFlowiseError(
+2 -1
View File
@@ -136,7 +136,8 @@ export class App {
'/api/v1/get-upload-file',
'/api/v1/ip',
'/api/v1/ping',
'/api/v1/version'
'/api/v1/version',
'/api/v1/attachments'
]
const URL_CASE_INSENSITIVE_REGEX: RegExp = /\/api\/v1\//i
const URL_CASE_SENSITIVE_REGEX: RegExp = /\/api\/v1\//
@@ -0,0 +1,13 @@
import express from 'express'
import multer from 'multer'
import path from 'path'
import attachmentsController from '../../controllers/attachments'
const router = express.Router()
const upload = multer({ dest: `${path.join(__dirname, '..', '..', '..', 'uploads')}/` })
// CREATE
router.post('/:chatflowId/:chatId', upload.array('files'), attachmentsController.createAttachment)
export default router
+2
View File
@@ -1,6 +1,7 @@
import express from 'express'
import apikeyRouter from './apikey'
import assistantsRouter from './assistants'
import attachmentsRouter from './attachments'
import chatMessageRouter from './chat-messages'
import chatflowsRouter from './chatflows'
import chatflowsStreamingRouter from './chatflows-streaming'
@@ -47,6 +48,7 @@ const router = express.Router()
router.use('/ping', pingRouter)
router.use('/apikey', apikeyRouter)
router.use('/assistants', assistantsRouter)
router.use('/attachments', attachmentsRouter)
router.use('/chatflows', chatflowsRouter)
router.use('/chatflows-streaming', chatflowsStreamingRouter)
router.use('/chatmessage', chatMessageRouter)
@@ -0,0 +1,20 @@
import { Request } from 'express'
import { StatusCodes } from 'http-status-codes'
import { createFileAttachment } from '../../utils/createAttachment'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { getErrorMessage } from '../../errors/utils'
const createAttachment = async (req: Request) => {
try {
return await createFileAttachment(req)
} catch (error) {
throw new InternalFlowiseError(
StatusCodes.INTERNAL_SERVER_ERROR,
`Error: attachmentService.createAttachment - ${getErrorMessage(error)}`
)
}
}
export default {
createAttachment
}
@@ -1,6 +1,6 @@
import { DeleteResult, FindOptionsWhere } from 'typeorm'
import { StatusCodes } from 'http-status-codes'
import { ChatMessageRatingType, chatType, IChatMessage } from '../../Interface'
import { ChatMessageRatingType, ChatType, IChatMessage } from '../../Interface'
import { utilGetChatMessage } from '../../utils/getChatMessage'
import { utilAddChatMessage } from '../../utils/addChatMesage'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
@@ -27,7 +27,7 @@ const createChatMessage = async (chatMessage: Partial<IChatMessage>) => {
// Get all chatmessages from chatflowid
const getAllChatMessages = async (
chatflowId: string,
chatTypeFilter: chatType | undefined,
chatTypeFilter: ChatType | undefined,
sortOrder: string = 'ASC',
chatId?: string,
memoryType?: string,
@@ -64,7 +64,7 @@ const getAllChatMessages = async (
// Get internal chatmessages from chatflowid
const getAllInternalChatMessages = async (
chatflowId: string,
chatTypeFilter: chatType | undefined,
chatTypeFilter: ChatType | undefined,
sortOrder: string = 'ASC',
chatId?: string,
memoryType?: string,
@@ -128,6 +128,35 @@ const removeAllChatMessages = async (
}
}
const removeChatMessagesByMessageIds = async (
chatflowid: string,
chatIdMap: Map<string, ChatMessage[]>,
messageIds: string[]
): Promise<DeleteResult> => {
try {
const appServer = getRunningExpressApp()
for (const [composite_key] of chatIdMap) {
const [chatId] = composite_key.split('_')
// Remove all related feedback records
const feedbackDeleteOptions: FindOptionsWhere<ChatMessageFeedback> = { chatId }
await appServer.AppDataSource.getRepository(ChatMessageFeedback).delete(feedbackDeleteOptions)
// Delete all uploads corresponding to this chatflow/chatId
await removeFilesFromStorage(chatflowid, chatId)
}
const dbResponse = await appServer.AppDataSource.getRepository(ChatMessage).delete(messageIds)
return dbResponse
} catch (error) {
throw new InternalFlowiseError(
StatusCodes.INTERNAL_SERVER_ERROR,
`Error: chatMessagesService.removeAllChatMessages - ${getErrorMessage(error)}`
)
}
}
const abortChatMessage = async (chatId: string, chatflowid: string) => {
try {
const appServer = getRunningExpressApp()
@@ -155,5 +184,6 @@ export default {
getAllChatMessages,
getAllInternalChatMessages,
removeAllChatMessages,
removeChatMessagesByMessageIds,
abortChatMessage
}
@@ -9,7 +9,7 @@ import {
removeSpecificFileFromStorage
} from 'flowise-components'
import {
chatType,
ChatType,
DocumentStoreStatus,
IDocumentStoreFileChunkPagedResponse,
IDocumentStoreLoader,
@@ -995,7 +995,7 @@ const _insertIntoVectorStoreWorkerThread = async (data: ICommonObject) => {
data: {
version: await getAppVersion(),
chatlowId: chatflowid,
type: chatType.INTERNAL,
type: ChatType.INTERNAL,
flowGraph: omit(indexResult['result'], ['totalKeys', 'addedDocs'])
}
})
+2 -2
View File
@@ -1,5 +1,5 @@
import { StatusCodes } from 'http-status-codes'
import { ChatMessageRatingType, chatType } from '../../Interface'
import { ChatMessageRatingType, ChatType } from '../../Interface'
import { ChatMessage } from '../../database/entities/ChatMessage'
import { utilGetChatMessage } from '../../utils/getChatMessage'
import { ChatMessageFeedback } from '../../database/entities/ChatMessageFeedback'
@@ -9,7 +9,7 @@ import { getErrorMessage } from '../../errors/utils'
// get stats for showing in chatflow
const getChatflowStats = async (
chatflowid: string,
chatTypeFilter: chatType | undefined,
chatTypeFilter: ChatType | undefined,
startDate?: string,
endDate?: string,
messageId?: string,
+44 -12
View File
@@ -63,7 +63,8 @@ export const buildAgentGraph = async (
isInternal: boolean,
baseURL?: string,
sseStreamer?: IServerSideEventStreamer,
shouldStreamResponse?: boolean
shouldStreamResponse?: boolean,
uploadedFilesContent?: string
): Promise<any> => {
try {
const appServer = getRunningExpressApp()
@@ -129,7 +130,8 @@ export const buildAgentGraph = async (
cachePool: appServer.cachePool,
isUpsert: false,
uploads: incomingInput.uploads,
baseURL
baseURL,
uploadedFilesContent
})
const options = {
@@ -188,7 +190,8 @@ export const buildAgentGraph = async (
chatHistory,
incomingInput?.overrideConfig,
sessionId || chatId,
seqAgentNodes.some((node) => node.data.inputs?.summarization)
seqAgentNodes.some((node) => node.data.inputs?.summarization),
uploadedFilesContent
)
} else {
isSequential = true
@@ -204,7 +207,8 @@ export const buildAgentGraph = async (
chatHistory,
incomingInput?.overrideConfig,
sessionId || chatId,
incomingInput.action
incomingInput.action,
uploadedFilesContent
)
}
@@ -348,7 +352,6 @@ export const buildAgentGraph = async (
if (isSequential && !finalResult && agentReasoning.length) {
const lastMessages = agentReasoning[agentReasoning.length - 1].messages
const lastAgentReasoningMessage = lastMessages[lastMessages.length - 1]
// If last message is an AI Message with tool calls, that means the last node was interrupted
if (lastMessageRaw.tool_calls && lastMessageRaw.tool_calls.length > 0) {
// The last node that got interrupted
@@ -456,6 +459,7 @@ export const buildAgentGraph = async (
* @param {ICommonObject} overrideConfig
* @param {string} threadId
* @param {boolean} summarization
* @param {string} uploadedFilesContent,
*/
const compileMultiAgentsGraph = async (
chatflow: IChatFlow,
@@ -470,7 +474,8 @@ const compileMultiAgentsGraph = async (
chatHistory: IMessage[] = [],
overrideConfig?: ICommonObject,
threadId?: string,
summarization?: boolean
summarization?: boolean,
uploadedFilesContent?: string
) => {
const appServer = getRunningExpressApp()
const channels: ITeamState = {
@@ -502,7 +507,15 @@ const compileMultiAgentsGraph = async (
let flowNodeData = cloneDeep(workerNode.data)
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
flowNodeData = await resolveVariables(appServer.AppDataSource, flowNodeData, reactflowNodes, question, chatHistory, overrideConfig)
flowNodeData = await resolveVariables(
appServer.AppDataSource,
flowNodeData,
reactflowNodes,
question,
chatHistory,
overrideConfig,
uploadedFilesContent
)
try {
const workerResult: IMultiAgentNode = await newNodeInstance.init(flowNodeData, question, options)
@@ -533,7 +546,15 @@ const compileMultiAgentsGraph = async (
let flowNodeData = cloneDeep(supervisorNode.data)
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
flowNodeData = await resolveVariables(appServer.AppDataSource, flowNodeData, reactflowNodes, question, chatHistory, overrideConfig)
flowNodeData = await resolveVariables(
appServer.AppDataSource,
flowNodeData,
reactflowNodes,
question,
chatHistory,
overrideConfig,
uploadedFilesContent
)
if (flowNodeData.inputs) flowNodeData.inputs.workerNodes = supervisorWorkers[supervisor]
@@ -603,9 +624,10 @@ const compileMultiAgentsGraph = async (
}
// Return stream result as we should only have 1 supervisor
const finalQuestion = uploadedFilesContent ? `${uploadedFilesContent}\n\n${question}` : question
return await graph.stream(
{
messages: [...prependMessages, new HumanMessage({ content: question })]
messages: [...prependMessages, new HumanMessage({ content: finalQuestion })]
},
{ recursionLimit: supervisorResult?.recursionLimit ?? 100, callbacks: [loggerHandler, ...callbacks], configurable: config }
)
@@ -641,7 +663,8 @@ const compileSeqAgentsGraph = async (
chatHistory: IMessage[] = [],
overrideConfig?: ICommonObject,
threadId?: string,
action?: IAction
action?: IAction,
uploadedFilesContent?: string
) => {
const appServer = getRunningExpressApp()
@@ -693,7 +716,15 @@ const compileSeqAgentsGraph = async (
flowNodeData = cloneDeep(node.data)
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
flowNodeData = await resolveVariables(appServer.AppDataSource, flowNodeData, reactflowNodes, question, chatHistory, overrideConfig)
flowNodeData = await resolveVariables(
appServer.AppDataSource,
flowNodeData,
reactflowNodes,
question,
chatHistory,
overrideConfig,
uploadedFilesContent
)
const seqAgentNode: ISeqAgentNode = await newNodeInstance.init(flowNodeData, question, options)
return seqAgentNode
@@ -997,8 +1028,9 @@ const compileSeqAgentsGraph = async (
}
}
const finalQuestion = uploadedFilesContent ? `${uploadedFilesContent}\n\n${question}` : question
let humanMsg: { messages: HumanMessage[] | ToolMessage[] } | null = {
messages: [...prependMessages, new HumanMessage({ content: question })]
messages: [...prependMessages, new HumanMessage({ content: finalQuestion })]
}
if (action && action.mapping && question === action.mapping.approve) {
+28 -13
View File
@@ -19,7 +19,7 @@ import {
IReactFlowObject,
IReactFlowNode,
IDepthQueue,
chatType,
ChatType,
IChatMessage,
IChatFlow,
IReactFlowEdge
@@ -88,12 +88,14 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
}
let fileUploads: IFileUpload[] = []
let uploadedFilesContent = ''
if (incomingInput.uploads) {
fileUploads = incomingInput.uploads
for (let i = 0; i < fileUploads.length; i += 1) {
const upload = fileUploads[i]
if ((upload.type === 'file' || upload.type === 'audio') && upload.data) {
// if upload in an image, a rag file, or audio
if ((upload.type === 'file' || upload.type === 'file:rag' || upload.type === 'audio') && upload.data) {
const filename = upload.name
const splitDataURI = upload.data.split(',')
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
@@ -139,6 +141,13 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
}
}
}
if (upload.type === 'file:full' && upload.data) {
upload.type = 'stored-file:full'
// Omit upload.data since we don't store the content in database
uploadedFilesContent += `<doc name='${upload.name}'>${upload.data}</doc>\n\n`
fileUploads[i] = omit(upload, ['data'])
}
}
}
@@ -229,7 +238,8 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
edges,
baseURL,
appServer.sseStreamer,
true
true,
uploadedFilesContent
)
}
@@ -345,6 +355,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
apiMessageId,
componentNodes: appServer.nodesPool.componentNodes,
question: incomingInput.question,
uploadedFilesContent,
chatHistory,
chatId,
sessionId: sessionId ?? '',
@@ -384,7 +395,8 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
reactFlowNodes,
incomingInput.question,
chatHistory,
flowData
flowData,
uploadedFilesContent
)
nodeToExecuteData = reactFlowNodeData
@@ -398,6 +410,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
const nodeInstance = new nodeModule.nodeClass({ sessionId })
isStreamValid = (req.body.streaming === 'true' || req.body.streaming === true) && isStreamValid
const finalQuestion = uploadedFilesContent ? `${uploadedFilesContent}\n\n${incomingInput.question}` : incomingInput.question
const runParams = {
chatId,
@@ -411,7 +424,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
prependMessages
}
let result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
let result = await nodeInstance.run(nodeToExecuteData, finalQuestion, {
...runParams,
...(isStreamValid && { sseStreamer: appServer.sseStreamer, shouldStreamResponse: true })
})
@@ -427,7 +440,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
role: 'userMessage',
content: incomingInput.question,
chatflowid,
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
chatType: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
chatId,
memoryType,
sessionId,
@@ -447,7 +460,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
role: 'apiMessage',
content: resultText,
chatflowid,
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
chatType: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
chatId,
memoryType,
sessionId
@@ -476,7 +489,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
version: await getAppVersion(),
chatflowId: chatflowid,
chatId,
type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
flowGraph: getTelemetryFlowObj(nodes, edges)
})
@@ -517,7 +530,8 @@ const utilBuildAgentResponse = async (
edges: IReactFlowEdge[],
baseURL?: string,
sseStreamer?: IServerSideEventStreamer,
shouldStreamResponse?: boolean
shouldStreamResponse?: boolean,
uploadedFilesContent?: string
) => {
try {
const appServer = getRunningExpressApp()
@@ -530,7 +544,8 @@ const utilBuildAgentResponse = async (
isInternal,
baseURL,
sseStreamer,
shouldStreamResponse
shouldStreamResponse,
uploadedFilesContent
)
if (streamResults) {
const { finalResult, finalAction, sourceDocuments, artifacts, usedTools, agentReasoning } = streamResults
@@ -538,7 +553,7 @@ const utilBuildAgentResponse = async (
role: 'userMessage',
content: incomingInput.question,
chatflowid: agentflow.id,
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
chatType: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
chatId,
memoryType,
sessionId,
@@ -553,7 +568,7 @@ const utilBuildAgentResponse = async (
role: 'apiMessage',
content: finalResult,
chatflowid: agentflow.id,
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
chatType: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
chatId,
memoryType,
sessionId
@@ -581,7 +596,7 @@ const utilBuildAgentResponse = async (
version: await getAppVersion(),
agentflowId: agentflow.id,
chatId,
type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
flowGraph: getTelemetryFlowObj(nodes, edges)
})
@@ -0,0 +1,84 @@
import { Request } from 'express'
import * as path from 'path'
import * as fs from 'fs'
import { addArrayFilesToStorage, IDocument, mapExtToInputField, mapMimeTypeToInputField } from 'flowise-components'
import { getRunningExpressApp } from './getRunningExpressApp'
import { getErrorMessage } from '../errors/utils'
/**
* Create attachment
* @param {Request} req
*/
export const createFileAttachment = async (req: Request) => {
const appServer = getRunningExpressApp()
const chatflowid = req.params.chatflowId
if (!chatflowid) {
throw new Error(
'Params chatflowId is required! Please provide chatflowId and chatId in the URL: /api/v1/attachments/:chatflowId/:chatId'
)
}
const chatId = req.params.chatId
if (!chatId) {
throw new Error(
'Params chatId is required! Please provide chatflowId and chatId in the URL: /api/v1/attachments/:chatflowId/:chatId'
)
}
// Find FileLoader node
const fileLoaderComponent = appServer.nodesPool.componentNodes['fileLoader']
const fileLoaderNodeInstanceFilePath = fileLoaderComponent.filePath as string
const fileLoaderNodeModule = await import(fileLoaderNodeInstanceFilePath)
const fileLoaderNodeInstance = new fileLoaderNodeModule.nodeClass()
const options = {
retrieveAttachmentChatId: true,
chatflowid,
chatId
}
const files = (req.files as Express.Multer.File[]) || []
const fileAttachments = []
if (files.length) {
for (const file of files) {
const fileBuffer = fs.readFileSync(file.path)
const fileNames: string[] = []
const storagePath = await addArrayFilesToStorage(file.mimetype, fileBuffer, file.originalname, fileNames, chatflowid, chatId)
const fileInputFieldFromMimeType = mapMimeTypeToInputField(file.mimetype)
const fileExtension = path.extname(file.originalname)
const fileInputFieldFromExt = mapExtToInputField(fileExtension)
let fileInputField = 'txtFile'
if (fileInputFieldFromExt !== 'txtFile') {
fileInputField = fileInputFieldFromExt
} else if (fileInputFieldFromMimeType !== 'txtFile') {
fileInputField = fileInputFieldFromExt
}
fs.unlinkSync(file.path)
try {
const nodeData = {
inputs: {
[fileInputField]: storagePath
}
}
const documents: IDocument[] = await fileLoaderNodeInstance.init(nodeData, '', options)
const pageContents = documents.map((doc) => doc.pageContent).join('\n')
fileAttachments.push({
name: file.originalname,
mimeType: file.mimetype,
size: file.size,
content: pageContents
})
} catch (error) {
throw new Error(`Failed operation: createFileAttachment - ${getErrorMessage(error)}`)
}
}
}
return fileAttachments
}
+4 -17
View File
@@ -1,13 +1,14 @@
import { MoreThanOrEqual, LessThanOrEqual } from 'typeorm'
import { ChatMessageRatingType, chatType } from '../Interface'
import { ChatMessageRatingType, ChatType } from '../Interface'
import { ChatMessage } from '../database/entities/ChatMessage'
import { ChatMessageFeedback } from '../database/entities/ChatMessageFeedback'
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
import { aMonthAgo, setDateToStartOrEndOfDay } from '.'
/**
* Method that get chat messages.
* @param {string} chatflowid
* @param {chatType} chatType
* @param {ChatType} chatType
* @param {string} sortOrder
* @param {string} chatId
* @param {string} memoryType
@@ -19,7 +20,7 @@ import { getRunningExpressApp } from '../utils/getRunningExpressApp'
*/
export const utilGetChatMessage = async (
chatflowid: string,
chatType: chatType | undefined,
chatType: ChatType | undefined,
sortOrder: string = 'ASC',
chatId?: string,
memoryType?: string,
@@ -31,20 +32,6 @@ export const utilGetChatMessage = async (
feedbackTypes?: ChatMessageRatingType[]
): Promise<ChatMessage[]> => {
const appServer = getRunningExpressApp()
const setDateToStartOrEndOfDay = (dateTimeStr: string, setHours: 'start' | 'end') => {
const date = new Date(dateTimeStr)
if (isNaN(date.getTime())) {
return undefined
}
setHours === 'start' ? date.setHours(0, 0, 0, 0) : date.setHours(23, 59, 59, 999)
return date
}
const aMonthAgo = () => {
const date = new Date()
date.setMonth(new Date().getMonth() - 1)
return date
}
let fromDate
if (startDate) fromDate = setDateToStartOrEndOfDay(startDate, 'start')
@@ -8,7 +8,7 @@ import { InternalFlowiseError } from '../errors/internalFlowiseError'
type IUploadConfig = {
isSpeechToTextEnabled: boolean
isImageUploadAllowed: boolean
isFileUploadAllowed: boolean
isRAGFileUploadAllowed: boolean
imgUploadSizeAndTypes: IUploadFileSizeAndTypes[]
fileUploadSizeAndTypes: IUploadFileSizeAndTypes[]
}
@@ -32,7 +32,7 @@ export const utilGetUploadsConfig = async (chatflowid: string): Promise<IUploadC
let isSpeechToTextEnabled = false
let isImageUploadAllowed = false
let isFileUploadAllowed = false
let isRAGFileUploadAllowed = false
/*
* Check for STT
@@ -51,7 +51,7 @@ export const utilGetUploadsConfig = async (chatflowid: string): Promise<IUploadC
}
/*
* Condition for isFileUploadAllowed
* Condition for isRAGFileUploadAllowed
* 1.) vector store with fileUpload = true && connected to a document loader with fileType
*/
const fileUploadSizeAndTypes: IUploadFileSizeAndTypes[] = []
@@ -70,7 +70,7 @@ export const utilGetUploadsConfig = async (chatflowid: string): Promise<IUploadC
fileTypes: fileType.split(', '),
maxUploadSize: 500
})
isFileUploadAllowed = true
isRAGFileUploadAllowed = true
}
}
break
@@ -114,7 +114,7 @@ export const utilGetUploadsConfig = async (chatflowid: string): Promise<IUploadC
return {
isSpeechToTextEnabled,
isImageUploadAllowed,
isFileUploadAllowed,
isRAGFileUploadAllowed,
imgUploadSizeAndTypes,
fileUploadSizeAndTypes
}
+34 -6
View File
@@ -48,6 +48,7 @@ import { InternalFlowiseError } from '../errors/internalFlowiseError'
import { StatusCodes } from 'http-status-codes'
const QUESTION_VAR_PREFIX = 'question'
const FILE_ATTACHMENT_PREFIX = 'file_attachment'
const CHAT_HISTORY_VAR_PREFIX = 'chat_history'
const REDACTED_CREDENTIAL_VALUE = '_FLOWISE_BLANK_07167752-1a71-43b1-bf8f-4f32252165db'
@@ -438,6 +439,7 @@ type BuildFlowParams = {
stopNodeId?: string
uploads?: IFileUpload[]
baseURL?: string
uploadedFilesContent?: string
}
/**
@@ -452,6 +454,7 @@ export const buildFlow = async ({
depthQueue,
componentNodes,
question,
uploadedFilesContent,
chatHistory,
apiMessageId,
chatId,
@@ -516,7 +519,8 @@ export const buildFlow = async ({
flowNodes,
question,
chatHistory,
flowData
flowData,
uploadedFilesContent
)
if (isUpsert && stopNodeId && nodeId === stopNodeId) {
@@ -546,7 +550,8 @@ export const buildFlow = async ({
initializedNodes.add(nodeId)
} else {
logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
let outputResult = await newNodeInstance.init(reactFlowNodeData, question, {
const finalQuestion = uploadedFilesContent ? `${uploadedFilesContent}\n\n${question}` : question
let outputResult = await newNodeInstance.init(reactFlowNodeData, finalQuestion, {
chatId,
sessionId,
chatflowid,
@@ -770,7 +775,8 @@ export const getVariableValue = async (
question: string,
chatHistory: IMessage[],
isAcceptVariable = false,
flowData?: ICommonObject
flowData?: ICommonObject,
uploadedFilesContent?: string
) => {
const isObject = typeof paramValue === 'object'
const initialValue = (isObject ? JSON.stringify(paramValue) : paramValue) ?? ''
@@ -803,6 +809,10 @@ export const getVariableValue = async (
variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(question, false)
}
if (isAcceptVariable && variableFullPath === FILE_ATTACHMENT_PREFIX) {
variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(uploadedFilesContent, false)
}
if (isAcceptVariable && variableFullPath === CHAT_HISTORY_VAR_PREFIX) {
variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(convertChatHistoryToText(chatHistory), false)
}
@@ -916,7 +926,8 @@ export const resolveVariables = async (
reactFlowNodes: IReactFlowNode[],
question: string,
chatHistory: IMessage[],
flowData?: ICommonObject
flowData?: ICommonObject,
uploadedFilesContent?: string
): Promise<INodeData> => {
let flowNodeData = cloneDeep(reactFlowNodeData)
const types = 'inputs'
@@ -934,7 +945,8 @@ export const resolveVariables = async (
question,
chatHistory,
undefined,
flowData
flowData,
uploadedFilesContent
)
resolvedInstances.push(resolvedInstance)
}
@@ -948,7 +960,8 @@ export const resolveVariables = async (
question,
chatHistory,
isAcceptVariable,
flowData
flowData,
uploadedFilesContent
)
paramsObj[key] = resolvedInstance
}
@@ -1572,3 +1585,18 @@ export const convertToValidFilename = (word: string) => {
.replace(' ', '')
.toLowerCase()
}
export const setDateToStartOrEndOfDay = (dateTimeStr: string, setHours: 'start' | 'end') => {
const date = new Date(dateTimeStr)
if (isNaN(date.getTime())) {
return undefined
}
setHours === 'start' ? date.setHours(0, 0, 0, 0) : date.setHours(23, 59, 59, 999)
return date
}
export const aMonthAgo = () => {
const date = new Date()
date.setMonth(new Date().getMonth() - 1)
return date
}
+2 -2
View File
@@ -16,7 +16,7 @@ import {
getStartingNodes
} from '../utils'
import { validateChatflowAPIKey } from './validateKey'
import { IncomingInput, INodeDirectedGraph, IReactFlowObject, chatType } from '../Interface'
import { IncomingInput, INodeDirectedGraph, IReactFlowObject, ChatType } from '../Interface'
import { ChatFlow } from '../database/entities/ChatFlow'
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
import { UpsertHistory } from '../database/entities/UpsertHistory'
@@ -195,7 +195,7 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
data: {
version: await getAppVersion(),
chatlowId: chatflowid,
type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
flowGraph: getTelemetryFlowObj(nodes, edges),
stopNodeId
}