Skip to content

Commit

Permalink
Feature/Full File Uploads & Message Delete API (FlowiseAI#3314)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
HenryHengZJ authored Oct 23, 2024
1 parent 116d02d commit 53e504c
Show file tree
Hide file tree
Showing 31 changed files with 1,011 additions and 192 deletions.
75 changes: 74 additions & 1 deletion packages/api-documentation/src/yml/swagger.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
tags:
- name: assistants
- name: attachments
- name: chatmessage
- name: chatflows
- name: document-store
Expand Down Expand Up @@ -270,6 +271,61 @@ paths:
'500':
description: Internal error

/attachments/{chatflowId}/{chatId}:
post:
tags:
- attachments
security:
- bearerAuth: []
operationId: createAttachment
summary: Create attachments array
description: Return contents of the files in plain string format
parameters:
- in: path
name: chatflowId
required: true
schema:
type: string
description: Chatflow ID
- in: path
name: chatId
required: true
schema:
type: string
description: Chat ID
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
files:
type: array
items:
type: string
format: binary
description: Files to be uploaded
required:
- files
required: true
responses:
'200':
description: Attachments created successfully
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/CreateAttachmentResponse'
'400':
description: Invalid input provided
'404':
description: Chatflow or ChatId not found
'422':
description: Validation error
'500':
description: Internal server error

/chatflows:
post:
tags:
Expand Down Expand Up @@ -1825,7 +1881,8 @@ components:
properties:
type:
type: string
description: The type of file upload (e.g., 'file', 'audio', 'url')
enum: [audio, url, file, file:rag, file:full]
description: The type of file upload
example: file
name:
type: string
Expand Down Expand Up @@ -2193,6 +2250,22 @@ components:
format: date-time
description: Date and time when the feedback was created

CreateAttachmentResponse:
type: object
properties:
name:
type: string
description: Name of the file
mimeType:
type: string
description: Mime type of the file
size:
type: string
description: Size of the file
content:
type: string
description: Content of the file in string format

securitySchemes:
bearerAuth:
type: http
Expand Down
28 changes: 22 additions & 6 deletions packages/components/nodes/documentloaders/File/File.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(']')) {
Expand Down Expand Up @@ -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`)
}
}
}

Expand Down
14 changes: 7 additions & 7 deletions packages/components/nodes/sequentialagents/Agent/Agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ const howToUseCode = `
"sourceDocuments": [
{
"pageContent": "This is the page content",
"metadata": "{foo: var}",
"metadata": "{foo: var}"
}
],
]
}
\`\`\`
Expand Down Expand Up @@ -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",
Expand All @@ -116,9 +116,9 @@ const howToUse = `
"sourceDocuments": [
{
"pageContent": "This is the page content",
"metadata": "{foo: var}",
"metadata": "{foo: var}"
}
],
]
}
\`\`\`
Expand Down Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?',
Expand Down
20 changes: 11 additions & 9 deletions packages/components/nodes/sequentialagents/ToolNode/ToolNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ const howToUseCode = `
"sourceDocuments": [
{
"pageContent": "This is the page content",
"metadata": "{foo: var}",
"metadata": "{foo: var}"
}
],
]
}
]
\`\`\`
Expand All @@ -64,7 +64,7 @@ const howToUseCode = `
*/
return {
"sources": $flow.output[0].sourceDocuments
"sources": $flow.output[0].toolOutput
}
\`\`\`
Expand All @@ -89,25 +89,27 @@ 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}"
}
],
]
}
]
\`\`\`
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\`
Expand Down Expand Up @@ -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'
Expand Down
25 changes: 22 additions & 3 deletions packages/components/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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) {
Expand All @@ -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
]
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type MessageType = 'apiMessage' | 'userMessage'

export type ChatflowType = 'CHATFLOW' | 'MULTIAGENT'

export enum chatType {
export enum ChatType {
INTERNAL = 'INTERNAL',
EXTERNAL = 'EXTERNAL'
}
Expand Down
15 changes: 15 additions & 0 deletions packages/server/src/controllers/attachments/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Request, Response, NextFunction } from 'express'
import attachmentsService from '../../services/attachments'

const createAttachment = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await attachmentsService.createAttachment(req)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}

export default {
createAttachment
}
Loading

0 comments on commit 53e504c

Please sign in to comment.