Skip to content

Commit 7a172fe

Browse files
authored
Merge branch 'FlowiseAI:main' into main
2 parents 55aa199 + b8f7a20 commit 7a172fe

19 files changed

Lines changed: 133 additions & 594 deletions

File tree

Dockerfile

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,41 @@
55
# docker run -d -p 3000:3000 flowise
66

77
FROM node:20-alpine
8-
RUN apk add --update libc6-compat python3 make g++
9-
# needed for pdfjs-dist
10-
RUN apk add --no-cache build-base cairo-dev pango-dev
118

12-
# Install Chromium
13-
RUN apk add --no-cache chromium
14-
15-
# Install curl for container-level health checks
16-
# Fixes: https://github.com/FlowiseAI/Flowise/issues/4126
17-
RUN apk add --no-cache curl
18-
19-
#install PNPM globaly
20-
RUN npm install -g pnpm
9+
# Install system dependencies and build tools
10+
RUN apk update && \
11+
apk add --no-cache \
12+
libc6-compat \
13+
python3 \
14+
make \
15+
g++ \
16+
build-base \
17+
cairo-dev \
18+
pango-dev \
19+
chromium \
20+
curl && \
21+
npm install -g pnpm
2122

2223
ENV PUPPETEER_SKIP_DOWNLOAD=true
2324
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
2425

2526
ENV NODE_OPTIONS=--max-old-space-size=8192
2627

27-
WORKDIR /usr/src
28+
WORKDIR /usr/src/flowise
2829

2930
# Copy app source
3031
COPY . .
3132

32-
RUN pnpm install
33+
# Install dependencies and build
34+
RUN pnpm install && \
35+
pnpm build
36+
37+
# Give the node user ownership of the application files
38+
RUN chown -R node:node .
3339

34-
RUN pnpm build
40+
# Switch to non-root user (node user already exists in node:20-alpine)
41+
USER node
3542

3643
EXPOSE 3000
3744

38-
CMD [ "pnpm", "start" ]
45+
CMD [ "pnpm", "start" ]

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "flowise",
3-
"version": "10",
3+
"version": "3.0.11",
44
"private": true,
55
"homepage": "https://flowiseai.com",
66
"workspaces": [

packages/components/nodes/documentloaders/Json/Json.ts

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class Json_DocumentLoaders implements INode {
4747
constructor() {
4848
this.label = 'Json File'
4949
this.name = 'jsonFile'
50-
this.version = 3.0
50+
this.version = 3.1
5151
this.type = 'Document'
5252
this.icon = 'json.svg'
5353
this.category = 'Document Loaders'
@@ -66,14 +66,25 @@ class Json_DocumentLoaders implements INode {
6666
type: 'TextSplitter',
6767
optional: true
6868
},
69+
{
70+
label: 'Separate by JSON Object (JSON Array)',
71+
name: 'separateByObject',
72+
type: 'boolean',
73+
description: 'If enabled and the file is a JSON Array, each JSON object will be extracted as a chunk',
74+
optional: true,
75+
additionalParams: true
76+
},
6977
{
7078
label: 'Pointers Extraction (separated by commas)',
7179
name: 'pointersName',
7280
type: 'string',
7381
description:
7482
'Ex: { "key": "value" }, Pointer Extraction = "key", "value" will be extracted as pageContent of the chunk. Use comma to separate multiple pointers',
7583
placeholder: 'key1, key2',
76-
optional: true
84+
optional: true,
85+
hide: {
86+
separateByObject: true
87+
}
7788
},
7889
{
7990
label: 'Additional Metadata',
@@ -122,6 +133,7 @@ class Json_DocumentLoaders implements INode {
122133
const pointersName = nodeData.inputs?.pointersName as string
123134
const metadata = nodeData.inputs?.metadata
124135
const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string
136+
const separateByObject = nodeData.inputs?.separateByObject as boolean
125137
const output = nodeData.outputs?.output as string
126138

127139
let omitMetadataKeys: string[] = []
@@ -153,7 +165,7 @@ class Json_DocumentLoaders implements INode {
153165
if (!file) continue
154166
const fileData = await getFileFromStorage(file, orgId, chatflowid)
155167
const blob = new Blob([fileData])
156-
const loader = new JSONLoader(blob, pointers.length != 0 ? pointers : undefined, metadata)
168+
const loader = new JSONLoader(blob, pointers.length != 0 ? pointers : undefined, metadata, separateByObject)
157169

158170
if (textSplitter) {
159171
let splittedDocs = await loader.load()
@@ -176,7 +188,7 @@ class Json_DocumentLoaders implements INode {
176188
splitDataURI.pop()
177189
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
178190
const blob = new Blob([bf])
179-
const loader = new JSONLoader(blob, pointers.length != 0 ? pointers : undefined, metadata)
191+
const loader = new JSONLoader(blob, pointers.length != 0 ? pointers : undefined, metadata, separateByObject)
180192

181193
if (textSplitter) {
182194
let splittedDocs = await loader.load()
@@ -306,13 +318,20 @@ class TextLoader extends BaseDocumentLoader {
306318
class JSONLoader extends TextLoader {
307319
public pointers: string[]
308320
private metadataMapping: Record<string, string>
309-
310-
constructor(filePathOrBlob: string | Blob, pointers: string | string[] = [], metadataMapping: Record<string, string> = {}) {
321+
private separateByObject: boolean
322+
323+
constructor(
324+
filePathOrBlob: string | Blob,
325+
pointers: string | string[] = [],
326+
metadataMapping: Record<string, string> = {},
327+
separateByObject: boolean = false
328+
) {
311329
super(filePathOrBlob)
312330
this.pointers = Array.isArray(pointers) ? pointers : [pointers]
313331
if (metadataMapping) {
314332
this.metadataMapping = typeof metadataMapping === 'object' ? metadataMapping : JSON.parse(metadataMapping)
315333
}
334+
this.separateByObject = separateByObject
316335
}
317336

318337
protected async parse(raw: string): Promise<Document[]> {
@@ -323,14 +342,24 @@ class JSONLoader extends TextLoader {
323342
const jsonArray = Array.isArray(json) ? json : [json]
324343

325344
for (const item of jsonArray) {
326-
const content = this.extractContent(item)
327-
const metadata = this.extractMetadata(item)
328-
329-
for (const pageContent of content) {
330-
documents.push({
331-
pageContent,
332-
metadata
333-
})
345+
if (this.separateByObject) {
346+
if (typeof item === 'object' && item !== null && !Array.isArray(item)) {
347+
const metadata = this.extractMetadata(item)
348+
const pageContent = this.formatObjectAsKeyValue(item)
349+
documents.push({
350+
pageContent,
351+
metadata
352+
})
353+
}
354+
} else {
355+
const content = this.extractContent(item)
356+
const metadata = this.extractMetadata(item)
357+
for (const pageContent of content) {
358+
documents.push({
359+
pageContent,
360+
metadata
361+
})
362+
}
334363
}
335364
}
336365

@@ -370,6 +399,30 @@ class JSONLoader extends TextLoader {
370399
return metadata
371400
}
372401

402+
/**
403+
* Formats a JSON object as readable key-value pairs
404+
*/
405+
private formatObjectAsKeyValue(obj: any, prefix: string = ''): string {
406+
const lines: string[] = []
407+
408+
for (const [key, value] of Object.entries(obj)) {
409+
const fullKey = prefix ? `${prefix}.${key}` : key
410+
411+
if (value === null || value === undefined) {
412+
lines.push(`${fullKey}: ${value}`)
413+
} else if (Array.isArray(value)) {
414+
lines.push(`${fullKey}: ${JSON.stringify(value)}`)
415+
} else if (typeof value === 'object') {
416+
// Recursively format nested objects
417+
lines.push(this.formatObjectAsKeyValue(value, fullKey))
418+
} else {
419+
lines.push(`${fullKey}: ${value}`)
420+
}
421+
}
422+
423+
return lines.join('\n')
424+
}
425+
373426
/**
374427
* If JSON pointers are specified, return all strings below any of them
375428
* and exclude all other nodes expect if they match a JSON pointer.

packages/components/nodes/documentloaders/Playwright/Playwright.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,14 @@ class Playwright_DocumentLoaders implements INode {
190190
async function playwrightLoader(url: string): Promise<Document[] | undefined> {
191191
try {
192192
let docs = []
193+
194+
const executablePath = process.env.PLAYWRIGHT_EXECUTABLE_PATH
195+
193196
const config: PlaywrightWebBaseLoaderOptions = {
194197
launchOptions: {
195198
args: ['--no-sandbox'],
196199
headless: true,
197-
executablePath: process.env.PLAYWRIGHT_EXECUTABLE_FILE_PATH
200+
executablePath: executablePath
198201
}
199202
}
200203
if (waitUntilGoToOption) {

packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,14 @@ class Puppeteer_DocumentLoaders implements INode {
181181
async function puppeteerLoader(url: string): Promise<Document[] | undefined> {
182182
try {
183183
let docs: Document[] = []
184+
185+
const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH
186+
184187
const config: PuppeteerWebBaseLoaderOptions = {
185188
launchOptions: {
186189
args: ['--no-sandbox'],
187190
headless: 'new',
188-
executablePath: process.env.PUPPETEER_EXECUTABLE_FILE_PATH
191+
executablePath: executablePath
189192
}
190193
}
191194
if (waitUntilGoToOption) {

packages/components/nodes/documentloaders/Unstructured/Unstructured.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ type Element = {
2727
}
2828

2929
export class UnstructuredLoader extends BaseDocumentLoader {
30-
public filePath: string
31-
3230
private apiUrl = process.env.UNSTRUCTURED_API_URL || 'https://api.unstructuredapp.io/general/v0/general'
3331

3432
private apiKey: string | undefined = process.env.UNSTRUCTURED_API_KEY
@@ -138,7 +136,7 @@ export class UnstructuredLoader extends BaseDocumentLoader {
138136
})
139137

140138
if (!response.ok) {
141-
throw new Error(`Failed to partition file ${this.filePath} with error ${response.status} and message ${await response.text()}`)
139+
throw new Error(`Failed to partition file with error ${response.status} and message ${await response.text()}`)
142140
}
143141

144142
const elements = await response.json()

packages/components/nodes/documentloaders/Unstructured/UnstructuredFile.ts

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,11 @@ import {
44
UnstructuredLoaderOptions,
55
UnstructuredLoaderStrategy,
66
SkipInferTableTypes,
7-
HiResModelName,
8-
UnstructuredLoader as LCUnstructuredLoader
7+
HiResModelName
98
} from '@langchain/community/document_loaders/fs/unstructured'
109
import { getCredentialData, getCredentialParam, handleEscapeCharacters } from '../../../src/utils'
1110
import { getFileFromStorage, INodeOutputsValue } from '../../../src'
1211
import { UnstructuredLoader } from './Unstructured'
13-
import { isPathTraversal } from '../../../src/validator'
14-
import sanitize from 'sanitize-filename'
15-
import path from 'path'
1612

1713
class UnstructuredFile_DocumentLoaders implements INode {
1814
label: string
@@ -44,17 +40,6 @@ class UnstructuredFile_DocumentLoaders implements INode {
4440
optional: true
4541
}
4642
this.inputs = [
47-
/** Deprecated
48-
{
49-
label: 'File Path',
50-
name: 'filePath',
51-
type: 'string',
52-
placeholder: '',
53-
optional: true,
54-
warning:
55-
'Use the File Upload instead of File path. If file is uploaded, this path is ignored. Path will be deprecated in future releases.'
56-
},
57-
*/
5843
{
5944
label: 'Files Upload',
6045
name: 'fileObject',
@@ -455,7 +440,6 @@ class UnstructuredFile_DocumentLoaders implements INode {
455440
}
456441

457442
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
458-
const filePath = nodeData.inputs?.filePath as string
459443
const unstructuredAPIUrl = nodeData.inputs?.unstructuredAPIUrl as string
460444
const strategy = nodeData.inputs?.strategy as UnstructuredLoaderStrategy
461445
const encoding = nodeData.inputs?.encoding as string
@@ -560,37 +544,8 @@ class UnstructuredFile_DocumentLoaders implements INode {
560544
docs.push(...loaderDocs)
561545
}
562546
}
563-
} else if (filePath) {
564-
if (!filePath || typeof filePath !== 'string') {
565-
throw new Error('Invalid file path format')
566-
}
567-
568-
if (isPathTraversal(filePath)) {
569-
throw new Error('Invalid path characters detected in filePath - path traversal not allowed')
570-
}
571-
572-
const parsedPath = path.parse(filePath)
573-
const sanitizedFilename = sanitize(parsedPath.base)
574-
575-
if (!sanitizedFilename || sanitizedFilename.trim() === '') {
576-
throw new Error('Invalid filename after sanitization')
577-
}
578-
579-
const sanitizedFilePath = path.join(parsedPath.dir, sanitizedFilename)
580-
581-
if (!path.isAbsolute(sanitizedFilePath)) {
582-
throw new Error('File path must be absolute')
583-
}
584-
585-
if (sanitizedFilePath.includes('..')) {
586-
throw new Error('Invalid file path - directory traversal not allowed')
587-
}
588-
589-
const loader = new LCUnstructuredLoader(sanitizedFilePath, obj)
590-
const loaderDocs = await loader.load()
591-
docs.push(...loaderDocs)
592547
} else {
593-
throw new Error('File path or File upload is required')
548+
throw new Error('File upload is required')
594549
}
595550

596551
if (metadata) {

packages/components/nodes/documentloaders/Unstructured/UnstructuredFolder.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/*
2+
* Uncomment this if you want to use the UnstructuredFolder to load a folder from the file system
3+
14
import { omit } from 'lodash'
25
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
36
import {
@@ -516,3 +519,4 @@ class UnstructuredFolder_DocumentLoaders implements INode {
516519
}
517520
518521
module.exports = { nodeClass: UnstructuredFolder_DocumentLoaders }
522+
*/

0 commit comments

Comments
 (0)