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

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

* add attachments swagger

* update question to include uploadedFilesContent

* make config dialog modal lg size
This commit is contained in:
Henry Heng
2024-10-23 11:00:46 +01:00
committed by GitHub
parent 116d02d0bc
commit 53e504c32f
31 changed files with 1012 additions and 193 deletions
@@ -121,11 +121,22 @@ class File_DocumentLoaders implements INode {
}
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() || '' })
// specific to createAttachment to get files from chatId
const retrieveAttachmentChatId = options.retrieveAttachmentChatId
if (retrieveAttachmentChatId) {
for (const file of files) {
if (!file) continue
const fileData = await getFileFromStorage(file, chatflowid, options.chatId)
const blob = new Blob([fileData])
fileBlobs.push({ blob, ext: file.split('.').pop() || '' })
}
} else {
for (const file of files) {
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(']')) {
@@ -288,7 +299,12 @@ class MultiFileLoader extends BaseDocumentLoader {
const loader = loaderFactory(fileBlob.blob)
documents.push(...(await loader.load()))
} else {
throw new Error(`Error loading file`)
const loader = new TextLoader(fileBlob.blob)
try {
documents.push(...(await loader.load()))
} catch (error) {
throw new Error(`Error loading file`)
}
}
}
@@ -68,9 +68,9 @@ const howToUseCode = `
"sourceDocuments": [
{
"pageContent": "This is the page content",
"metadata": "{foo: var}",
"metadata": "{foo: var}"
}
],
]
}
\`\`\`
@@ -102,10 +102,10 @@ const howToUse = `
|-----------|-----------|
| user | john doe |
2. If you want to use the agent's output as the value to update state, it is available as available as \`$flow.output\` with the following structure:
2. If you want to use the Agent's output as the value to update state, it is available as available as \`$flow.output\` with the following structure:
\`\`\`json
{
"output": "Hello! How can I assist you today?",
"content": "Hello! How can I assist you today?",
"usedTools": [
{
"tool": "tool-name",
@@ -116,9 +116,9 @@ const howToUse = `
"sourceDocuments": [
{
"pageContent": "This is the page content",
"metadata": "{foo: var}",
"metadata": "{foo: var}"
}
],
]
}
\`\`\`
@@ -195,7 +195,7 @@ class Agent_SeqAgents implements INode {
constructor() {
this.label = 'Agent'
this.name = 'seqAgent'
this.version = 3.0
this.version = 3.1
this.type = 'Agent'
this.icon = 'seqAgent.png'
this.category = 'Sequential Agents'
@@ -88,7 +88,7 @@ const howToUse = `
|-----------|-----------|
| user | john doe |
2. If you want to use the agent's output as the value to update state, it is available as available as \`$flow.output\` with the following structure:
2. If you want to use the LLM Node's output as the value to update state, it is available as available as \`$flow.output\` with the following structure:
\`\`\`json
{
"content": 'Hello! How can I assist you today?',
@@ -48,9 +48,9 @@ const howToUseCode = `
"sourceDocuments": [
{
"pageContent": "This is the page content",
"metadata": "{foo: var}",
"metadata": "{foo: var}"
}
],
]
}
]
\`\`\`
@@ -64,7 +64,7 @@ const howToUseCode = `
*/
return {
"sources": $flow.output[0].sourceDocuments
"sources": $flow.output[0].toolOutput
}
\`\`\`
@@ -89,17 +89,19 @@ const howToUse = `
|-----------|-----------|
| user | john doe |
2. If you want to use the agent's output as the value to update state, it is available as available as \`$flow.output\` with the following structure (array):
2. If you want to use the Tool Node's output as the value to update state, it is available as available as \`$flow.output\` with the following structure (array):
\`\`\`json
[
{
"content": "Hello! How can I assist you today?",
"tool": "tool's name",
"toolInput": {},
"toolOutput": "tool's output content",
"sourceDocuments": [
{
"pageContent": "This is the page content",
"metadata": "{foo: var}",
"metadata": "{foo: var}"
}
],
]
}
]
\`\`\`
@@ -107,7 +109,7 @@ const howToUse = `
For example:
| Key | Value |
|--------------|-------------------------------------------|
| sources | \`$flow.output[0].sourceDocuments\` |
| sources | \`$flow.output[0].toolOutput\` |
3. You can get default flow config, including the current "state":
- \`$flow.sessionId\`
@@ -152,7 +154,7 @@ class ToolNode_SeqAgents implements INode {
constructor() {
this.label = 'Tool Node'
this.name = 'seqToolNode'
this.version = 2.0
this.version = 2.1
this.type = 'ToolNode'
this.icon = 'toolNode.svg'
this.category = 'Sequential Agents'
+22 -3
View File
@@ -5,7 +5,7 @@ import * as path from 'path'
import { JSDOM } from 'jsdom'
import { z } from 'zod'
import { DataSource } from 'typeorm'
import { ICommonObject, IDatabaseEntity, IMessage, INodeData, IVariable, MessageContentImageUrl } from './Interface'
import { ICommonObject, IDatabaseEntity, IDocument, IMessage, INodeData, IVariable, MessageContentImageUrl } from './Interface'
import { AES, enc } from 'crypto-js'
import { AIMessage, HumanMessage, BaseMessage } from '@langchain/core/messages'
import { getFileFromStorage } from './storageUtils'
@@ -609,10 +609,11 @@ export const mapChatMessageToBaseMessage = async (chatmessages: any[] = []): Pro
if (message.role === 'apiMessage' || message.type === 'apiMessage') {
chatHistory.push(new AIMessage(message.content || ''))
} else if (message.role === 'userMessage' || message.role === 'userMessage') {
// check for image uploads
// check for image/files uploads
if (message.fileUploads) {
// example: [{"type":"stored-file","name":"0_DiXc4ZklSTo3M8J4.jpg","mime":"image/jpeg"}]
try {
let messageWithFileUploads = ''
const uploads = JSON.parse(message.fileUploads)
const imageContents: MessageContentImageUrl[] = []
for (const upload of uploads) {
@@ -634,14 +635,32 @@ export const mapChatMessageToBaseMessage = async (chatmessages: any[] = []): Pro
url: upload.data
}
})
} else if (upload.type === 'stored-file:full') {
const fileLoaderNodeModule = await import('../nodes/documentloaders/File/File')
// @ts-ignore
const fileLoaderNodeInstance = new fileLoaderNodeModule.nodeClass()
const options = {
retrieveAttachmentChatId: true,
chatflowid: message.chatflowid,
chatId: message.chatId
}
const nodeData = {
inputs: {
txtFile: `FILE-STORAGE::${JSON.stringify([upload.name])}`
}
}
const documents: IDocument[] = await fileLoaderNodeInstance.init(nodeData, '', options)
const pageContents = documents.map((doc) => doc.pageContent).join('\n')
messageWithFileUploads += `<doc name='${upload.name}'>${pageContents}</doc>\n\n`
}
}
const messageContent = messageWithFileUploads ? `${messageWithFileUploads}\n\n${message.content}` : message.content
chatHistory.push(
new HumanMessage({
content: [
{
type: 'text',
text: message.content
text: messageContent
},
...imageContents
]