mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-29 07:01:04 +03:00
Feature/Add teams, gmail, outlook tools (#4577)
* add teams, gmail, outlook tools * update docs link * update credentials for oauth2 * add jira tool * add google drive, google calendar, google sheets tools, powerpoint, excel, word doc loader * update jira logo * Refactor Gmail and Outlook tools to remove maxOutputLength parameter and enhance request handling. Update response formatting to include parameters in the output. Adjust Google Drive tools to simplify success messages by removing unnecessary parameter details.
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import { TextSplitter } from 'langchain/text_splitter'
|
||||
import { PowerpointLoader } from './PowerpointLoader'
|
||||
import { getFileFromStorage, handleDocumentLoaderDocuments, handleDocumentLoaderMetadata, handleDocumentLoaderOutput } from '../../../src'
|
||||
import { ICommonObject, IDocument, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||
|
||||
class MicrosoftPowerpoint_DocumentLoaders implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
outputs: INodeOutputsValue[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Microsoft PowerPoint'
|
||||
this.name = 'microsoftPowerpoint'
|
||||
this.version = 1.0
|
||||
this.type = 'Document'
|
||||
this.icon = 'powerpoint.svg'
|
||||
this.category = 'Document Loaders'
|
||||
this.description = `Load data from Microsoft PowerPoint files`
|
||||
this.baseClasses = [this.type]
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'PowerPoint File',
|
||||
name: 'powerpointFile',
|
||||
type: 'file',
|
||||
fileType: '.pptx, .ppt'
|
||||
},
|
||||
{
|
||||
label: 'Text Splitter',
|
||||
name: 'textSplitter',
|
||||
type: 'TextSplitter',
|
||||
optional: 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
|
||||
}
|
||||
]
|
||||
this.outputs = [
|
||||
{
|
||||
label: 'Document',
|
||||
name: 'document',
|
||||
description: 'Array of document objects containing metadata and pageContent',
|
||||
baseClasses: [...this.baseClasses, 'json']
|
||||
},
|
||||
{
|
||||
label: 'Text',
|
||||
name: 'text',
|
||||
description: 'Concatenated string from pageContent of documents',
|
||||
baseClasses: ['string', 'json']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
getFiles(nodeData: INodeData) {
|
||||
const powerpointFileBase64 = nodeData.inputs?.powerpointFile as string
|
||||
|
||||
let files: string[] = []
|
||||
let fromStorage: boolean = true
|
||||
|
||||
if (powerpointFileBase64.startsWith('FILE-STORAGE::')) {
|
||||
const fileName = powerpointFileBase64.replace('FILE-STORAGE::', '')
|
||||
if (fileName.startsWith('[') && fileName.endsWith(']')) {
|
||||
files = JSON.parse(fileName)
|
||||
} else {
|
||||
files = [fileName]
|
||||
}
|
||||
} else {
|
||||
if (powerpointFileBase64.startsWith('[') && powerpointFileBase64.endsWith(']')) {
|
||||
files = JSON.parse(powerpointFileBase64)
|
||||
} else {
|
||||
files = [powerpointFileBase64]
|
||||
}
|
||||
|
||||
fromStorage = false
|
||||
}
|
||||
|
||||
return { files, fromStorage }
|
||||
}
|
||||
|
||||
async getFileData(file: string, { orgId, chatflowid }: { orgId: string; chatflowid: string }, fromStorage?: boolean) {
|
||||
if (fromStorage) {
|
||||
return getFileFromStorage(file, orgId, chatflowid)
|
||||
} else {
|
||||
const splitDataURI = file.split(',')
|
||||
splitDataURI.pop()
|
||||
return Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||
}
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
|
||||
const metadata = nodeData.inputs?.metadata
|
||||
const output = nodeData.outputs?.output as string
|
||||
const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string
|
||||
|
||||
let docs: IDocument[] = []
|
||||
|
||||
const orgId = options.orgId
|
||||
const chatflowid = options.chatflowid
|
||||
|
||||
const { files, fromStorage } = this.getFiles(nodeData)
|
||||
|
||||
for (const file of files) {
|
||||
if (!file) continue
|
||||
|
||||
const fileData = await this.getFileData(file, { orgId, chatflowid }, fromStorage)
|
||||
const blob = new Blob([fileData])
|
||||
const loader = new PowerpointLoader(blob)
|
||||
|
||||
// use spread instead of push, because it raises RangeError: Maximum call stack size exceeded when too many docs
|
||||
docs = [...docs, ...(await handleDocumentLoaderDocuments(loader, textSplitter))]
|
||||
}
|
||||
|
||||
docs = handleDocumentLoaderMetadata(docs, _omitMetadataKeys, metadata)
|
||||
|
||||
return handleDocumentLoaderOutput(docs, output)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: MicrosoftPowerpoint_DocumentLoaders }
|
||||
@@ -0,0 +1,101 @@
|
||||
import { Document } from '@langchain/core/documents'
|
||||
import { BufferLoader } from 'langchain/document_loaders/fs/buffer'
|
||||
import { parseOfficeAsync } from 'officeparser'
|
||||
|
||||
/**
|
||||
* Document loader that uses officeparser to load PowerPoint documents.
|
||||
*
|
||||
* Each slide is parsed into a separate Document with metadata including
|
||||
* slide number and extracted text content.
|
||||
*/
|
||||
export class PowerpointLoader extends BufferLoader {
|
||||
attributes: { name: string; description: string; type: string }[] = []
|
||||
|
||||
constructor(filePathOrBlob: string | Blob) {
|
||||
super(filePathOrBlob)
|
||||
this.attributes = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse PowerPoint document
|
||||
*
|
||||
* @param raw Raw data Buffer
|
||||
* @param metadata Document metadata
|
||||
* @returns Array of Documents
|
||||
*/
|
||||
async parse(raw: Buffer, metadata: Document['metadata']): Promise<Document[]> {
|
||||
const result: Document[] = []
|
||||
|
||||
this.attributes = [
|
||||
{ name: 'slideNumber', description: 'Slide number', type: 'number' },
|
||||
{ name: 'documentType', description: 'Type of document', type: 'string' }
|
||||
]
|
||||
|
||||
try {
|
||||
// Use officeparser to extract text from PowerPoint
|
||||
const data = await parseOfficeAsync(raw)
|
||||
|
||||
if (typeof data === 'string' && data.trim()) {
|
||||
// Split content by common slide separators or use the entire content as one document
|
||||
const slides = this.splitIntoSlides(data)
|
||||
|
||||
slides.forEach((slideContent, index) => {
|
||||
if (slideContent.trim()) {
|
||||
result.push({
|
||||
pageContent: slideContent.trim(),
|
||||
metadata: {
|
||||
slideNumber: index + 1,
|
||||
documentType: 'powerpoint',
|
||||
...metadata
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing PowerPoint file:', error)
|
||||
throw new Error(`Failed to parse PowerPoint file: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Split content into slides based on common patterns
|
||||
* This is a heuristic approach since officeparser returns plain text
|
||||
*/
|
||||
private splitIntoSlides(content: string): string[] {
|
||||
// Try to split by common slide patterns
|
||||
const slidePatterns = [
|
||||
/\n\s*Slide\s+\d+/gi,
|
||||
/\n\s*Page\s+\d+/gi,
|
||||
/\n\s*\d+\s*\/\s*\d+/gi,
|
||||
/\n\s*_{3,}/g, // Underscores as separators
|
||||
/\n\s*-{3,}/g // Dashes as separators
|
||||
]
|
||||
|
||||
let slides: string[] = []
|
||||
|
||||
// Try each pattern and use the one that creates the most reasonable splits
|
||||
for (const pattern of slidePatterns) {
|
||||
const potentialSlides = content.split(pattern)
|
||||
if (potentialSlides.length > 1 && potentialSlides.length < 100) {
|
||||
// Reasonable number of slides
|
||||
slides = potentialSlides
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If no good pattern found, split by double newlines as a fallback
|
||||
if (slides.length === 0) {
|
||||
slides = content.split(/\n\s*\n\s*\n/)
|
||||
}
|
||||
|
||||
// If still no good split, treat entire content as one slide
|
||||
if (slides.length === 0 || slides.every((slide) => slide.trim().length < 10)) {
|
||||
slides = [content]
|
||||
}
|
||||
|
||||
return slides.filter((slide) => slide.trim().length > 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#d35230" d="M8,24c0,9.941,8.059,18,18,18s18-8.059,18-18H26H8z"/><path fill="#ff8f6b" d="M26,6v18h18C44,14.059,35.941,6,26,6z"/><path fill="#ed6c47" d="M26,6C16.059,6,8,14.059,8,24h18V6z"/><path d="M26,16.681C26,14.648,24.352,13,22.319,13H11.774C9.417,16.044,8,19.852,8,24 c0,5.116,2.145,9.723,5.571,13h8.747C24.352,37,26,35.352,26,33.319V16.681z" opacity=".05"/><path d="M22.213,13.333H11.525C9.32,16.321,8,20.002,8,24c0,4.617,1.753,8.814,4.611,12h9.602 c1.724,0,3.121-1.397,3.121-3.121V16.454C25.333,14.731,23.936,13.333,22.213,13.333z" opacity=".07"/><path d="M22.106,13.667H11.276C9.218,16.593,8,20.151,8,24c0,4.148,1.417,7.956,3.774,11h10.332 c1.414,0,2.56-1.146,2.56-2.56V16.227C24.667,14.813,23.52,13.667,22.106,13.667z" opacity=".09"/><linearGradient id="N~uyq1CljjkKMh72IFt0Fa" x1="4.586" x2="22.77" y1="14.586" y2="32.77" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ca4e2a"/><stop offset="1" stop-color="#b63016"/></linearGradient><path fill="url(#N~uyq1CljjkKMh72IFt0Fa)" d="M22,34H6c-1.105,0-2-0.895-2-2V16c0-1.105,0.895-2,2-2h16c1.105,0,2,0.895,2,2v16 C24,33.105,23.105,34,22,34z"/><path fill="#fff" d="M14.673,19.012H10v10h2.024v-3.521H14.3c1.876,0,3.397-1.521,3.397-3.397v-0.058 C17.697,20.366,16.343,19.012,14.673,19.012z M15.57,22.358c0,0.859-0.697,1.556-1.556,1.556h-1.99v-3.325h1.99 c0.859,0,1.556,0.697,1.556,1.556V22.358z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
Reference in New Issue
Block a user