mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 19:00:59 +03:00
Feature/add ability to upload file from chat (#3059)
add ability to upload file from chat
This commit is contained in:
@@ -112,6 +112,7 @@ class CSV_Agents implements INode {
|
|||||||
const chatflowid = options.chatflowid
|
const chatflowid = options.chatflowid
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
const fileData = await getFileFromStorage(file, chatflowid)
|
const fileData = await getFileFromStorage(file, chatflowid)
|
||||||
base64String += fileData.toString('base64')
|
base64String += fileData.toString('base64')
|
||||||
}
|
}
|
||||||
@@ -123,6 +124,7 @@ class CSV_Agents implements INode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
const splitDataURI = file.split(',')
|
const splitDataURI = file.split(',')
|
||||||
splitDataURI.pop()
|
splitDataURI.pop()
|
||||||
base64String += splitDataURI.pop() ?? ''
|
base64String += splitDataURI.pop() ?? ''
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class AWSChatBedrock_ChatModels implements INode {
|
|||||||
name: 'allowImageUploads',
|
name: 'allowImageUploads',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description:
|
description:
|
||||||
'Only works with claude-3-* models when image is being uploaded from chat. Compatible with LLMChain, Conversation Chain, ReAct Agent, and Conversational Agent',
|
'Only works with claude-3-* models when image is being uploaded from chat. Compatible with LLMChain, Conversation Chain, ReAct Agent, Conversational Agent, Tool Agent',
|
||||||
default: false,
|
default: false,
|
||||||
optional: true
|
optional: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class AzureChatOpenAI_ChatModels implements INode {
|
|||||||
name: 'allowImageUploads',
|
name: 'allowImageUploads',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description:
|
description:
|
||||||
'Automatically uses gpt-4-vision-preview when image is being uploaded from chat. Only works with LLMChain, Conversation Chain, ReAct Agent, and Conversational Agent',
|
'Automatically uses gpt-4-vision-preview when image is being uploaded from chat. Only works with LLMChain, Conversation Chain, ReAct Agent, Conversational Agent, Tool Agent',
|
||||||
default: false,
|
default: false,
|
||||||
optional: true
|
optional: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class ChatAnthropic_ChatModels implements INode {
|
|||||||
name: 'allowImageUploads',
|
name: 'allowImageUploads',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description:
|
description:
|
||||||
'Automatically uses claude-3-* models when image is being uploaded from chat. Only works with LLMChain, Conversation Chain, ReAct Agent, and Conversational Agent',
|
'Automatically uses claude-3-* models when image is being uploaded from chat. Only works with LLMChain, Conversation Chain, ReAct Agent, Conversational Agent, Tool Agent',
|
||||||
default: false,
|
default: false,
|
||||||
optional: true
|
optional: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AnthropicInput, ChatAnthropic as LangchainChatAnthropic } from '@langchain/anthropic'
|
import { AnthropicInput, ChatAnthropic as LangchainChatAnthropic } from '@langchain/anthropic'
|
||||||
import { BaseLLMParams } from '@langchain/core/language_models/llms'
|
import { type BaseChatModelParams } from '@langchain/core/language_models/chat_models'
|
||||||
import { IVisionChatModal, IMultiModalOption } from '../../../src'
|
import { IVisionChatModal, IMultiModalOption } from '../../../src'
|
||||||
|
|
||||||
export class ChatAnthropic extends LangchainChatAnthropic implements IVisionChatModal {
|
export class ChatAnthropic extends LangchainChatAnthropic implements IVisionChatModal {
|
||||||
@@ -8,8 +8,9 @@ export class ChatAnthropic extends LangchainChatAnthropic implements IVisionChat
|
|||||||
multiModalOption: IMultiModalOption
|
multiModalOption: IMultiModalOption
|
||||||
id: string
|
id: string
|
||||||
|
|
||||||
constructor(id: string, fields: Partial<AnthropicInput> & BaseLLMParams & { anthropicApiKey?: string }) {
|
constructor(id: string, fields?: Partial<AnthropicInput> & BaseChatModelParams) {
|
||||||
super(fields)
|
// @ts-ignore
|
||||||
|
super(fields ?? {})
|
||||||
this.id = id
|
this.id = id
|
||||||
this.configuredModel = fields?.modelName || ''
|
this.configuredModel = fields?.modelName || ''
|
||||||
this.configuredMaxToken = fields?.maxTokens ?? 2048
|
this.configuredMaxToken = fields?.maxTokens ?? 2048
|
||||||
|
|||||||
+1
-1
@@ -145,7 +145,7 @@ class GoogleGenerativeAI_ChatModels implements INode {
|
|||||||
name: 'allowImageUploads',
|
name: 'allowImageUploads',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description:
|
description:
|
||||||
'Automatically uses vision model when image is being uploaded from chat. Only works with LLMChain, Conversation Chain, ReAct Agent, and Conversational Agent',
|
'Automatically uses vision model when image is being uploaded from chat. Only works with LLMChain, Conversation Chain, ReAct Agent, Conversational Agent, Tool Agent',
|
||||||
default: false,
|
default: false,
|
||||||
optional: true
|
optional: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ class ChatOpenAI_ChatModels implements INode {
|
|||||||
name: 'allowImageUploads',
|
name: 'allowImageUploads',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description:
|
description:
|
||||||
'Automatically uses gpt-4-vision-preview when image is being uploaded from chat. Only works with LLMChain, Conversation Chain, ReAct Agent, and Conversational Agent',
|
'Automatically uses gpt-4-vision-preview when image is being uploaded from chat. Only works with LLMChain, Conversation Chain, ReAct Agent, Conversational Agent, Tool Agent',
|
||||||
default: false,
|
default: false,
|
||||||
optional: true
|
optional: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ class Csv_DocumentLoaders implements INode {
|
|||||||
const chatflowid = options.chatflowid
|
const chatflowid = options.chatflowid
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
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])
|
||||||
const loader = new CSVLoader(blob, columnName.trim().length === 0 ? undefined : columnName.trim())
|
const loader = new CSVLoader(blob, columnName.trim().length === 0 ? undefined : columnName.trim())
|
||||||
@@ -127,6 +128,7 @@ class Csv_DocumentLoaders implements INode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
const splitDataURI = file.split(',')
|
const splitDataURI = file.split(',')
|
||||||
splitDataURI.pop()
|
splitDataURI.pop()
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ class Docx_DocumentLoaders implements INode {
|
|||||||
const chatflowid = options.chatflowid
|
const chatflowid = options.chatflowid
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
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])
|
||||||
const loader = new DocxLoader(blob)
|
const loader = new DocxLoader(blob)
|
||||||
@@ -103,6 +104,7 @@ class Docx_DocumentLoaders implements INode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
const splitDataURI = file.split(',')
|
const splitDataURI = file.split(',')
|
||||||
splitDataURI.pop()
|
splitDataURI.pop()
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
|
|||||||
@@ -0,0 +1,299 @@
|
|||||||
|
import { omit } from 'lodash'
|
||||||
|
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
|
import { TextSplitter } from 'langchain/text_splitter'
|
||||||
|
import { TextLoader } from 'langchain/document_loaders/fs/text'
|
||||||
|
import { JSONLinesLoader, JSONLoader } from 'langchain/document_loaders/fs/json'
|
||||||
|
import { CSVLoader } from '@langchain/community/document_loaders/fs/csv'
|
||||||
|
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf'
|
||||||
|
import { DocxLoader } from '@langchain/community/document_loaders/fs/docx'
|
||||||
|
import { BaseDocumentLoader } from 'langchain/document_loaders/base'
|
||||||
|
import { Document } from '@langchain/core/documents'
|
||||||
|
import { getFileFromStorage } from '../../../src/storageUtils'
|
||||||
|
import { mapMimeTypeToExt } from '../../../src/utils'
|
||||||
|
|
||||||
|
class File_DocumentLoaders implements INode {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
version: number
|
||||||
|
description: string
|
||||||
|
type: string
|
||||||
|
icon: string
|
||||||
|
category: string
|
||||||
|
baseClasses: string[]
|
||||||
|
inputs: INodeParams[]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.label = 'File Loader'
|
||||||
|
this.name = 'fileLoader'
|
||||||
|
this.version = 1.0
|
||||||
|
this.type = 'Document'
|
||||||
|
this.icon = 'file.svg'
|
||||||
|
this.category = 'Document Loaders'
|
||||||
|
this.description = `A generic file loader that can load txt, json, csv, docx, pdf, and other files`
|
||||||
|
this.baseClasses = [this.type]
|
||||||
|
this.inputs = [
|
||||||
|
{
|
||||||
|
label: 'File',
|
||||||
|
name: 'file',
|
||||||
|
type: 'file',
|
||||||
|
fileType: '*'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Text Splitter',
|
||||||
|
name: 'textSplitter',
|
||||||
|
type: 'TextSplitter',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Pdf Usage',
|
||||||
|
name: 'pdfUsage',
|
||||||
|
type: 'options',
|
||||||
|
description: 'Only when loading PDF files',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'One document per page',
|
||||||
|
name: 'perPage'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'One document per file',
|
||||||
|
name: 'perFile'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default: 'perPage',
|
||||||
|
optional: true,
|
||||||
|
additionalParams: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'JSONL Pointer Extraction',
|
||||||
|
name: 'pointerName',
|
||||||
|
type: 'string',
|
||||||
|
description: 'Only when loading JSONL files',
|
||||||
|
placeholder: '<pointerName>',
|
||||||
|
optional: true,
|
||||||
|
additionalParams: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Additional Metadata',
|
||||||
|
name: 'metadata',
|
||||||
|
type: 'json',
|
||||||
|
description: 'Additional metadata to be added to the extracted documents',
|
||||||
|
optional: true,
|
||||||
|
additionalParams: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Omit Metadata Keys',
|
||||||
|
name: 'omitMetadataKeys',
|
||||||
|
type: 'string',
|
||||||
|
rows: 4,
|
||||||
|
description:
|
||||||
|
'Each document loader comes with a default set of metadata keys that are extracted from the document. You can use this field to omit some of the default metadata keys. The value should be a list of keys, seperated by comma. Use * to omit all metadata keys execept the ones you specify in the Additional Metadata field',
|
||||||
|
placeholder: 'key1, key2, key3.nestedKey1',
|
||||||
|
optional: true,
|
||||||
|
additionalParams: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||||
|
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
|
||||||
|
const fileBase64 = nodeData.inputs?.file as string
|
||||||
|
const metadata = nodeData.inputs?.metadata
|
||||||
|
const pdfUsage = nodeData.inputs?.pdfUsage
|
||||||
|
const pointerName = nodeData.inputs?.pointerName as string
|
||||||
|
const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string
|
||||||
|
|
||||||
|
let omitMetadataKeys: string[] = []
|
||||||
|
if (_omitMetadataKeys) {
|
||||||
|
omitMetadataKeys = _omitMetadataKeys.split(',').map((key) => key.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
let files: string[] = []
|
||||||
|
const fileBlobs: { blob: Blob; ext: string }[] = []
|
||||||
|
|
||||||
|
//FILE-STORAGE::["CONTRIBUTING.md","LICENSE.md","README.md"]
|
||||||
|
const totalFiles = getOverrideFileInputs(nodeData) || fileBase64
|
||||||
|
if (totalFiles.startsWith('FILE-STORAGE::')) {
|
||||||
|
const fileName = totalFiles.replace('FILE-STORAGE::', '')
|
||||||
|
if (fileName.startsWith('[') && fileName.endsWith(']')) {
|
||||||
|
files = JSON.parse(fileName)
|
||||||
|
} else {
|
||||||
|
files = [fileName]
|
||||||
|
}
|
||||||
|
const chatflowid = options.chatflowid
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
|
const fileData = await getFileFromStorage(file, chatflowid)
|
||||||
|
const blob = new Blob([fileData])
|
||||||
|
fileBlobs.push({ blob, ext: file.split('.').pop() || '' })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (totalFiles.startsWith('[') && totalFiles.endsWith(']')) {
|
||||||
|
files = JSON.parse(totalFiles)
|
||||||
|
} else {
|
||||||
|
files = [totalFiles]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
|
const splitDataURI = file.split(',')
|
||||||
|
splitDataURI.pop()
|
||||||
|
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
|
const blob = new Blob([bf])
|
||||||
|
|
||||||
|
let extension = ''
|
||||||
|
// eslint-disable-next-line no-useless-escape
|
||||||
|
const match = file.match(/^data:([A-Za-z-+\/]+);base64,/)
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
fileBlobs.push({
|
||||||
|
blob,
|
||||||
|
ext: extension
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const mimeType = match[1]
|
||||||
|
fileBlobs.push({
|
||||||
|
blob,
|
||||||
|
ext: mapMimeTypeToExt(mimeType)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loader = new MultiFileLoader(fileBlobs, {
|
||||||
|
json: (blob) => new JSONLoader(blob),
|
||||||
|
jsonl: (blob) => new JSONLinesLoader(blob, '/' + pointerName.trim()),
|
||||||
|
txt: (blob) => new TextLoader(blob),
|
||||||
|
csv: (blob) => new CSVLoader(blob),
|
||||||
|
xls: (blob) => new CSVLoader(blob),
|
||||||
|
xlsx: (blob) => new CSVLoader(blob),
|
||||||
|
docx: (blob) => new DocxLoader(blob),
|
||||||
|
doc: (blob) => new DocxLoader(blob),
|
||||||
|
pdf: (blob) =>
|
||||||
|
pdfUsage === 'perFile'
|
||||||
|
? // @ts-ignore
|
||||||
|
new PDFLoader(blob, { splitPages: false, pdfjs: () => import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js') })
|
||||||
|
: // @ts-ignore
|
||||||
|
new PDFLoader(blob, { pdfjs: () => import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js') }),
|
||||||
|
'': (blob) => new TextLoader(blob)
|
||||||
|
})
|
||||||
|
let docs = []
|
||||||
|
|
||||||
|
if (textSplitter) {
|
||||||
|
docs = await loader.load()
|
||||||
|
docs = await textSplitter.splitDocuments(docs)
|
||||||
|
} else {
|
||||||
|
docs = await loader.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata) {
|
||||||
|
const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata)
|
||||||
|
docs = docs.map((doc) => ({
|
||||||
|
...doc,
|
||||||
|
metadata:
|
||||||
|
_omitMetadataKeys === '*'
|
||||||
|
? {
|
||||||
|
...parsedMetadata
|
||||||
|
}
|
||||||
|
: omit(
|
||||||
|
{
|
||||||
|
...doc.metadata,
|
||||||
|
...parsedMetadata
|
||||||
|
},
|
||||||
|
omitMetadataKeys
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
docs = docs.map((doc) => ({
|
||||||
|
...doc,
|
||||||
|
metadata:
|
||||||
|
_omitMetadataKeys === '*'
|
||||||
|
? {}
|
||||||
|
: omit(
|
||||||
|
{
|
||||||
|
...doc.metadata
|
||||||
|
},
|
||||||
|
omitMetadataKeys
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return docs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOverrideFileInputs = (nodeData: INodeData) => {
|
||||||
|
const txtFileBase64 = nodeData.inputs?.txtFile as string
|
||||||
|
const pdfFileBase64 = nodeData.inputs?.pdfFile as string
|
||||||
|
const jsonFileBase64 = nodeData.inputs?.jsonFile as string
|
||||||
|
const csvFileBase64 = nodeData.inputs?.csvFile as string
|
||||||
|
const jsonlinesFileBase64 = nodeData.inputs?.jsonlinesFile as string
|
||||||
|
const docxFileBase64 = nodeData.inputs?.docxFile as string
|
||||||
|
const yamlFileBase64 = nodeData.inputs?.yamlFile as string
|
||||||
|
|
||||||
|
const removePrefix = (storageFile: string): string[] => {
|
||||||
|
const fileName = storageFile.replace('FILE-STORAGE::', '')
|
||||||
|
if (fileName.startsWith('[') && fileName.endsWith(']')) {
|
||||||
|
return JSON.parse(fileName)
|
||||||
|
}
|
||||||
|
return [fileName]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If exists, combine all file inputs into an array
|
||||||
|
const files: string[] = []
|
||||||
|
if (txtFileBase64) {
|
||||||
|
files.push(...removePrefix(txtFileBase64))
|
||||||
|
}
|
||||||
|
if (pdfFileBase64) {
|
||||||
|
files.push(...removePrefix(pdfFileBase64))
|
||||||
|
}
|
||||||
|
if (jsonFileBase64) {
|
||||||
|
files.push(...removePrefix(jsonFileBase64))
|
||||||
|
}
|
||||||
|
if (csvFileBase64) {
|
||||||
|
files.push(...removePrefix(csvFileBase64))
|
||||||
|
}
|
||||||
|
if (jsonlinesFileBase64) {
|
||||||
|
files.push(...removePrefix(jsonlinesFileBase64))
|
||||||
|
}
|
||||||
|
if (docxFileBase64) {
|
||||||
|
files.push(...removePrefix(docxFileBase64))
|
||||||
|
}
|
||||||
|
if (yamlFileBase64) {
|
||||||
|
files.push(...removePrefix(yamlFileBase64))
|
||||||
|
}
|
||||||
|
|
||||||
|
return files.length ? `FILE-STORAGE::${JSON.stringify(files)}` : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoadersMapping {
|
||||||
|
[extension: string]: (blob: Blob) => BaseDocumentLoader
|
||||||
|
}
|
||||||
|
|
||||||
|
class MultiFileLoader extends BaseDocumentLoader {
|
||||||
|
constructor(public fileBlobs: { blob: Blob; ext: string }[], public loaders: LoadersMapping) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
if (Object.keys(loaders).length === 0) {
|
||||||
|
throw new Error('Must provide at least one loader')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async load(): Promise<Document[]> {
|
||||||
|
const documents: Document[] = []
|
||||||
|
|
||||||
|
for (const fileBlob of this.fileBlobs) {
|
||||||
|
const loaderFactory = this.loaders[fileBlob.ext]
|
||||||
|
if (loaderFactory) {
|
||||||
|
const loader = loaderFactory(fileBlob.blob)
|
||||||
|
documents.push(...(await loader.load()))
|
||||||
|
} else {
|
||||||
|
throw new Error(`Error loading file`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return documents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { nodeClass: File_DocumentLoaders }
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-files"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 3v4a1 1 0 0 0 1 1h4" /><path d="M18 17h-7a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h4l5 5v7a2 2 0 0 1 -2 2z" /><path d="M16 17v2a2 2 0 0 1 -2 2h-7a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h2" /></svg>
|
||||||
|
After Width: | Height: | Size: 505 B |
@@ -3,7 +3,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
|||||||
import { TextSplitter } from 'langchain/text_splitter'
|
import { TextSplitter } from 'langchain/text_splitter'
|
||||||
import { TextLoader } from 'langchain/document_loaders/fs/text'
|
import { TextLoader } from 'langchain/document_loaders/fs/text'
|
||||||
import { DirectoryLoader } from 'langchain/document_loaders/fs/directory'
|
import { DirectoryLoader } from 'langchain/document_loaders/fs/directory'
|
||||||
import { JSONLoader } from 'langchain/document_loaders/fs/json'
|
import { JSONLinesLoader, JSONLoader } from 'langchain/document_loaders/fs/json'
|
||||||
import { CSVLoader } from '@langchain/community/document_loaders/fs/csv'
|
import { CSVLoader } from '@langchain/community/document_loaders/fs/csv'
|
||||||
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf'
|
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf'
|
||||||
import { DocxLoader } from '@langchain/community/document_loaders/fs/docx'
|
import { DocxLoader } from '@langchain/community/document_loaders/fs/docx'
|
||||||
@@ -22,7 +22,7 @@ class Folder_DocumentLoaders implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Folder with Files'
|
this.label = 'Folder with Files'
|
||||||
this.name = 'folderFiles'
|
this.name = 'folderFiles'
|
||||||
this.version = 2.0
|
this.version = 3.0
|
||||||
this.type = 'Document'
|
this.type = 'Document'
|
||||||
this.icon = 'folder.svg'
|
this.icon = 'folder.svg'
|
||||||
this.category = 'Document Loaders'
|
this.category = 'Document Loaders'
|
||||||
@@ -51,6 +51,7 @@ class Folder_DocumentLoaders implements INode {
|
|||||||
label: 'Pdf Usage',
|
label: 'Pdf Usage',
|
||||||
name: 'pdfUsage',
|
name: 'pdfUsage',
|
||||||
type: 'options',
|
type: 'options',
|
||||||
|
description: 'Only when loading PDF files',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: 'One document per page',
|
label: 'One document per page',
|
||||||
@@ -65,6 +66,15 @@ class Folder_DocumentLoaders implements INode {
|
|||||||
optional: true,
|
optional: true,
|
||||||
additionalParams: true
|
additionalParams: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'JSONL Pointer Extraction',
|
||||||
|
name: 'pointerName',
|
||||||
|
type: 'string',
|
||||||
|
description: 'Only when loading JSONL files',
|
||||||
|
placeholder: '<pointerName>',
|
||||||
|
optional: true,
|
||||||
|
additionalParams: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Additional Metadata',
|
label: 'Additional Metadata',
|
||||||
name: 'metadata',
|
name: 'metadata',
|
||||||
@@ -93,6 +103,7 @@ class Folder_DocumentLoaders implements INode {
|
|||||||
const metadata = nodeData.inputs?.metadata
|
const metadata = nodeData.inputs?.metadata
|
||||||
const recursive = nodeData.inputs?.recursive as boolean
|
const recursive = nodeData.inputs?.recursive as boolean
|
||||||
const pdfUsage = nodeData.inputs?.pdfUsage
|
const pdfUsage = nodeData.inputs?.pdfUsage
|
||||||
|
const pointerName = nodeData.inputs?.pointerName as string
|
||||||
const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string
|
const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string
|
||||||
|
|
||||||
let omitMetadataKeys: string[] = []
|
let omitMetadataKeys: string[] = []
|
||||||
@@ -104,8 +115,12 @@ class Folder_DocumentLoaders implements INode {
|
|||||||
folderPath,
|
folderPath,
|
||||||
{
|
{
|
||||||
'.json': (path) => new JSONLoader(path),
|
'.json': (path) => new JSONLoader(path),
|
||||||
|
'.jsonl': (blob) => new JSONLinesLoader(blob, '/' + pointerName.trim()),
|
||||||
'.txt': (path) => new TextLoader(path),
|
'.txt': (path) => new TextLoader(path),
|
||||||
'.csv': (path) => new CSVLoader(path),
|
'.csv': (path) => new CSVLoader(path),
|
||||||
|
'.xls': (path) => new CSVLoader(path),
|
||||||
|
'.xlsx': (path) => new CSVLoader(path),
|
||||||
|
'.doc': (path) => new DocxLoader(path),
|
||||||
'.docx': (path) => new DocxLoader(path),
|
'.docx': (path) => new DocxLoader(path),
|
||||||
'.pdf': (path) =>
|
'.pdf': (path) =>
|
||||||
pdfUsage === 'perFile'
|
pdfUsage === 'perFile'
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ class Json_DocumentLoaders implements INode {
|
|||||||
const chatflowid = options.chatflowid
|
const chatflowid = options.chatflowid
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
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])
|
||||||
const loader = new JSONLoader(blob, pointers.length != 0 ? pointers : undefined)
|
const loader = new JSONLoader(blob, pointers.length != 0 ? pointers : undefined)
|
||||||
@@ -119,6 +120,7 @@ class Json_DocumentLoaders implements INode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
const splitDataURI = file.split(',')
|
const splitDataURI = file.split(',')
|
||||||
splitDataURI.pop()
|
splitDataURI.pop()
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ class Jsonlines_DocumentLoaders implements INode {
|
|||||||
const chatflowid = options.chatflowid
|
const chatflowid = options.chatflowid
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
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])
|
||||||
const loader = new JSONLinesLoader(blob, pointer)
|
const loader = new JSONLinesLoader(blob, pointer)
|
||||||
@@ -113,6 +114,7 @@ class Jsonlines_DocumentLoaders implements INode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
const splitDataURI = file.split(',')
|
const splitDataURI = file.split(',')
|
||||||
splitDataURI.pop()
|
splitDataURI.pop()
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ class Pdf_DocumentLoaders implements INode {
|
|||||||
const chatflowid = options.chatflowid
|
const chatflowid = options.chatflowid
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
const fileData = await getFileFromStorage(file, chatflowid)
|
const fileData = await getFileFromStorage(file, chatflowid)
|
||||||
const bf = Buffer.from(fileData)
|
const bf = Buffer.from(fileData)
|
||||||
await this.extractDocs(usage, bf, legacyBuild, textSplitter, docs)
|
await this.extractDocs(usage, bf, legacyBuild, textSplitter, docs)
|
||||||
@@ -121,6 +122,7 @@ class Pdf_DocumentLoaders implements INode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
const splitDataURI = file.split(',')
|
const splitDataURI = file.split(',')
|
||||||
splitDataURI.pop()
|
splitDataURI.pop()
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ class Text_DocumentLoaders implements INode {
|
|||||||
const chatflowid = options.chatflowid
|
const chatflowid = options.chatflowid
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
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])
|
||||||
const loader = new TextLoader(blob)
|
const loader = new TextLoader(blob)
|
||||||
@@ -121,6 +122,7 @@ class Text_DocumentLoaders implements INode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
const splitDataURI = file.split(',')
|
const splitDataURI = file.split(',')
|
||||||
splitDataURI.pop()
|
splitDataURI.pop()
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
|
|||||||
@@ -496,6 +496,7 @@ class UnstructuredFile_DocumentLoaders implements INode {
|
|||||||
const chatflowid = options.chatflowid
|
const chatflowid = options.chatflowid
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
const fileData = await getFileFromStorage(file, chatflowid)
|
const fileData = await getFileFromStorage(file, chatflowid)
|
||||||
const loaderDocs = await loader.loadAndSplitBuffer(fileData, file)
|
const loaderDocs = await loader.loadAndSplitBuffer(fileData, file)
|
||||||
docs.push(...loaderDocs)
|
docs.push(...loaderDocs)
|
||||||
@@ -508,6 +509,7 @@ class UnstructuredFile_DocumentLoaders implements INode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
const splitDataURI = file.split(',')
|
const splitDataURI = file.split(',')
|
||||||
const filename = splitDataURI.pop()?.split(':')[1] ?? ''
|
const filename = splitDataURI.pop()?.split(':')[1] ?? ''
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
|
|||||||
@@ -209,11 +209,11 @@ class Supervisor_MultiAgents implements INode {
|
|||||||
prompt = messages.prompt
|
prompt = messages.prompt
|
||||||
multiModalMessageContent = messages.multiModalMessageContent
|
multiModalMessageContent = messages.multiModalMessageContent
|
||||||
|
|
||||||
if (llm.bindTools === undefined) {
|
if ((llm as any).bindTools === undefined) {
|
||||||
throw new Error(`This agent only compatible with function calling models.`)
|
throw new Error(`This agent only compatible with function calling models.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelWithTool = llm.bindTools([tool])
|
const modelWithTool = (llm as any).bindTools([tool])
|
||||||
|
|
||||||
const outputParser = new ToolCallingAgentOutputParser()
|
const outputParser = new ToolCallingAgentOutputParser()
|
||||||
|
|
||||||
@@ -464,11 +464,11 @@ class Supervisor_MultiAgents implements INode {
|
|||||||
prompt = messages.prompt
|
prompt = messages.prompt
|
||||||
multiModalMessageContent = messages.multiModalMessageContent
|
multiModalMessageContent = messages.multiModalMessageContent
|
||||||
|
|
||||||
if (llm.bindTools === undefined) {
|
if ((llm as any).bindTools === undefined) {
|
||||||
throw new Error(`This agent only compatible with function calling models.`)
|
throw new Error(`This agent only compatible with function calling models.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelWithTool = llm.bindTools([tool])
|
const modelWithTool = (llm as any).bindTools([tool])
|
||||||
|
|
||||||
const outputParser = new ToolCallingAgentOutputParser()
|
const outputParser = new ToolCallingAgentOutputParser()
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { Document } from '@langchain/core/documents'
|
|||||||
import { MilvusLibArgs, Milvus } from '@langchain/community/vectorstores/milvus'
|
import { MilvusLibArgs, Milvus } from '@langchain/community/vectorstores/milvus'
|
||||||
import { Embeddings } from '@langchain/core/embeddings'
|
import { Embeddings } from '@langchain/core/embeddings'
|
||||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams, IndexingResult } from '../../../src/Interface'
|
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams, IndexingResult } from '../../../src/Interface'
|
||||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
import { FLOWISE_CHATID, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||||
|
import { howToUseFileUpload } from '../VectorStoreUtils'
|
||||||
|
|
||||||
interface InsertRow {
|
interface InsertRow {
|
||||||
[x: string]: string | number[]
|
[x: string]: string | number[]
|
||||||
@@ -27,7 +28,7 @@ class Milvus_VectorStores implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Milvus'
|
this.label = 'Milvus'
|
||||||
this.name = 'milvus'
|
this.name = 'milvus'
|
||||||
this.version = 1.0
|
this.version = 2.0
|
||||||
this.type = 'Milvus'
|
this.type = 'Milvus'
|
||||||
this.icon = 'milvus.svg'
|
this.icon = 'milvus.svg'
|
||||||
this.category = 'Vector Stores'
|
this.category = 'Vector Stores'
|
||||||
@@ -64,6 +65,18 @@ class Milvus_VectorStores implements INode {
|
|||||||
name: 'milvusCollection',
|
name: 'milvusCollection',
|
||||||
type: 'string'
|
type: 'string'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'File Upload',
|
||||||
|
name: 'fileUpload',
|
||||||
|
description: 'Allow file upload on the chat',
|
||||||
|
hint: {
|
||||||
|
label: 'How to use',
|
||||||
|
value: howToUseFileUpload
|
||||||
|
},
|
||||||
|
type: 'boolean',
|
||||||
|
additionalParams: true,
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Milvus Text Field',
|
label: 'Milvus Text Field',
|
||||||
name: 'milvusTextField',
|
name: 'milvusTextField',
|
||||||
@@ -116,6 +129,7 @@ class Milvus_VectorStores implements INode {
|
|||||||
// embeddings
|
// embeddings
|
||||||
const docs = nodeData.inputs?.document as Document[]
|
const docs = nodeData.inputs?.document as Document[]
|
||||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||||
|
const isFileUploadEnabled = nodeData.inputs?.fileUpload as boolean
|
||||||
|
|
||||||
// credential
|
// credential
|
||||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||||
@@ -135,6 +149,9 @@ class Milvus_VectorStores implements INode {
|
|||||||
const finalDocs = []
|
const finalDocs = []
|
||||||
for (let i = 0; i < flattenDocs.length; i += 1) {
|
for (let i = 0; i < flattenDocs.length; i += 1) {
|
||||||
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
||||||
|
if (isFileUploadEnabled && options.chatId) {
|
||||||
|
flattenDocs[i].metadata = { ...flattenDocs[i].metadata, [FLOWISE_CHATID]: options.chatId }
|
||||||
|
}
|
||||||
finalDocs.push(new Document(flattenDocs[i]))
|
finalDocs.push(new Document(flattenDocs[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,8 +175,9 @@ class Milvus_VectorStores implements INode {
|
|||||||
// server setup
|
// server setup
|
||||||
const address = nodeData.inputs?.milvusServerUrl as string
|
const address = nodeData.inputs?.milvusServerUrl as string
|
||||||
const collectionName = nodeData.inputs?.milvusCollection as string
|
const collectionName = nodeData.inputs?.milvusCollection as string
|
||||||
const milvusFilter = nodeData.inputs?.milvusFilter as string
|
const _milvusFilter = nodeData.inputs?.milvusFilter as string
|
||||||
const textField = nodeData.inputs?.milvusTextField as string
|
const textField = nodeData.inputs?.milvusTextField as string
|
||||||
|
const isFileUploadEnabled = nodeData.inputs?.fileUpload as boolean
|
||||||
|
|
||||||
// embeddings
|
// embeddings
|
||||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||||
@@ -186,6 +204,12 @@ class Milvus_VectorStores implements INode {
|
|||||||
if (milvusUser) milVusArgs.username = milvusUser
|
if (milvusUser) milVusArgs.username = milvusUser
|
||||||
if (milvusPassword) milVusArgs.password = milvusPassword
|
if (milvusPassword) milVusArgs.password = milvusPassword
|
||||||
|
|
||||||
|
let milvusFilter = _milvusFilter
|
||||||
|
if (isFileUploadEnabled && options.chatId) {
|
||||||
|
if (milvusFilter) milvusFilter += ` OR ${FLOWISE_CHATID} == "${options.chatId}" OR NOT EXISTS(${FLOWISE_CHATID})`
|
||||||
|
else milvusFilter = `${FLOWISE_CHATID} == "${options.chatId}" OR NOT EXISTS(${FLOWISE_CHATID})`
|
||||||
|
}
|
||||||
|
|
||||||
const vectorStore = await Milvus.fromExistingCollection(embeddings, milVusArgs)
|
const vectorStore = await Milvus.fromExistingCollection(embeddings, milVusArgs)
|
||||||
|
|
||||||
// Avoid Illegal Invocation
|
// Avoid Illegal Invocation
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { Embeddings } from '@langchain/core/embeddings'
|
|||||||
import { Document } from '@langchain/core/documents'
|
import { Document } from '@langchain/core/documents'
|
||||||
import { VectorStore } from '@langchain/core/vectorstores'
|
import { VectorStore } from '@langchain/core/vectorstores'
|
||||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams, IndexingResult } from '../../../src/Interface'
|
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams, IndexingResult } from '../../../src/Interface'
|
||||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
import { FLOWISE_CHATID, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||||
import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
|
import { addMMRInputParams, howToUseFileUpload, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
|
||||||
import { index } from '../../../src/indexing'
|
import { index } from '../../../src/indexing'
|
||||||
|
|
||||||
let pineconeClientSingleton: Pinecone
|
let pineconeClientSingleton: Pinecone
|
||||||
@@ -43,7 +43,7 @@ class Pinecone_VectorStores implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Pinecone'
|
this.label = 'Pinecone'
|
||||||
this.name = 'pinecone'
|
this.name = 'pinecone'
|
||||||
this.version = 4.0
|
this.version = 5.0
|
||||||
this.type = 'Pinecone'
|
this.type = 'Pinecone'
|
||||||
this.icon = 'pinecone.svg'
|
this.icon = 'pinecone.svg'
|
||||||
this.category = 'Vector Stores'
|
this.category = 'Vector Stores'
|
||||||
@@ -88,6 +88,18 @@ class Pinecone_VectorStores implements INode {
|
|||||||
additionalParams: true,
|
additionalParams: true,
|
||||||
optional: true
|
optional: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'File Upload',
|
||||||
|
name: 'fileUpload',
|
||||||
|
description: 'Allow file upload on the chat',
|
||||||
|
hint: {
|
||||||
|
label: 'How to use',
|
||||||
|
value: howToUseFileUpload
|
||||||
|
},
|
||||||
|
type: 'boolean',
|
||||||
|
additionalParams: true,
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Pinecone Text Key',
|
label: 'Pinecone Text Key',
|
||||||
name: 'pineconeTextKey',
|
name: 'pineconeTextKey',
|
||||||
@@ -138,6 +150,7 @@ class Pinecone_VectorStores implements INode {
|
|||||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||||
const recordManager = nodeData.inputs?.recordManager
|
const recordManager = nodeData.inputs?.recordManager
|
||||||
const pineconeTextKey = nodeData.inputs?.pineconeTextKey as string
|
const pineconeTextKey = nodeData.inputs?.pineconeTextKey as string
|
||||||
|
const isFileUploadEnabled = nodeData.inputs?.fileUpload as boolean
|
||||||
|
|
||||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||||
const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData)
|
const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData)
|
||||||
@@ -150,6 +163,9 @@ class Pinecone_VectorStores implements INode {
|
|||||||
const finalDocs = []
|
const finalDocs = []
|
||||||
for (let i = 0; i < flattenDocs.length; i += 1) {
|
for (let i = 0; i < flattenDocs.length; i += 1) {
|
||||||
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
||||||
|
if (isFileUploadEnabled && options.chatId) {
|
||||||
|
flattenDocs[i].metadata = { ...flattenDocs[i].metadata, [FLOWISE_CHATID]: options.chatId }
|
||||||
|
}
|
||||||
finalDocs.push(new Document(flattenDocs[i]))
|
finalDocs.push(new Document(flattenDocs[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,6 +248,7 @@ class Pinecone_VectorStores implements INode {
|
|||||||
const pineconeMetadataFilter = nodeData.inputs?.pineconeMetadataFilter
|
const pineconeMetadataFilter = nodeData.inputs?.pineconeMetadataFilter
|
||||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||||
const pineconeTextKey = nodeData.inputs?.pineconeTextKey as string
|
const pineconeTextKey = nodeData.inputs?.pineconeTextKey as string
|
||||||
|
const isFileUploadEnabled = nodeData.inputs?.fileUpload as boolean
|
||||||
|
|
||||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||||
const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData)
|
const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData)
|
||||||
@@ -250,6 +267,14 @@ class Pinecone_VectorStores implements INode {
|
|||||||
const metadatafilter = typeof pineconeMetadataFilter === 'object' ? pineconeMetadataFilter : JSON.parse(pineconeMetadataFilter)
|
const metadatafilter = typeof pineconeMetadataFilter === 'object' ? pineconeMetadataFilter : JSON.parse(pineconeMetadataFilter)
|
||||||
obj.filter = metadatafilter
|
obj.filter = metadatafilter
|
||||||
}
|
}
|
||||||
|
if (isFileUploadEnabled && options.chatId) {
|
||||||
|
obj.filter = obj.filter || {}
|
||||||
|
obj.filter.$or = [
|
||||||
|
...(obj.filter.$or || []),
|
||||||
|
{ [FLOWISE_CHATID]: { $eq: options.chatId } },
|
||||||
|
{ [FLOWISE_CHATID]: { $exists: false } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
const vectorStore = (await PineconeStore.fromExistingIndex(embeddings, obj)) as unknown as VectorStore
|
const vectorStore = (await PineconeStore.fromExistingIndex(embeddings, obj)) as unknown as VectorStore
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import { Embeddings } from '@langchain/core/embeddings'
|
|||||||
import { Document } from '@langchain/core/documents'
|
import { Document } from '@langchain/core/documents'
|
||||||
import { TypeORMVectorStore, TypeORMVectorStoreDocument } from '@langchain/community/vectorstores/typeorm'
|
import { TypeORMVectorStore, TypeORMVectorStoreDocument } from '@langchain/community/vectorstores/typeorm'
|
||||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams, IndexingResult } from '../../../src/Interface'
|
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams, IndexingResult } from '../../../src/Interface'
|
||||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
import { FLOWISE_CHATID, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||||
import { index } from '../../../src/indexing'
|
import { index } from '../../../src/indexing'
|
||||||
|
import { howToUseFileUpload } from '../VectorStoreUtils'
|
||||||
|
|
||||||
class Postgres_VectorStores implements INode {
|
class Postgres_VectorStores implements INode {
|
||||||
label: string
|
label: string
|
||||||
@@ -25,7 +26,7 @@ class Postgres_VectorStores implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Postgres'
|
this.label = 'Postgres'
|
||||||
this.name = 'postgres'
|
this.name = 'postgres'
|
||||||
this.version = 5.0
|
this.version = 6.0
|
||||||
this.type = 'Postgres'
|
this.type = 'Postgres'
|
||||||
this.icon = 'postgres.svg'
|
this.icon = 'postgres.svg'
|
||||||
this.category = 'Vector Stores'
|
this.category = 'Vector Stores'
|
||||||
@@ -82,6 +83,18 @@ class Postgres_VectorStores implements INode {
|
|||||||
additionalParams: true,
|
additionalParams: true,
|
||||||
optional: true
|
optional: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'File Upload',
|
||||||
|
name: 'fileUpload',
|
||||||
|
description: 'Allow file upload on the chat',
|
||||||
|
hint: {
|
||||||
|
label: 'How to use',
|
||||||
|
value: howToUseFileUpload
|
||||||
|
},
|
||||||
|
type: 'boolean',
|
||||||
|
additionalParams: true,
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Additional Configuration',
|
label: 'Additional Configuration',
|
||||||
name: 'additionalConfig',
|
name: 'additionalConfig',
|
||||||
@@ -132,6 +145,7 @@ class Postgres_VectorStores implements INode {
|
|||||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||||
const additionalConfig = nodeData.inputs?.additionalConfig as string
|
const additionalConfig = nodeData.inputs?.additionalConfig as string
|
||||||
const recordManager = nodeData.inputs?.recordManager
|
const recordManager = nodeData.inputs?.recordManager
|
||||||
|
const isFileUploadEnabled = nodeData.inputs?.fileUpload as boolean
|
||||||
|
|
||||||
let additionalConfiguration = {}
|
let additionalConfiguration = {}
|
||||||
if (additionalConfig) {
|
if (additionalConfig) {
|
||||||
@@ -161,6 +175,9 @@ class Postgres_VectorStores implements INode {
|
|||||||
const finalDocs = []
|
const finalDocs = []
|
||||||
for (let i = 0; i < flattenDocs.length; i += 1) {
|
for (let i = 0; i < flattenDocs.length; i += 1) {
|
||||||
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
||||||
|
if (isFileUploadEnabled && options.chatId) {
|
||||||
|
flattenDocs[i].metadata = { ...flattenDocs[i].metadata, [FLOWISE_CHATID]: options.chatId }
|
||||||
|
}
|
||||||
finalDocs.push(new Document(flattenDocs[i]))
|
finalDocs.push(new Document(flattenDocs[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,11 +285,20 @@ class Postgres_VectorStores implements INode {
|
|||||||
const topK = nodeData.inputs?.topK as string
|
const topK = nodeData.inputs?.topK as string
|
||||||
const k = topK ? parseFloat(topK) : 4
|
const k = topK ? parseFloat(topK) : 4
|
||||||
const _pgMetadataFilter = nodeData.inputs?.pgMetadataFilter
|
const _pgMetadataFilter = nodeData.inputs?.pgMetadataFilter
|
||||||
|
const isFileUploadEnabled = nodeData.inputs?.fileUpload as boolean
|
||||||
|
|
||||||
let pgMetadataFilter: any
|
let pgMetadataFilter: any
|
||||||
if (_pgMetadataFilter) {
|
if (_pgMetadataFilter) {
|
||||||
pgMetadataFilter = typeof _pgMetadataFilter === 'object' ? _pgMetadataFilter : JSON.parse(_pgMetadataFilter)
|
pgMetadataFilter = typeof _pgMetadataFilter === 'object' ? _pgMetadataFilter : JSON.parse(_pgMetadataFilter)
|
||||||
}
|
}
|
||||||
|
if (isFileUploadEnabled && options.chatId) {
|
||||||
|
pgMetadataFilter = pgMetadataFilter || {}
|
||||||
|
pgMetadataFilter = {
|
||||||
|
...pgMetadataFilter,
|
||||||
|
[FLOWISE_CHATID]: options.chatId,
|
||||||
|
$notexists: FLOWISE_CHATID // special filter to check if the field does not exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let additionalConfiguration = {}
|
let additionalConfiguration = {}
|
||||||
if (additionalConfig) {
|
if (additionalConfig) {
|
||||||
@@ -334,12 +360,20 @@ const similaritySearchVectorWithScore = async (
|
|||||||
) => {
|
) => {
|
||||||
const embeddingString = `[${query.join(',')}]`
|
const embeddingString = `[${query.join(',')}]`
|
||||||
let _filter = '{}'
|
let _filter = '{}'
|
||||||
if (filter && typeof filter === 'object') _filter = JSON.stringify(filter)
|
let notExists = ''
|
||||||
|
if (filter && typeof filter === 'object') {
|
||||||
|
if (filter.$notexists) {
|
||||||
|
notExists = `OR NOT (metadata ? '${filter.$notexists}')`
|
||||||
|
delete filter.$notexists
|
||||||
|
}
|
||||||
|
_filter = JSON.stringify(filter)
|
||||||
|
}
|
||||||
|
|
||||||
const queryString = `
|
const queryString = `
|
||||||
SELECT *, embedding <=> $1 as "_distance"
|
SELECT *, embedding <=> $1 as "_distance"
|
||||||
FROM ${tableName}
|
FROM ${tableName}
|
||||||
WHERE metadata @> $2
|
WHERE metadata @> $2
|
||||||
|
${notExists}
|
||||||
ORDER BY "_distance" ASC
|
ORDER BY "_distance" ASC
|
||||||
LIMIT $3;`
|
LIMIT $3;`
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import { Document } from '@langchain/core/documents'
|
|||||||
import { QdrantVectorStore, QdrantLibArgs } from '@langchain/qdrant'
|
import { QdrantVectorStore, QdrantLibArgs } from '@langchain/qdrant'
|
||||||
import { Embeddings } from '@langchain/core/embeddings'
|
import { Embeddings } from '@langchain/core/embeddings'
|
||||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams, IndexingResult } from '../../../src/Interface'
|
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams, IndexingResult } from '../../../src/Interface'
|
||||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
import { FLOWISE_CHATID, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||||
import { index } from '../../../src/indexing'
|
import { index } from '../../../src/indexing'
|
||||||
|
import { howToUseFileUpload } from '../VectorStoreUtils'
|
||||||
|
|
||||||
type RetrieverConfig = Partial<VectorStoreRetrieverInput<QdrantVectorStore>>
|
type RetrieverConfig = Partial<VectorStoreRetrieverInput<QdrantVectorStore>>
|
||||||
type QdrantAddDocumentOptions = {
|
type QdrantAddDocumentOptions = {
|
||||||
@@ -32,7 +33,7 @@ class Qdrant_VectorStores implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Qdrant'
|
this.label = 'Qdrant'
|
||||||
this.name = 'qdrant'
|
this.name = 'qdrant'
|
||||||
this.version = 4.0
|
this.version = 5.0
|
||||||
this.type = 'Qdrant'
|
this.type = 'Qdrant'
|
||||||
this.icon = 'qdrant.png'
|
this.icon = 'qdrant.png'
|
||||||
this.category = 'Vector Stores'
|
this.category = 'Vector Stores'
|
||||||
@@ -78,6 +79,18 @@ class Qdrant_VectorStores implements INode {
|
|||||||
name: 'qdrantCollection',
|
name: 'qdrantCollection',
|
||||||
type: 'string'
|
type: 'string'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'File Upload',
|
||||||
|
name: 'fileUpload',
|
||||||
|
description: 'Allow file upload on the chat',
|
||||||
|
hint: {
|
||||||
|
label: 'How to use',
|
||||||
|
value: howToUseFileUpload
|
||||||
|
},
|
||||||
|
type: 'boolean',
|
||||||
|
additionalParams: true,
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Vector Dimension',
|
label: 'Vector Dimension',
|
||||||
name: 'qdrantVectorDimension',
|
name: 'qdrantVectorDimension',
|
||||||
@@ -188,6 +201,7 @@ class Qdrant_VectorStores implements INode {
|
|||||||
const _batchSize = nodeData.inputs?.batchSize
|
const _batchSize = nodeData.inputs?.batchSize
|
||||||
const contentPayloadKey = nodeData.inputs?.contentPayloadKey || 'content'
|
const contentPayloadKey = nodeData.inputs?.contentPayloadKey || 'content'
|
||||||
const metadataPayloadKey = nodeData.inputs?.metadataPayloadKey || 'metadata'
|
const metadataPayloadKey = nodeData.inputs?.metadataPayloadKey || 'metadata'
|
||||||
|
const isFileUploadEnabled = nodeData.inputs?.fileUpload as boolean
|
||||||
|
|
||||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||||
const qdrantApiKey = getCredentialParam('qdrantApiKey', credentialData, nodeData)
|
const qdrantApiKey = getCredentialParam('qdrantApiKey', credentialData, nodeData)
|
||||||
@@ -204,6 +218,9 @@ class Qdrant_VectorStores implements INode {
|
|||||||
const finalDocs = []
|
const finalDocs = []
|
||||||
for (let i = 0; i < flattenDocs.length; i += 1) {
|
for (let i = 0; i < flattenDocs.length; i += 1) {
|
||||||
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
||||||
|
if (isFileUploadEnabled && options.chatId) {
|
||||||
|
flattenDocs[i].metadata = { ...flattenDocs[i].metadata, [FLOWISE_CHATID]: options.chatId }
|
||||||
|
}
|
||||||
finalDocs.push(new Document(flattenDocs[i]))
|
finalDocs.push(new Document(flattenDocs[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,6 +408,7 @@ class Qdrant_VectorStores implements INode {
|
|||||||
let queryFilter = nodeData.inputs?.qdrantFilter
|
let queryFilter = nodeData.inputs?.qdrantFilter
|
||||||
const contentPayloadKey = nodeData.inputs?.contentPayloadKey || 'content'
|
const contentPayloadKey = nodeData.inputs?.contentPayloadKey || 'content'
|
||||||
const metadataPayloadKey = nodeData.inputs?.metadataPayloadKey || 'metadata'
|
const metadataPayloadKey = nodeData.inputs?.metadataPayloadKey || 'metadata'
|
||||||
|
const isFileUploadEnabled = nodeData.inputs?.fileUpload as boolean
|
||||||
|
|
||||||
const k = topK ? parseFloat(topK) : 4
|
const k = topK ? parseFloat(topK) : 4
|
||||||
|
|
||||||
@@ -434,6 +452,25 @@ class Qdrant_VectorStores implements INode {
|
|||||||
if (queryFilter) {
|
if (queryFilter) {
|
||||||
retrieverConfig.filter = typeof queryFilter === 'object' ? queryFilter : JSON.parse(queryFilter)
|
retrieverConfig.filter = typeof queryFilter === 'object' ? queryFilter : JSON.parse(queryFilter)
|
||||||
}
|
}
|
||||||
|
if (isFileUploadEnabled && options.chatId) {
|
||||||
|
retrieverConfig.filter = retrieverConfig.filter || {}
|
||||||
|
|
||||||
|
retrieverConfig.filter.should = Array.isArray(retrieverConfig.filter.should) ? retrieverConfig.filter.should : []
|
||||||
|
|
||||||
|
retrieverConfig.filter.should.push(
|
||||||
|
{
|
||||||
|
key: `metadata.${FLOWISE_CHATID}`,
|
||||||
|
match: {
|
||||||
|
value: options.chatId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
is_empty: {
|
||||||
|
key: `metadata.${FLOWISE_CHATID}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const vectorStore = await QdrantVectorStore.fromExistingCollection(embeddings, dbConfig)
|
const vectorStore = await QdrantVectorStore.fromExistingCollection(embeddings, dbConfig)
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { flatten } from 'lodash'
|
import { flatten } from 'lodash'
|
||||||
import { IndexingResult, INode, INodeOutputsValue, INodeParams, INodeData, ICommonObject } from '../../../src/Interface'
|
import { IndexingResult, INode, INodeOutputsValue, INodeParams, INodeData, ICommonObject } from '../../../src/Interface'
|
||||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
import { FLOWISE_CHATID, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||||
import { Embeddings } from '@langchain/core/embeddings'
|
import { Embeddings } from '@langchain/core/embeddings'
|
||||||
import { Document } from '@langchain/core/documents'
|
import { Document } from '@langchain/core/documents'
|
||||||
import { UpstashVectorStore } from '@langchain/community/vectorstores/upstash'
|
import { UpstashVectorStore } from '@langchain/community/vectorstores/upstash'
|
||||||
import { Index as UpstashIndex } from '@upstash/vector'
|
import { Index as UpstashIndex } from '@upstash/vector'
|
||||||
import { index } from '../../../src/indexing'
|
import { index } from '../../../src/indexing'
|
||||||
import { resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
|
import { howToUseFileUpload, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
|
||||||
|
|
||||||
type UpstashVectorStoreParams = {
|
type UpstashVectorStoreParams = {
|
||||||
index: UpstashIndex
|
index: UpstashIndex
|
||||||
@@ -29,7 +29,7 @@ class Upstash_VectorStores implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Upstash Vector'
|
this.label = 'Upstash Vector'
|
||||||
this.name = 'upstash'
|
this.name = 'upstash'
|
||||||
this.version = 1.0
|
this.version = 2.0
|
||||||
this.type = 'Upstash'
|
this.type = 'Upstash'
|
||||||
this.icon = 'upstash.svg'
|
this.icon = 'upstash.svg'
|
||||||
this.category = 'Vector Stores'
|
this.category = 'Vector Stores'
|
||||||
@@ -63,6 +63,18 @@ class Upstash_VectorStores implements INode {
|
|||||||
description: 'Keep track of the record to prevent duplication',
|
description: 'Keep track of the record to prevent duplication',
|
||||||
optional: true
|
optional: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'File Upload',
|
||||||
|
name: 'fileUpload',
|
||||||
|
description: 'Allow file upload on the chat',
|
||||||
|
hint: {
|
||||||
|
label: 'How to use',
|
||||||
|
value: howToUseFileUpload
|
||||||
|
},
|
||||||
|
type: 'boolean',
|
||||||
|
additionalParams: true,
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Upstash Metadata Filter',
|
label: 'Upstash Metadata Filter',
|
||||||
name: 'upstashMetadataFilter',
|
name: 'upstashMetadataFilter',
|
||||||
@@ -100,6 +112,7 @@ class Upstash_VectorStores implements INode {
|
|||||||
const docs = nodeData.inputs?.document as Document[]
|
const docs = nodeData.inputs?.document as Document[]
|
||||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||||
const recordManager = nodeData.inputs?.recordManager
|
const recordManager = nodeData.inputs?.recordManager
|
||||||
|
const isFileUploadEnabled = nodeData.inputs?.fileUpload as boolean
|
||||||
|
|
||||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||||
const UPSTASH_VECTOR_REST_URL = getCredentialParam('UPSTASH_VECTOR_REST_URL', credentialData, nodeData)
|
const UPSTASH_VECTOR_REST_URL = getCredentialParam('UPSTASH_VECTOR_REST_URL', credentialData, nodeData)
|
||||||
@@ -114,6 +127,9 @@ class Upstash_VectorStores implements INode {
|
|||||||
const finalDocs = []
|
const finalDocs = []
|
||||||
for (let i = 0; i < flattenDocs.length; i += 1) {
|
for (let i = 0; i < flattenDocs.length; i += 1) {
|
||||||
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
||||||
|
if (isFileUploadEnabled && options.chatId) {
|
||||||
|
flattenDocs[i].metadata = { ...flattenDocs[i].metadata, [FLOWISE_CHATID]: options.chatId }
|
||||||
|
}
|
||||||
finalDocs.push(new Document(flattenDocs[i]))
|
finalDocs.push(new Document(flattenDocs[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,6 +202,7 @@ class Upstash_VectorStores implements INode {
|
|||||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||||
const upstashMetadataFilter = nodeData.inputs?.upstashMetadataFilter
|
const upstashMetadataFilter = nodeData.inputs?.upstashMetadataFilter
|
||||||
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
const embeddings = nodeData.inputs?.embeddings as Embeddings
|
||||||
|
const isFileUploadEnabled = nodeData.inputs?.fileUpload as boolean
|
||||||
|
|
||||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||||
const UPSTASH_VECTOR_REST_URL = getCredentialParam('UPSTASH_VECTOR_REST_URL', credentialData, nodeData)
|
const UPSTASH_VECTOR_REST_URL = getCredentialParam('UPSTASH_VECTOR_REST_URL', credentialData, nodeData)
|
||||||
@@ -203,6 +220,10 @@ class Upstash_VectorStores implements INode {
|
|||||||
if (upstashMetadataFilter) {
|
if (upstashMetadataFilter) {
|
||||||
obj.filter = upstashMetadataFilter
|
obj.filter = upstashMetadataFilter
|
||||||
}
|
}
|
||||||
|
if (isFileUploadEnabled && options.chatId) {
|
||||||
|
if (upstashMetadataFilter) obj.filter += ` OR ${FLOWISE_CHATID} = "${options.chatId}" OR HAS NOT FIELD ${FLOWISE_CHATID}`
|
||||||
|
else obj.filter = `${FLOWISE_CHATID} = "${options.chatId}" OR HAS NOT FIELD ${FLOWISE_CHATID}`
|
||||||
|
}
|
||||||
|
|
||||||
const vectorStore = await UpstashVectorStore.fromExistingIndex(embeddings, obj)
|
const vectorStore = await UpstashVectorStore.fromExistingIndex(embeddings, obj)
|
||||||
|
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ class Vectara_VectorStores implements INode {
|
|||||||
const chatflowid = options.chatflowid
|
const chatflowid = options.chatflowid
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
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])
|
||||||
vectaraFiles.push({ blob: blob, fileName: getFileName(file) })
|
vectaraFiles.push({ blob: blob, fileName: getFileName(file) })
|
||||||
@@ -206,6 +207,7 @@ class Vectara_VectorStores implements INode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
const splitDataURI = file.split(',')
|
const splitDataURI = file.split(',')
|
||||||
splitDataURI.pop()
|
splitDataURI.pop()
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
|
|||||||
@@ -83,3 +83,19 @@ export const addMMRInputParams = (inputs: any[]) => {
|
|||||||
|
|
||||||
inputs.push(...mmrInputParams)
|
inputs.push(...mmrInputParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const howToUseFileUpload = `
|
||||||
|
**File Upload**
|
||||||
|
|
||||||
|
This allows file upload on the chat. Uploaded files will be upserted on the fly to the vector store.
|
||||||
|
|
||||||
|
**Note:**
|
||||||
|
- You can only turn on file upload for one vector store at a time.
|
||||||
|
- At least one Document Loader node should be connected to the document input.
|
||||||
|
- Document Loader should be file types like PDF, DOCX, TXT, etc.
|
||||||
|
|
||||||
|
**How it works**
|
||||||
|
- Uploaded files will have the metadata updated with the chatId.
|
||||||
|
- This will allow the file to be associated with the chatId.
|
||||||
|
- When querying, metadata will be filtered by chatId to retrieve files associated with the chatId.
|
||||||
|
`
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { AIMessage, HumanMessage, BaseMessage } from '@langchain/core/messages'
|
|||||||
|
|
||||||
export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}}
|
export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}}
|
||||||
export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank
|
export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank
|
||||||
|
export const FLOWISE_CHATID = 'flowise_chatId'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* List of dependencies allowed to be import in vm2
|
* List of dependencies allowed to be import in vm2
|
||||||
*/
|
*/
|
||||||
@@ -815,3 +817,67 @@ export const getVersion: () => Promise<{ version: string }> = async () => {
|
|||||||
|
|
||||||
throw new Error('None of the package.json paths could be parsed')
|
throw new Error('None of the package.json paths could be parsed')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map MimeType to InputField
|
||||||
|
* @param {string} mimeType
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const mapMimeTypeToInputField = (mimeType: string) => {
|
||||||
|
switch (mimeType) {
|
||||||
|
case 'text/plain':
|
||||||
|
return 'txtFile'
|
||||||
|
case 'application/pdf':
|
||||||
|
return 'pdfFile'
|
||||||
|
case 'application/json':
|
||||||
|
return 'jsonFile'
|
||||||
|
case 'text/csv':
|
||||||
|
return 'csvFile'
|
||||||
|
case 'application/json-lines':
|
||||||
|
case 'application/jsonl':
|
||||||
|
case 'text/jsonl':
|
||||||
|
return 'jsonlinesFile'
|
||||||
|
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
||||||
|
return 'docxFile'
|
||||||
|
case 'application/vnd.yaml':
|
||||||
|
case 'application/x-yaml':
|
||||||
|
case 'text/vnd.yaml':
|
||||||
|
case 'text/x-yaml':
|
||||||
|
case 'text/yaml':
|
||||||
|
return 'yamlFile'
|
||||||
|
default:
|
||||||
|
return 'txtFile'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map MimeType to Extension
|
||||||
|
* @param {string} mimeType
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const mapMimeTypeToExt = (mimeType: string) => {
|
||||||
|
switch (mimeType) {
|
||||||
|
case 'text/plain':
|
||||||
|
return 'txt'
|
||||||
|
case 'application/pdf':
|
||||||
|
return 'pdf'
|
||||||
|
case 'application/json':
|
||||||
|
return 'json'
|
||||||
|
case 'text/csv':
|
||||||
|
return 'csv'
|
||||||
|
case 'application/json-lines':
|
||||||
|
case 'application/jsonl':
|
||||||
|
case 'text/jsonl':
|
||||||
|
return 'jsonl'
|
||||||
|
case 'application/msword':
|
||||||
|
return 'doc'
|
||||||
|
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
||||||
|
return 'docx'
|
||||||
|
case 'application/vnd.ms-excel':
|
||||||
|
return 'xls'
|
||||||
|
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
||||||
|
return 'xlsx'
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 513,
|
"height": 511,
|
||||||
"id": "promptTemplate_0",
|
"id": "promptTemplate_0",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 384.4880563109088,
|
"x": 384.4880563109088,
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 508,
|
"height": 507,
|
||||||
"id": "llmChain_0",
|
"id": "llmChain_0",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 770.4559230968546,
|
"x": 770.4559230968546,
|
||||||
@@ -164,7 +164,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 670,
|
"height": 669,
|
||||||
"id": "chatOpenAI_0",
|
"id": "chatOpenAI_0",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 376.92707114970364,
|
"x": 376.92707114970364,
|
||||||
@@ -343,7 +343,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 670,
|
"height": 669,
|
||||||
"id": "chatOpenAI_1",
|
"id": "chatOpenAI_1",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 2653.726672579251,
|
"x": 2653.726672579251,
|
||||||
@@ -522,7 +522,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 508,
|
"height": 507,
|
||||||
"id": "llmChain_1",
|
"id": "llmChain_1",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 3089.9937691022837,
|
"x": 3089.9937691022837,
|
||||||
@@ -621,7 +621,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 670,
|
"height": 674,
|
||||||
"id": "customFunction_2",
|
"id": "customFunction_2",
|
||||||
"position": {
|
"position": {
|
||||||
"x": -19.95227863012829,
|
"x": -19.95227863012829,
|
||||||
@@ -709,7 +709,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 670,
|
"height": 674,
|
||||||
"id": "customFunction_1",
|
"id": "customFunction_1",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 1887.4670208331604,
|
"x": 1887.4670208331604,
|
||||||
@@ -797,7 +797,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 513,
|
"height": 511,
|
||||||
"id": "promptTemplate_1",
|
"id": "promptTemplate_1",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 2655.2632506040304,
|
"x": 2655.2632506040304,
|
||||||
@@ -857,7 +857,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 305,
|
"height": 304,
|
||||||
"id": "getVariable_1",
|
"id": "getVariable_1",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 2272.8555266616872,
|
"x": 2272.8555266616872,
|
||||||
@@ -919,7 +919,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 356,
|
"height": 355,
|
||||||
"id": "setVariable_1",
|
"id": "setVariable_1",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 1516.338224315744,
|
"x": 1516.338224315744,
|
||||||
@@ -991,7 +991,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 757,
|
"height": 765,
|
||||||
"id": "ifElseFunction_0",
|
"id": "ifElseFunction_0",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 1147.8020838770517,
|
"x": 1147.8020838770517,
|
||||||
@@ -1050,7 +1050,7 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"functionInputVariables": "{\"sqlQuery\":\"{{llmChain_0.data.instance}}\"}",
|
"functionInputVariables": "{\"sqlQuery\":\"{{llmChain_0.data.instance}}\"}",
|
||||||
"functionName": "IF SQL Query contains SELECT and WHERE",
|
"functionName": "IF SQL Query contains SELECT and WHERE",
|
||||||
"ifFunction": "const sqlQuery = $sqlQuery.trim();\n\nif (sqlQuery.includes(\"SELECT\") && sqlQuery.includes(\"WHERE\")) {\n return sqlQuery;\n}",
|
"ifFunction": "const sqlQuery = $sqlQuery.trim();\n\nconst regex = /SELECT\\s.*?(?:\\n|$)/gi;\n\n// Extracting the SQL part\nconst matches = sqlQuery.match(regex);\nconst cleanSql = matches ? matches[0].trim() : \"\";\n\nif (cleanSql.includes(\"SELECT\") && cleanSql.includes(\"WHERE\")) {\n return cleanSql;\n}",
|
||||||
"elseFunction": "return $sqlQuery;"
|
"elseFunction": "return $sqlQuery;"
|
||||||
},
|
},
|
||||||
"outputAnchors": [
|
"outputAnchors": [
|
||||||
@@ -1092,7 +1092,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 513,
|
"height": 511,
|
||||||
"id": "promptTemplate_2",
|
"id": "promptTemplate_2",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 1193.7489579044463,
|
"x": 1193.7489579044463,
|
||||||
@@ -1152,7 +1152,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 670,
|
"height": 669,
|
||||||
"id": "chatOpenAI_2",
|
"id": "chatOpenAI_2",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 1545.1023725538003,
|
"x": 1545.1023725538003,
|
||||||
@@ -1331,7 +1331,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"width": 300,
|
"width": 300,
|
||||||
"height": 508,
|
"height": 507,
|
||||||
"id": "llmChain_2",
|
"id": "llmChain_2",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 1914.509823868027,
|
"x": 1914.509823868027,
|
||||||
|
|||||||
@@ -16,13 +16,21 @@ export class ChatflowPool {
|
|||||||
* @param {IReactFlowNode[]} startingNodes
|
* @param {IReactFlowNode[]} startingNodes
|
||||||
* @param {ICommonObject} overrideConfig
|
* @param {ICommonObject} overrideConfig
|
||||||
*/
|
*/
|
||||||
add(chatflowid: string, endingNodeData: INodeData | undefined, startingNodes: IReactFlowNode[], overrideConfig?: ICommonObject) {
|
add(
|
||||||
|
chatflowid: string,
|
||||||
|
endingNodeData: INodeData | undefined,
|
||||||
|
startingNodes: IReactFlowNode[],
|
||||||
|
overrideConfig?: ICommonObject,
|
||||||
|
chatId?: string
|
||||||
|
) {
|
||||||
this.activeChatflows[chatflowid] = {
|
this.activeChatflows[chatflowid] = {
|
||||||
startingNodes,
|
startingNodes,
|
||||||
endingNodeData,
|
endingNodeData,
|
||||||
inSync: true
|
inSync: true
|
||||||
}
|
}
|
||||||
if (overrideConfig) this.activeChatflows[chatflowid].overrideConfig = overrideConfig
|
if (overrideConfig) this.activeChatflows[chatflowid].overrideConfig = overrideConfig
|
||||||
|
if (chatId) this.activeChatflows[chatflowid].chatId = chatId
|
||||||
|
|
||||||
logger.info(`[server]: Chatflow ${chatflowid} added into ChatflowPool`)
|
logger.info(`[server]: Chatflow ${chatflowid} added into ChatflowPool`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ export interface IActiveChatflows {
|
|||||||
endingNodeData?: INodeData
|
endingNodeData?: INodeData
|
||||||
inSync: boolean
|
inSync: boolean
|
||||||
overrideConfig?: ICommonObject
|
overrideConfig?: ICommonObject
|
||||||
|
chatId?: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ router.post(
|
|||||||
vectorsController.getRateLimiterMiddleware,
|
vectorsController.getRateLimiterMiddleware,
|
||||||
vectorsController.upsertVectorMiddleware
|
vectorsController.upsertVectorMiddleware
|
||||||
)
|
)
|
||||||
router.post(['/internal-upsert/', '/internal-upsert/:id'], vectorsController.createInternalUpsert)
|
router.post(['/internal-upsert/', '/internal-upsert/:id'], upload.array('files'), vectorsController.createInternalUpsert)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { Request } from 'express'
|
import { Request } from 'express'
|
||||||
import { IFileUpload, convertSpeechToText, ICommonObject, addSingleFileToStorage, addArrayFilesToStorage } from 'flowise-components'
|
import {
|
||||||
|
IFileUpload,
|
||||||
|
convertSpeechToText,
|
||||||
|
ICommonObject,
|
||||||
|
addSingleFileToStorage,
|
||||||
|
addArrayFilesToStorage,
|
||||||
|
mapMimeTypeToInputField
|
||||||
|
} from 'flowise-components'
|
||||||
import { StatusCodes } from 'http-status-codes'
|
import { StatusCodes } from 'http-status-codes'
|
||||||
import {
|
import {
|
||||||
IncomingInput,
|
IncomingInput,
|
||||||
@@ -18,7 +25,6 @@ import { ChatFlow } from '../database/entities/ChatFlow'
|
|||||||
import { Server } from 'socket.io'
|
import { Server } from 'socket.io'
|
||||||
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
||||||
import {
|
import {
|
||||||
mapMimeTypeToInputField,
|
|
||||||
isFlowValidForStream,
|
isFlowValidForStream,
|
||||||
buildFlow,
|
buildFlow,
|
||||||
getTelemetryFlowObj,
|
getTelemetryFlowObj,
|
||||||
@@ -32,7 +38,8 @@ import {
|
|||||||
getMemorySessionId,
|
getMemorySessionId,
|
||||||
isSameOverrideConfig,
|
isSameOverrideConfig,
|
||||||
getEndingNodes,
|
getEndingNodes,
|
||||||
constructGraphs
|
constructGraphs,
|
||||||
|
isSameChatId
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { validateChatflowAPIKey } from './validateKey'
|
import { validateChatflowAPIKey } from './validateKey'
|
||||||
import { databaseEntities } from '.'
|
import { databaseEntities } from '.'
|
||||||
@@ -201,6 +208,7 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter
|
|||||||
* - Node Data already exists in pool
|
* - Node Data already exists in pool
|
||||||
* - Still in sync (i.e the flow has not been modified since)
|
* - Still in sync (i.e the flow has not been modified since)
|
||||||
* - Existing overrideConfig and new overrideConfig are the same
|
* - Existing overrideConfig and new overrideConfig are the same
|
||||||
|
* - Existing chatId and new chatId is the same
|
||||||
* - Flow doesn't start with/contain nodes that depend on incomingInput.question
|
* - Flow doesn't start with/contain nodes that depend on incomingInput.question
|
||||||
***/
|
***/
|
||||||
const isFlowReusable = () => {
|
const isFlowReusable = () => {
|
||||||
@@ -209,6 +217,7 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter
|
|||||||
Object.prototype.hasOwnProperty.call(appServer.chatflowPool.activeChatflows, chatflowid) &&
|
Object.prototype.hasOwnProperty.call(appServer.chatflowPool.activeChatflows, chatflowid) &&
|
||||||
appServer.chatflowPool.activeChatflows[chatflowid].inSync &&
|
appServer.chatflowPool.activeChatflows[chatflowid].inSync &&
|
||||||
appServer.chatflowPool.activeChatflows[chatflowid].endingNodeData &&
|
appServer.chatflowPool.activeChatflows[chatflowid].endingNodeData &&
|
||||||
|
isSameChatId(appServer.chatflowPool.activeChatflows[chatflowid].chatId, chatId) &&
|
||||||
isSameOverrideConfig(
|
isSameOverrideConfig(
|
||||||
isInternal,
|
isInternal,
|
||||||
appServer.chatflowPool.activeChatflows[chatflowid].overrideConfig,
|
appServer.chatflowPool.activeChatflows[chatflowid].overrideConfig,
|
||||||
@@ -338,7 +347,7 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter
|
|||||||
)
|
)
|
||||||
nodeToExecuteData = reactFlowNodeData
|
nodeToExecuteData = reactFlowNodeData
|
||||||
|
|
||||||
appServer.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, incomingInput?.overrideConfig)
|
appServer.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, incomingInput?.overrideConfig, chatId)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`)
|
logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`)
|
||||||
|
|||||||
@@ -2,14 +2,22 @@ import { StatusCodes } from 'http-status-codes'
|
|||||||
import { INodeParams } from 'flowise-components'
|
import { INodeParams } from 'flowise-components'
|
||||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||||
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
||||||
import { IUploadFileSizeAndTypes, IReactFlowNode } from '../Interface'
|
import { IUploadFileSizeAndTypes, IReactFlowNode, IReactFlowEdge } from '../Interface'
|
||||||
import { InternalFlowiseError } from '../errors/internalFlowiseError'
|
import { InternalFlowiseError } from '../errors/internalFlowiseError'
|
||||||
|
|
||||||
|
type IUploadConfig = {
|
||||||
|
isSpeechToTextEnabled: boolean
|
||||||
|
isImageUploadAllowed: boolean
|
||||||
|
isFileUploadAllowed: boolean
|
||||||
|
imgUploadSizeAndTypes: IUploadFileSizeAndTypes[]
|
||||||
|
fileUploadSizeAndTypes: IUploadFileSizeAndTypes[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method that checks if uploads are enabled in the chatflow
|
* Method that checks if uploads are enabled in the chatflow
|
||||||
* @param {string} chatflowid
|
* @param {string} chatflowid
|
||||||
*/
|
*/
|
||||||
export const utilGetUploadsConfig = async (chatflowid: string): Promise<any> => {
|
export const utilGetUploadsConfig = async (chatflowid: string): Promise<IUploadConfig> => {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
const chatflow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
|
const chatflow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
|
||||||
id: chatflowid
|
id: chatflowid
|
||||||
@@ -18,21 +26,17 @@ export const utilGetUploadsConfig = async (chatflowid: string): Promise<any> =>
|
|||||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowid} not found`)
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowid} not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadAllowedNodes = [
|
|
||||||
'llmChain',
|
|
||||||
'conversationChain',
|
|
||||||
'reactAgentChat',
|
|
||||||
'conversationalAgent',
|
|
||||||
'toolAgent',
|
|
||||||
'supervisor',
|
|
||||||
'seqStart'
|
|
||||||
]
|
|
||||||
const uploadProcessingNodes = ['chatOpenAI', 'chatAnthropic', 'awsChatBedrock', 'azureChatOpenAI', 'chatGoogleGenerativeAI']
|
|
||||||
|
|
||||||
const flowObj = JSON.parse(chatflow.flowData)
|
const flowObj = JSON.parse(chatflow.flowData)
|
||||||
const imgUploadSizeAndTypes: IUploadFileSizeAndTypes[] = []
|
const nodes: IReactFlowNode[] = flowObj.nodes
|
||||||
|
const edges: IReactFlowEdge[] = flowObj.edges
|
||||||
|
|
||||||
let isSpeechToTextEnabled = false
|
let isSpeechToTextEnabled = false
|
||||||
|
let isImageUploadAllowed = false
|
||||||
|
let isFileUploadAllowed = false
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check for STT
|
||||||
|
*/
|
||||||
if (chatflow.speechToText) {
|
if (chatflow.speechToText) {
|
||||||
const speechToTextProviders = JSON.parse(chatflow.speechToText)
|
const speechToTextProviders = JSON.parse(chatflow.speechToText)
|
||||||
for (const provider in speechToTextProviders) {
|
for (const provider in speechToTextProviders) {
|
||||||
@@ -46,39 +50,72 @@ export const utilGetUploadsConfig = async (chatflowid: string): Promise<any> =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let isImageUploadAllowed = false
|
/*
|
||||||
const nodes: IReactFlowNode[] = flowObj.nodes
|
* Condition for isFileUploadAllowed
|
||||||
|
* 1.) vector store with fileUpload = true && connected to a document loader with fileType
|
||||||
|
*/
|
||||||
|
const fileUploadSizeAndTypes: IUploadFileSizeAndTypes[] = []
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.data.category === 'Vector Stores' && node.data.inputs?.fileUpload) {
|
||||||
|
// Get the connected document loader node fileTypes
|
||||||
|
const sourceDocumentEdges = edges.filter(
|
||||||
|
(edge) => edge.target === node.id && edge.targetHandle === `${node.id}-input-document-Document`
|
||||||
|
)
|
||||||
|
for (const edge of sourceDocumentEdges) {
|
||||||
|
const sourceNode = nodes.find((node) => node.id === edge.source)
|
||||||
|
if (!sourceNode) continue
|
||||||
|
const fileType = sourceNode.data.inputParams.find((param) => param.type === 'file' && param.fileType)?.fileType
|
||||||
|
if (fileType) {
|
||||||
|
fileUploadSizeAndTypes.push({
|
||||||
|
fileTypes: fileType.split(', '),
|
||||||
|
maxUploadSize: 500
|
||||||
|
})
|
||||||
|
isFileUploadAllowed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Condition for isImageUploadAllowed
|
* Condition for isImageUploadAllowed
|
||||||
* 1.) one of the uploadAllowedNodes exists
|
* 1.) one of the imgUploadAllowedNodes exists
|
||||||
* 2.) one of the uploadProcessingNodes exists + allowImageUploads is ON
|
* 2.) one of the imgUploadLLMNodes exists + allowImageUploads is ON
|
||||||
*/
|
*/
|
||||||
if (!nodes.some((node) => uploadAllowedNodes.includes(node.data.name))) {
|
const imgUploadSizeAndTypes: IUploadFileSizeAndTypes[] = []
|
||||||
return {
|
const imgUploadAllowedNodes = [
|
||||||
isSpeechToTextEnabled,
|
'llmChain',
|
||||||
isImageUploadAllowed: false,
|
'conversationChain',
|
||||||
imgUploadSizeAndTypes
|
'reactAgentChat',
|
||||||
}
|
'conversationalAgent',
|
||||||
|
'toolAgent',
|
||||||
|
'supervisor',
|
||||||
|
'seqStart'
|
||||||
|
]
|
||||||
|
const imgUploadLLMNodes = ['chatOpenAI', 'chatAnthropic', 'awsChatBedrock', 'azureChatOpenAI', 'chatGoogleGenerativeAI']
|
||||||
|
|
||||||
|
if (nodes.some((node) => imgUploadAllowedNodes.includes(node.data.name))) {
|
||||||
|
nodes.forEach((node: IReactFlowNode) => {
|
||||||
|
if (imgUploadLLMNodes.indexOf(node.data.name) > -1) {
|
||||||
|
// TODO: for now the maxUploadSize is hardcoded to 5MB, we need to add it to the node properties
|
||||||
|
node.data.inputParams.map((param: INodeParams) => {
|
||||||
|
if (param.name === 'allowImageUploads' && node.data.inputs?.['allowImageUploads']) {
|
||||||
|
imgUploadSizeAndTypes.push({
|
||||||
|
fileTypes: 'image/gif;image/jpeg;image/png;image/webp;'.split(';'),
|
||||||
|
maxUploadSize: 5
|
||||||
|
})
|
||||||
|
isImageUploadAllowed = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes.forEach((node: IReactFlowNode) => {
|
|
||||||
if (uploadProcessingNodes.indexOf(node.data.name) > -1) {
|
|
||||||
// TODO: for now the maxUploadSize is hardcoded to 5MB, we need to add it to the node properties
|
|
||||||
node.data.inputParams.map((param: INodeParams) => {
|
|
||||||
if (param.name === 'allowImageUploads' && node.data.inputs?.['allowImageUploads']) {
|
|
||||||
imgUploadSizeAndTypes.push({
|
|
||||||
fileTypes: 'image/gif;image/jpeg;image/png;image/webp;'.split(';'),
|
|
||||||
maxUploadSize: 5
|
|
||||||
})
|
|
||||||
isImageUploadAllowed = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return {
|
return {
|
||||||
isSpeechToTextEnabled,
|
isSpeechToTextEnabled,
|
||||||
isImageUploadAllowed,
|
isImageUploadAllowed,
|
||||||
imgUploadSizeAndTypes
|
isFileUploadAllowed,
|
||||||
|
imgUploadSizeAndTypes,
|
||||||
|
fileUploadSizeAndTypes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1074,35 +1074,16 @@ export const isSameOverrideConfig = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map MimeType to InputField
|
* @param {string} existingChatId
|
||||||
* @param {string} mimeType
|
* @param {string} newChatId
|
||||||
* @returns {Promise<string>}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export const mapMimeTypeToInputField = (mimeType: string) => {
|
export const isSameChatId = (existingChatId?: string, newChatId?: string): boolean => {
|
||||||
switch (mimeType) {
|
if (isEqual(existingChatId, newChatId)) {
|
||||||
case 'text/plain':
|
return true
|
||||||
return 'txtFile'
|
|
||||||
case 'application/pdf':
|
|
||||||
return 'pdfFile'
|
|
||||||
case 'application/json':
|
|
||||||
return 'jsonFile'
|
|
||||||
case 'text/csv':
|
|
||||||
return 'csvFile'
|
|
||||||
case 'application/json-lines':
|
|
||||||
case 'application/jsonl':
|
|
||||||
case 'text/jsonl':
|
|
||||||
return 'jsonlinesFile'
|
|
||||||
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
|
||||||
return 'docxFile'
|
|
||||||
case 'application/vnd.yaml':
|
|
||||||
case 'application/x-yaml':
|
|
||||||
case 'text/vnd.yaml':
|
|
||||||
case 'text/x-yaml':
|
|
||||||
case 'text/yaml':
|
|
||||||
return 'yamlFile'
|
|
||||||
default:
|
|
||||||
return 'txtFile'
|
|
||||||
}
|
}
|
||||||
|
if (!existingChatId && !newChatId) return true
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { Request } from 'express'
|
import { Request } from 'express'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import { cloneDeep, omit } from 'lodash'
|
import { cloneDeep, omit } from 'lodash'
|
||||||
import { ICommonObject, IMessage, addArrayFilesToStorage } from 'flowise-components'
|
import { ICommonObject, IMessage, addArrayFilesToStorage, mapMimeTypeToInputField } from 'flowise-components'
|
||||||
import telemetryService from '../services/telemetry'
|
import telemetryService from '../services/telemetry'
|
||||||
import logger from '../utils/logger'
|
import logger from '../utils/logger'
|
||||||
import {
|
import {
|
||||||
buildFlow,
|
buildFlow,
|
||||||
constructGraphs,
|
constructGraphs,
|
||||||
getAllConnectedNodes,
|
getAllConnectedNodes,
|
||||||
mapMimeTypeToInputField,
|
|
||||||
findMemoryNode,
|
findMemoryNode,
|
||||||
getMemorySessionId,
|
getMemorySessionId,
|
||||||
getAppVersion,
|
getAppVersion,
|
||||||
@@ -70,6 +69,9 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
|
|||||||
overrideConfig,
|
overrideConfig,
|
||||||
stopNodeId: req.body.stopNodeId
|
stopNodeId: req.body.stopNodeId
|
||||||
}
|
}
|
||||||
|
if (req.body.chatId) {
|
||||||
|
incomingInput.chatId = req.body.chatId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Get chatflows and prepare data ***/
|
/*** Get chatflows and prepare data ***/
|
||||||
@@ -87,10 +89,15 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
|
|||||||
const memoryNode = findMemoryNode(nodes, edges)
|
const memoryNode = findMemoryNode(nodes, edges)
|
||||||
let sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal)
|
let sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal)
|
||||||
|
|
||||||
const vsNodes = nodes.filter(
|
const vsNodes = nodes.filter((node) => node.data.category === 'Vector Stores')
|
||||||
(node) =>
|
|
||||||
node.data.category === 'Vector Stores' && !node.data.label.includes('Upsert') && !node.data.label.includes('Load Existing')
|
// Get StopNodeId for vector store which has fielUpload
|
||||||
)
|
const vsNodesWithFileUpload = vsNodes.filter((node) => node.data.inputs?.fileUpload)
|
||||||
|
if (vsNodesWithFileUpload.length > 1) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, 'Multiple vector store nodes with fileUpload enabled')
|
||||||
|
} else if (vsNodesWithFileUpload.length === 1 && !stopNodeId) {
|
||||||
|
stopNodeId = vsNodesWithFileUpload[0].data.id
|
||||||
|
}
|
||||||
|
|
||||||
// Check if multiple vector store nodes exist, and if stopNodeId is specified
|
// Check if multiple vector store nodes exist, and if stopNodeId is specified
|
||||||
if (vsNodes.length > 1 && !stopNodeId) {
|
if (vsNodes.length > 1 && !stopNodeId) {
|
||||||
@@ -138,7 +145,7 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
|
|||||||
|
|
||||||
const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.data.id))
|
const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.data.id))
|
||||||
|
|
||||||
await appServer.chatflowPool.add(chatflowid, undefined, startingNodes, incomingInput?.overrideConfig)
|
await appServer.chatflowPool.add(chatflowid, undefined, startingNodes, incomingInput?.overrideConfig, chatId)
|
||||||
|
|
||||||
// Save to DB
|
// Save to DB
|
||||||
if (upsertedResult['flowData'] && upsertedResult['result']) {
|
if (upsertedResult['flowData'] && upsertedResult['result']) {
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import client from './client'
|
import client from './client'
|
||||||
|
|
||||||
const upsertVectorStore = (id, input) => client.post(`/vector/internal-upsert/${id}`, input)
|
const upsertVectorStore = (id, input) => client.post(`/vector/internal-upsert/${id}`, input)
|
||||||
|
const upsertVectorStoreWithFormData = (id, formData) =>
|
||||||
|
client.post(`/vector/internal-upsert/${id}`, formData, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
const getUpsertHistory = (id, params = {}) => client.get(`/upsert-history/${id}`, { params: { order: 'DESC', ...params } })
|
const getUpsertHistory = (id, params = {}) => client.get(`/upsert-history/${id}`, { params: { order: 'DESC', ...params } })
|
||||||
const deleteUpsertHistory = (ids) => client.patch(`/upsert-history`, { ids })
|
const deleteUpsertHistory = (ids) => client.patch(`/upsert-history`, { ids })
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getUpsertHistory,
|
getUpsertHistory,
|
||||||
upsertVectorStore,
|
upsertVectorStore,
|
||||||
|
upsertVectorStoreWithFormData,
|
||||||
deleteUpsertHistory
|
deleteUpsertHistory
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import userPNG from '@/assets/images/account.png'
|
|||||||
import msgEmptySVG from '@/assets/images/message_empty.svg'
|
import msgEmptySVG from '@/assets/images/message_empty.svg'
|
||||||
import multiagent_supervisorPNG from '@/assets/images/multiagent_supervisor.png'
|
import multiagent_supervisorPNG from '@/assets/images/multiagent_supervisor.png'
|
||||||
import multiagent_workerPNG from '@/assets/images/multiagent_worker.png'
|
import multiagent_workerPNG from '@/assets/images/multiagent_worker.png'
|
||||||
import { IconTool, IconDeviceSdCard, IconFileExport, IconEraser, IconX, IconDownload } from '@tabler/icons-react'
|
import { IconTool, IconDeviceSdCard, IconFileExport, IconEraser, IconX, IconDownload, IconPaperclip } from '@tabler/icons-react'
|
||||||
|
|
||||||
// Project import
|
// Project import
|
||||||
import { MemoizedReactMarkdown } from '@/ui-component/markdown/MemoizedReactMarkdown'
|
import { MemoizedReactMarkdown } from '@/ui-component/markdown/MemoizedReactMarkdown'
|
||||||
@@ -438,6 +438,59 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
setSourceDialogOpen(true)
|
setSourceDialogOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderFileUploads = (item, index) => {
|
||||||
|
if (item?.mime?.startsWith('image/')) {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
p: 0,
|
||||||
|
m: 0,
|
||||||
|
maxWidth: 128,
|
||||||
|
marginRight: '10px',
|
||||||
|
flex: '0 0 auto'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardMedia component='img' image={item.data} sx={{ height: 64 }} alt={'preview'} style={messageImageStyle} />
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
} else if (item?.mime?.startsWith('audio/')) {
|
||||||
|
return (
|
||||||
|
/* eslint-disable jsx-a11y/media-has-caption */
|
||||||
|
<audio controls='controls'>
|
||||||
|
Your browser does not support the <audio> tag.
|
||||||
|
<source src={item.data} type={item.mime} />
|
||||||
|
</audio>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '48px',
|
||||||
|
width: 'max-content',
|
||||||
|
p: 2,
|
||||||
|
mr: 1,
|
||||||
|
flex: '0 0 auto',
|
||||||
|
backgroundColor: customization.isDarkMode ? 'rgba(0, 0, 0, 0.3)' : 'transparent'
|
||||||
|
}}
|
||||||
|
variant='outlined'
|
||||||
|
>
|
||||||
|
<IconPaperclip size={20} />
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginLeft: '5px',
|
||||||
|
color: customization.isDarkMode ? 'white' : 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</span>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const leadEmailFromChatMessages = chatMessages.filter((message) => message.type === 'userMessage' && message.leadEmail)
|
const leadEmailFromChatMessages = chatMessages.filter((message) => message.type === 'userMessage' && message.leadEmail)
|
||||||
if (leadEmailFromChatMessages.length) {
|
if (leadEmailFromChatMessages.length) {
|
||||||
@@ -855,37 +908,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{message.fileUploads.map((item, index) => {
|
{message.fileUploads.map((item, index) => {
|
||||||
return (
|
return <>{renderFileUploads(item, index)}</>
|
||||||
<>
|
|
||||||
{item.mime.startsWith('image/') ? (
|
|
||||||
<Card
|
|
||||||
key={index}
|
|
||||||
sx={{
|
|
||||||
p: 0,
|
|
||||||
m: 0,
|
|
||||||
maxWidth: 128,
|
|
||||||
marginRight: '10px',
|
|
||||||
flex: '0 0 auto'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CardMedia
|
|
||||||
component='img'
|
|
||||||
image={item.data}
|
|
||||||
sx={{ height: 64 }}
|
|
||||||
alt={'preview'}
|
|
||||||
style={messageImageStyle}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
) : (
|
|
||||||
// eslint-disable-next-line jsx-a11y/media-has-caption
|
|
||||||
<audio controls='controls'>
|
|
||||||
Your browser does not support the <audio>
|
|
||||||
tag.
|
|
||||||
<source src={item.data} type={item.mime} />
|
|
||||||
</audio>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ import {
|
|||||||
IconTool,
|
IconTool,
|
||||||
IconSquareFilled,
|
IconSquareFilled,
|
||||||
IconDeviceSdCard,
|
IconDeviceSdCard,
|
||||||
IconCheck
|
IconCheck,
|
||||||
|
IconPaperclip
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import robotPNG from '@/assets/images/robot.png'
|
import robotPNG from '@/assets/images/robot.png'
|
||||||
import userPNG from '@/assets/images/account.png'
|
import userPNG from '@/assets/images/account.png'
|
||||||
@@ -64,6 +65,7 @@ import './ChatMessage.css'
|
|||||||
import chatmessageApi from '@/api/chatmessage'
|
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 chatmessagefeedbackApi from '@/api/chatmessagefeedback'
|
import chatmessagefeedbackApi from '@/api/chatmessagefeedback'
|
||||||
import leadsApi from '@/api/lead'
|
import leadsApi from '@/api/lead'
|
||||||
|
|
||||||
@@ -84,6 +86,71 @@ const messageImageStyle = {
|
|||||||
objectFit: 'cover'
|
objectFit: 'cover'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CardWithDeleteOverlay = ({ item, customization, onDelete }) => {
|
||||||
|
const [isHovered, setIsHovered] = useState(false)
|
||||||
|
const defaultBackgroundColor = customization.isDarkMode ? 'rgba(0, 0, 0, 0.3)' : 'transparent'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
|
style={{ position: 'relative', display: 'inline-block' }}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '48px',
|
||||||
|
width: 'max-content',
|
||||||
|
p: 2,
|
||||||
|
mr: 1,
|
||||||
|
flex: '0 0 auto',
|
||||||
|
transition: 'opacity 0.3s',
|
||||||
|
opacity: isHovered ? 1 : 1,
|
||||||
|
backgroundColor: isHovered ? 'rgba(0, 0, 0, 0.3)' : defaultBackgroundColor
|
||||||
|
}}
|
||||||
|
variant='outlined'
|
||||||
|
>
|
||||||
|
<IconPaperclip size={20} style={{ transition: 'filter 0.3s', filter: isHovered ? 'blur(2px)' : 'none' }} />
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginLeft: '5px',
|
||||||
|
color: customization.isDarkMode ? 'white' : 'inherit',
|
||||||
|
transition: 'filter 0.3s',
|
||||||
|
filter: isHovered ? 'blur(2px)' : 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</span>
|
||||||
|
</Card>
|
||||||
|
{isHovered && (
|
||||||
|
<Button
|
||||||
|
onClick={() => onDelete(item)}
|
||||||
|
startIcon={<IconTrash color='white' size={22} />}
|
||||||
|
title='Remove attachment'
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
CardWithDeleteOverlay.propTypes = {
|
||||||
|
item: PropTypes.object,
|
||||||
|
customization: PropTypes.object,
|
||||||
|
onDelete: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setPreviews }) => {
|
export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setPreviews }) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const customization = useSelector((state) => state.customization)
|
const customization = useSelector((state) => state.customization)
|
||||||
@@ -111,6 +178,9 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
const [sourceDialogProps, setSourceDialogProps] = useState({})
|
const [sourceDialogProps, setSourceDialogProps] = useState({})
|
||||||
const [chatId, setChatId] = useState(uuidv4())
|
const [chatId, setChatId] = useState(uuidv4())
|
||||||
const [isMessageStopping, setIsMessageStopping] = useState(false)
|
const [isMessageStopping, setIsMessageStopping] = useState(false)
|
||||||
|
const [uploadedFiles, setUploadedFiles] = useState([])
|
||||||
|
const [imageUploadAllowedTypes, setImageUploadAllowedTypes] = useState('')
|
||||||
|
const [fileUploadAllowedTypes, setFileUploadAllowedTypes] = useState('')
|
||||||
|
|
||||||
const inputRef = useRef(null)
|
const inputRef = useRef(null)
|
||||||
const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow)
|
const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow)
|
||||||
@@ -134,8 +204,10 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
const [isLeadSaved, setIsLeadSaved] = useState(false)
|
const [isLeadSaved, setIsLeadSaved] = useState(false)
|
||||||
|
|
||||||
// drag & drop and file input
|
// drag & drop and file input
|
||||||
|
const imgUploadRef = useRef(null)
|
||||||
const fileUploadRef = useRef(null)
|
const fileUploadRef = useRef(null)
|
||||||
const [isChatFlowAvailableForUploads, setIsChatFlowAvailableForUploads] = useState(false)
|
const [isChatFlowAvailableForImageUploads, setIsChatFlowAvailableForImageUploads] = useState(false)
|
||||||
|
const [isChatFlowAvailableForFileUploads, setIsChatFlowAvailableForFileUploads] = useState(false)
|
||||||
const [isDragActive, setIsDragActive] = useState(false)
|
const [isDragActive, setIsDragActive] = useState(false)
|
||||||
|
|
||||||
// recording
|
// recording
|
||||||
@@ -158,6 +230,18 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if (constraints.isFileUploadAllowed) {
|
||||||
|
const fileExt = file.name.split('.').pop()
|
||||||
|
if (fileExt) {
|
||||||
|
constraints.fileUploadSizeAndTypes.map((allowed) => {
|
||||||
|
if (allowed.fileTypes.length === 1 && allowed.fileTypes[0] === '*') {
|
||||||
|
acceptFile = true
|
||||||
|
} else if (allowed.fileTypes.includes(`.${fileExt}`)) {
|
||||||
|
acceptFile = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!acceptFile) {
|
if (!acceptFile) {
|
||||||
alert(`Cannot upload file. Kindly check the allowed file types and maximum allowed size.`)
|
alert(`Cannot upload file. Kindly check the allowed file types and maximum allowed size.`)
|
||||||
}
|
}
|
||||||
@@ -165,12 +249,14 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDrop = async (e) => {
|
const handleDrop = async (e) => {
|
||||||
if (!isChatFlowAvailableForUploads) {
|
if (!isChatFlowAvailableForImageUploads && !isChatFlowAvailableForFileUploads) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setIsDragActive(false)
|
setIsDragActive(false)
|
||||||
let files = []
|
let files = []
|
||||||
|
let uploadedFiles = []
|
||||||
|
|
||||||
if (e.dataTransfer.files.length > 0) {
|
if (e.dataTransfer.files.length > 0) {
|
||||||
for (const file of e.dataTransfer.files) {
|
for (const file of e.dataTransfer.files) {
|
||||||
if (isFileAllowedForUpload(file) === false) {
|
if (isFileAllowedForUpload(file) === false) {
|
||||||
@@ -178,6 +264,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
}
|
}
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
const { name } = file
|
const { name } = file
|
||||||
|
uploadedFiles.push(file)
|
||||||
files.push(
|
files.push(
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
reader.onload = (evt) => {
|
reader.onload = (evt) => {
|
||||||
@@ -188,7 +275,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
let previewUrl
|
let previewUrl
|
||||||
if (file.type.startsWith('audio/')) {
|
if (file.type.startsWith('audio/')) {
|
||||||
previewUrl = audioUploadSVG
|
previewUrl = audioUploadSVG
|
||||||
} else if (file.type.startsWith('image/')) {
|
} else {
|
||||||
previewUrl = URL.createObjectURL(file)
|
previewUrl = URL.createObjectURL(file)
|
||||||
}
|
}
|
||||||
resolve({
|
resolve({
|
||||||
@@ -205,10 +292,12 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newFiles = await Promise.all(files)
|
const newFiles = await Promise.all(files)
|
||||||
|
setUploadedFiles(uploadedFiles)
|
||||||
setPreviews((prevPreviews) => [...prevPreviews, ...newFiles])
|
setPreviews((prevPreviews) => [...prevPreviews, ...newFiles])
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.dataTransfer.items) {
|
if (e.dataTransfer.items) {
|
||||||
|
//TODO set files
|
||||||
for (const item of e.dataTransfer.items) {
|
for (const item of e.dataTransfer.items) {
|
||||||
if (item.kind === 'string' && item.type.match('^text/uri-list')) {
|
if (item.kind === 'string' && item.type.match('^text/uri-list')) {
|
||||||
item.getAsString((s) => {
|
item.getAsString((s) => {
|
||||||
@@ -246,10 +335,12 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let files = []
|
let files = []
|
||||||
|
let uploadedFiles = []
|
||||||
for (const file of event.target.files) {
|
for (const file of event.target.files) {
|
||||||
if (isFileAllowedForUpload(file) === false) {
|
if (isFileAllowedForUpload(file) === false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
uploadedFiles.push(file)
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
const { name } = file
|
const { name } = file
|
||||||
files.push(
|
files.push(
|
||||||
@@ -273,6 +364,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newFiles = await Promise.all(files)
|
const newFiles = await Promise.all(files)
|
||||||
|
setUploadedFiles(uploadedFiles)
|
||||||
setPreviews((prevPreviews) => [...prevPreviews, ...newFiles])
|
setPreviews((prevPreviews) => [...prevPreviews, ...newFiles])
|
||||||
// 👇️ reset file input
|
// 👇️ reset file input
|
||||||
event.target.value = null
|
event.target.value = null
|
||||||
@@ -303,7 +395,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDrag = (e) => {
|
const handleDrag = (e) => {
|
||||||
if (isChatFlowAvailableForUploads) {
|
if (isChatFlowAvailableForImageUploads || isChatFlowAvailableForFileUploads) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
if (e.type === 'dragenter' || e.type === 'dragover') {
|
if (e.type === 'dragenter' || e.type === 'dragover') {
|
||||||
@@ -343,11 +435,16 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
setPreviews(previews.filter((item) => item !== itemToDelete))
|
setPreviews(previews.filter((item) => item !== itemToDelete))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUploadClick = () => {
|
const handleFileUploadClick = () => {
|
||||||
// 👇️ open file input box on click of another element
|
// 👇️ open file input box on click of another element
|
||||||
fileUploadRef.current.click()
|
fileUploadRef.current.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleImageUploadClick = () => {
|
||||||
|
// 👇️ open file input box on click of another element
|
||||||
|
imgUploadRef.current.click()
|
||||||
|
}
|
||||||
|
|
||||||
const clearPreviews = () => {
|
const clearPreviews = () => {
|
||||||
// Revoke the data uris to avoid memory leaks
|
// Revoke the data uris to avoid memory leaks
|
||||||
previews.forEach((file) => URL.revokeObjectURL(file.preview))
|
previews.forEach((file) => URL.revokeObjectURL(file.preview))
|
||||||
@@ -489,6 +586,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
setMessages((prevMessages) => [...prevMessages, { message, type: 'apiMessage' }])
|
setMessages((prevMessages) => [...prevMessages, { message, type: 'apiMessage' }])
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
setUserInput('')
|
setUserInput('')
|
||||||
|
setUploadedFiles([])
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
inputRef.current?.focus()
|
inputRef.current?.focus()
|
||||||
}, 100)
|
}, 100)
|
||||||
@@ -526,7 +624,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 urls = previews.map((item) => {
|
const uploads = previews.map((item) => {
|
||||||
return {
|
return {
|
||||||
data: item.data,
|
data: item.data,
|
||||||
type: item.type,
|
type: item.type,
|
||||||
@@ -535,7 +633,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
clearPreviews()
|
clearPreviews()
|
||||||
setMessages((prevMessages) => [...prevMessages, { message: input, type: 'userMessage', fileUploads: urls }])
|
setMessages((prevMessages) => [...prevMessages, { message: input, type: 'userMessage', fileUploads: uploads }])
|
||||||
|
|
||||||
// Send user question to Prediction Internal API
|
// Send user question to Prediction Internal API
|
||||||
try {
|
try {
|
||||||
@@ -543,11 +641,30 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
question: input,
|
question: input,
|
||||||
chatId
|
chatId
|
||||||
}
|
}
|
||||||
if (urls && urls.length > 0) params.uploads = urls
|
if (uploads && uploads.length > 0) params.uploads = uploads
|
||||||
if (leadEmail) params.leadEmail = leadEmail
|
if (leadEmail) params.leadEmail = leadEmail
|
||||||
if (isChatFlowAvailableToStream) params.socketIOClientId = socketIOClientId
|
if (isChatFlowAvailableToStream) params.socketIOClientId = socketIOClientId
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const response = await predictionApi.sendMessageAndGetPrediction(chatflowid, params)
|
const response = await predictionApi.sendMessageAndGetPrediction(chatflowid, params)
|
||||||
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
@@ -598,6 +715,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
setLocalStorageChatflow(chatflowid, data.chatId)
|
setLocalStorageChatflow(chatflowid, data.chatId)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
setUserInput('')
|
setUserInput('')
|
||||||
|
setUploadedFiles([])
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
inputRef.current?.focus()
|
inputRef.current?.focus()
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
@@ -717,8 +835,11 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
// Get chatflow uploads capability
|
// Get chatflow uploads capability
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getAllowChatFlowUploads.data) {
|
if (getAllowChatFlowUploads.data) {
|
||||||
setIsChatFlowAvailableForUploads(getAllowChatFlowUploads.data?.isImageUploadAllowed ?? false)
|
setIsChatFlowAvailableForImageUploads(getAllowChatFlowUploads.data?.isImageUploadAllowed ?? false)
|
||||||
|
setIsChatFlowAvailableForFileUploads(getAllowChatFlowUploads.data?.isFileUploadAllowed ?? false)
|
||||||
setIsChatFlowAvailableForSpeech(getAllowChatFlowUploads.data?.isSpeechToTextEnabled ?? false)
|
setIsChatFlowAvailableForSpeech(getAllowChatFlowUploads.data?.isSpeechToTextEnabled ?? false)
|
||||||
|
setImageUploadAllowedTypes(getAllowChatFlowUploads.data?.imgUploadSizeAndTypes.map((allowed) => allowed.fileTypes).join(','))
|
||||||
|
setFileUploadAllowedTypes(getAllowChatFlowUploads.data?.fileUploadSizeAndTypes.map((allowed) => allowed.fileTypes).join(','))
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [getAllowChatFlowUploads.data])
|
}, [getAllowChatFlowUploads.data])
|
||||||
@@ -822,6 +943,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setUserInput('')
|
setUserInput('')
|
||||||
|
setUploadedFiles([])
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
setMessages([
|
setMessages([
|
||||||
{
|
{
|
||||||
@@ -965,6 +1087,105 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const previewDisplay = (item) => {
|
||||||
|
if (item.mime.startsWith('image/')) {
|
||||||
|
return (
|
||||||
|
<ImageButton
|
||||||
|
focusRipple
|
||||||
|
style={{
|
||||||
|
width: '48px',
|
||||||
|
height: '48px',
|
||||||
|
marginRight: '10px',
|
||||||
|
flex: '0 0 auto'
|
||||||
|
}}
|
||||||
|
onClick={() => handleDeletePreview(item)}
|
||||||
|
>
|
||||||
|
<ImageSrc style={{ backgroundImage: `url(${item.data})` }} />
|
||||||
|
<ImageBackdrop className='MuiImageBackdrop-root' />
|
||||||
|
<ImageMarked className='MuiImageMarked-root'>
|
||||||
|
<IconTrash size={20} color='white' />
|
||||||
|
</ImageMarked>
|
||||||
|
</ImageButton>
|
||||||
|
)
|
||||||
|
} else if (item.mime.startsWith('audio/')) {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '48px',
|
||||||
|
width: isDialog ? ps?.current?.offsetWidth / 4 : ps?.current?.offsetWidth / 2,
|
||||||
|
p: 0.5,
|
||||||
|
mr: 1,
|
||||||
|
backgroundColor: theme.palette.grey[500],
|
||||||
|
flex: '0 0 auto'
|
||||||
|
}}
|
||||||
|
variant='outlined'
|
||||||
|
>
|
||||||
|
<CardMedia component='audio' sx={{ color: 'transparent' }} controls src={item.data} />
|
||||||
|
<IconButton onClick={() => handleDeletePreview(item)} size='small'>
|
||||||
|
<IconTrash size={20} color='white' />
|
||||||
|
</IconButton>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return <CardWithDeleteOverlay item={item} customization={customization} onDelete={() => handleDeletePreview(item)} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderFileUploads = (item, index) => {
|
||||||
|
if (item?.mime?.startsWith('image/')) {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
p: 0,
|
||||||
|
m: 0,
|
||||||
|
maxWidth: 128,
|
||||||
|
marginRight: '10px',
|
||||||
|
flex: '0 0 auto'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardMedia component='img' image={item.data} sx={{ height: 64 }} alt={'preview'} style={messageImageStyle} />
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
} else if (item?.mime?.startsWith('audio/')) {
|
||||||
|
return (
|
||||||
|
/* eslint-disable jsx-a11y/media-has-caption */
|
||||||
|
<audio controls='controls'>
|
||||||
|
Your browser does not support the <audio> tag.
|
||||||
|
<source src={item.data} type={item.mime} />
|
||||||
|
</audio>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '48px',
|
||||||
|
width: 'max-content',
|
||||||
|
p: 2,
|
||||||
|
mr: 1,
|
||||||
|
flex: '0 0 auto',
|
||||||
|
backgroundColor: customization.isDarkMode ? 'rgba(0, 0, 0, 0.3)' : 'transparent'
|
||||||
|
}}
|
||||||
|
variant='outlined'
|
||||||
|
>
|
||||||
|
<IconPaperclip size={20} />
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginLeft: '5px',
|
||||||
|
color: customization.isDarkMode ? 'white' : 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</span>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onDragEnter={handleDrag}>
|
<div onDragEnter={handleDrag}>
|
||||||
{isDragActive && (
|
{isDragActive && (
|
||||||
@@ -976,19 +1197,25 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isDragActive && getAllowChatFlowUploads.data?.isImageUploadAllowed && (
|
{isDragActive &&
|
||||||
<Box className='drop-overlay'>
|
(getAllowChatFlowUploads.data?.isImageUploadAllowed || getAllowChatFlowUploads.data?.isFileAllowedForUpload) && (
|
||||||
<Typography variant='h2'>Drop here to upload</Typography>
|
<Box className='drop-overlay'>
|
||||||
{getAllowChatFlowUploads.data.imgUploadSizeAndTypes.map((allowed) => {
|
<Typography variant='h2'>Drop here to upload</Typography>
|
||||||
return (
|
{[
|
||||||
<>
|
...getAllowChatFlowUploads.data.imgUploadSizeAndTypes,
|
||||||
<Typography variant='subtitle1'>{allowed.fileTypes?.join(', ')}</Typography>
|
...getAllowChatFlowUploads.data.fileUploadSizeAndTypes
|
||||||
<Typography variant='subtitle1'>Max Allowed Size: {allowed.maxUploadSize} MB</Typography>
|
].map((allowed) => {
|
||||||
</>
|
return (
|
||||||
)
|
<>
|
||||||
})}
|
<Typography variant='subtitle1'>{allowed.fileTypes?.join(', ')}</Typography>
|
||||||
</Box>
|
{allowed.maxUploadSize && (
|
||||||
)}
|
<Typography variant='subtitle1'>Max Allowed Size: {allowed.maxUploadSize} MB</Typography>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<div ref={ps} className={`${isDialog ? 'cloud-dialog' : 'cloud'}`}>
|
<div ref={ps} className={`${isDialog ? 'cloud-dialog' : 'cloud'}`}>
|
||||||
<div id='messagelist' className={'messagelist'}>
|
<div id='messagelist' className={'messagelist'}>
|
||||||
{messages &&
|
{messages &&
|
||||||
@@ -1038,36 +1265,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{message.fileUploads.map((item, index) => {
|
{message.fileUploads.map((item, index) => {
|
||||||
return (
|
return <>{renderFileUploads(item, index)}</>
|
||||||
<>
|
|
||||||
{item?.mime?.startsWith('image/') ? (
|
|
||||||
<Card
|
|
||||||
key={index}
|
|
||||||
sx={{
|
|
||||||
p: 0,
|
|
||||||
m: 0,
|
|
||||||
maxWidth: 128,
|
|
||||||
marginRight: '10px',
|
|
||||||
flex: '0 0 auto'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CardMedia
|
|
||||||
component='img'
|
|
||||||
image={item.data}
|
|
||||||
sx={{ height: 64 }}
|
|
||||||
alt={'preview'}
|
|
||||||
style={messageImageStyle}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
) : (
|
|
||||||
// eslint-disable-next-line jsx-a11y/media-has-caption
|
|
||||||
<audio controls='controls'>
|
|
||||||
Your browser does not support the <audio> tag.
|
|
||||||
<source src={item.data} type={item.mime} />
|
|
||||||
</audio>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1564,45 +1762,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
{previews && previews.length > 0 && (
|
{previews && previews.length > 0 && (
|
||||||
<Box sx={{ width: '100%', mb: 1.5, display: 'flex', alignItems: 'center' }}>
|
<Box sx={{ width: '100%', mb: 1.5, display: 'flex', alignItems: 'center' }}>
|
||||||
{previews.map((item, index) => (
|
{previews.map((item, index) => (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>{previewDisplay(item)}</Fragment>
|
||||||
{item.mime.startsWith('image/') ? (
|
|
||||||
<ImageButton
|
|
||||||
focusRipple
|
|
||||||
style={{
|
|
||||||
width: '48px',
|
|
||||||
height: '48px',
|
|
||||||
marginRight: '10px',
|
|
||||||
flex: '0 0 auto'
|
|
||||||
}}
|
|
||||||
onClick={() => handleDeletePreview(item)}
|
|
||||||
>
|
|
||||||
<ImageSrc style={{ backgroundImage: `url(${item.data})` }} />
|
|
||||||
<ImageBackdrop className='MuiImageBackdrop-root' />
|
|
||||||
<ImageMarked className='MuiImageMarked-root'>
|
|
||||||
<IconTrash size={20} color='white' />
|
|
||||||
</ImageMarked>
|
|
||||||
</ImageButton>
|
|
||||||
) : (
|
|
||||||
<Card
|
|
||||||
sx={{
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
height: '48px',
|
|
||||||
width: isDialog ? ps?.current?.offsetWidth / 4 : ps?.current?.offsetWidth / 2,
|
|
||||||
p: 0.5,
|
|
||||||
mr: 1,
|
|
||||||
backgroundColor: theme.palette.grey[500],
|
|
||||||
flex: '0 0 auto'
|
|
||||||
}}
|
|
||||||
variant='outlined'
|
|
||||||
>
|
|
||||||
<CardMedia component='audio' sx={{ color: 'transparent' }} controls src={item.data} />
|
|
||||||
<IconButton onClick={() => handleDeletePreview(item)} size='small'>
|
|
||||||
<IconTrash size={20} color='white' />
|
|
||||||
</IconButton>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@@ -1679,15 +1839,62 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
multiline={true}
|
multiline={true}
|
||||||
maxRows={isDialog ? 7 : 2}
|
maxRows={isDialog ? 7 : 2}
|
||||||
startAdornment={
|
startAdornment={
|
||||||
isChatFlowAvailableForUploads && (
|
<>
|
||||||
<InputAdornment position='start' sx={{ pl: 2 }}>
|
{isChatFlowAvailableForImageUploads && !isChatFlowAvailableForFileUploads && (
|
||||||
<IconButton onClick={handleUploadClick} type='button' disabled={getInputDisabled()} edge='start'>
|
<InputAdornment position='start' sx={{ ml: 2 }}>
|
||||||
<IconPhotoPlus
|
<IconButton
|
||||||
color={getInputDisabled() ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
|
onClick={handleImageUploadClick}
|
||||||
/>
|
type='button'
|
||||||
</IconButton>
|
disabled={getInputDisabled()}
|
||||||
</InputAdornment>
|
edge='start'
|
||||||
)
|
>
|
||||||
|
<IconPhotoPlus
|
||||||
|
color={getInputDisabled() ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)}
|
||||||
|
{!isChatFlowAvailableForImageUploads && isChatFlowAvailableForFileUploads && (
|
||||||
|
<InputAdornment position='start' sx={{ ml: 2 }}>
|
||||||
|
<IconButton
|
||||||
|
onClick={handleFileUploadClick}
|
||||||
|
type='button'
|
||||||
|
disabled={getInputDisabled()}
|
||||||
|
edge='start'
|
||||||
|
>
|
||||||
|
<IconPaperclip
|
||||||
|
color={getInputDisabled() ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)}
|
||||||
|
{isChatFlowAvailableForImageUploads && isChatFlowAvailableForFileUploads && (
|
||||||
|
<InputAdornment position='start' sx={{ ml: 2 }}>
|
||||||
|
<IconButton
|
||||||
|
onClick={handleImageUploadClick}
|
||||||
|
type='button'
|
||||||
|
disabled={getInputDisabled()}
|
||||||
|
edge='start'
|
||||||
|
>
|
||||||
|
<IconPhotoPlus
|
||||||
|
color={getInputDisabled() ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
sx={{ ml: 0 }}
|
||||||
|
onClick={handleFileUploadClick}
|
||||||
|
type='button'
|
||||||
|
disabled={getInputDisabled()}
|
||||||
|
edge='start'
|
||||||
|
>
|
||||||
|
<IconPaperclip
|
||||||
|
color={getInputDisabled() ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)}
|
||||||
|
{!isChatFlowAvailableForImageUploads && !isChatFlowAvailableForFileUploads && <Box sx={{ pl: 1 }} />}
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
endAdornment={
|
endAdornment={
|
||||||
<>
|
<>
|
||||||
@@ -1707,7 +1914,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
)}
|
)}
|
||||||
{!isAgentCanvas && (
|
{!isAgentCanvas && (
|
||||||
<InputAdornment position='end' sx={{ padding: '15px' }}>
|
<InputAdornment position='end' sx={{ paddingRight: '15px' }}>
|
||||||
<IconButton type='submit' disabled={getInputDisabled()} edge='end'>
|
<IconButton type='submit' disabled={getInputDisabled()} edge='end'>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div>
|
<div>
|
||||||
@@ -1727,7 +1934,7 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
{isAgentCanvas && (
|
{isAgentCanvas && (
|
||||||
<>
|
<>
|
||||||
{!loading && (
|
{!loading && (
|
||||||
<InputAdornment position='end' sx={{ padding: '15px' }}>
|
<InputAdornment position='end' sx={{ paddingRight: '15px' }}>
|
||||||
<IconButton type='submit' disabled={getInputDisabled()} edge='end'>
|
<IconButton type='submit' disabled={getInputDisabled()} edge='end'>
|
||||||
<IconSend
|
<IconSend
|
||||||
color={
|
color={
|
||||||
@@ -1765,8 +1972,25 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{isChatFlowAvailableForUploads && (
|
{isChatFlowAvailableForImageUploads && (
|
||||||
<input style={{ display: 'none' }} multiple ref={fileUploadRef} type='file' onChange={handleFileChange} />
|
<input
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
multiple
|
||||||
|
ref={imgUploadRef}
|
||||||
|
type='file'
|
||||||
|
onChange={handleFileChange}
|
||||||
|
accept={imageUploadAllowedTypes || '*'}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isChatFlowAvailableForFileUploads && (
|
||||||
|
<input
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
multiple
|
||||||
|
ref={fileUploadRef}
|
||||||
|
type='file'
|
||||||
|
onChange={handleFileChange}
|
||||||
|
accept={fileUploadAllowedTypes.includes('*') ? '*' : fileUploadAllowedTypes || '*'}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user