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:
parent
116d02d0bc
commit
53e504c32f
|
|
@ -1,5 +1,6 @@
|
||||||
tags:
|
tags:
|
||||||
- name: assistants
|
- name: assistants
|
||||||
|
- name: attachments
|
||||||
- name: chatmessage
|
- name: chatmessage
|
||||||
- name: chatflows
|
- name: chatflows
|
||||||
- name: document-store
|
- name: document-store
|
||||||
|
|
@ -270,6 +271,61 @@ paths:
|
||||||
'500':
|
'500':
|
||||||
description: Internal error
|
description: Internal error
|
||||||
|
|
||||||
|
/attachments/{chatflowId}/{chatId}:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- attachments
|
||||||
|
security:
|
||||||
|
- bearerAuth: []
|
||||||
|
operationId: createAttachment
|
||||||
|
summary: Create attachments array
|
||||||
|
description: Return contents of the files in plain string format
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: chatflowId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Chatflow ID
|
||||||
|
- in: path
|
||||||
|
name: chatId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Chat ID
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
files:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
description: Files to be uploaded
|
||||||
|
required:
|
||||||
|
- files
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Attachments created successfully
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/CreateAttachmentResponse'
|
||||||
|
'400':
|
||||||
|
description: Invalid input provided
|
||||||
|
'404':
|
||||||
|
description: Chatflow or ChatId not found
|
||||||
|
'422':
|
||||||
|
description: Validation error
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
|
||||||
/chatflows:
|
/chatflows:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -1825,7 +1881,8 @@ components:
|
||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
description: The type of file upload (e.g., 'file', 'audio', 'url')
|
enum: [audio, url, file, file:rag, file:full]
|
||||||
|
description: The type of file upload
|
||||||
example: file
|
example: file
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
|
@ -2193,6 +2250,22 @@ components:
|
||||||
format: date-time
|
format: date-time
|
||||||
description: Date and time when the feedback was created
|
description: Date and time when the feedback was created
|
||||||
|
|
||||||
|
CreateAttachmentResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Name of the file
|
||||||
|
mimeType:
|
||||||
|
type: string
|
||||||
|
description: Mime type of the file
|
||||||
|
size:
|
||||||
|
type: string
|
||||||
|
description: Size of the file
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
description: Content of the file in string format
|
||||||
|
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
bearerAuth:
|
bearerAuth:
|
||||||
type: http
|
type: http
|
||||||
|
|
|
||||||
|
|
@ -121,12 +121,23 @@ class File_DocumentLoaders implements INode {
|
||||||
}
|
}
|
||||||
const chatflowid = options.chatflowid
|
const chatflowid = options.chatflowid
|
||||||
|
|
||||||
|
// specific to createAttachment to get files from chatId
|
||||||
|
const retrieveAttachmentChatId = options.retrieveAttachmentChatId
|
||||||
|
if (retrieveAttachmentChatId) {
|
||||||
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
|
const fileData = await getFileFromStorage(file, chatflowid, options.chatId)
|
||||||
|
const blob = new Blob([fileData])
|
||||||
|
fileBlobs.push({ blob, ext: file.split('.').pop() || '' })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (!file) continue
|
if (!file) continue
|
||||||
const fileData = await getFileFromStorage(file, chatflowid)
|
const fileData = await getFileFromStorage(file, chatflowid)
|
||||||
const blob = new Blob([fileData])
|
const blob = new Blob([fileData])
|
||||||
fileBlobs.push({ blob, ext: file.split('.').pop() || '' })
|
fileBlobs.push({ blob, ext: file.split('.').pop() || '' })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (totalFiles.startsWith('[') && totalFiles.endsWith(']')) {
|
if (totalFiles.startsWith('[') && totalFiles.endsWith(']')) {
|
||||||
files = JSON.parse(totalFiles)
|
files = JSON.parse(totalFiles)
|
||||||
|
|
@ -288,9 +299,14 @@ class MultiFileLoader extends BaseDocumentLoader {
|
||||||
const loader = loaderFactory(fileBlob.blob)
|
const loader = loaderFactory(fileBlob.blob)
|
||||||
documents.push(...(await loader.load()))
|
documents.push(...(await loader.load()))
|
||||||
} else {
|
} else {
|
||||||
|
const loader = new TextLoader(fileBlob.blob)
|
||||||
|
try {
|
||||||
|
documents.push(...(await loader.load()))
|
||||||
|
} catch (error) {
|
||||||
throw new Error(`Error loading file`)
|
throw new Error(`Error loading file`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return documents
|
return documents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,9 @@ const howToUseCode = `
|
||||||
"sourceDocuments": [
|
"sourceDocuments": [
|
||||||
{
|
{
|
||||||
"pageContent": "This is the page content",
|
"pageContent": "This is the page content",
|
||||||
"metadata": "{foo: var}",
|
"metadata": "{foo: var}"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
|
|
@ -102,10 +102,10 @@ const howToUse = `
|
||||||
|-----------|-----------|
|
|-----------|-----------|
|
||||||
| user | john doe |
|
| user | john doe |
|
||||||
|
|
||||||
2. If you want to use the agent's output as the value to update state, it is available as available as \`$flow.output\` with the following structure:
|
2. If you want to use the Agent's output as the value to update state, it is available as available as \`$flow.output\` with the following structure:
|
||||||
\`\`\`json
|
\`\`\`json
|
||||||
{
|
{
|
||||||
"output": "Hello! How can I assist you today?",
|
"content": "Hello! How can I assist you today?",
|
||||||
"usedTools": [
|
"usedTools": [
|
||||||
{
|
{
|
||||||
"tool": "tool-name",
|
"tool": "tool-name",
|
||||||
|
|
@ -116,9 +116,9 @@ const howToUse = `
|
||||||
"sourceDocuments": [
|
"sourceDocuments": [
|
||||||
{
|
{
|
||||||
"pageContent": "This is the page content",
|
"pageContent": "This is the page content",
|
||||||
"metadata": "{foo: var}",
|
"metadata": "{foo: var}"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
|
|
@ -195,7 +195,7 @@ class Agent_SeqAgents implements INode {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Agent'
|
this.label = 'Agent'
|
||||||
this.name = 'seqAgent'
|
this.name = 'seqAgent'
|
||||||
this.version = 3.0
|
this.version = 3.1
|
||||||
this.type = 'Agent'
|
this.type = 'Agent'
|
||||||
this.icon = 'seqAgent.png'
|
this.icon = 'seqAgent.png'
|
||||||
this.category = 'Sequential Agents'
|
this.category = 'Sequential Agents'
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ const howToUse = `
|
||||||
|-----------|-----------|
|
|-----------|-----------|
|
||||||
| user | john doe |
|
| user | john doe |
|
||||||
|
|
||||||
2. If you want to use the agent's output as the value to update state, it is available as available as \`$flow.output\` with the following structure:
|
2. If you want to use the LLM Node's output as the value to update state, it is available as available as \`$flow.output\` with the following structure:
|
||||||
\`\`\`json
|
\`\`\`json
|
||||||
{
|
{
|
||||||
"content": 'Hello! How can I assist you today?',
|
"content": 'Hello! How can I assist you today?',
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,9 @@ const howToUseCode = `
|
||||||
"sourceDocuments": [
|
"sourceDocuments": [
|
||||||
{
|
{
|
||||||
"pageContent": "This is the page content",
|
"pageContent": "This is the page content",
|
||||||
"metadata": "{foo: var}",
|
"metadata": "{foo: var}"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
@ -64,7 +64,7 @@ const howToUseCode = `
|
||||||
*/
|
*/
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"sources": $flow.output[0].sourceDocuments
|
"sources": $flow.output[0].toolOutput
|
||||||
}
|
}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
|
|
@ -89,17 +89,19 @@ const howToUse = `
|
||||||
|-----------|-----------|
|
|-----------|-----------|
|
||||||
| user | john doe |
|
| user | john doe |
|
||||||
|
|
||||||
2. If you want to use the agent's output as the value to update state, it is available as available as \`$flow.output\` with the following structure (array):
|
2. If you want to use the Tool Node's output as the value to update state, it is available as available as \`$flow.output\` with the following structure (array):
|
||||||
\`\`\`json
|
\`\`\`json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"content": "Hello! How can I assist you today?",
|
"tool": "tool's name",
|
||||||
|
"toolInput": {},
|
||||||
|
"toolOutput": "tool's output content",
|
||||||
"sourceDocuments": [
|
"sourceDocuments": [
|
||||||
{
|
{
|
||||||
"pageContent": "This is the page content",
|
"pageContent": "This is the page content",
|
||||||
"metadata": "{foo: var}",
|
"metadata": "{foo: var}"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
@ -107,7 +109,7 @@ const howToUse = `
|
||||||
For example:
|
For example:
|
||||||
| Key | Value |
|
| Key | Value |
|
||||||
|--------------|-------------------------------------------|
|
|--------------|-------------------------------------------|
|
||||||
| sources | \`$flow.output[0].sourceDocuments\` |
|
| sources | \`$flow.output[0].toolOutput\` |
|
||||||
|
|
||||||
3. You can get default flow config, including the current "state":
|
3. You can get default flow config, including the current "state":
|
||||||
- \`$flow.sessionId\`
|
- \`$flow.sessionId\`
|
||||||
|
|
@ -152,7 +154,7 @@ class ToolNode_SeqAgents implements INode {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Tool Node'
|
this.label = 'Tool Node'
|
||||||
this.name = 'seqToolNode'
|
this.name = 'seqToolNode'
|
||||||
this.version = 2.0
|
this.version = 2.1
|
||||||
this.type = 'ToolNode'
|
this.type = 'ToolNode'
|
||||||
this.icon = 'toolNode.svg'
|
this.icon = 'toolNode.svg'
|
||||||
this.category = 'Sequential Agents'
|
this.category = 'Sequential Agents'
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import * as path from 'path'
|
||||||
import { JSDOM } from 'jsdom'
|
import { JSDOM } from 'jsdom'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { DataSource } from 'typeorm'
|
import { DataSource } from 'typeorm'
|
||||||
import { ICommonObject, IDatabaseEntity, IMessage, INodeData, IVariable, MessageContentImageUrl } from './Interface'
|
import { ICommonObject, IDatabaseEntity, IDocument, IMessage, INodeData, IVariable, MessageContentImageUrl } from './Interface'
|
||||||
import { AES, enc } from 'crypto-js'
|
import { AES, enc } from 'crypto-js'
|
||||||
import { AIMessage, HumanMessage, BaseMessage } from '@langchain/core/messages'
|
import { AIMessage, HumanMessage, BaseMessage } from '@langchain/core/messages'
|
||||||
import { getFileFromStorage } from './storageUtils'
|
import { getFileFromStorage } from './storageUtils'
|
||||||
|
|
@ -609,10 +609,11 @@ export const mapChatMessageToBaseMessage = async (chatmessages: any[] = []): Pro
|
||||||
if (message.role === 'apiMessage' || message.type === 'apiMessage') {
|
if (message.role === 'apiMessage' || message.type === 'apiMessage') {
|
||||||
chatHistory.push(new AIMessage(message.content || ''))
|
chatHistory.push(new AIMessage(message.content || ''))
|
||||||
} else if (message.role === 'userMessage' || message.role === 'userMessage') {
|
} else if (message.role === 'userMessage' || message.role === 'userMessage') {
|
||||||
// check for image uploads
|
// check for image/files uploads
|
||||||
if (message.fileUploads) {
|
if (message.fileUploads) {
|
||||||
// example: [{"type":"stored-file","name":"0_DiXc4ZklSTo3M8J4.jpg","mime":"image/jpeg"}]
|
// example: [{"type":"stored-file","name":"0_DiXc4ZklSTo3M8J4.jpg","mime":"image/jpeg"}]
|
||||||
try {
|
try {
|
||||||
|
let messageWithFileUploads = ''
|
||||||
const uploads = JSON.parse(message.fileUploads)
|
const uploads = JSON.parse(message.fileUploads)
|
||||||
const imageContents: MessageContentImageUrl[] = []
|
const imageContents: MessageContentImageUrl[] = []
|
||||||
for (const upload of uploads) {
|
for (const upload of uploads) {
|
||||||
|
|
@ -634,14 +635,32 @@ export const mapChatMessageToBaseMessage = async (chatmessages: any[] = []): Pro
|
||||||
url: upload.data
|
url: upload.data
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
} else if (upload.type === 'stored-file:full') {
|
||||||
|
const fileLoaderNodeModule = await import('../nodes/documentloaders/File/File')
|
||||||
|
// @ts-ignore
|
||||||
|
const fileLoaderNodeInstance = new fileLoaderNodeModule.nodeClass()
|
||||||
|
const options = {
|
||||||
|
retrieveAttachmentChatId: true,
|
||||||
|
chatflowid: message.chatflowid,
|
||||||
|
chatId: message.chatId
|
||||||
|
}
|
||||||
|
const nodeData = {
|
||||||
|
inputs: {
|
||||||
|
txtFile: `FILE-STORAGE::${JSON.stringify([upload.name])}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const documents: IDocument[] = await fileLoaderNodeInstance.init(nodeData, '', options)
|
||||||
|
const pageContents = documents.map((doc) => doc.pageContent).join('\n')
|
||||||
|
messageWithFileUploads += `<doc name='${upload.name}'>${pageContents}</doc>\n\n`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const messageContent = messageWithFileUploads ? `${messageWithFileUploads}\n\n${message.content}` : message.content
|
||||||
chatHistory.push(
|
chatHistory.push(
|
||||||
new HumanMessage({
|
new HumanMessage({
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: message.content
|
text: messageContent
|
||||||
},
|
},
|
||||||
...imageContents
|
...imageContents
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ export type MessageType = 'apiMessage' | 'userMessage'
|
||||||
|
|
||||||
export type ChatflowType = 'CHATFLOW' | 'MULTIAGENT'
|
export type ChatflowType = 'CHATFLOW' | 'MULTIAGENT'
|
||||||
|
|
||||||
export enum chatType {
|
export enum ChatType {
|
||||||
INTERNAL = 'INTERNAL',
|
INTERNAL = 'INTERNAL',
|
||||||
EXTERNAL = 'EXTERNAL'
|
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 { Request, Response, NextFunction } from 'express'
|
||||||
import { ChatMessageRatingType, chatType, IReactFlowObject } from '../../Interface'
|
import { ChatMessageRatingType, ChatType, IReactFlowObject } from '../../Interface'
|
||||||
import chatflowsService from '../../services/chatflows'
|
import chatflowsService from '../../services/chatflows'
|
||||||
import chatMessagesService from '../../services/chat-messages'
|
import chatMessagesService from '../../services/chat-messages'
|
||||||
import { clearSessionMemory } from '../../utils'
|
import { aMonthAgo, clearSessionMemory, setDateToStartOrEndOfDay } from '../../utils'
|
||||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||||
import { FindOptionsWhere } from 'typeorm'
|
import { Between, FindOptionsWhere } from 'typeorm'
|
||||||
import { ChatMessage } from '../../database/entities/ChatMessage'
|
import { ChatMessage } from '../../database/entities/ChatMessage'
|
||||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||||
import { StatusCodes } from 'http-status-codes'
|
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) => {
|
const createChatMessage = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -26,16 +49,16 @@ const createChatMessage = async (req: Request, res: Response, next: NextFunction
|
||||||
|
|
||||||
const getAllChatMessages = async (req: Request, res: Response, next: NextFunction) => {
|
const getAllChatMessages = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
let chatTypeFilter = req.query?.chatType as chatType | undefined
|
let chatTypeFilter = req.query?.chatType as ChatType | undefined
|
||||||
if (chatTypeFilter) {
|
if (chatTypeFilter) {
|
||||||
try {
|
try {
|
||||||
const chatTypeFilterArray = JSON.parse(chatTypeFilter)
|
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
|
chatTypeFilter = undefined
|
||||||
} else if (chatTypeFilterArray.includes(chatType.EXTERNAL)) {
|
} else if (chatTypeFilterArray.includes(ChatType.EXTERNAL)) {
|
||||||
chatTypeFilter = chatType.EXTERNAL
|
chatTypeFilter = ChatType.EXTERNAL
|
||||||
} else if (chatTypeFilterArray.includes(chatType.INTERNAL)) {
|
} else if (chatTypeFilterArray.includes(ChatType.INTERNAL)) {
|
||||||
chatTypeFilter = chatType.INTERNAL
|
chatTypeFilter = ChatType.INTERNAL
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return res.status(500).send(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
|
const feedback = req.query?.feedback as boolean | undefined
|
||||||
let feedbackTypeFilters = req.query?.feedbackType as ChatMessageRatingType[] | undefined
|
let feedbackTypeFilters = req.query?.feedbackType as ChatMessageRatingType[] | undefined
|
||||||
if (feedbackTypeFilters) {
|
if (feedbackTypeFilters) {
|
||||||
try {
|
feedbackTypeFilters = getFeedbackTypeFilters(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
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return res.status(500).send(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (typeof req.params === 'undefined' || !req.params.id) {
|
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||||
throw new InternalFlowiseError(
|
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 startDate = req.query?.startDate as string | undefined
|
||||||
const endDate = req.query?.endDate as string | undefined
|
const endDate = req.query?.endDate as string | undefined
|
||||||
const feedback = req.query?.feedback as boolean | 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(
|
const apiResponse = await chatMessagesService.getAllInternalChatMessages(
|
||||||
req.params.id,
|
req.params.id,
|
||||||
chatType.INTERNAL,
|
ChatType.INTERNAL,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
chatId,
|
chatId,
|
||||||
memoryType,
|
memoryType,
|
||||||
|
|
@ -115,7 +126,8 @@ const getAllInternalChatMessages = async (req: Request, res: Response, next: Nex
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
messageId,
|
messageId,
|
||||||
feedback
|
feedback,
|
||||||
|
feedbackTypeFilters
|
||||||
)
|
)
|
||||||
return res.json(parseAPIResponse(apiResponse))
|
return res.json(parseAPIResponse(apiResponse))
|
||||||
} catch (error) {
|
} 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) => {
|
const removeAllChatMessages = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
|
|
@ -138,14 +149,75 @@ const removeAllChatMessages = async (req: Request, res: Response, next: NextFunc
|
||||||
if (!chatflow) {
|
if (!chatflow) {
|
||||||
return res.status(404).send(`Chatflow ${req.params.id} not found`)
|
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 flowData = chatflow.flowData
|
||||||
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
|
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
|
||||||
const nodes = parsedFlowData.nodes
|
const nodes = parsedFlowData.nodes
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
await clearSessionMemory(
|
await clearSessionMemory(
|
||||||
nodes,
|
nodes,
|
||||||
|
|
@ -164,9 +236,15 @@ const removeAllChatMessages = async (req: Request, res: Response, next: NextFunc
|
||||||
if (chatId) deleteOptions.chatId = chatId
|
if (chatId) deleteOptions.chatId = chatId
|
||||||
if (memoryType) deleteOptions.memoryType = memoryType
|
if (memoryType) deleteOptions.memoryType = memoryType
|
||||||
if (sessionId) deleteOptions.sessionId = sessionId
|
if (sessionId) deleteOptions.sessionId = sessionId
|
||||||
if (chatType) deleteOptions.chatType = chatType
|
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)
|
const apiResponse = await chatMessagesService.removeAllChatMessages(chatId, chatflowid, deleteOptions)
|
||||||
return res.json(apiResponse)
|
return res.json(apiResponse)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error)
|
next(error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { StatusCodes } from 'http-status-codes'
|
import { StatusCodes } from 'http-status-codes'
|
||||||
import { Request, Response, NextFunction } from 'express'
|
import { Request, Response, NextFunction } from 'express'
|
||||||
import statsService from '../../services/stats'
|
import statsService from '../../services/stats'
|
||||||
import { ChatMessageRatingType, chatType } from '../../Interface'
|
import { ChatMessageRatingType, ChatType } from '../../Interface'
|
||||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||||
import { getErrorMessage } from '../../errors/utils'
|
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!`)
|
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: statsController.getChatflowStats - id not provided!`)
|
||||||
}
|
}
|
||||||
const chatflowid = req.params.id
|
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 startDate = req.query?.startDate as string | undefined
|
||||||
const endDate = req.query?.endDate as string | undefined
|
const endDate = req.query?.endDate as string | undefined
|
||||||
let feedbackTypeFilters = req.query?.feedbackType as ChatMessageRatingType[] | undefined
|
let feedbackTypeFilters = req.query?.feedbackType as ChatMessageRatingType[] | undefined
|
||||||
if (chatTypeFilter) {
|
if (chatTypeFilter) {
|
||||||
try {
|
try {
|
||||||
const chatTypeFilterArray = JSON.parse(chatTypeFilter)
|
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
|
chatTypeFilter = undefined
|
||||||
} else if (chatTypeFilterArray.includes(chatType.EXTERNAL)) {
|
} else if (chatTypeFilterArray.includes(ChatType.EXTERNAL)) {
|
||||||
chatTypeFilter = chatType.EXTERNAL
|
chatTypeFilter = ChatType.EXTERNAL
|
||||||
} else if (chatTypeFilterArray.includes(chatType.INTERNAL)) {
|
} else if (chatTypeFilterArray.includes(ChatType.INTERNAL)) {
|
||||||
chatTypeFilter = chatType.INTERNAL
|
chatTypeFilter = ChatType.INTERNAL
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new InternalFlowiseError(
|
throw new InternalFlowiseError(
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,8 @@ export class App {
|
||||||
'/api/v1/get-upload-file',
|
'/api/v1/get-upload-file',
|
||||||
'/api/v1/ip',
|
'/api/v1/ip',
|
||||||
'/api/v1/ping',
|
'/api/v1/ping',
|
||||||
'/api/v1/version'
|
'/api/v1/version',
|
||||||
|
'/api/v1/attachments'
|
||||||
]
|
]
|
||||||
const URL_CASE_INSENSITIVE_REGEX: RegExp = /\/api\/v1\//i
|
const URL_CASE_INSENSITIVE_REGEX: RegExp = /\/api\/v1\//i
|
||||||
const URL_CASE_SENSITIVE_REGEX: RegExp = /\/api\/v1\//
|
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
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import apikeyRouter from './apikey'
|
import apikeyRouter from './apikey'
|
||||||
import assistantsRouter from './assistants'
|
import assistantsRouter from './assistants'
|
||||||
|
import attachmentsRouter from './attachments'
|
||||||
import chatMessageRouter from './chat-messages'
|
import chatMessageRouter from './chat-messages'
|
||||||
import chatflowsRouter from './chatflows'
|
import chatflowsRouter from './chatflows'
|
||||||
import chatflowsStreamingRouter from './chatflows-streaming'
|
import chatflowsStreamingRouter from './chatflows-streaming'
|
||||||
|
|
@ -47,6 +48,7 @@ const router = express.Router()
|
||||||
router.use('/ping', pingRouter)
|
router.use('/ping', pingRouter)
|
||||||
router.use('/apikey', apikeyRouter)
|
router.use('/apikey', apikeyRouter)
|
||||||
router.use('/assistants', assistantsRouter)
|
router.use('/assistants', assistantsRouter)
|
||||||
|
router.use('/attachments', attachmentsRouter)
|
||||||
router.use('/chatflows', chatflowsRouter)
|
router.use('/chatflows', chatflowsRouter)
|
||||||
router.use('/chatflows-streaming', chatflowsStreamingRouter)
|
router.use('/chatflows-streaming', chatflowsStreamingRouter)
|
||||||
router.use('/chatmessage', chatMessageRouter)
|
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 { DeleteResult, FindOptionsWhere } from 'typeorm'
|
||||||
import { StatusCodes } from 'http-status-codes'
|
import { StatusCodes } from 'http-status-codes'
|
||||||
import { ChatMessageRatingType, chatType, IChatMessage } from '../../Interface'
|
import { ChatMessageRatingType, ChatType, IChatMessage } from '../../Interface'
|
||||||
import { utilGetChatMessage } from '../../utils/getChatMessage'
|
import { utilGetChatMessage } from '../../utils/getChatMessage'
|
||||||
import { utilAddChatMessage } from '../../utils/addChatMesage'
|
import { utilAddChatMessage } from '../../utils/addChatMesage'
|
||||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||||
|
|
@ -27,7 +27,7 @@ const createChatMessage = async (chatMessage: Partial<IChatMessage>) => {
|
||||||
// Get all chatmessages from chatflowid
|
// Get all chatmessages from chatflowid
|
||||||
const getAllChatMessages = async (
|
const getAllChatMessages = async (
|
||||||
chatflowId: string,
|
chatflowId: string,
|
||||||
chatTypeFilter: chatType | undefined,
|
chatTypeFilter: ChatType | undefined,
|
||||||
sortOrder: string = 'ASC',
|
sortOrder: string = 'ASC',
|
||||||
chatId?: string,
|
chatId?: string,
|
||||||
memoryType?: string,
|
memoryType?: string,
|
||||||
|
|
@ -64,7 +64,7 @@ const getAllChatMessages = async (
|
||||||
// Get internal chatmessages from chatflowid
|
// Get internal chatmessages from chatflowid
|
||||||
const getAllInternalChatMessages = async (
|
const getAllInternalChatMessages = async (
|
||||||
chatflowId: string,
|
chatflowId: string,
|
||||||
chatTypeFilter: chatType | undefined,
|
chatTypeFilter: ChatType | undefined,
|
||||||
sortOrder: string = 'ASC',
|
sortOrder: string = 'ASC',
|
||||||
chatId?: string,
|
chatId?: string,
|
||||||
memoryType?: 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) => {
|
const abortChatMessage = async (chatId: string, chatflowid: string) => {
|
||||||
try {
|
try {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
|
|
@ -155,5 +184,6 @@ export default {
|
||||||
getAllChatMessages,
|
getAllChatMessages,
|
||||||
getAllInternalChatMessages,
|
getAllInternalChatMessages,
|
||||||
removeAllChatMessages,
|
removeAllChatMessages,
|
||||||
|
removeChatMessagesByMessageIds,
|
||||||
abortChatMessage
|
abortChatMessage
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
removeSpecificFileFromStorage
|
removeSpecificFileFromStorage
|
||||||
} from 'flowise-components'
|
} from 'flowise-components'
|
||||||
import {
|
import {
|
||||||
chatType,
|
ChatType,
|
||||||
DocumentStoreStatus,
|
DocumentStoreStatus,
|
||||||
IDocumentStoreFileChunkPagedResponse,
|
IDocumentStoreFileChunkPagedResponse,
|
||||||
IDocumentStoreLoader,
|
IDocumentStoreLoader,
|
||||||
|
|
@ -995,7 +995,7 @@ const _insertIntoVectorStoreWorkerThread = async (data: ICommonObject) => {
|
||||||
data: {
|
data: {
|
||||||
version: await getAppVersion(),
|
version: await getAppVersion(),
|
||||||
chatlowId: chatflowid,
|
chatlowId: chatflowid,
|
||||||
type: chatType.INTERNAL,
|
type: ChatType.INTERNAL,
|
||||||
flowGraph: omit(indexResult['result'], ['totalKeys', 'addedDocs'])
|
flowGraph: omit(indexResult['result'], ['totalKeys', 'addedDocs'])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { StatusCodes } from 'http-status-codes'
|
import { StatusCodes } from 'http-status-codes'
|
||||||
import { ChatMessageRatingType, chatType } from '../../Interface'
|
import { ChatMessageRatingType, ChatType } from '../../Interface'
|
||||||
import { ChatMessage } from '../../database/entities/ChatMessage'
|
import { ChatMessage } from '../../database/entities/ChatMessage'
|
||||||
import { utilGetChatMessage } from '../../utils/getChatMessage'
|
import { utilGetChatMessage } from '../../utils/getChatMessage'
|
||||||
import { ChatMessageFeedback } from '../../database/entities/ChatMessageFeedback'
|
import { ChatMessageFeedback } from '../../database/entities/ChatMessageFeedback'
|
||||||
|
|
@ -9,7 +9,7 @@ import { getErrorMessage } from '../../errors/utils'
|
||||||
// get stats for showing in chatflow
|
// get stats for showing in chatflow
|
||||||
const getChatflowStats = async (
|
const getChatflowStats = async (
|
||||||
chatflowid: string,
|
chatflowid: string,
|
||||||
chatTypeFilter: chatType | undefined,
|
chatTypeFilter: ChatType | undefined,
|
||||||
startDate?: string,
|
startDate?: string,
|
||||||
endDate?: string,
|
endDate?: string,
|
||||||
messageId?: string,
|
messageId?: string,
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,8 @@ export const buildAgentGraph = async (
|
||||||
isInternal: boolean,
|
isInternal: boolean,
|
||||||
baseURL?: string,
|
baseURL?: string,
|
||||||
sseStreamer?: IServerSideEventStreamer,
|
sseStreamer?: IServerSideEventStreamer,
|
||||||
shouldStreamResponse?: boolean
|
shouldStreamResponse?: boolean,
|
||||||
|
uploadedFilesContent?: string
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
|
|
@ -129,7 +130,8 @@ export const buildAgentGraph = async (
|
||||||
cachePool: appServer.cachePool,
|
cachePool: appServer.cachePool,
|
||||||
isUpsert: false,
|
isUpsert: false,
|
||||||
uploads: incomingInput.uploads,
|
uploads: incomingInput.uploads,
|
||||||
baseURL
|
baseURL,
|
||||||
|
uploadedFilesContent
|
||||||
})
|
})
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
|
@ -188,7 +190,8 @@ export const buildAgentGraph = async (
|
||||||
chatHistory,
|
chatHistory,
|
||||||
incomingInput?.overrideConfig,
|
incomingInput?.overrideConfig,
|
||||||
sessionId || chatId,
|
sessionId || chatId,
|
||||||
seqAgentNodes.some((node) => node.data.inputs?.summarization)
|
seqAgentNodes.some((node) => node.data.inputs?.summarization),
|
||||||
|
uploadedFilesContent
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
isSequential = true
|
isSequential = true
|
||||||
|
|
@ -204,7 +207,8 @@ export const buildAgentGraph = async (
|
||||||
chatHistory,
|
chatHistory,
|
||||||
incomingInput?.overrideConfig,
|
incomingInput?.overrideConfig,
|
||||||
sessionId || chatId,
|
sessionId || chatId,
|
||||||
incomingInput.action
|
incomingInput.action,
|
||||||
|
uploadedFilesContent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -348,7 +352,6 @@ export const buildAgentGraph = async (
|
||||||
if (isSequential && !finalResult && agentReasoning.length) {
|
if (isSequential && !finalResult && agentReasoning.length) {
|
||||||
const lastMessages = agentReasoning[agentReasoning.length - 1].messages
|
const lastMessages = agentReasoning[agentReasoning.length - 1].messages
|
||||||
const lastAgentReasoningMessage = lastMessages[lastMessages.length - 1]
|
const lastAgentReasoningMessage = lastMessages[lastMessages.length - 1]
|
||||||
|
|
||||||
// If last message is an AI Message with tool calls, that means the last node was interrupted
|
// 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) {
|
if (lastMessageRaw.tool_calls && lastMessageRaw.tool_calls.length > 0) {
|
||||||
// The last node that got interrupted
|
// The last node that got interrupted
|
||||||
|
|
@ -456,6 +459,7 @@ export const buildAgentGraph = async (
|
||||||
* @param {ICommonObject} overrideConfig
|
* @param {ICommonObject} overrideConfig
|
||||||
* @param {string} threadId
|
* @param {string} threadId
|
||||||
* @param {boolean} summarization
|
* @param {boolean} summarization
|
||||||
|
* @param {string} uploadedFilesContent,
|
||||||
*/
|
*/
|
||||||
const compileMultiAgentsGraph = async (
|
const compileMultiAgentsGraph = async (
|
||||||
chatflow: IChatFlow,
|
chatflow: IChatFlow,
|
||||||
|
|
@ -470,7 +474,8 @@ const compileMultiAgentsGraph = async (
|
||||||
chatHistory: IMessage[] = [],
|
chatHistory: IMessage[] = [],
|
||||||
overrideConfig?: ICommonObject,
|
overrideConfig?: ICommonObject,
|
||||||
threadId?: string,
|
threadId?: string,
|
||||||
summarization?: boolean
|
summarization?: boolean,
|
||||||
|
uploadedFilesContent?: string
|
||||||
) => {
|
) => {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
const channels: ITeamState = {
|
const channels: ITeamState = {
|
||||||
|
|
@ -502,7 +507,15 @@ const compileMultiAgentsGraph = async (
|
||||||
|
|
||||||
let flowNodeData = cloneDeep(workerNode.data)
|
let flowNodeData = cloneDeep(workerNode.data)
|
||||||
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
|
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 {
|
try {
|
||||||
const workerResult: IMultiAgentNode = await newNodeInstance.init(flowNodeData, question, options)
|
const workerResult: IMultiAgentNode = await newNodeInstance.init(flowNodeData, question, options)
|
||||||
|
|
@ -533,7 +546,15 @@ const compileMultiAgentsGraph = async (
|
||||||
let flowNodeData = cloneDeep(supervisorNode.data)
|
let flowNodeData = cloneDeep(supervisorNode.data)
|
||||||
|
|
||||||
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
|
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]
|
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
|
// Return stream result as we should only have 1 supervisor
|
||||||
|
const finalQuestion = uploadedFilesContent ? `${uploadedFilesContent}\n\n${question}` : question
|
||||||
return await graph.stream(
|
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 }
|
{ recursionLimit: supervisorResult?.recursionLimit ?? 100, callbacks: [loggerHandler, ...callbacks], configurable: config }
|
||||||
)
|
)
|
||||||
|
|
@ -641,7 +663,8 @@ const compileSeqAgentsGraph = async (
|
||||||
chatHistory: IMessage[] = [],
|
chatHistory: IMessage[] = [],
|
||||||
overrideConfig?: ICommonObject,
|
overrideConfig?: ICommonObject,
|
||||||
threadId?: string,
|
threadId?: string,
|
||||||
action?: IAction
|
action?: IAction,
|
||||||
|
uploadedFilesContent?: string
|
||||||
) => {
|
) => {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
|
|
||||||
|
|
@ -693,7 +716,15 @@ const compileSeqAgentsGraph = async (
|
||||||
|
|
||||||
flowNodeData = cloneDeep(node.data)
|
flowNodeData = cloneDeep(node.data)
|
||||||
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
|
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)
|
const seqAgentNode: ISeqAgentNode = await newNodeInstance.init(flowNodeData, question, options)
|
||||||
return seqAgentNode
|
return seqAgentNode
|
||||||
|
|
@ -997,8 +1028,9 @@ const compileSeqAgentsGraph = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const finalQuestion = uploadedFilesContent ? `${uploadedFilesContent}\n\n${question}` : question
|
||||||
let humanMsg: { messages: HumanMessage[] | ToolMessage[] } | null = {
|
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) {
|
if (action && action.mapping && question === action.mapping.approve) {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import {
|
||||||
IReactFlowObject,
|
IReactFlowObject,
|
||||||
IReactFlowNode,
|
IReactFlowNode,
|
||||||
IDepthQueue,
|
IDepthQueue,
|
||||||
chatType,
|
ChatType,
|
||||||
IChatMessage,
|
IChatMessage,
|
||||||
IChatFlow,
|
IChatFlow,
|
||||||
IReactFlowEdge
|
IReactFlowEdge
|
||||||
|
|
@ -88,12 +88,14 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||||
}
|
}
|
||||||
|
|
||||||
let fileUploads: IFileUpload[] = []
|
let fileUploads: IFileUpload[] = []
|
||||||
|
let uploadedFilesContent = ''
|
||||||
if (incomingInput.uploads) {
|
if (incomingInput.uploads) {
|
||||||
fileUploads = incomingInput.uploads
|
fileUploads = incomingInput.uploads
|
||||||
for (let i = 0; i < fileUploads.length; i += 1) {
|
for (let i = 0; i < fileUploads.length; i += 1) {
|
||||||
const upload = fileUploads[i]
|
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 filename = upload.name
|
||||||
const splitDataURI = upload.data.split(',')
|
const splitDataURI = upload.data.split(',')
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
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,
|
edges,
|
||||||
baseURL,
|
baseURL,
|
||||||
appServer.sseStreamer,
|
appServer.sseStreamer,
|
||||||
true
|
true,
|
||||||
|
uploadedFilesContent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -345,6 +355,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||||
apiMessageId,
|
apiMessageId,
|
||||||
componentNodes: appServer.nodesPool.componentNodes,
|
componentNodes: appServer.nodesPool.componentNodes,
|
||||||
question: incomingInput.question,
|
question: incomingInput.question,
|
||||||
|
uploadedFilesContent,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
chatId,
|
chatId,
|
||||||
sessionId: sessionId ?? '',
|
sessionId: sessionId ?? '',
|
||||||
|
|
@ -384,7 +395,8 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||||
reactFlowNodes,
|
reactFlowNodes,
|
||||||
incomingInput.question,
|
incomingInput.question,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
flowData
|
flowData,
|
||||||
|
uploadedFilesContent
|
||||||
)
|
)
|
||||||
nodeToExecuteData = reactFlowNodeData
|
nodeToExecuteData = reactFlowNodeData
|
||||||
|
|
||||||
|
|
@ -398,6 +410,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||||
const nodeInstance = new nodeModule.nodeClass({ sessionId })
|
const nodeInstance = new nodeModule.nodeClass({ sessionId })
|
||||||
|
|
||||||
isStreamValid = (req.body.streaming === 'true' || req.body.streaming === true) && isStreamValid
|
isStreamValid = (req.body.streaming === 'true' || req.body.streaming === true) && isStreamValid
|
||||||
|
const finalQuestion = uploadedFilesContent ? `${uploadedFilesContent}\n\n${incomingInput.question}` : incomingInput.question
|
||||||
|
|
||||||
const runParams = {
|
const runParams = {
|
||||||
chatId,
|
chatId,
|
||||||
|
|
@ -411,7 +424,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||||
prependMessages
|
prependMessages
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
|
let result = await nodeInstance.run(nodeToExecuteData, finalQuestion, {
|
||||||
...runParams,
|
...runParams,
|
||||||
...(isStreamValid && { sseStreamer: appServer.sseStreamer, shouldStreamResponse: true })
|
...(isStreamValid && { sseStreamer: appServer.sseStreamer, shouldStreamResponse: true })
|
||||||
})
|
})
|
||||||
|
|
@ -427,7 +440,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||||
role: 'userMessage',
|
role: 'userMessage',
|
||||||
content: incomingInput.question,
|
content: incomingInput.question,
|
||||||
chatflowid,
|
chatflowid,
|
||||||
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
|
chatType: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||||
chatId,
|
chatId,
|
||||||
memoryType,
|
memoryType,
|
||||||
sessionId,
|
sessionId,
|
||||||
|
|
@ -447,7 +460,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||||
role: 'apiMessage',
|
role: 'apiMessage',
|
||||||
content: resultText,
|
content: resultText,
|
||||||
chatflowid,
|
chatflowid,
|
||||||
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
|
chatType: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||||
chatId,
|
chatId,
|
||||||
memoryType,
|
memoryType,
|
||||||
sessionId
|
sessionId
|
||||||
|
|
@ -476,7 +489,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
||||||
version: await getAppVersion(),
|
version: await getAppVersion(),
|
||||||
chatflowId: chatflowid,
|
chatflowId: chatflowid,
|
||||||
chatId,
|
chatId,
|
||||||
type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
|
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||||
flowGraph: getTelemetryFlowObj(nodes, edges)
|
flowGraph: getTelemetryFlowObj(nodes, edges)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -517,7 +530,8 @@ const utilBuildAgentResponse = async (
|
||||||
edges: IReactFlowEdge[],
|
edges: IReactFlowEdge[],
|
||||||
baseURL?: string,
|
baseURL?: string,
|
||||||
sseStreamer?: IServerSideEventStreamer,
|
sseStreamer?: IServerSideEventStreamer,
|
||||||
shouldStreamResponse?: boolean
|
shouldStreamResponse?: boolean,
|
||||||
|
uploadedFilesContent?: string
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
|
|
@ -530,7 +544,8 @@ const utilBuildAgentResponse = async (
|
||||||
isInternal,
|
isInternal,
|
||||||
baseURL,
|
baseURL,
|
||||||
sseStreamer,
|
sseStreamer,
|
||||||
shouldStreamResponse
|
shouldStreamResponse,
|
||||||
|
uploadedFilesContent
|
||||||
)
|
)
|
||||||
if (streamResults) {
|
if (streamResults) {
|
||||||
const { finalResult, finalAction, sourceDocuments, artifacts, usedTools, agentReasoning } = streamResults
|
const { finalResult, finalAction, sourceDocuments, artifacts, usedTools, agentReasoning } = streamResults
|
||||||
|
|
@ -538,7 +553,7 @@ const utilBuildAgentResponse = async (
|
||||||
role: 'userMessage',
|
role: 'userMessage',
|
||||||
content: incomingInput.question,
|
content: incomingInput.question,
|
||||||
chatflowid: agentflow.id,
|
chatflowid: agentflow.id,
|
||||||
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
|
chatType: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||||
chatId,
|
chatId,
|
||||||
memoryType,
|
memoryType,
|
||||||
sessionId,
|
sessionId,
|
||||||
|
|
@ -553,7 +568,7 @@ const utilBuildAgentResponse = async (
|
||||||
role: 'apiMessage',
|
role: 'apiMessage',
|
||||||
content: finalResult,
|
content: finalResult,
|
||||||
chatflowid: agentflow.id,
|
chatflowid: agentflow.id,
|
||||||
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
|
chatType: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||||
chatId,
|
chatId,
|
||||||
memoryType,
|
memoryType,
|
||||||
sessionId
|
sessionId
|
||||||
|
|
@ -581,7 +596,7 @@ const utilBuildAgentResponse = async (
|
||||||
version: await getAppVersion(),
|
version: await getAppVersion(),
|
||||||
agentflowId: agentflow.id,
|
agentflowId: agentflow.id,
|
||||||
chatId,
|
chatId,
|
||||||
type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
|
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||||
flowGraph: getTelemetryFlowObj(nodes, edges)
|
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
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import { MoreThanOrEqual, LessThanOrEqual } from 'typeorm'
|
import { MoreThanOrEqual, LessThanOrEqual } from 'typeorm'
|
||||||
import { ChatMessageRatingType, chatType } from '../Interface'
|
import { ChatMessageRatingType, ChatType } from '../Interface'
|
||||||
import { ChatMessage } from '../database/entities/ChatMessage'
|
import { ChatMessage } from '../database/entities/ChatMessage'
|
||||||
import { ChatMessageFeedback } from '../database/entities/ChatMessageFeedback'
|
import { ChatMessageFeedback } from '../database/entities/ChatMessageFeedback'
|
||||||
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
||||||
|
import { aMonthAgo, setDateToStartOrEndOfDay } from '.'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method that get chat messages.
|
* Method that get chat messages.
|
||||||
* @param {string} chatflowid
|
* @param {string} chatflowid
|
||||||
* @param {chatType} chatType
|
* @param {ChatType} chatType
|
||||||
* @param {string} sortOrder
|
* @param {string} sortOrder
|
||||||
* @param {string} chatId
|
* @param {string} chatId
|
||||||
* @param {string} memoryType
|
* @param {string} memoryType
|
||||||
|
|
@ -19,7 +20,7 @@ import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
||||||
*/
|
*/
|
||||||
export const utilGetChatMessage = async (
|
export const utilGetChatMessage = async (
|
||||||
chatflowid: string,
|
chatflowid: string,
|
||||||
chatType: chatType | undefined,
|
chatType: ChatType | undefined,
|
||||||
sortOrder: string = 'ASC',
|
sortOrder: string = 'ASC',
|
||||||
chatId?: string,
|
chatId?: string,
|
||||||
memoryType?: string,
|
memoryType?: string,
|
||||||
|
|
@ -31,20 +32,6 @@ export const utilGetChatMessage = async (
|
||||||
feedbackTypes?: ChatMessageRatingType[]
|
feedbackTypes?: ChatMessageRatingType[]
|
||||||
): Promise<ChatMessage[]> => {
|
): Promise<ChatMessage[]> => {
|
||||||
const appServer = getRunningExpressApp()
|
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
|
let fromDate
|
||||||
if (startDate) fromDate = setDateToStartOrEndOfDay(startDate, 'start')
|
if (startDate) fromDate = setDateToStartOrEndOfDay(startDate, 'start')
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { InternalFlowiseError } from '../errors/internalFlowiseError'
|
||||||
type IUploadConfig = {
|
type IUploadConfig = {
|
||||||
isSpeechToTextEnabled: boolean
|
isSpeechToTextEnabled: boolean
|
||||||
isImageUploadAllowed: boolean
|
isImageUploadAllowed: boolean
|
||||||
isFileUploadAllowed: boolean
|
isRAGFileUploadAllowed: boolean
|
||||||
imgUploadSizeAndTypes: IUploadFileSizeAndTypes[]
|
imgUploadSizeAndTypes: IUploadFileSizeAndTypes[]
|
||||||
fileUploadSizeAndTypes: IUploadFileSizeAndTypes[]
|
fileUploadSizeAndTypes: IUploadFileSizeAndTypes[]
|
||||||
}
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ export const utilGetUploadsConfig = async (chatflowid: string): Promise<IUploadC
|
||||||
|
|
||||||
let isSpeechToTextEnabled = false
|
let isSpeechToTextEnabled = false
|
||||||
let isImageUploadAllowed = false
|
let isImageUploadAllowed = false
|
||||||
let isFileUploadAllowed = false
|
let isRAGFileUploadAllowed = false
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check for STT
|
* 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
|
* 1.) vector store with fileUpload = true && connected to a document loader with fileType
|
||||||
*/
|
*/
|
||||||
const fileUploadSizeAndTypes: IUploadFileSizeAndTypes[] = []
|
const fileUploadSizeAndTypes: IUploadFileSizeAndTypes[] = []
|
||||||
|
|
@ -70,7 +70,7 @@ export const utilGetUploadsConfig = async (chatflowid: string): Promise<IUploadC
|
||||||
fileTypes: fileType.split(', '),
|
fileTypes: fileType.split(', '),
|
||||||
maxUploadSize: 500
|
maxUploadSize: 500
|
||||||
})
|
})
|
||||||
isFileUploadAllowed = true
|
isRAGFileUploadAllowed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
@ -114,7 +114,7 @@ export const utilGetUploadsConfig = async (chatflowid: string): Promise<IUploadC
|
||||||
return {
|
return {
|
||||||
isSpeechToTextEnabled,
|
isSpeechToTextEnabled,
|
||||||
isImageUploadAllowed,
|
isImageUploadAllowed,
|
||||||
isFileUploadAllowed,
|
isRAGFileUploadAllowed,
|
||||||
imgUploadSizeAndTypes,
|
imgUploadSizeAndTypes,
|
||||||
fileUploadSizeAndTypes
|
fileUploadSizeAndTypes
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import { InternalFlowiseError } from '../errors/internalFlowiseError'
|
||||||
import { StatusCodes } from 'http-status-codes'
|
import { StatusCodes } from 'http-status-codes'
|
||||||
|
|
||||||
const QUESTION_VAR_PREFIX = 'question'
|
const QUESTION_VAR_PREFIX = 'question'
|
||||||
|
const FILE_ATTACHMENT_PREFIX = 'file_attachment'
|
||||||
const CHAT_HISTORY_VAR_PREFIX = 'chat_history'
|
const CHAT_HISTORY_VAR_PREFIX = 'chat_history'
|
||||||
const REDACTED_CREDENTIAL_VALUE = '_FLOWISE_BLANK_07167752-1a71-43b1-bf8f-4f32252165db'
|
const REDACTED_CREDENTIAL_VALUE = '_FLOWISE_BLANK_07167752-1a71-43b1-bf8f-4f32252165db'
|
||||||
|
|
||||||
|
|
@ -438,6 +439,7 @@ type BuildFlowParams = {
|
||||||
stopNodeId?: string
|
stopNodeId?: string
|
||||||
uploads?: IFileUpload[]
|
uploads?: IFileUpload[]
|
||||||
baseURL?: string
|
baseURL?: string
|
||||||
|
uploadedFilesContent?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -452,6 +454,7 @@ export const buildFlow = async ({
|
||||||
depthQueue,
|
depthQueue,
|
||||||
componentNodes,
|
componentNodes,
|
||||||
question,
|
question,
|
||||||
|
uploadedFilesContent,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
apiMessageId,
|
apiMessageId,
|
||||||
chatId,
|
chatId,
|
||||||
|
|
@ -516,7 +519,8 @@ export const buildFlow = async ({
|
||||||
flowNodes,
|
flowNodes,
|
||||||
question,
|
question,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
flowData
|
flowData,
|
||||||
|
uploadedFilesContent
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isUpsert && stopNodeId && nodeId === stopNodeId) {
|
if (isUpsert && stopNodeId && nodeId === stopNodeId) {
|
||||||
|
|
@ -546,7 +550,8 @@ export const buildFlow = async ({
|
||||||
initializedNodes.add(nodeId)
|
initializedNodes.add(nodeId)
|
||||||
} else {
|
} else {
|
||||||
logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`)
|
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,
|
chatId,
|
||||||
sessionId,
|
sessionId,
|
||||||
chatflowid,
|
chatflowid,
|
||||||
|
|
@ -770,7 +775,8 @@ export const getVariableValue = async (
|
||||||
question: string,
|
question: string,
|
||||||
chatHistory: IMessage[],
|
chatHistory: IMessage[],
|
||||||
isAcceptVariable = false,
|
isAcceptVariable = false,
|
||||||
flowData?: ICommonObject
|
flowData?: ICommonObject,
|
||||||
|
uploadedFilesContent?: string
|
||||||
) => {
|
) => {
|
||||||
const isObject = typeof paramValue === 'object'
|
const isObject = typeof paramValue === 'object'
|
||||||
const initialValue = (isObject ? JSON.stringify(paramValue) : paramValue) ?? ''
|
const initialValue = (isObject ? JSON.stringify(paramValue) : paramValue) ?? ''
|
||||||
|
|
@ -803,6 +809,10 @@ export const getVariableValue = async (
|
||||||
variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(question, false)
|
variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(question, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAcceptVariable && variableFullPath === FILE_ATTACHMENT_PREFIX) {
|
||||||
|
variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(uploadedFilesContent, false)
|
||||||
|
}
|
||||||
|
|
||||||
if (isAcceptVariable && variableFullPath === CHAT_HISTORY_VAR_PREFIX) {
|
if (isAcceptVariable && variableFullPath === CHAT_HISTORY_VAR_PREFIX) {
|
||||||
variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(convertChatHistoryToText(chatHistory), false)
|
variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(convertChatHistoryToText(chatHistory), false)
|
||||||
}
|
}
|
||||||
|
|
@ -916,7 +926,8 @@ export const resolveVariables = async (
|
||||||
reactFlowNodes: IReactFlowNode[],
|
reactFlowNodes: IReactFlowNode[],
|
||||||
question: string,
|
question: string,
|
||||||
chatHistory: IMessage[],
|
chatHistory: IMessage[],
|
||||||
flowData?: ICommonObject
|
flowData?: ICommonObject,
|
||||||
|
uploadedFilesContent?: string
|
||||||
): Promise<INodeData> => {
|
): Promise<INodeData> => {
|
||||||
let flowNodeData = cloneDeep(reactFlowNodeData)
|
let flowNodeData = cloneDeep(reactFlowNodeData)
|
||||||
const types = 'inputs'
|
const types = 'inputs'
|
||||||
|
|
@ -934,7 +945,8 @@ export const resolveVariables = async (
|
||||||
question,
|
question,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
undefined,
|
undefined,
|
||||||
flowData
|
flowData,
|
||||||
|
uploadedFilesContent
|
||||||
)
|
)
|
||||||
resolvedInstances.push(resolvedInstance)
|
resolvedInstances.push(resolvedInstance)
|
||||||
}
|
}
|
||||||
|
|
@ -948,7 +960,8 @@ export const resolveVariables = async (
|
||||||
question,
|
question,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
isAcceptVariable,
|
isAcceptVariable,
|
||||||
flowData
|
flowData,
|
||||||
|
uploadedFilesContent
|
||||||
)
|
)
|
||||||
paramsObj[key] = resolvedInstance
|
paramsObj[key] = resolvedInstance
|
||||||
}
|
}
|
||||||
|
|
@ -1572,3 +1585,18 @@ export const convertToValidFilename = (word: string) => {
|
||||||
.replace(' ', '')
|
.replace(' ', '')
|
||||||
.toLowerCase()
|
.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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
getStartingNodes
|
getStartingNodes
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { validateChatflowAPIKey } from './validateKey'
|
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 { ChatFlow } from '../database/entities/ChatFlow'
|
||||||
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
||||||
import { UpsertHistory } from '../database/entities/UpsertHistory'
|
import { UpsertHistory } from '../database/entities/UpsertHistory'
|
||||||
|
|
@ -195,7 +195,7 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
|
||||||
data: {
|
data: {
|
||||||
version: await getAppVersion(),
|
version: await getAppVersion(),
|
||||||
chatlowId: chatflowid,
|
chatlowId: chatflowid,
|
||||||
type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
|
type: isInternal ? ChatType.INTERNAL : ChatType.EXTERNAL,
|
||||||
flowGraph: getTelemetryFlowObj(nodes, edges),
|
flowGraph: getTelemetryFlowObj(nodes, edges),
|
||||||
stopNodeId
|
stopNodeId
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import client from './client'
|
||||||
|
|
||||||
|
const createAttachment = (chatflowid, chatid, formData) =>
|
||||||
|
client.post(`/attachments/${chatflowid}/${chatid}`, formData, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
|
||||||
|
export default {
|
||||||
|
createAttachment
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
|
|
@ -11,6 +11,7 @@ import AnalyseFlow from '@/ui-component/extended/AnalyseFlow'
|
||||||
import StarterPrompts from '@/ui-component/extended/StarterPrompts'
|
import StarterPrompts from '@/ui-component/extended/StarterPrompts'
|
||||||
import Leads from '@/ui-component/extended/Leads'
|
import Leads from '@/ui-component/extended/Leads'
|
||||||
import FollowUpPrompts from '@/ui-component/extended/FollowUpPrompts'
|
import FollowUpPrompts from '@/ui-component/extended/FollowUpPrompts'
|
||||||
|
import FileUpload from '@/ui-component/extended/FileUpload'
|
||||||
|
|
||||||
const CHATFLOW_CONFIGURATION_TABS = [
|
const CHATFLOW_CONFIGURATION_TABS = [
|
||||||
{
|
{
|
||||||
|
|
@ -44,6 +45,10 @@ const CHATFLOW_CONFIGURATION_TABS = [
|
||||||
{
|
{
|
||||||
label: 'Leads',
|
label: 'Leads',
|
||||||
id: 'leads'
|
id: 'leads'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'File Upload',
|
||||||
|
id: 'fileUpload'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -85,7 +90,7 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
onClose={onCancel}
|
onClose={onCancel}
|
||||||
open={show}
|
open={show}
|
||||||
fullWidth
|
fullWidth
|
||||||
maxWidth={'md'}
|
maxWidth={'lg'}
|
||||||
aria-labelledby='alert-dialog-title'
|
aria-labelledby='alert-dialog-title'
|
||||||
aria-describedby='alert-dialog-description'
|
aria-describedby='alert-dialog-description'
|
||||||
>
|
>
|
||||||
|
|
@ -127,6 +132,7 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
{item.id === 'allowedDomains' ? <AllowedDomains dialogProps={dialogProps} /> : null}
|
{item.id === 'allowedDomains' ? <AllowedDomains dialogProps={dialogProps} /> : null}
|
||||||
{item.id === 'analyseChatflow' ? <AnalyseFlow dialogProps={dialogProps} /> : null}
|
{item.id === 'analyseChatflow' ? <AnalyseFlow dialogProps={dialogProps} /> : null}
|
||||||
{item.id === 'leads' ? <Leads dialogProps={dialogProps} /> : null}
|
{item.id === 'leads' ? <Leads dialogProps={dialogProps} /> : null}
|
||||||
|
{item.id === 'fileUpload' ? <FileUpload dialogProps={dialogProps} /> : null}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
))}
|
))}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,10 @@ import {
|
||||||
Chip,
|
Chip,
|
||||||
Card,
|
Card,
|
||||||
CardMedia,
|
CardMedia,
|
||||||
CardContent
|
CardContent,
|
||||||
|
FormControlLabel,
|
||||||
|
Checkbox,
|
||||||
|
DialogActions
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useTheme } from '@mui/material/styles'
|
import { useTheme } from '@mui/material/styles'
|
||||||
import DatePicker from 'react-datepicker'
|
import DatePicker from 'react-datepicker'
|
||||||
|
|
@ -84,6 +87,52 @@ const messageImageStyle = {
|
||||||
objectFit: 'cover'
|
objectFit: 'cover'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ConfirmDeleteMessageDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||||
|
const portalElement = document.getElementById('portal')
|
||||||
|
const [hardDelete, setHardDelete] = useState(false)
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
onConfirm(hardDelete)
|
||||||
|
}
|
||||||
|
|
||||||
|
const component = show ? (
|
||||||
|
<Dialog
|
||||||
|
fullWidth
|
||||||
|
maxWidth='xs'
|
||||||
|
open={show}
|
||||||
|
onClose={onCancel}
|
||||||
|
aria-labelledby='alert-dialog-title'
|
||||||
|
aria-describedby='alert-dialog-description'
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||||
|
{dialogProps.title}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<span style={{ marginTop: '20px', marginBottom: '20px' }}>{dialogProps.description}</span>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Checkbox checked={hardDelete} onChange={(event) => setHardDelete(event.target.checked)} />}
|
||||||
|
label='Remove messages from 3rd party Memory Node'
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onCancel}>{dialogProps.cancelButtonName}</Button>
|
||||||
|
<StyledButton variant='contained' onClick={onSubmit}>
|
||||||
|
{dialogProps.confirmButtonName}
|
||||||
|
</StyledButton>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
return createPortal(component, portalElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmDeleteMessageDialog.propTypes = {
|
||||||
|
show: PropTypes.bool,
|
||||||
|
dialogProps: PropTypes.object,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
onConfirm: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
@ -103,6 +152,8 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
const [selectedChatId, setSelectedChatId] = useState('')
|
const [selectedChatId, setSelectedChatId] = useState('')
|
||||||
const [sourceDialogOpen, setSourceDialogOpen] = useState(false)
|
const [sourceDialogOpen, setSourceDialogOpen] = useState(false)
|
||||||
const [sourceDialogProps, setSourceDialogProps] = useState({})
|
const [sourceDialogProps, setSourceDialogProps] = useState({})
|
||||||
|
const [hardDeleteDialogOpen, setHardDeleteDialogOpen] = useState(false)
|
||||||
|
const [hardDeleteDialogProps, setHardDeleteDialogProps] = useState({})
|
||||||
const [chatTypeFilter, setChatTypeFilter] = useState([])
|
const [chatTypeFilter, setChatTypeFilter] = useState([])
|
||||||
const [feedbackTypeFilter, setFeedbackTypeFilter] = useState([])
|
const [feedbackTypeFilter, setFeedbackTypeFilter] = useState([])
|
||||||
const [startDate, setStartDate] = useState(new Date().setMonth(new Date().getMonth() - 1))
|
const [startDate, setStartDate] = useState(new Date().setMonth(new Date().getMonth() - 1))
|
||||||
|
|
@ -175,6 +226,83 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onDeleteMessages = () => {
|
||||||
|
setHardDeleteDialogProps({
|
||||||
|
title: 'Delete Messages',
|
||||||
|
description: 'Are you sure you want to delete messages? This action cannot be undone.',
|
||||||
|
confirmButtonName: 'Delete',
|
||||||
|
cancelButtonName: 'Cancel'
|
||||||
|
})
|
||||||
|
setHardDeleteDialogOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteMessages = async (hardDelete) => {
|
||||||
|
setHardDeleteDialogOpen(false)
|
||||||
|
const chatflowid = dialogProps.chatflow.id
|
||||||
|
try {
|
||||||
|
const obj = { chatflowid, isClearFromViewMessageDialog: true }
|
||||||
|
|
||||||
|
let _chatTypeFilter = chatTypeFilter
|
||||||
|
if (typeof chatTypeFilter === 'string') {
|
||||||
|
_chatTypeFilter = JSON.parse(chatTypeFilter)
|
||||||
|
}
|
||||||
|
if (_chatTypeFilter.length === 1) {
|
||||||
|
obj.chatType = _chatTypeFilter[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
let _feedbackTypeFilter = feedbackTypeFilter
|
||||||
|
if (typeof feedbackTypeFilter === 'string') {
|
||||||
|
_feedbackTypeFilter = JSON.parse(feedbackTypeFilter)
|
||||||
|
}
|
||||||
|
if (_feedbackTypeFilter.length === 1) {
|
||||||
|
obj.feedbackType = _feedbackTypeFilter[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate) obj.startDate = startDate
|
||||||
|
if (endDate) obj.endDate = endDate
|
||||||
|
if (hardDelete) obj.hardDelete = true
|
||||||
|
|
||||||
|
await chatmessageApi.deleteChatmessage(chatflowid, obj)
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'Succesfully deleted messages',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'success',
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
getChatmessageApi.request(chatflowid, {
|
||||||
|
chatType: chatTypeFilter.length ? chatTypeFilter : undefined,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate
|
||||||
|
})
|
||||||
|
getStatsApi.request(chatflowid, {
|
||||||
|
chatType: chatTypeFilter.length ? chatTypeFilter : undefined,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: typeof error.response.data === 'object' ? error.response.data.message : error.response.data,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const exportMessages = async () => {
|
const exportMessages = async () => {
|
||||||
if (!storagePath && getStoragePathFromServer.data) {
|
if (!storagePath && getStoragePathFromServer.data) {
|
||||||
storagePath = getStoragePathFromServer.data.storagePath
|
storagePath = getStoragePathFromServer.data.storagePath
|
||||||
|
|
@ -675,7 +803,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
onClose={onCancel}
|
onClose={onCancel}
|
||||||
open={show}
|
open={show}
|
||||||
fullWidth
|
fullWidth
|
||||||
maxWidth={chatlogs && chatlogs.length == 0 ? 'md' : 'lg'}
|
maxWidth={'lg'}
|
||||||
aria-labelledby='alert-dialog-title'
|
aria-labelledby='alert-dialog-title'
|
||||||
aria-describedby='alert-dialog-description'
|
aria-describedby='alert-dialog-description'
|
||||||
>
|
>
|
||||||
|
|
@ -781,6 +909,11 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }}></div>
|
<div style={{ flex: 1 }}></div>
|
||||||
|
{stats.totalMessages > 0 && (
|
||||||
|
<Button color='error' variant='outlined' onClick={() => onDeleteMessages()} startIcon={<IconEraser />}>
|
||||||
|
Delete Messages
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -1375,6 +1508,12 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<SourceDocDialog show={sourceDialogOpen} dialogProps={sourceDialogProps} onCancel={() => setSourceDialogOpen(false)} />
|
<SourceDocDialog show={sourceDialogOpen} dialogProps={sourceDialogProps} onCancel={() => setSourceDialogOpen(false)} />
|
||||||
|
<ConfirmDeleteMessageDialog
|
||||||
|
show={hardDeleteDialogOpen}
|
||||||
|
dialogProps={hardDeleteDialogProps}
|
||||||
|
onCancel={() => setHardDeleteDialogOpen(false)}
|
||||||
|
onConfirm={(hardDelete) => deleteMessages(hardDelete)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Button, Box, Typography } from '@mui/material'
|
||||||
|
import { IconX } from '@tabler/icons-react'
|
||||||
|
|
||||||
|
// Project import
|
||||||
|
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||||
|
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
||||||
|
import { SwitchInput } from '@/ui-component/switch/Switch'
|
||||||
|
|
||||||
|
// store
|
||||||
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
|
||||||
|
// API
|
||||||
|
import chatflowsApi from '@/api/chatflows'
|
||||||
|
|
||||||
|
const message = `Allow files to be uploaded from the chat. Uploaded files will be parsed as string and sent to LLM. If File Upload is enabled on Vector Store as well, this will override and takes precedence.`
|
||||||
|
|
||||||
|
const FileUpload = ({ dialogProps }) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
useNotifier()
|
||||||
|
|
||||||
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
|
||||||
|
const [fullFileUpload, setFullFileUpload] = useState(false)
|
||||||
|
const [chatbotConfig, setChatbotConfig] = useState({})
|
||||||
|
|
||||||
|
const handleChange = (value) => {
|
||||||
|
setFullFileUpload(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSave = async () => {
|
||||||
|
try {
|
||||||
|
const value = {
|
||||||
|
status: fullFileUpload
|
||||||
|
}
|
||||||
|
chatbotConfig.fullFileUpload = value
|
||||||
|
|
||||||
|
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||||
|
chatbotConfig: JSON.stringify(chatbotConfig)
|
||||||
|
})
|
||||||
|
if (saveResp.data) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'File Upload Configuration Saved',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'success',
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to save File Upload Configuration: ${
|
||||||
|
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||||
|
}`,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dialogProps.chatflow) {
|
||||||
|
if (dialogProps.chatflow.chatbotConfig) {
|
||||||
|
try {
|
||||||
|
let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig)
|
||||||
|
setChatbotConfig(chatbotConfig || {})
|
||||||
|
if (chatbotConfig.fullFileUpload) {
|
||||||
|
setFullFileUpload(chatbotConfig.fullFileUpload.status)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setChatbotConfig({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {}
|
||||||
|
}, [dialogProps])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', mb: 2 }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
|
<Typography>
|
||||||
|
Enable Full File Upload
|
||||||
|
<TooltipWithParser style={{ marginLeft: 10 }} title={message} />
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<SwitchInput onChange={handleChange} value={fullFileUpload} />
|
||||||
|
</Box>
|
||||||
|
<StyledButton style={{ marginBottom: 10, marginTop: 10 }} variant='contained' onClick={onSave}>
|
||||||
|
Save
|
||||||
|
</StyledButton>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
FileUpload.propTypes = {
|
||||||
|
dialogProps: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileUpload
|
||||||
|
|
@ -5,6 +5,7 @@ import PerfectScrollbar from 'react-perfect-scrollbar'
|
||||||
import robotPNG from '@/assets/images/robot.png'
|
import robotPNG from '@/assets/images/robot.png'
|
||||||
import chatPNG from '@/assets/images/chathistory.png'
|
import chatPNG from '@/assets/images/chathistory.png'
|
||||||
import diskPNG from '@/assets/images/floppy-disc.png'
|
import diskPNG from '@/assets/images/floppy-disc.png'
|
||||||
|
import fileAttachmentPNG from '@/assets/images/fileAttachment.png'
|
||||||
import { baseURL } from '@/store/constant'
|
import { baseURL } from '@/store/constant'
|
||||||
|
|
||||||
const sequentialStateMessagesSelection = [
|
const sequentialStateMessagesSelection = [
|
||||||
|
|
@ -119,6 +120,45 @@ const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectA
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
|
<ListItemButton
|
||||||
|
sx={{
|
||||||
|
p: 0,
|
||||||
|
borderRadius: `${customization.borderRadius}px`,
|
||||||
|
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)',
|
||||||
|
mb: 1
|
||||||
|
}}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={() => onSelectOutputResponseClick(null, 'file_attachment')}
|
||||||
|
>
|
||||||
|
<ListItem alignItems='center'>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
padding: 10,
|
||||||
|
objectFit: 'contain'
|
||||||
|
}}
|
||||||
|
alt='fileAttachment'
|
||||||
|
src={fileAttachmentPNG}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
sx={{ ml: 1 }}
|
||||||
|
primary='file_attachment'
|
||||||
|
secondary={`Files uploaded from the chat when Full File Upload is enabled on the Configuration`}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</ListItemButton>
|
||||||
{availableNodesForVariable &&
|
{availableNodesForVariable &&
|
||||||
availableNodesForVariable.length > 0 &&
|
availableNodesForVariable.length > 0 &&
|
||||||
availableNodesForVariable.map((node, index) => {
|
availableNodesForVariable.map((node, index) => {
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ import chatmessageApi from '@/api/chatmessage'
|
||||||
import chatflowsApi from '@/api/chatflows'
|
import chatflowsApi from '@/api/chatflows'
|
||||||
import predictionApi from '@/api/prediction'
|
import predictionApi from '@/api/prediction'
|
||||||
import vectorstoreApi from '@/api/vectorstore'
|
import vectorstoreApi from '@/api/vectorstore'
|
||||||
|
import attachmentsApi from '@/api/attachments'
|
||||||
import chatmessagefeedbackApi from '@/api/chatmessagefeedback'
|
import chatmessagefeedbackApi from '@/api/chatmessagefeedback'
|
||||||
import leadsApi from '@/api/lead'
|
import leadsApi from '@/api/lead'
|
||||||
|
|
||||||
|
|
@ -88,7 +89,7 @@ const messageImageStyle = {
|
||||||
objectFit: 'cover'
|
objectFit: 'cover'
|
||||||
}
|
}
|
||||||
|
|
||||||
const CardWithDeleteOverlay = ({ item, customization, onDelete }) => {
|
const CardWithDeleteOverlay = ({ item, disabled, customization, onDelete }) => {
|
||||||
const [isHovered, setIsHovered] = useState(false)
|
const [isHovered, setIsHovered] = useState(false)
|
||||||
const defaultBackgroundColor = customization.isDarkMode ? 'rgba(0, 0, 0, 0.3)' : 'transparent'
|
const defaultBackgroundColor = customization.isDarkMode ? 'rgba(0, 0, 0, 0.3)' : 'transparent'
|
||||||
|
|
||||||
|
|
@ -125,8 +126,9 @@ const CardWithDeleteOverlay = ({ item, customization, onDelete }) => {
|
||||||
{item.name}
|
{item.name}
|
||||||
</span>
|
</span>
|
||||||
</Card>
|
</Card>
|
||||||
{isHovered && (
|
{isHovered && !disabled && (
|
||||||
<Button
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
onClick={() => onDelete(item)}
|
onClick={() => onDelete(item)}
|
||||||
startIcon={<IconTrash color='white' size={22} />}
|
startIcon={<IconTrash color='white' size={22} />}
|
||||||
title='Remove attachment'
|
title='Remove attachment'
|
||||||
|
|
@ -150,6 +152,7 @@ const CardWithDeleteOverlay = ({ item, customization, onDelete }) => {
|
||||||
CardWithDeleteOverlay.propTypes = {
|
CardWithDeleteOverlay.propTypes = {
|
||||||
item: PropTypes.object,
|
item: PropTypes.object,
|
||||||
customization: PropTypes.object,
|
customization: PropTypes.object,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
onDelete: PropTypes.func
|
onDelete: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,6 +194,9 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
|
|
||||||
const [starterPrompts, setStarterPrompts] = useState([])
|
const [starterPrompts, setStarterPrompts] = useState([])
|
||||||
|
|
||||||
|
// full file upload
|
||||||
|
const [fullFileUpload, setFullFileUpload] = useState(false)
|
||||||
|
|
||||||
// feedback
|
// feedback
|
||||||
const [chatFeedbackStatus, setChatFeedbackStatus] = useState(false)
|
const [chatFeedbackStatus, setChatFeedbackStatus] = useState(false)
|
||||||
const [feedbackId, setFeedbackId] = useState('')
|
const [feedbackId, setFeedbackId] = useState('')
|
||||||
|
|
@ -213,6 +219,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
const fileUploadRef = useRef(null)
|
const fileUploadRef = useRef(null)
|
||||||
const [isChatFlowAvailableForImageUploads, setIsChatFlowAvailableForImageUploads] = useState(false)
|
const [isChatFlowAvailableForImageUploads, setIsChatFlowAvailableForImageUploads] = useState(false)
|
||||||
const [isChatFlowAvailableForFileUploads, setIsChatFlowAvailableForFileUploads] = useState(false)
|
const [isChatFlowAvailableForFileUploads, setIsChatFlowAvailableForFileUploads] = useState(false)
|
||||||
|
const [isChatFlowAvailableForRAGFileUploads, setIsChatFlowAvailableForRAGFileUploads] = useState(false)
|
||||||
const [isDragActive, setIsDragActive] = useState(false)
|
const [isDragActive, setIsDragActive] = useState(false)
|
||||||
|
|
||||||
// recording
|
// recording
|
||||||
|
|
@ -235,7 +242,10 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (constraints.isFileUploadAllowed) {
|
|
||||||
|
if (fullFileUpload) {
|
||||||
|
return true
|
||||||
|
} else if (constraints.isRAGFileUploadAllowed) {
|
||||||
const fileExt = file.name.split('.').pop()
|
const fileExt = file.name.split('.').pop()
|
||||||
if (fileExt) {
|
if (fileExt) {
|
||||||
constraints.fileUploadSizeAndTypes.map((allowed) => {
|
constraints.fileUploadSizeAndTypes.map((allowed) => {
|
||||||
|
|
@ -271,7 +281,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
const { name } = file
|
const { name } = file
|
||||||
// Only add files
|
// Only add files
|
||||||
if (!imageUploadAllowedTypes.includes(file.type)) {
|
if (!imageUploadAllowedTypes.includes(file.type)) {
|
||||||
uploadedFiles.push(file)
|
uploadedFiles.push({ file, type: fullFileUpload ? 'file:full' : 'file:rag' })
|
||||||
}
|
}
|
||||||
files.push(
|
files.push(
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
|
|
@ -350,7 +360,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
}
|
}
|
||||||
// Only add files
|
// Only add files
|
||||||
if (!imageUploadAllowedTypes.includes(file.type)) {
|
if (!imageUploadAllowedTypes.includes(file.type)) {
|
||||||
uploadedFiles.push(file)
|
uploadedFiles.push({ file, type: fullFileUpload ? 'file:full' : 'file:rag' })
|
||||||
}
|
}
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
const { name } = file
|
const { name } = file
|
||||||
|
|
@ -683,6 +693,66 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFileUploads = async (uploads) => {
|
||||||
|
if (!uploadedFiles.length) return uploads
|
||||||
|
|
||||||
|
if (fullFileUpload) {
|
||||||
|
const filesWithFullUploadType = uploadedFiles.filter((file) => file.type === 'file:full')
|
||||||
|
|
||||||
|
if (filesWithFullUploadType.length > 0) {
|
||||||
|
const formData = new FormData()
|
||||||
|
for (const file of filesWithFullUploadType) {
|
||||||
|
formData.append('files', file.file)
|
||||||
|
}
|
||||||
|
formData.append('chatId', chatId)
|
||||||
|
|
||||||
|
const response = await attachmentsApi.createAttachment(chatflowid, chatId, formData)
|
||||||
|
const data = response.data
|
||||||
|
for (const extractedFileData of data) {
|
||||||
|
const content = extractedFileData.content
|
||||||
|
const fileName = extractedFileData.name
|
||||||
|
|
||||||
|
// find matching name in previews and replace data with content
|
||||||
|
const uploadIndex = uploads.findIndex((upload) => upload.name === fileName)
|
||||||
|
if (uploadIndex !== -1) {
|
||||||
|
uploads[uploadIndex] = {
|
||||||
|
...uploads[uploadIndex],
|
||||||
|
data: content,
|
||||||
|
name: fileName,
|
||||||
|
type: 'file:full'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isChatFlowAvailableForRAGFileUploads) {
|
||||||
|
const filesWithRAGUploadType = uploadedFiles.filter((file) => file.type === 'file:rag')
|
||||||
|
|
||||||
|
if (filesWithRAGUploadType.length > 0) {
|
||||||
|
const formData = new FormData()
|
||||||
|
for (const file of filesWithRAGUploadType) {
|
||||||
|
formData.append('files', file.file)
|
||||||
|
}
|
||||||
|
formData.append('chatId', chatId)
|
||||||
|
|
||||||
|
await vectorstoreApi.upsertVectorStoreWithFormData(chatflowid, formData)
|
||||||
|
|
||||||
|
// delay for vector store to be updated
|
||||||
|
const delay = (delayInms) => {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, delayInms))
|
||||||
|
}
|
||||||
|
await delay(2500) //TODO: check if embeddings can be retrieved using file name as metadata filter
|
||||||
|
|
||||||
|
uploads = uploads.map((upload) => {
|
||||||
|
return {
|
||||||
|
...upload,
|
||||||
|
type: 'file:rag'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uploads
|
||||||
|
}
|
||||||
|
|
||||||
// Handle form submission
|
// Handle form submission
|
||||||
const handleSubmit = async (e, selectedInput, action) => {
|
const handleSubmit = async (e, selectedInput, action) => {
|
||||||
if (e) e.preventDefault()
|
if (e) e.preventDefault()
|
||||||
|
|
@ -699,7 +769,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
if (selectedInput !== undefined && selectedInput.trim() !== '') input = selectedInput
|
if (selectedInput !== undefined && selectedInput.trim() !== '') input = selectedInput
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const uploads = previews.map((item) => {
|
let uploads = previews.map((item) => {
|
||||||
return {
|
return {
|
||||||
data: item.data,
|
data: item.data,
|
||||||
type: item.type,
|
type: item.type,
|
||||||
|
|
@ -707,6 +777,14 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
mime: item.mime
|
mime: item.mime
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
uploads = await handleFileUploads(uploads)
|
||||||
|
} catch (error) {
|
||||||
|
handleError('Unable to upload documents')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
clearPreviews()
|
clearPreviews()
|
||||||
setMessages((prevMessages) => [...prevMessages, { message: input, type: 'userMessage', fileUploads: uploads }])
|
setMessages((prevMessages) => [...prevMessages, { message: input, type: 'userMessage', fileUploads: uploads }])
|
||||||
|
|
||||||
|
|
@ -718,28 +796,8 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
}
|
}
|
||||||
if (uploads && uploads.length > 0) params.uploads = uploads
|
if (uploads && uploads.length > 0) params.uploads = uploads
|
||||||
if (leadEmail) params.leadEmail = leadEmail
|
if (leadEmail) params.leadEmail = leadEmail
|
||||||
|
|
||||||
if (action) params.action = action
|
if (action) params.action = action
|
||||||
|
|
||||||
if (uploadedFiles.length > 0) {
|
|
||||||
const formData = new FormData()
|
|
||||||
for (const file of uploadedFiles) {
|
|
||||||
formData.append('files', file)
|
|
||||||
}
|
|
||||||
formData.append('chatId', chatId)
|
|
||||||
|
|
||||||
const response = await vectorstoreApi.upsertVectorStoreWithFormData(chatflowid, formData)
|
|
||||||
if (!response.data) {
|
|
||||||
setMessages((prevMessages) => [...prevMessages, { message: 'Unable to upload documents', type: 'apiMessage' }])
|
|
||||||
} else {
|
|
||||||
// delay for vector store to be updated
|
|
||||||
const delay = (delayInms) => {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, delayInms))
|
|
||||||
}
|
|
||||||
await delay(2500) //TODO: check if embeddings can be retrieved using file name as metadata filter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isChatFlowAvailableToStream) {
|
if (isChatFlowAvailableToStream) {
|
||||||
fetchResponseFromEventStream(chatflowid, params)
|
fetchResponseFromEventStream(chatflowid, params)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -905,6 +963,11 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getFileUploadAllowedTypes = () => {
|
||||||
|
if (fullFileUpload) return '*'
|
||||||
|
return fileUploadAllowedTypes.includes('*') ? '*' : fileUploadAllowedTypes || '*'
|
||||||
|
}
|
||||||
|
|
||||||
const downloadFile = async (fileAnnotation) => {
|
const downloadFile = async (fileAnnotation) => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
|
|
@ -993,7 +1056,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getAllowChatFlowUploads.data) {
|
if (getAllowChatFlowUploads.data) {
|
||||||
setIsChatFlowAvailableForImageUploads(getAllowChatFlowUploads.data?.isImageUploadAllowed ?? false)
|
setIsChatFlowAvailableForImageUploads(getAllowChatFlowUploads.data?.isImageUploadAllowed ?? false)
|
||||||
setIsChatFlowAvailableForFileUploads(getAllowChatFlowUploads.data?.isFileUploadAllowed ?? false)
|
setIsChatFlowAvailableForRAGFileUploads(getAllowChatFlowUploads.data?.isRAGFileUploadAllowed ?? false)
|
||||||
setIsChatFlowAvailableForSpeech(getAllowChatFlowUploads.data?.isSpeechToTextEnabled ?? false)
|
setIsChatFlowAvailableForSpeech(getAllowChatFlowUploads.data?.isSpeechToTextEnabled ?? false)
|
||||||
setImageUploadAllowedTypes(getAllowChatFlowUploads.data?.imgUploadSizeAndTypes.map((allowed) => allowed.fileTypes).join(','))
|
setImageUploadAllowedTypes(getAllowChatFlowUploads.data?.imgUploadSizeAndTypes.map((allowed) => allowed.fileTypes).join(','))
|
||||||
setFileUploadAllowedTypes(getAllowChatFlowUploads.data?.fileUploadSizeAndTypes.map((allowed) => allowed.fileTypes).join(','))
|
setFileUploadAllowedTypes(getAllowChatFlowUploads.data?.fileUploadSizeAndTypes.map((allowed) => allowed.fileTypes).join(','))
|
||||||
|
|
@ -1035,11 +1098,25 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
if (config.followUpPrompts) {
|
if (config.followUpPrompts) {
|
||||||
setFollowUpPromptsStatus(config.followUpPrompts.status)
|
setFollowUpPromptsStatus(config.followUpPrompts.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.fullFileUpload) {
|
||||||
|
setFullFileUpload(config.fullFileUpload.status)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [getChatflowConfig.data])
|
}, [getChatflowConfig.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (fullFileUpload) {
|
||||||
|
setIsChatFlowAvailableForFileUploads(true)
|
||||||
|
} else if (isChatFlowAvailableForRAGFileUploads) {
|
||||||
|
setIsChatFlowAvailableForFileUploads(true)
|
||||||
|
} else {
|
||||||
|
setIsChatFlowAvailableForFileUploads(false)
|
||||||
|
}
|
||||||
|
}, [isChatFlowAvailableForRAGFileUploads, fullFileUpload])
|
||||||
|
|
||||||
// Auto scroll chat to bottom
|
// Auto scroll chat to bottom
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
|
|
@ -1238,6 +1315,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
marginRight: '10px',
|
marginRight: '10px',
|
||||||
flex: '0 0 auto'
|
flex: '0 0 auto'
|
||||||
}}
|
}}
|
||||||
|
disabled={getInputDisabled()}
|
||||||
onClick={() => handleDeletePreview(item)}
|
onClick={() => handleDeletePreview(item)}
|
||||||
>
|
>
|
||||||
<ImageSrc style={{ backgroundImage: `url(${item.data})` }} />
|
<ImageSrc style={{ backgroundImage: `url(${item.data})` }} />
|
||||||
|
|
@ -1263,13 +1341,20 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
variant='outlined'
|
variant='outlined'
|
||||||
>
|
>
|
||||||
<CardMedia component='audio' sx={{ color: 'transparent' }} controls src={item.data} />
|
<CardMedia component='audio' sx={{ color: 'transparent' }} controls src={item.data} />
|
||||||
<IconButton onClick={() => handleDeletePreview(item)} size='small'>
|
<IconButton disabled={getInputDisabled()} onClick={() => handleDeletePreview(item)} size='small'>
|
||||||
<IconTrash size={20} color='white' />
|
<IconTrash size={20} color='white' />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return <CardWithDeleteOverlay item={item} customization={customization} onDelete={() => handleDeletePreview(item)} />
|
return (
|
||||||
|
<CardWithDeleteOverlay
|
||||||
|
disabled={getInputDisabled()}
|
||||||
|
item={item}
|
||||||
|
customization={customization}
|
||||||
|
onDelete={() => handleDeletePreview(item)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1415,11 +1500,14 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isDragActive && (getAllowChatFlowUploads.data?.isImageUploadAllowed || getAllowChatFlowUploads.data?.isFileUploadAllowed) && (
|
{isDragActive &&
|
||||||
|
(getAllowChatFlowUploads.data?.isImageUploadAllowed || getAllowChatFlowUploads.data?.isRAGFileUploadAllowed) && (
|
||||||
<Box className='drop-overlay'>
|
<Box className='drop-overlay'>
|
||||||
<Typography variant='h2'>Drop here to upload</Typography>
|
<Typography variant='h2'>Drop here to upload</Typography>
|
||||||
{[...getAllowChatFlowUploads.data.imgUploadSizeAndTypes, ...getAllowChatFlowUploads.data.fileUploadSizeAndTypes].map(
|
{[
|
||||||
(allowed) => {
|
...getAllowChatFlowUploads.data.imgUploadSizeAndTypes,
|
||||||
|
...getAllowChatFlowUploads.data.fileUploadSizeAndTypes
|
||||||
|
].map((allowed) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography variant='subtitle1'>{allowed.fileTypes?.join(', ')}</Typography>
|
<Typography variant='subtitle1'>{allowed.fileTypes?.join(', ')}</Typography>
|
||||||
|
|
@ -1428,8 +1516,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
})}
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<div ref={ps} className={`${isDialog ? 'cloud-dialog' : 'cloud'}`}>
|
<div ref={ps} className={`${isDialog ? 'cloud-dialog' : 'cloud'}`}>
|
||||||
|
|
@ -2256,7 +2343,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
ref={fileUploadRef}
|
ref={fileUploadRef}
|
||||||
type='file'
|
type='file'
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
accept={fileUploadAllowedTypes.includes('*') ? '*' : fileUploadAllowedTypes || '*'}
|
accept={getFileUploadAllowedTypes()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue