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
@@ -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(