From e89785080b2b6e93c963cadfdc329e3c106ea1e1 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 22 May 2023 17:16:21 +0100 Subject: [PATCH 001/398] enable streaming --- .../ConversationChain/ConversationChain.ts | 12 +- .../ConversationalRetrievalQAChain.ts | 19 +- .../nodes/chains/LLMChain/LLMChain.ts | 58 ++- .../RetrievalQAChain/RetrievalQAChain.ts | 17 +- .../SqlDatabaseChain/SqlDatabaseChain.ts | 16 +- .../chains/VectorDBQAChain/VectorDBQAChain.ts | 17 +- .../AzureChatOpenAI/AzureChatOpenAI.ts | 4 +- .../chatmodels/ChatAnthropic/ChatAnthropic.ts | 4 +- .../nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts | 4 +- .../nodes/llms/Azure OpenAI/AzureOpenAI.ts | 4 +- .../components/nodes/llms/OpenAI/OpenAI.ts | 4 +- packages/components/src/utils.ts | 36 ++ packages/server/package.json | 1 + packages/server/src/Interface.ts | 1 + packages/server/src/index.ts | 70 ++- packages/server/src/utils/index.ts | 25 + packages/ui/package.json | 5 + packages/ui/src/api/chatflows.js | 5 +- packages/ui/src/themes/compStyleOverride.js | 33 ++ packages/ui/src/themes/palette.js | 3 +- .../src/ui-component/dialog/ConfirmDialog.js | 6 +- .../ui/src/ui-component/markdown/CodeBlock.js | 123 +++++ .../markdown/MemoizedReactMarkdown.js | 4 + packages/ui/src/utils/genericHelper.js | 20 + packages/ui/src/views/canvas/index.js | 4 +- .../src/views/chatmessage/ChatExpandDialog.js | 62 +++ .../ui/src/views/chatmessage/ChatMessage.css | 12 +- .../ui/src/views/chatmessage/ChatMessage.js | 437 ++++++++---------- .../ui/src/views/chatmessage/ChatPopUp.js | 208 +++++++++ 29 files changed, 909 insertions(+), 305 deletions(-) create mode 100644 packages/ui/src/ui-component/markdown/CodeBlock.js create mode 100644 packages/ui/src/ui-component/markdown/MemoizedReactMarkdown.js create mode 100644 packages/ui/src/views/chatmessage/ChatExpandDialog.js create mode 100644 packages/ui/src/views/chatmessage/ChatPopUp.js diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index 19e28752..a9030a21 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -1,6 +1,6 @@ import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { ConversationChain } from 'langchain/chains' -import { getBaseClasses } from '../../../src/utils' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts' import { BufferMemory, ChatMessageHistory } from 'langchain/memory' import { BaseChatModel } from 'langchain/chat_models/base' @@ -89,8 +89,14 @@ class ConversationChain_Chains implements INode { chain.memory = memory } - const res = await chain.call({ input }) - return res?.response + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const res = await chain.call({ input }, [handler]) + return res?.response + } else { + const res = await chain.call({ input }) + return res?.text + } } } diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 81ac3a6e..589bc524 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -1,6 +1,6 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { ConversationalRetrievalQAChain } from 'langchain/chains' import { BaseRetriever } from 'langchain/schema' @@ -48,6 +48,12 @@ class ConversationalRetrievalQAChain_Chains implements INode { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = nodeData.instance as ConversationalRetrievalQAChain + let model = nodeData.inputs?.model + + // Temporary fix: https://github.com/hwchase17/langchainjs/issues/754 + model.streaming = false + chain.questionGeneratorChain.llm = model + let chatHistory = '' if (options && options.chatHistory) { @@ -64,9 +70,14 @@ class ConversationalRetrievalQAChain_Chains implements INode { chat_history: chatHistory ? chatHistory : [] } - const res = await chain.call(obj) - - return res?.text + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const res = await chain.call(obj, [handler]) + return res?.text + } else { + const res = await chain.call(obj) + return res?.text + } } } diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index b178e28d..9cd08d35 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -1,5 +1,5 @@ import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { LLMChain } from 'langchain/chains' import { BaseLanguageModel } from 'langchain/base_language' @@ -76,12 +76,14 @@ class LLMChain_Chains implements INode { } } - async run(nodeData: INodeData, input: string): Promise { + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const inputVariables = nodeData.instance.prompt.inputVariables as string[] // ["product"] const chain = nodeData.instance as LLMChain const promptValues = nodeData.inputs?.prompt.promptValues as ICommonObject - const res = await runPrediction(inputVariables, chain, input, promptValues) + const res = options.socketIO + ? await runPrediction(inputVariables, chain, input, promptValues, true, options.socketIO, options.socketIOClientId) + : await runPrediction(inputVariables, chain, input, promptValues) // eslint-disable-next-line no-console console.log('\x1b[93m\x1b[1m\n*****FINAL RESULT*****\n\x1b[0m\x1b[0m') // eslint-disable-next-line no-console @@ -90,10 +92,24 @@ class LLMChain_Chains implements INode { } } -const runPrediction = async (inputVariables: string[], chain: LLMChain, input: string, promptValues: ICommonObject) => { +const runPrediction = async ( + inputVariables: string[], + chain: LLMChain, + input: string, + promptValues: ICommonObject, + isStreaming?: boolean, + socketIO?: any, + socketIOClientId = '' +) => { if (inputVariables.length === 1) { - const res = await chain.run(input) - return res + if (isStreaming) { + const handler = new CustomChainHandler(socketIO, socketIOClientId) + const res = await chain.run(input, [handler]) + return res + } else { + const res = await chain.run(input) + return res + } } else if (inputVariables.length > 1) { let seen: string[] = [] @@ -109,8 +125,14 @@ const runPrediction = async (inputVariables: string[], chain: LLMChain, input: s const options = { ...promptValues } - const res = await chain.call(options) - return res?.text + if (isStreaming) { + const handler = new CustomChainHandler(socketIO, socketIOClientId) + const res = await chain.call(options, [handler]) + return res?.text + } else { + const res = await chain.call(options) + return res?.text + } } else if (seen.length === 1) { // If one inputVariable is not specify, use input (user's question) as value const lastValue = seen.pop() @@ -119,14 +141,26 @@ const runPrediction = async (inputVariables: string[], chain: LLMChain, input: s ...promptValues, [lastValue]: input } - const res = await chain.call(options) - return res?.text + if (isStreaming) { + const handler = new CustomChainHandler(socketIO, socketIOClientId) + const res = await chain.call(options, [handler]) + return res?.text + } else { + const res = await chain.call(options) + return res?.text + } } else { throw new Error(`Please provide Prompt Values for: ${seen.join(', ')}`) } } else { - const res = await chain.run(input) - return res + if (isStreaming) { + const handler = new CustomChainHandler(socketIO, socketIOClientId) + const res = await chain.run(input, [handler]) + return res + } else { + const res = await chain.run(input) + return res + } } } diff --git a/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts b/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts index c002b684..97fa51a1 100644 --- a/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts +++ b/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts @@ -1,7 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { RetrievalQAChain } from 'langchain/chains' import { BaseRetriever } from 'langchain/schema' -import { getBaseClasses } from '../../../src/utils' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' class RetrievalQAChain_Chains implements INode { @@ -44,13 +44,20 @@ class RetrievalQAChain_Chains implements INode { return chain } - async run(nodeData: INodeData, input: string): Promise { + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = nodeData.instance as RetrievalQAChain const obj = { query: input } - const res = await chain.call(obj) - return res?.text + + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const res = await chain.call(obj, [handler]) + return res?.text + } else { + const res = await chain.call(obj) + return res?.text + } } } diff --git a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts index 7ea10d94..27a245e1 100644 --- a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts +++ b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts @@ -1,6 +1,6 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { SqlDatabaseChain, SqlDatabaseChainInput } from 'langchain/chains' -import { getBaseClasses } from '../../../src/utils' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { DataSource } from 'typeorm' import { SqlDatabase } from 'langchain/sql_db' import { BaseLanguageModel } from 'langchain/base_language' @@ -59,14 +59,20 @@ class SqlDatabaseChain_Chains implements INode { return chain } - async run(nodeData: INodeData, input: string): Promise { + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const databaseType = nodeData.inputs?.database as 'sqlite' const model = nodeData.inputs?.model as BaseLanguageModel const dbFilePath = nodeData.inputs?.dbFilePath const chain = await getSQLDBChain(databaseType, dbFilePath, model) - const res = await chain.run(input) - return res + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const res = await chain.run(input, [handler]) + return res + } else { + const res = await chain.run(input) + return res + } } } diff --git a/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts b/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts index 37d388b4..f752d60c 100644 --- a/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts +++ b/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { VectorDBQAChain } from 'langchain/chains' import { BaseLanguageModel } from 'langchain/base_language' import { VectorStore } from 'langchain/vectorstores' @@ -44,13 +44,20 @@ class VectorDBQAChain_Chains implements INode { return chain } - async run(nodeData: INodeData, input: string): Promise { + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = nodeData.instance as VectorDBQAChain const obj = { query: input } - const res = await chain.call(obj) - return res?.text + + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const res = await chain.call(obj, [handler]) + return res?.text + } else { + const res = await chain.call(obj) + return res?.text + } } } diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts index 1d2fabc7..7857bfdf 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts @@ -121,6 +121,7 @@ class AzureChatOpenAI_ChatModels implements INode { const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string const presencePenalty = nodeData.inputs?.presencePenalty as string const timeout = nodeData.inputs?.timeout as string + const streaming = nodeData.inputs?.streaming as boolean const obj: Partial & Partial = { temperature: parseInt(temperature, 10), @@ -128,7 +129,8 @@ class AzureChatOpenAI_ChatModels implements INode { azureOpenAIApiKey, azureOpenAIApiInstanceName, azureOpenAIApiDeploymentName, - azureOpenAIApiVersion + azureOpenAIApiVersion, + streaming: streaming ?? true } if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts index b13339ad..708849e5 100644 --- a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts +++ b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts @@ -117,11 +117,13 @@ class ChatAnthropic_ChatModels implements INode { const maxTokensToSample = nodeData.inputs?.maxTokensToSample as string const topP = nodeData.inputs?.topP as string const topK = nodeData.inputs?.topK as string + const streaming = nodeData.inputs?.streaming as boolean const obj: Partial & { anthropicApiKey?: string } = { temperature: parseInt(temperature, 10), modelName, - anthropicApiKey + anthropicApiKey, + streaming: streaming ?? true } if (maxTokensToSample) obj.maxTokensToSample = parseInt(maxTokensToSample, 10) diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts index 5d608c5e..7d2098ec 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts @@ -109,11 +109,13 @@ class ChatOpenAI_ChatModels implements INode { const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string const presencePenalty = nodeData.inputs?.presencePenalty as string const timeout = nodeData.inputs?.timeout as string + const streaming = nodeData.inputs?.streaming as boolean const obj: Partial & { openAIApiKey?: string } = { temperature: parseInt(temperature, 10), modelName, - openAIApiKey + openAIApiKey, + streaming: streaming ?? true } if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) diff --git a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts index b5d7d1e0..c19aa83a 100644 --- a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts +++ b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts @@ -176,6 +176,7 @@ class AzureOpenAI_LLMs implements INode { const presencePenalty = nodeData.inputs?.presencePenalty as string const timeout = nodeData.inputs?.timeout as string const bestOf = nodeData.inputs?.bestOf as string + const streaming = nodeData.inputs?.streaming as boolean const obj: Partial & Partial = { temperature: parseInt(temperature, 10), @@ -183,7 +184,8 @@ class AzureOpenAI_LLMs implements INode { azureOpenAIApiKey, azureOpenAIApiInstanceName, azureOpenAIApiDeploymentName, - azureOpenAIApiVersion + azureOpenAIApiVersion, + streaming: streaming ?? true } if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) diff --git a/packages/components/nodes/llms/OpenAI/OpenAI.ts b/packages/components/nodes/llms/OpenAI/OpenAI.ts index af44965e..48b1bc84 100644 --- a/packages/components/nodes/llms/OpenAI/OpenAI.ts +++ b/packages/components/nodes/llms/OpenAI/OpenAI.ts @@ -121,11 +121,13 @@ class OpenAI_LLMs implements INode { const timeout = nodeData.inputs?.timeout as string const batchSize = nodeData.inputs?.batchSize as string const bestOf = nodeData.inputs?.bestOf as string + const streaming = nodeData.inputs?.streaming as boolean const obj: Partial & { openAIApiKey?: string } = { temperature: parseInt(temperature, 10), modelName, - openAIApiKey + openAIApiKey, + streaming: streaming ?? true } if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 10091d60..68c098fd 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -2,6 +2,8 @@ import axios from 'axios' import { load } from 'cheerio' import * as fs from 'fs' import * as path from 'path' +import { BaseCallbackHandler } from 'langchain/callbacks' +import { Server } from 'socket.io' 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 @@ -152,6 +154,12 @@ export const getInputVariables = (paramValue: string): string[] => { return inputVariables } +/** + * Crawl all available urls given a domain url and limit + * @param {string} url + * @param {number} limit + * @returns {string[]} + */ export const getAvailableURLs = async (url: string, limit: number) => { try { const availableUrls: string[] = [] @@ -190,3 +198,31 @@ export const getAvailableURLs = async (url: string, limit: number) => { throw new Error(`getAvailableURLs: ${err?.message}`) } } + +/** + * Custom chain handler class + */ +export class CustomChainHandler extends BaseCallbackHandler { + name = 'custom_chain_handler' + isLLMStarted = false + socketIO: Server + socketIOClientId = '' + + constructor(socketIO: Server, socketIOClientId: string) { + super() + this.socketIO = socketIO + this.socketIOClientId = socketIOClientId + } + + handleLLMNewToken(token: string) { + if (!this.isLLMStarted) { + this.isLLMStarted = true + this.socketIO.to(this.socketIOClientId).emit('start', token) + } + this.socketIO.to(this.socketIOClientId).emit('token', token) + } + + handleLLMEnd() { + this.socketIO.to(this.socketIOClientId).emit('end') + } +} diff --git a/packages/server/package.json b/packages/server/package.json index b8777a21..a230f94a 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -57,6 +57,7 @@ "moment-timezone": "^0.5.34", "multer": "^1.4.5-lts.1", "reflect-metadata": "^0.1.13", + "socket.io": "^4.6.1", "sqlite3": "^5.1.6", "typeorm": "^0.3.6" }, diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 30f9fb29..0dede024 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -115,6 +115,7 @@ export interface IncomingInput { question: string history: IMessage[] overrideConfig?: ICommonObject + socketIOClientId?: string } export interface IActiveChatflows { diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 1ce66117..59dfe3cf 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -5,6 +5,7 @@ import cors from 'cors' import http from 'http' import * as fs from 'fs' import basicAuth from 'express-basic-auth' +import { Server } from 'socket.io' import { IChatFlow, @@ -32,7 +33,8 @@ import { mapMimeTypeToInputField, findAvailableConfigs, isSameOverrideConfig, - replaceAllAPIKeys + replaceAllAPIKeys, + isFlowValidForStream } from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' @@ -73,7 +75,7 @@ export class App { }) } - async config() { + async config(socketIO?: Server) { // Limit is needed to allow sending/receiving base64 encoded string this.app.use(express.json({ limit: '50mb' })) this.app.use(express.urlencoded({ limit: '50mb', extended: true })) @@ -200,6 +202,30 @@ export class App { return res.json(results) }) + // Check if chatflow valid for streaming + this.app.get('/api/v1/chatflows-streaming/:id', async (req: Request, res: Response) => { + const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ + id: req.params.id + }) + if (!chatflow) return res.status(404).send(`Chatflow ${req.params.id} not found`) + + /*** Get Ending Node with Directed Graph ***/ + const flowData = chatflow.flowData + const parsedFlowData: IReactFlowObject = JSON.parse(flowData) + const nodes = parsedFlowData.nodes + const edges = parsedFlowData.edges + const { graph, nodeDependencies } = constructGraphs(nodes, edges) + const endingNodeId = getEndingNode(nodeDependencies, graph) + if (!endingNodeId) return res.status(500).send(`Ending node must be either a Chain or Agent`) + const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data + if (!endingNodeData) return res.status(500).send(`Ending node must be either a Chain or Agent`) + + const obj = { + isStreaming: isFlowValidForStream(nodes, endingNodeData) + } + return res.json(obj) + }) + // ---------------------------------------- // ChatMessage // ---------------------------------------- @@ -303,12 +329,12 @@ export class App { // Send input message and get prediction result (External) this.app.post('/api/v1/prediction/:id', upload.array('files'), async (req: Request, res: Response) => { - await this.processPrediction(req, res) + await this.processPrediction(req, res, socketIO) }) // Send input message and get prediction result (Internal) this.app.post('/api/v1/internal-prediction/:id', async (req: Request, res: Response) => { - await this.processPrediction(req, res, true) + await this.processPrediction(req, res, socketIO, true) }) // ---------------------------------------- @@ -464,9 +490,10 @@ export class App { * Process Prediction * @param {Request} req * @param {Response} res + * @param {Server} socketIO * @param {boolean} isInternal */ - async processPrediction(req: Request, res: Response, isInternal = false) { + async processPrediction(req: Request, res: Response, socketIO?: Server, isInternal = false) { try { const chatflowid = req.params.id let incomingInput: IncomingInput = req.body @@ -482,6 +509,8 @@ export class App { await this.validateKey(req, res, chatflow) } + let isStreamValid = false + const files = (req.files as any[]) || [] if (files.length) { @@ -542,15 +571,16 @@ export class App { } } } else { + /*** Get chatflows and prepare data ***/ + const flowData = chatflow.flowData + const parsedFlowData: IReactFlowObject = JSON.parse(flowData) + const nodes = parsedFlowData.nodes + const edges = parsedFlowData.edges + if (isRebuildNeeded()) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData + isStreamValid = isFlowValidForStream(nodes, nodeToExecuteData) } else { - /*** Get chatflows and prepare data ***/ - const flowData = chatflow.flowData - const parsedFlowData: IReactFlowObject = JSON.parse(flowData) - const nodes = parsedFlowData.nodes - const edges = parsedFlowData.edges - /*** Get Ending Node with Directed Graph ***/ const { graph, nodeDependencies } = constructGraphs(nodes, edges) const directedGraph = graph @@ -572,6 +602,8 @@ export class App { ) } + isStreamValid = isFlowValidForStream(nodes, endingNodeData) + /*** Get Starting Nodes with Non-Directed Graph ***/ const constructedObj = constructGraphs(nodes, edges, true) const nonDirectedGraph = constructedObj.graph @@ -602,7 +634,13 @@ export class App { const nodeModule = await import(nodeInstanceFilePath) const nodeInstance = new nodeModule.nodeClass() - const result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history }) + const result = isStreamValid + ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { + chatHistory: incomingInput.history, + socketIO, + socketIOClientId: incomingInput.socketIOClientId + }) + : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history }) return res.json(result) } @@ -629,8 +667,14 @@ export async function start(): Promise { const port = parseInt(process.env.PORT || '', 10) || 3000 const server = http.createServer(serverApp.app) + const io = new Server(server, { + cors: { + origin: '*' + } + }) + await serverApp.initDatabase() - await serverApp.config() + await serverApp.config(io) server.listen(port, () => { console.info(`⚡️[server]: Flowise Server is listening at ${port}`) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 78761284..982a82d0 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -610,3 +610,28 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => { return configs } + +/** + * Check to see if flow valid for stream + * @param {IReactFlowNode[]} reactFlowNodes + * @param {INodeData} endingNodeData + * @returns {boolean} + */ +export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNodeData: INodeData) => { + const streamAvailableLLMs = { + 'Chat Models': ['azureChatOpenAI', 'chatOpenAI', 'chatAnthropic'], + LLMs: ['azureOpenAI', 'openAI'] + } + + let isChatOrLLMsExist = false + for (const flowNode of reactFlowNodes) { + const data = flowNode.data + if (data.category === 'Chat Models' || data.category === 'LLMs') { + isChatOrLLMsExist = true + const validLLMs = streamAvailableLLMs[data.category] + if (!validLLMs.includes(data.name)) return false + } + } + + return isChatOrLLMsExist && endingNodeData.category === 'Chains' +} diff --git a/packages/ui/package.json b/packages/ui/package.json index b367b183..fc2961fc 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -36,8 +36,13 @@ "react-router": "~6.3.0", "react-router-dom": "~6.3.0", "react-simple-code-editor": "^0.11.2", + "react-syntax-highlighter": "^15.5.0", "reactflow": "^11.5.6", "redux": "^4.0.5", + "rehype-mathjax": "^4.0.2", + "remark-gfm": "^3.0.1", + "remark-math": "^5.1.1", + "socket.io-client": "^4.6.1", "yup": "^0.32.9" }, "scripts": { diff --git a/packages/ui/src/api/chatflows.js b/packages/ui/src/api/chatflows.js index eae010ed..1cd1ebb0 100644 --- a/packages/ui/src/api/chatflows.js +++ b/packages/ui/src/api/chatflows.js @@ -10,10 +10,13 @@ const updateChatflow = (id, body) => client.put(`/chatflows/${id}`, body) const deleteChatflow = (id) => client.delete(`/chatflows/${id}`) +const getIsChatflowStreaming = (id) => client.get(`/chatflows-streaming/${id}`) + export default { getAllChatflows, getSpecificChatflow, createNewChatflow, updateChatflow, - deleteChatflow + deleteChatflow, + getIsChatflowStreaming } diff --git a/packages/ui/src/themes/compStyleOverride.js b/packages/ui/src/themes/compStyleOverride.js index eb6f6de9..b7ebc8b2 100644 --- a/packages/ui/src/themes/compStyleOverride.js +++ b/packages/ui/src/themes/compStyleOverride.js @@ -1,6 +1,39 @@ export default function componentStyleOverrides(theme) { const bgColor = theme.colors?.grey50 return { + MuiCssBaseline: { + styleOverrides: { + body: { + scrollbarWidth: 'thin', + scrollbarColor: theme?.customization?.isDarkMode + ? `${theme.colors?.grey500} ${theme.colors?.darkPrimaryMain}` + : `${theme.colors?.grey300} ${theme.paper}`, + '&::-webkit-scrollbar, & *::-webkit-scrollbar': { + width: 12, + height: 12, + backgroundColor: theme?.customization?.isDarkMode ? theme.colors?.darkPrimaryMain : theme.paper + }, + '&::-webkit-scrollbar-thumb, & *::-webkit-scrollbar-thumb': { + borderRadius: 8, + backgroundColor: theme?.customization?.isDarkMode ? theme.colors?.grey500 : theme.colors?.grey300, + minHeight: 24, + border: `3px solid ${theme?.customization?.isDarkMode ? theme.colors?.darkPrimaryMain : theme.paper}` + }, + '&::-webkit-scrollbar-thumb:focus, & *::-webkit-scrollbar-thumb:focus': { + backgroundColor: theme?.customization?.isDarkMode ? theme.colors?.darkPrimary200 : theme.colors?.grey500 + }, + '&::-webkit-scrollbar-thumb:active, & *::-webkit-scrollbar-thumb:active': { + backgroundColor: theme?.customization?.isDarkMode ? theme.colors?.darkPrimary200 : theme.colors?.grey500 + }, + '&::-webkit-scrollbar-thumb:hover, & *::-webkit-scrollbar-thumb:hover': { + backgroundColor: theme?.customization?.isDarkMode ? theme.colors?.darkPrimary200 : theme.colors?.grey500 + }, + '&::-webkit-scrollbar-corner, & *::-webkit-scrollbar-corner': { + backgroundColor: theme?.customization?.isDarkMode ? theme.colors?.darkPrimaryMain : theme.paper + } + } + } + }, MuiButton: { styleOverrides: { root: { diff --git a/packages/ui/src/themes/palette.js b/packages/ui/src/themes/palette.js index a4a5104d..9e7b7620 100644 --- a/packages/ui/src/themes/palette.js +++ b/packages/ui/src/themes/palette.js @@ -7,7 +7,8 @@ export default function themePalette(theme) { return { mode: theme?.customization?.navType, common: { - black: theme.colors?.darkPaper + black: theme.colors?.darkPaper, + dark: theme.colors?.darkPrimaryMain }, primary: { light: theme.customization.isDarkMode ? theme.colors?.darkPrimaryLight : theme.colors?.primaryLight, diff --git a/packages/ui/src/ui-component/dialog/ConfirmDialog.js b/packages/ui/src/ui-component/dialog/ConfirmDialog.js index 8176ecd1..6f8712f5 100644 --- a/packages/ui/src/ui-component/dialog/ConfirmDialog.js +++ b/packages/ui/src/ui-component/dialog/ConfirmDialog.js @@ -1,5 +1,5 @@ import { createPortal } from 'react-dom' -import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from '@mui/material' +import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material' import useConfirm from 'hooks/useConfirm' import { StyledButton } from 'ui-component/button/StyledButton' @@ -20,9 +20,7 @@ const ConfirmDialog = () => { {confirmState.title} - - {confirmState.description} - + {confirmState.description} diff --git a/packages/ui/src/ui-component/markdown/CodeBlock.js b/packages/ui/src/ui-component/markdown/CodeBlock.js new file mode 100644 index 00000000..77caa346 --- /dev/null +++ b/packages/ui/src/ui-component/markdown/CodeBlock.js @@ -0,0 +1,123 @@ +import { IconClipboard, IconDownload } from '@tabler/icons' +import { memo, useState } from 'react' +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' +import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism' +import PropTypes from 'prop-types' +import { Box, IconButton, Popover, Typography } from '@mui/material' +import { useTheme } from '@mui/material/styles' + +const programmingLanguages = { + javascript: '.js', + python: '.py', + java: '.java', + c: '.c', + cpp: '.cpp', + 'c++': '.cpp', + 'c#': '.cs', + ruby: '.rb', + php: '.php', + swift: '.swift', + 'objective-c': '.m', + kotlin: '.kt', + typescript: '.ts', + go: '.go', + perl: '.pl', + rust: '.rs', + scala: '.scala', + haskell: '.hs', + lua: '.lua', + shell: '.sh', + sql: '.sql', + html: '.html', + css: '.css' +} + +export const CodeBlock = memo(({ language, chatflowid, isDialog, value }) => { + const theme = useTheme() + const [anchorEl, setAnchorEl] = useState(null) + const openPopOver = Boolean(anchorEl) + + const handleClosePopOver = () => { + setAnchorEl(null) + } + + const copyToClipboard = (event) => { + if (!navigator.clipboard || !navigator.clipboard.writeText) { + return + } + + navigator.clipboard.writeText(value) + setAnchorEl(event.currentTarget) + setTimeout(() => { + handleClosePopOver() + }, 1500) + } + + const downloadAsFile = () => { + const fileExtension = programmingLanguages[language] || '.file' + const suggestedFileName = `file-${chatflowid}${fileExtension}` + const fileName = suggestedFileName + + if (!fileName) { + // user pressed cancel on prompt + return + } + + const blob = new Blob([value], { type: 'text/plain' }) + const url = URL.createObjectURL(blob) + const link = document.createElement('a') + link.download = fileName + link.href = url + link.style.display = 'none' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + URL.revokeObjectURL(url) + } + + return ( +
+ +
+ {language} +
+ + + + + + Copied! + + + + + +
+
+ + + {value} + +
+ ) +}) +CodeBlock.displayName = 'CodeBlock' + +CodeBlock.propTypes = { + language: PropTypes.string, + chatflowid: PropTypes.string, + isDialog: PropTypes.bool, + value: PropTypes.string +} diff --git a/packages/ui/src/ui-component/markdown/MemoizedReactMarkdown.js b/packages/ui/src/ui-component/markdown/MemoizedReactMarkdown.js new file mode 100644 index 00000000..f9770a9f --- /dev/null +++ b/packages/ui/src/ui-component/markdown/MemoizedReactMarkdown.js @@ -0,0 +1,4 @@ +import { memo } from 'react' +import ReactMarkdown from 'react-markdown' + +export const MemoizedReactMarkdown = memo(ReactMarkdown, (prevProps, nextProps) => prevProps.children === nextProps.children) diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index c1dcb108..fac83225 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -314,3 +314,23 @@ export const rearrangeToolsOrdering = (newValues, sourceNodeId) => { newValues.sort((a, b) => sortKey(a) - sortKey(b)) } + +export const throttle = (func, limit) => { + let lastFunc + let lastRan + + return (...args) => { + if (!lastRan) { + func(...args) + lastRan = Date.now() + } else { + clearTimeout(lastFunc) + lastFunc = setTimeout(() => { + if (Date.now() - lastRan >= limit) { + func(...args) + lastRan = Date.now() + } + }, limit - (Date.now() - lastRan)) + } + } +} diff --git a/packages/ui/src/views/canvas/index.js b/packages/ui/src/views/canvas/index.js index f71acbdf..2d71f03a 100644 --- a/packages/ui/src/views/canvas/index.js +++ b/packages/ui/src/views/canvas/index.js @@ -23,7 +23,7 @@ import ButtonEdge from './ButtonEdge' import CanvasHeader from './CanvasHeader' import AddNodes from './AddNodes' import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' -import { ChatMessage } from 'views/chatmessage/ChatMessage' +import { ChatPopUp } from 'views/chatmessage/ChatPopUp' import { flowContext } from 'store/context/ReactFlowContext' // API @@ -514,7 +514,7 @@ const Canvas = () => { /> - + diff --git a/packages/ui/src/views/chatmessage/ChatExpandDialog.js b/packages/ui/src/views/chatmessage/ChatExpandDialog.js new file mode 100644 index 00000000..aa5cd504 --- /dev/null +++ b/packages/ui/src/views/chatmessage/ChatExpandDialog.js @@ -0,0 +1,62 @@ +import { createPortal } from 'react-dom' +import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' + +import { Dialog, DialogContent, DialogTitle, Button } from '@mui/material' +import { ChatMessage } from './ChatMessage' +import { StyledButton } from 'ui-component/button/StyledButton' +import { IconEraser } from '@tabler/icons' + +const ChatExpandDialog = ({ show, dialogProps, onClear, onCancel }) => { + const portalElement = document.getElementById('portal') + const customization = useSelector((state) => state.customization) + + const component = show ? ( + + +
+ {dialogProps.title} +
+ {customization.isDarkMode && ( + } + > + Clear Chat + + )} + {!customization.isDarkMode && ( + + )} +
+
+ + + +
+ ) : null + + return createPortal(component, portalElement) +} + +ChatExpandDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onClear: PropTypes.func, + onCancel: PropTypes.func +} + +export default ChatExpandDialog diff --git a/packages/ui/src/views/chatmessage/ChatMessage.css b/packages/ui/src/views/chatmessage/ChatMessage.css index a29e49ff..9086fb13 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.css +++ b/packages/ui/src/views/chatmessage/ChatMessage.css @@ -80,7 +80,7 @@ } .markdownanswer code { - color: #15cb19; + color: #0ab126; font-weight: 500; white-space: pre-wrap !important; } @@ -92,6 +92,7 @@ .boticon, .usericon { + margin-top: 1rem; margin-right: 1rem; border-radius: 1rem; } @@ -119,3 +120,12 @@ justify-content: center; align-items: center; } + +.cloud-dialog { + width: 100%; + height: calc(100vh - 230px); + border-radius: 0.5rem; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index e50f5bd5..8500833c 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -1,53 +1,40 @@ -import { useState, useRef, useEffect } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import ReactMarkdown from 'react-markdown' +import { useState, useRef, useEffect, useCallback } from 'react' +import { useSelector } from 'react-redux' import PropTypes from 'prop-types' -import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import socketIOClient from 'socket.io-client' +import { cloneDeep } from 'lodash' +import rehypeMathjax from 'rehype-mathjax' +import remarkGfm from 'remark-gfm' +import remarkMath from 'remark-math' -import { - ClickAwayListener, - Paper, - Popper, - CircularProgress, - OutlinedInput, - Divider, - InputAdornment, - IconButton, - Box, - Button -} from '@mui/material' +import { CircularProgress, OutlinedInput, Divider, InputAdornment, IconButton, Box } from '@mui/material' import { useTheme } from '@mui/material/styles' -import { IconMessage, IconX, IconSend, IconEraser } from '@tabler/icons' +import { IconSend } from '@tabler/icons' // project import -import { StyledFab } from 'ui-component/button/StyledFab' -import MainCard from 'ui-component/cards/MainCard' -import Transitions from 'ui-component/extended/Transitions' +import { CodeBlock } from 'ui-component/markdown/CodeBlock' +import { MemoizedReactMarkdown } from 'ui-component/markdown/MemoizedReactMarkdown' import './ChatMessage.css' // api import chatmessageApi from 'api/chatmessage' +import chatflowsApi from 'api/chatflows' import predictionApi from 'api/prediction' // Hooks import useApi from 'hooks/useApi' -import useConfirm from 'hooks/useConfirm' -import useNotifier from 'utils/useNotifier' -import { maxScroll } from 'store/constant' +// Const +import { baseURL } from 'store/constant' +import { throttle } from 'utils/genericHelper' -export const ChatMessage = ({ chatflowid }) => { +export const ChatMessage = ({ open, chatflowid, isDialog }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) - const { confirm } = useConfirm() - const dispatch = useDispatch() + const ps = useRef() + const messagesEndRef = useRef() - useNotifier() - const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) - const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) - - const [open, setOpen] = useState(false) const [userInput, setUserInput] = useState('') const [loading, setLoading] = useState(false) const [messages, setMessages] = useState([ @@ -56,72 +43,21 @@ export const ChatMessage = ({ chatflowid }) => { type: 'apiMessage' } ]) + const [socketIOClientId, setSocketIOClientId] = useState('') + const [isChatFlowAvailableToStream, setIsChatFlowAvailableToStream] = useState(false) const inputRef = useRef(null) - const anchorRef = useRef(null) - const prevOpen = useRef(open) const getChatmessageApi = useApi(chatmessageApi.getChatmessageFromChatflow) - - const handleClose = (event) => { - if (anchorRef.current && anchorRef.current.contains(event.target)) { - return - } - setOpen(false) - } - - const handleToggle = () => { - setOpen((prevOpen) => !prevOpen) - } - - const clearChat = async () => { - const confirmPayload = { - title: `Clear Chat History`, - description: `Are you sure you want to clear all chat history?`, - confirmButtonName: 'Clear', - cancelButtonName: 'Cancel' - } - const isConfirmed = await confirm(confirmPayload) - - if (isConfirmed) { - try { - await chatmessageApi.deleteChatmessage(chatflowid) - enqueueSnackbar({ - message: 'Succesfully cleared all chat history', - options: { - key: new Date().getTime() + Math.random(), - variant: 'success', - action: (key) => ( - - ) - } - }) - } catch (error) { - const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` - enqueueSnackbar({ - message: errorData, - options: { - key: new Date().getTime() + Math.random(), - variant: 'error', - persist: true, - action: (key) => ( - - ) - } - }) - } - } - } + const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming) const scrollToBottom = () => { - if (ps.current) { - ps.current.scrollTo({ top: maxScroll, behavior: 'smooth' }) - } + messagesEndRef.current?.scrollIntoView(true) } + const onChange = useCallback((e) => setUserInput(e.target.value), [setUserInput]) + + const scrollThrottle = throttle(scrollToBottom, 250) + const addChatMessage = async (message, type) => { try { const newChatMessageBody = { @@ -135,6 +71,15 @@ export const ChatMessage = ({ chatflowid }) => { } } + const updateLastMessage = (text) => { + setMessages((prevMessages) => { + let allMessages = [...cloneDeep(prevMessages)] + if (allMessages[allMessages.length - 1].type === 'userMessage') return allMessages + allMessages[allMessages.length - 1].message += text + return allMessages + }) + } + // Handle errors const handleError = (message = 'Oops! There seems to be an error. Please try again.') => { message = message.replace(`Unable to parse JSON response from chat agent.\n\n`, '') @@ -143,7 +88,7 @@ export const ChatMessage = ({ chatflowid }) => { setLoading(false) setUserInput('') setTimeout(() => { - inputRef.current.focus() + inputRef.current?.focus() }, 100) } @@ -161,18 +106,22 @@ export const ChatMessage = ({ chatflowid }) => { // Send user question and history to API try { - const response = await predictionApi.sendMessageAndGetPrediction(chatflowid, { + const params = { question: userInput, history: messages.filter((msg) => msg.message !== 'Hi there! How can I help?') - }) + } + if (isChatFlowAvailableToStream) params.socketIOClientId = socketIOClientId + + const response = await predictionApi.sendMessageAndGetPrediction(chatflowid, params) + if (response.data) { const data = response.data - setMessages((prevMessages) => [...prevMessages, { message: data, type: 'apiMessage' }]) + if (!isChatFlowAvailableToStream) setMessages((prevMessages) => [...prevMessages, { message: data, type: 'apiMessage' }]) addChatMessage(data, 'apiMessage') setLoading(false) setUserInput('') setTimeout(() => { - inputRef.current.focus() + inputRef.current?.focus() scrollToBottom() }, 100) } @@ -210,22 +159,47 @@ export const ChatMessage = ({ chatflowid }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [getChatmessageApi.data]) + // Get chatflow streaming capability + useEffect(() => { + if (getIsChatflowStreamingApi.data) { + setIsChatFlowAvailableToStream(getIsChatflowStreamingApi.data?.isStreaming ?? false) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getIsChatflowStreamingApi.data]) + // Auto scroll chat to bottom useEffect(() => { - scrollToBottom() - }, [messages]) + scrollThrottle() + }, [messages, scrollThrottle]) useEffect(() => { - if (prevOpen.current === true && open === false) { - anchorRef.current.focus() + if (isDialog && inputRef) { + setTimeout(() => { + inputRef.current?.focus() + }, 100) } + }, [isDialog, inputRef]) + useEffect(() => { + let socket if (open && chatflowid) { getChatmessageApi.request(chatflowid) + getIsChatflowStreamingApi.request(chatflowid) scrollToBottom() - } - prevOpen.current = open + socket = socketIOClient(baseURL) + + socket.on('connect', () => { + setSocketIOClientId(socket.id) + }) + + socket.on('start', () => { + setMessages((prevMessages) => [...prevMessages, { message: '', type: 'apiMessage' }]) + }) + + socket.on('token', updateLastMessage) + } return () => { setUserInput('') @@ -236,6 +210,10 @@ export const ChatMessage = ({ chatflowid }) => { type: 'apiMessage' } ]) + if (socket) { + socket.disconnect() + setSocketIOClientId('') + } } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -243,151 +221,122 @@ export const ChatMessage = ({ chatflowid }) => { return ( <> - - {open ? : } - - {open && ( - - - - )} - +
+ {messages && + messages.map((message, index) => { + return ( + // The latest message sent by the user will be animated while waiting for a response + + {/* Display the correct icon depending on the message type */} + {message.type === 'apiMessage' ? ( + AI + ) : ( + Me + )} +
+ {/* Messages are being rendered in Markdown format */} + + ) : ( + + {children} + + ) + } + }} + > + {message.message} + +
+
+ ) + })} +
+
+
+ +
+
+
+ + + {loading ? ( +
+ +
+ ) : ( + // Send icon SVG in input field + + )} +
+ } - } - ] - }} - sx={{ zIndex: 1000 }} - > - {({ TransitionProps }) => ( - - - - -
-
- {messages.map((message, index) => { - return ( - // The latest message sent by the user will be animated while waiting for a response - - {/* Display the correct icon depending on the message type */} - {message.type === 'apiMessage' ? ( - AI - ) : ( - Me - )} -
- {/* Messages are being rendered in Markdown format */} - {message.message} -
-
- ) - })} -
-
- -
-
- - setUserInput(e.target.value)} - endAdornment={ - - - {loading ? ( -
- -
- ) : ( - // Send icon SVG in input field - - )} -
-
- } - /> - -
-
-
-
-
-
- )} - + /> + +
+
) } -ChatMessage.propTypes = { chatflowid: PropTypes.string } +ChatMessage.propTypes = { + open: PropTypes.bool, + chatflowid: PropTypes.string, + isDialog: PropTypes.bool +} diff --git a/packages/ui/src/views/chatmessage/ChatPopUp.js b/packages/ui/src/views/chatmessage/ChatPopUp.js new file mode 100644 index 00000000..93050c3a --- /dev/null +++ b/packages/ui/src/views/chatmessage/ChatPopUp.js @@ -0,0 +1,208 @@ +import { useState, useRef, useEffect } from 'react' +import { useDispatch } from 'react-redux' +import PropTypes from 'prop-types' + +import { ClickAwayListener, Paper, Popper, Button } from '@mui/material' +import { useTheme } from '@mui/material/styles' +import { IconMessage, IconX, IconEraser, IconArrowsMaximize } from '@tabler/icons' + +// project import +import { StyledFab } from 'ui-component/button/StyledFab' +import MainCard from 'ui-component/cards/MainCard' +import Transitions from 'ui-component/extended/Transitions' +import { ChatMessage } from './ChatMessage' +import ChatExpandDialog from './ChatExpandDialog' + +// api +import chatmessageApi from 'api/chatmessage' + +// Hooks +import useConfirm from 'hooks/useConfirm' +import useNotifier from 'utils/useNotifier' + +// Const +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' + +export const ChatPopUp = ({ chatflowid }) => { + const theme = useTheme() + const { confirm } = useConfirm() + const dispatch = useDispatch() + + useNotifier() + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [open, setOpen] = useState(false) + const [showExpandDialog, setShowExpandDialog] = useState(false) + const [expandDialogProps, setExpandDialogProps] = useState({}) + + const anchorRef = useRef(null) + const prevOpen = useRef(open) + + const handleClose = (event) => { + if (anchorRef.current && anchorRef.current.contains(event.target)) { + return + } + setOpen(false) + } + + const handleToggle = () => { + setOpen((prevOpen) => !prevOpen) + } + + const expandChat = () => { + const props = { + open: true, + chatflowid: chatflowid + } + setExpandDialogProps(props) + setShowExpandDialog(true) + } + + const resetChatDialog = () => { + const props = { + ...expandDialogProps, + open: false + } + setExpandDialogProps(props) + setTimeout(() => { + const resetProps = { + ...expandDialogProps, + open: true + } + setExpandDialogProps(resetProps) + }, 500) + } + + const clearChat = async () => { + const confirmPayload = { + title: `Clear Chat History`, + description: `Are you sure you want to clear all chat history?`, + confirmButtonName: 'Clear', + cancelButtonName: 'Cancel' + } + const isConfirmed = await confirm(confirmPayload) + + if (isConfirmed) { + try { + await chatmessageApi.deleteChatmessage(chatflowid) + resetChatDialog() + enqueueSnackbar({ + message: 'Succesfully cleared all chat history', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: errorData, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + } + } + } + + useEffect(() => { + if (prevOpen.current === true && open === false) { + anchorRef.current.focus() + } + prevOpen.current = open + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open, chatflowid]) + + return ( + <> + + {open ? : } + + {open && ( + + + + )} + {open && ( + + + + )} + + {({ TransitionProps }) => ( + + + + + + + + + + )} + + setShowExpandDialog(false)} + > + + ) +} + +ChatPopUp.propTypes = { chatflowid: PropTypes.string } From 7f3d850bf67c32f0f9e6bf74e98364eb7719b407 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 22 May 2023 17:47:57 +0100 Subject: [PATCH 002/398] update message scroll --- packages/ui/src/views/chatmessage/ChatMessage.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 8500833c..2ebfa677 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -25,7 +25,7 @@ import predictionApi from 'api/prediction' import useApi from 'hooks/useApi' // Const -import { baseURL } from 'store/constant' +import { baseURL, maxScroll } from 'store/constant' import { throttle } from 'utils/genericHelper' export const ChatMessage = ({ open, chatflowid, isDialog }) => { @@ -33,7 +33,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { const customization = useSelector((state) => state.customization) const ps = useRef() - const messagesEndRef = useRef() const [userInput, setUserInput] = useState('') const [loading, setLoading] = useState(false) @@ -51,7 +50,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming) const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView(true) + if (ps.current) { + ps.current.scrollTo({ top: maxScroll }) + } } const onChange = useCallback((e) => setUserInput(e.target.value), [setUserInput]) @@ -170,8 +171,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { // Auto scroll chat to bottom useEffect(() => { - scrollThrottle() - }, [messages, scrollThrottle]) + scrollToBottom() + console.log('throeel') + }, [messages]) useEffect(() => { if (isDialog && inputRef) { @@ -292,7 +294,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { ) })} -
From 34bfd4c793be8136a5c9843b0bcf7182b4447223 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 22 May 2023 17:52:18 +0100 Subject: [PATCH 003/398] lint-fix --- packages/ui/src/views/chatmessage/ChatMessage.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 2ebfa677..e894f46b 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -26,7 +26,6 @@ import useApi from 'hooks/useApi' // Const import { baseURL, maxScroll } from 'store/constant' -import { throttle } from 'utils/genericHelper' export const ChatMessage = ({ open, chatflowid, isDialog }) => { const theme = useTheme() @@ -57,8 +56,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { const onChange = useCallback((e) => setUserInput(e.target.value), [setUserInput]) - const scrollThrottle = throttle(scrollToBottom, 250) - const addChatMessage = async (message, type) => { try { const newChatMessageBody = { @@ -172,7 +169,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { // Auto scroll chat to bottom useEffect(() => { scrollToBottom() - console.log('throeel') }, [messages]) useEffect(() => { From f8232bd20165a6bfcf3bfdb393e5422ec1f9360d Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Tue, 23 May 2023 21:34:37 +0700 Subject: [PATCH 004/398] add Buffer Window Memory --- .../BufferWindowMemory/BufferWindowMemory.ts | 61 +++++++++++++++++++ .../memory/BufferWindowMemory/memory.svg | 8 +++ 2 files changed, 69 insertions(+) create mode 100644 packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts create mode 100644 packages/components/nodes/memory/BufferWindowMemory/memory.svg diff --git a/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts new file mode 100644 index 00000000..0f6a9542 --- /dev/null +++ b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts @@ -0,0 +1,61 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { BufferWindowMemory, BufferWindowMemoryInput } from 'langchain/memory' + +class BufferWindowMemory_Memory implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Buffer Window Memory' + this.name = 'bufferWindowMemory' + this.type = 'BufferWindowMemory' + this.icon = 'memory.svg' + this.category = 'Memory' + this.description = 'Uses a window of size k to surface the last k back-and-forths to use as memory' + this.baseClasses = [this.type, ...getBaseClasses(BufferWindowMemory)] + this.inputs = [ + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history' + }, + { + label: 'Input Key', + name: 'inputKey', + type: 'string', + default: 'input' + }, + { + label: 'Size', + name: 'k', + type: 'number', + default: '4' + } + ] + } + + async init(nodeData: INodeData): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const inputKey = nodeData.inputs?.inputKey as string + const k = nodeData.inputs?.k as string + + const obj: Partial = { + returnMessages: true, + memoryKey: memoryKey, + inputKey: inputKey, + k: parseInt(k, 10) + } + + return new BufferWindowMemory(obj) + } +} + +module.exports = { nodeClass: BufferWindowMemory_Memory } diff --git a/packages/components/nodes/memory/BufferWindowMemory/memory.svg b/packages/components/nodes/memory/BufferWindowMemory/memory.svg new file mode 100644 index 00000000..ca8e17da --- /dev/null +++ b/packages/components/nodes/memory/BufferWindowMemory/memory.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From 7f7a7fb120756a58fca8eed37648ad74da8dc381 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Tue, 23 May 2023 22:22:37 +0700 Subject: [PATCH 005/398] add Conversation Summary Memory --- .../ConversationSummaryMemory.ts | 61 +++++++++++++++++++ .../ConversationSummaryMemory/memory.svg | 8 +++ 2 files changed, 69 insertions(+) create mode 100644 packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts create mode 100644 packages/components/nodes/memory/ConversationSummaryMemory/memory.svg diff --git a/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts b/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts new file mode 100644 index 00000000..3a94b657 --- /dev/null +++ b/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts @@ -0,0 +1,61 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { ConversationSummaryMemory, ConversationSummaryMemoryInput } from 'langchain/memory' +import { BaseLanguageModel } from 'langchain/base_language' + +class ConversationSummaryMemory_Memory implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Conversation Summary Memory' + this.name = 'conversationSummaryMemory' + this.type = 'ConversationSummaryMemory' + this.icon = 'memory.svg' + this.category = 'Memory' + this.description = 'Remembers previous conversational back and forths directly' + this.baseClasses = [this.type, ...getBaseClasses(ConversationSummaryMemory)] + this.inputs = [ + { + label: 'Language Model', + name: 'model', + type: 'BaseLanguageModel' + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history' + }, + { + label: 'Input Key', + name: 'inputKey', + type: 'string', + default: 'input' + } + ] + } + + async init(nodeData: INodeData): Promise { + const model = nodeData.inputs?.model as BaseLanguageModel + const memoryKey = nodeData.inputs?.memoryKey as string + const inputKey = nodeData.inputs?.inputKey as string + + const obj: ConversationSummaryMemoryInput = { + llm: model, + returnMessages: true, + memoryKey, + inputKey + } + + return new ConversationSummaryMemory(obj) + } +} + +module.exports = { nodeClass: ConversationSummaryMemory_Memory } diff --git a/packages/components/nodes/memory/ConversationSummaryMemory/memory.svg b/packages/components/nodes/memory/ConversationSummaryMemory/memory.svg new file mode 100644 index 00000000..ca8e17da --- /dev/null +++ b/packages/components/nodes/memory/ConversationSummaryMemory/memory.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From baf638d0c0ffd9af1608589d70261778fa524b1c Mon Sep 17 00:00:00 2001 From: xianjianlf2 Date: Wed, 24 May 2023 11:09:40 +0800 Subject: [PATCH 006/398] style: improve AddNodes click --- packages/ui/src/views/canvas/AddNodes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/views/canvas/AddNodes.js b/packages/ui/src/views/canvas/AddNodes.js index 968b2c06..50978700 100644 --- a/packages/ui/src/views/canvas/AddNodes.js +++ b/packages/ui/src/views/canvas/AddNodes.js @@ -218,6 +218,7 @@ const AddNodes = ({ nodesData, node }) => { expanded={categoryExpanded[category] || false} onChange={handleAccordionChange(category)} key={category} + disableGutters > } From 0ed84312523d0c4d95a41b93f231d57911138664 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 24 May 2023 13:27:38 +0100 Subject: [PATCH 007/398] fix Cohere API key not found --- .../components/nodes/llms/Cohere/Cohere.ts | 2 +- packages/components/nodes/llms/Cohere/core.ts | 78 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 packages/components/nodes/llms/Cohere/core.ts diff --git a/packages/components/nodes/llms/Cohere/Cohere.ts b/packages/components/nodes/llms/Cohere/Cohere.ts index dc632ec3..a7e9c696 100644 --- a/packages/components/nodes/llms/Cohere/Cohere.ts +++ b/packages/components/nodes/llms/Cohere/Cohere.ts @@ -1,6 +1,6 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { Cohere, CohereInput } from 'langchain/llms/cohere' +import { Cohere, CohereInput } from './core' class Cohere_LLMs implements INode { label: string diff --git a/packages/components/nodes/llms/Cohere/core.ts b/packages/components/nodes/llms/Cohere/core.ts new file mode 100644 index 00000000..97c81571 --- /dev/null +++ b/packages/components/nodes/llms/Cohere/core.ts @@ -0,0 +1,78 @@ +import { LLM, BaseLLMParams } from 'langchain/llms/base' + +export interface CohereInput extends BaseLLMParams { + /** Sampling temperature to use */ + temperature?: number + + /** + * Maximum number of tokens to generate in the completion. + */ + maxTokens?: number + + /** Model to use */ + model?: string + + apiKey?: string +} + +export class Cohere extends LLM implements CohereInput { + temperature = 0 + + maxTokens = 250 + + model: string + + apiKey: string + + constructor(fields?: CohereInput) { + super(fields ?? {}) + + const apiKey = fields?.apiKey ?? undefined + + if (!apiKey) { + throw new Error('Please set the COHERE_API_KEY environment variable or pass it to the constructor as the apiKey field.') + } + + this.apiKey = apiKey + this.maxTokens = fields?.maxTokens ?? this.maxTokens + this.temperature = fields?.temperature ?? this.temperature + this.model = fields?.model ?? this.model + } + + _llmType() { + return 'cohere' + } + + /** @ignore */ + async _call(prompt: string, options: this['ParsedCallOptions']): Promise { + const { cohere } = await Cohere.imports() + + cohere.init(this.apiKey) + + // Hit the `generate` endpoint on the `large` model + const generateResponse = await this.caller.callWithOptions({ signal: options.signal }, cohere.generate.bind(cohere), { + prompt, + model: this.model, + max_tokens: this.maxTokens, + temperature: this.temperature, + end_sequences: options.stop + }) + try { + return generateResponse.body.generations[0].text + } catch { + throw new Error('Could not parse response.') + } + } + + /** @ignore */ + static async imports(): Promise<{ + cohere: typeof import('cohere-ai') + }> { + try { + const { default: cohere } = await import('cohere-ai') + return { cohere } + } catch (e) { + throw new Error('Please install cohere-ai as a dependency with, e.g. `yarn add cohere-ai`') + } + } +} From 3ce4c3b167f610bae48f7a1ca62bbdb32e8add31 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 24 May 2023 13:39:47 +0100 Subject: [PATCH 008/398] add serper --- .../components/nodes/tools/Serper/Serper.ts | 38 ++++++++++++++++++ .../components/nodes/tools/Serper/serper.png | Bin 0 -> 2422 bytes 2 files changed, 38 insertions(+) create mode 100644 packages/components/nodes/tools/Serper/Serper.ts create mode 100644 packages/components/nodes/tools/Serper/serper.png diff --git a/packages/components/nodes/tools/Serper/Serper.ts b/packages/components/nodes/tools/Serper/Serper.ts new file mode 100644 index 00000000..65dff57c --- /dev/null +++ b/packages/components/nodes/tools/Serper/Serper.ts @@ -0,0 +1,38 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { Serper } from 'langchain/tools' + +class Serper_Tools implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Serper' + this.name = 'serper' + this.type = 'Serper' + this.icon = 'serper.png' + this.category = 'Tools' + this.description = 'Wrapper around Serper.dev - Google Search API' + this.inputs = [ + { + label: 'Serper Api Key', + name: 'apiKey', + type: 'password' + } + ] + this.baseClasses = [this.type, ...getBaseClasses(Serper)] + } + + async init(nodeData: INodeData): Promise { + const apiKey = nodeData.inputs?.apiKey as string + return new Serper(apiKey) + } +} + +module.exports = { nodeClass: Serper_Tools } diff --git a/packages/components/nodes/tools/Serper/serper.png b/packages/components/nodes/tools/Serper/serper.png new file mode 100644 index 0000000000000000000000000000000000000000..0b094037b067f0f61b25fbd03e54d49888320f37 GIT binary patch literal 2422 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 NmS(o5k4(JN{szcy8TtSK literal 0 HcmV?d00001 From 47ef5b35184de4a5bdf0419b411c4a9b3e5be76c Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Wed, 24 May 2023 21:12:31 +0700 Subject: [PATCH 009/398] modify model limitation from BaseLanguageModel to BaseChatModel --- .../ConversationSummaryMemory/ConversationSummaryMemory.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts b/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts index 3a94b657..3c055e8e 100644 --- a/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts +++ b/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts @@ -19,13 +19,13 @@ class ConversationSummaryMemory_Memory implements INode { this.type = 'ConversationSummaryMemory' this.icon = 'memory.svg' this.category = 'Memory' - this.description = 'Remembers previous conversational back and forths directly' + this.description = 'Summarizes the conversation and stores the current summary in memory' this.baseClasses = [this.type, ...getBaseClasses(ConversationSummaryMemory)] this.inputs = [ { - label: 'Language Model', + label: 'Chat Model', name: 'model', - type: 'BaseLanguageModel' + type: 'BaseChatModel' }, { label: 'Memory Key', From baf958f5c26c7bc5ac72a0d9f0819aea078c4f00 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Wed, 24 May 2023 21:41:30 +0700 Subject: [PATCH 010/398] add description --- .../nodes/memory/BufferWindowMemory/BufferWindowMemory.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts index 0f6a9542..ae783fec 100644 --- a/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts +++ b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts @@ -37,7 +37,8 @@ class BufferWindowMemory_Memory implements INode { label: 'Size', name: 'k', type: 'number', - default: '4' + default: '4', + description: 'Window of size k to surface the last k back-and-forths to use as memory.' } ] } From 0ea651e7c513aee9e9eef67840d97f0f998206cc Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 24 May 2023 19:04:47 +0100 Subject: [PATCH 011/398] update marketplaces --- .../Conversational Retrieval QA Chain.json | 13 ++++++++++++- packages/server/marketplaces/Github Repo QnA.json | 13 ++++++++++++- .../server/marketplaces/Metadata Filter Load.json | 13 ++++++++++++- .../server/marketplaces/Metadata Filter Upsert.json | 13 ++++++++++++- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/server/marketplaces/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/Conversational Retrieval QA Chain.json index daa5235e..584a175a 100644 --- a/packages/server/marketplaces/Conversational Retrieval QA Chain.json +++ b/packages/server/marketplaces/Conversational Retrieval QA Chain.json @@ -444,7 +444,18 @@ "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "BaseLangChain"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", - "inputParams": [], + "inputParams": [ + { + "label": "System Message", + "name": "systemMessagePrompt", + "type": "string", + "rows": 4, + "additionalParams": true, + "optional": true, + "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", + "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + } + ], "inputAnchors": [ { "label": "Language Model", diff --git a/packages/server/marketplaces/Github Repo QnA.json b/packages/server/marketplaces/Github Repo QnA.json index a198bbfc..debc2aad 100644 --- a/packages/server/marketplaces/Github Repo QnA.json +++ b/packages/server/marketplaces/Github Repo QnA.json @@ -461,7 +461,18 @@ "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "BaseLangChain"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", - "inputParams": [], + "inputParams": [ + { + "label": "System Message", + "name": "systemMessagePrompt", + "type": "string", + "rows": 4, + "additionalParams": true, + "optional": true, + "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", + "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + } + ], "inputAnchors": [ { "label": "Language Model", diff --git a/packages/server/marketplaces/Metadata Filter Load.json b/packages/server/marketplaces/Metadata Filter Load.json index cacabe71..c496b199 100644 --- a/packages/server/marketplaces/Metadata Filter Load.json +++ b/packages/server/marketplaces/Metadata Filter Load.json @@ -338,7 +338,18 @@ "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "BaseLangChain"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", - "inputParams": [], + "inputParams": [ + { + "label": "System Message", + "name": "systemMessagePrompt", + "type": "string", + "rows": 4, + "additionalParams": true, + "optional": true, + "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", + "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + } + ], "inputAnchors": [ { "label": "Language Model", diff --git a/packages/server/marketplaces/Metadata Filter Upsert.json b/packages/server/marketplaces/Metadata Filter Upsert.json index ab66cf74..d0335f16 100644 --- a/packages/server/marketplaces/Metadata Filter Upsert.json +++ b/packages/server/marketplaces/Metadata Filter Upsert.json @@ -542,7 +542,18 @@ "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "BaseLangChain"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", - "inputParams": [], + "inputParams": [ + { + "label": "System Message", + "name": "systemMessagePrompt", + "type": "string", + "rows": 4, + "additionalParams": true, + "optional": true, + "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", + "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + } + ], "inputAnchors": [ { "label": "Language Model", From 1d42456ba1d94cf898d8dfe8559bea30ddaac701 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 25 May 2023 00:36:09 +0100 Subject: [PATCH 012/398] add multi prompt chain --- .../ConversationChain/ConversationChain.ts | 3 +- .../MultiPromptChain/MultiPromptChain.ts | 68 +++ .../nodes/chains/MultiPromptChain/chain.svg | 6 + .../PromptRetriever/PromptRetriever.ts | 62 +++ .../PromptRetriever/promptretriever.svg | 8 + packages/components/src/Interface.ts | 21 + .../marketplaces/Multi Prompt Chain.json | 442 ++++++++++++++++++ 7 files changed, 609 insertions(+), 1 deletion(-) create mode 100644 packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts create mode 100644 packages/components/nodes/chains/MultiPromptChain/chain.svg create mode 100644 packages/components/nodes/retrievers/PromptRetriever/PromptRetriever.ts create mode 100644 packages/components/nodes/retrievers/PromptRetriever/promptretriever.svg create mode 100644 packages/server/marketplaces/Multi Prompt Chain.json diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index 19e28752..2233ebf9 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -56,7 +56,8 @@ class ConversationChain_Chains implements INode { const obj: any = { llm: model, - memory + memory, + verbose: process.env.DEBUG === 'true' ? true : false } const chatPrompt = ChatPromptTemplate.fromPromptMessages([ diff --git a/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts new file mode 100644 index 00000000..a7b00f46 --- /dev/null +++ b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts @@ -0,0 +1,68 @@ +import { BaseLanguageModel } from 'langchain/base_language' +import { INode, INodeData, INodeParams, PromptRetriever } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { MultiPromptChain } from 'langchain/chains' + +class MultiPromptChain_Chains implements INode { + label: string + name: string + type: string + icon: string + category: string + baseClasses: string[] + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Multi Prompt Chain' + this.name = 'multiPromptChain' + this.type = 'MultiPromptChain' + this.icon = 'chain.svg' + this.category = 'Chains' + this.description = 'Chain automatically picks an appropriate prompt from multiple prompt templates' + this.baseClasses = [this.type, ...getBaseClasses(MultiPromptChain)] + this.inputs = [ + { + label: 'Language Model', + name: 'model', + type: 'BaseLanguageModel' + }, + { + label: 'Prompt Retriever', + name: 'promptRetriever', + type: 'PromptRetriever', + list: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const model = nodeData.inputs?.model as BaseLanguageModel + const promptRetriever = nodeData.inputs?.promptRetriever as PromptRetriever[] + const promptNames = [] + const promptDescriptions = [] + const promptTemplates = [] + + for (const prompt of promptRetriever) { + promptNames.push(prompt.name) + promptDescriptions.push(prompt.description) + promptTemplates.push(prompt.systemMessage) + } + + const chain = MultiPromptChain.fromPrompts(model, promptNames, promptDescriptions, promptTemplates, undefined, { + verbose: process.env.DEBUG === 'true' ? true : false + } as any) + + return chain + } + + async run(nodeData: INodeData, input: string): Promise { + const chain = nodeData.instance as MultiPromptChain + + const res = await chain.call({ input }) + + return res?.text + } +} + +module.exports = { nodeClass: MultiPromptChain_Chains } diff --git a/packages/components/nodes/chains/MultiPromptChain/chain.svg b/packages/components/nodes/chains/MultiPromptChain/chain.svg new file mode 100644 index 00000000..a5b32f90 --- /dev/null +++ b/packages/components/nodes/chains/MultiPromptChain/chain.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/components/nodes/retrievers/PromptRetriever/PromptRetriever.ts b/packages/components/nodes/retrievers/PromptRetriever/PromptRetriever.ts new file mode 100644 index 00000000..e3b9a4ac --- /dev/null +++ b/packages/components/nodes/retrievers/PromptRetriever/PromptRetriever.ts @@ -0,0 +1,62 @@ +import { INode, INodeData, INodeParams, PromptRetriever, PromptRetrieverInput } from '../../../src/Interface' + +class PromptRetriever_Retrievers implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Prompt Retriever' + this.name = 'promptRetriever' + this.type = 'PromptRetriever' + this.icon = 'promptretriever.svg' + this.category = 'Retrievers' + this.description = 'Store prompt template with name & description to be later queried by MultiPromptChain' + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Prompt Name', + name: 'name', + type: 'string', + placeholder: 'physics-qa' + }, + { + label: 'Prompt Description', + name: 'description', + type: 'string', + rows: 3, + description: 'Description of what the prompt does and when it should be used', + placeholder: 'Good for answering questions about physics' + }, + { + label: 'Prompt System Message', + name: 'systemMessage', + type: 'string', + rows: 4, + placeholder: `You are a very smart physics professor. You are great at answering questions about physics in a concise and easy to understand manner. When you don't know the answer to a question you admit that you don't know.` + } + ] + } + + async init(nodeData: INodeData): Promise { + const name = nodeData.inputs?.name as string + const description = nodeData.inputs?.description as string + const systemMessage = nodeData.inputs?.systemMessage as string + + const obj = { + name, + description, + systemMessage + } as PromptRetrieverInput + + const retriever = new PromptRetriever(obj) + return retriever + } +} + +module.exports = { nodeClass: PromptRetriever_Retrievers } diff --git a/packages/components/nodes/retrievers/PromptRetriever/promptretriever.svg b/packages/components/nodes/retrievers/PromptRetriever/promptretriever.svg new file mode 100644 index 00000000..db48e8a5 --- /dev/null +++ b/packages/components/nodes/retrievers/PromptRetriever/promptretriever.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index 181cc206..ea3d1f0d 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -103,3 +103,24 @@ export class PromptTemplate extends LangchainPromptTemplate { super(input) } } + +export interface PromptRetrieverInput { + name: string + description: string + systemMessage: string +} + +const fixedTemplate = `Here is a question: +{input} +` +export class PromptRetriever { + name: string + description: string + systemMessage: string + + constructor(fields: PromptRetrieverInput) { + this.name = fields.name + this.description = fields.description + this.systemMessage = `${fields.systemMessage}\n${fixedTemplate}` + } +} diff --git a/packages/server/marketplaces/Multi Prompt Chain.json b/packages/server/marketplaces/Multi Prompt Chain.json new file mode 100644 index 00000000..fa99cd91 --- /dev/null +++ b/packages/server/marketplaces/Multi Prompt Chain.json @@ -0,0 +1,442 @@ +{ + "description": "A chain that automatically picks an appropriate prompt from multiple prompts", + "nodes": [ + { + "width": 300, + "height": 632, + "id": "promptRetriever_0", + "position": { + "x": 197.46642699727397, + "y": 25.945621297410923 + }, + "type": "customNode", + "data": { + "id": "promptRetriever_0", + "label": "Prompt Retriever", + "name": "promptRetriever", + "type": "PromptRetriever", + "baseClasses": ["PromptRetriever"], + "category": "Retrievers", + "description": "Store prompt template with name & description to be later queried by MultiPromptChain", + "inputParams": [ + { + "label": "Prompt Name", + "name": "name", + "type": "string", + "placeholder": "physics-qa", + "id": "promptRetriever_0-input-name-string" + }, + { + "label": "Prompt Description", + "name": "description", + "type": "string", + "rows": 3, + "description": "Description of what the prompt does and when it should be used", + "placeholder": "Good for answering questions about physics", + "id": "promptRetriever_0-input-description-string" + }, + { + "label": "Prompt System Message", + "name": "systemMessage", + "type": "string", + "rows": 4, + "placeholder": "You are a very smart physics professor. You are great at answering questions about physics in a concise and easy to understand manner. When you don't know the answer to a question you admit that you don't know.", + "id": "promptRetriever_0-input-systemMessage-string" + } + ], + "inputAnchors": [], + "inputs": { + "name": "physics", + "description": "Good for answering questions about physics", + "systemMessage": "You are a very smart physics professor. You are great at answering questions about physics in a concise and easy to understand manner. When you don't know the answer to a question you admit that you don't know." + }, + "outputAnchors": [ + { + "id": "promptRetriever_0-output-promptRetriever-PromptRetriever", + "name": "promptRetriever", + "label": "PromptRetriever", + "type": "PromptRetriever" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 197.46642699727397, + "y": 25.945621297410923 + }, + "dragging": false + }, + { + "width": 300, + "height": 280, + "id": "multiPromptChain_0", + "position": { + "x": 1619.1305522575494, + "y": 210.28103293821243 + }, + "type": "customNode", + "data": { + "id": "multiPromptChain_0", + "label": "Multi Prompt Chain", + "name": "multiPromptChain", + "type": "MultiPromptChain", + "baseClasses": ["MultiPromptChain", "MultiRouteChain", "BaseChain", "BaseLangChain"], + "category": "Chains", + "description": "Chain automatically picks an appropriate prompt from multiple prompt templates", + "inputParams": [], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "multiPromptChain_0-input-model-BaseLanguageModel" + }, + { + "label": "Prompt Retriever", + "name": "promptRetriever", + "type": "PromptRetriever", + "list": true, + "id": "multiPromptChain_0-input-promptRetriever-PromptRetriever" + } + ], + "inputs": { + "model": "{{chatOpenAI_0.data.instance}}", + "promptRetriever": [ + "{{promptRetriever_0.data.instance}}", + "{{promptRetriever_2.data.instance}}", + "{{promptRetriever_1.data.instance}}" + ] + }, + "outputAnchors": [ + { + "id": "multiPromptChain_0-output-multiPromptChain-MultiPromptChain|MultiRouteChain|BaseChain|BaseLangChain", + "name": "multiPromptChain", + "label": "MultiPromptChain", + "type": "MultiPromptChain | MultiRouteChain | BaseChain | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "positionAbsolute": { + "x": 1619.1305522575494, + "y": 210.28103293821243 + }, + "selected": false, + "dragging": false + }, + { + "width": 300, + "height": 632, + "id": "promptRetriever_1", + "position": { + "x": 539.1322780233141, + "y": -250.72967142925938 + }, + "type": "customNode", + "data": { + "id": "promptRetriever_1", + "label": "Prompt Retriever", + "name": "promptRetriever", + "type": "PromptRetriever", + "baseClasses": ["PromptRetriever"], + "category": "Retrievers", + "description": "Store prompt template with name & description to be later queried by MultiPromptChain", + "inputParams": [ + { + "label": "Prompt Name", + "name": "name", + "type": "string", + "placeholder": "physics-qa", + "id": "promptRetriever_1-input-name-string" + }, + { + "label": "Prompt Description", + "name": "description", + "type": "string", + "rows": 3, + "description": "Description of what the prompt does and when it should be used", + "placeholder": "Good for answering questions about physics", + "id": "promptRetriever_1-input-description-string" + }, + { + "label": "Prompt System Message", + "name": "systemMessage", + "type": "string", + "rows": 4, + "placeholder": "You are a very smart physics professor. You are great at answering questions about physics in a concise and easy to understand manner. When you don't know the answer to a question you admit that you don't know.", + "id": "promptRetriever_1-input-systemMessage-string" + } + ], + "inputAnchors": [], + "inputs": { + "name": "math", + "description": "Good for answering math questions", + "systemMessage": "You are a very good mathematician. You are great at answering math questions. You are so good because you are able to break down hard problems into their component parts, answer the component parts, and then put them together to answer the broader question." + }, + "outputAnchors": [ + { + "id": "promptRetriever_1-output-promptRetriever-PromptRetriever", + "name": "promptRetriever", + "label": "PromptRetriever", + "type": "PromptRetriever" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 539.1322780233141, + "y": -250.72967142925938 + }, + "dragging": false + }, + { + "width": 300, + "height": 632, + "id": "promptRetriever_2", + "position": { + "x": 872.6184534864304, + "y": -366.9443140594265 + }, + "type": "customNode", + "data": { + "id": "promptRetriever_2", + "label": "Prompt Retriever", + "name": "promptRetriever", + "type": "PromptRetriever", + "baseClasses": ["PromptRetriever"], + "category": "Retrievers", + "description": "Store prompt template with name & description to be later queried by MultiPromptChain", + "inputParams": [ + { + "label": "Prompt Name", + "name": "name", + "type": "string", + "placeholder": "physics-qa", + "id": "promptRetriever_2-input-name-string" + }, + { + "label": "Prompt Description", + "name": "description", + "type": "string", + "rows": 3, + "description": "Description of what the prompt does and when it should be used", + "placeholder": "Good for answering questions about physics", + "id": "promptRetriever_2-input-description-string" + }, + { + "label": "Prompt System Message", + "name": "systemMessage", + "type": "string", + "rows": 4, + "placeholder": "You are a very smart physics professor. You are great at answering questions about physics in a concise and easy to understand manner. When you don't know the answer to a question you admit that you don't know.", + "id": "promptRetriever_2-input-systemMessage-string" + } + ], + "inputAnchors": [], + "inputs": { + "name": "history", + "description": "Good for answering questions about history", + "systemMessage": "You are a very smart history professor. You are great at answering questions about history in a concise and easy to understand manner. When you don't know the answer to a question you admit that you don't know." + }, + "outputAnchors": [ + { + "id": "promptRetriever_2-output-promptRetriever-PromptRetriever", + "name": "promptRetriever", + "label": "PromptRetriever", + "type": "PromptRetriever" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 872.6184534864304, + "y": -366.9443140594265 + }, + "dragging": false + }, + { + "width": 300, + "height": 524, + "id": "chatOpenAI_0", + "position": { + "x": 1230.07368145571, + "y": -296.44522826934826 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "BaseLangChain"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password", + "id": "chatOpenAI_0-input-openAIApiKey-password" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-0314", + "name": "gpt-4-0314" + }, + { + "label": "gpt-4-32k-0314", + "name": "gpt-4-32k-0314" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-0301", + "name": "gpt-3.5-turbo-0301" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_0-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.9, + "optional": true, + "id": "chatOpenAI_0-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-timeout-number" + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "gpt-3.5-turbo", + "temperature": 0.9, + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1230.07368145571, + "y": -296.44522826934826 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "promptRetriever_0", + "sourceHandle": "promptRetriever_0-output-promptRetriever-PromptRetriever", + "target": "multiPromptChain_0", + "targetHandle": "multiPromptChain_0-input-promptRetriever-PromptRetriever", + "type": "buttonedge", + "id": "promptRetriever_0-promptRetriever_0-output-promptRetriever-PromptRetriever-multiPromptChain_0-multiPromptChain_0-input-promptRetriever-PromptRetriever", + "data": { + "label": "" + } + }, + { + "source": "promptRetriever_2", + "sourceHandle": "promptRetriever_2-output-promptRetriever-PromptRetriever", + "target": "multiPromptChain_0", + "targetHandle": "multiPromptChain_0-input-promptRetriever-PromptRetriever", + "type": "buttonedge", + "id": "promptRetriever_2-promptRetriever_2-output-promptRetriever-PromptRetriever-multiPromptChain_0-multiPromptChain_0-input-promptRetriever-PromptRetriever", + "data": { + "label": "" + } + }, + { + "source": "promptRetriever_1", + "sourceHandle": "promptRetriever_1-output-promptRetriever-PromptRetriever", + "target": "multiPromptChain_0", + "targetHandle": "multiPromptChain_0-input-promptRetriever-PromptRetriever", + "type": "buttonedge", + "id": "promptRetriever_1-promptRetriever_1-output-promptRetriever-PromptRetriever-multiPromptChain_0-multiPromptChain_0-input-promptRetriever-PromptRetriever", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain", + "target": "multiPromptChain_0", + "targetHandle": "multiPromptChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain-multiPromptChain_0-multiPromptChain_0-input-model-BaseLanguageModel", + "data": { + "label": "" + } + } + ] +} From 769b4ea567baa8e78ed3e4ac0466aea7036c6c51 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 25 May 2023 13:50:11 +0100 Subject: [PATCH 013/398] add multi retrieval qa chain --- .../MultiRetrievalQAChain.ts | 68 ++ .../chains/MultiRetrievalQAChain/chain.svg | 6 + .../VectorStoreRetriever.ts | 61 ++ .../VectorStoreRetriever/vectorretriever.svg | 9 + packages/components/src/Interface.ts | 19 + .../Multi Retrieval QA Chain.json | 859 ++++++++++++++++++ .../marketplaces/Multiple VectorDB.json | 2 +- 7 files changed, 1023 insertions(+), 1 deletion(-) create mode 100644 packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts create mode 100644 packages/components/nodes/chains/MultiRetrievalQAChain/chain.svg create mode 100644 packages/components/nodes/retrievers/VectorStoreRetriever/VectorStoreRetriever.ts create mode 100644 packages/components/nodes/retrievers/VectorStoreRetriever/vectorretriever.svg create mode 100644 packages/server/marketplaces/Multi Retrieval QA Chain.json diff --git a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts new file mode 100644 index 00000000..7b8778f1 --- /dev/null +++ b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts @@ -0,0 +1,68 @@ +import { BaseLanguageModel } from 'langchain/base_language' +import { INode, INodeData, INodeParams, VectorStoreRetriever } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { MultiRetrievalQAChain } from 'langchain/chains' + +class MultiRetrievalQAChain_Chains implements INode { + label: string + name: string + type: string + icon: string + category: string + baseClasses: string[] + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Multi Retrieval QA Chain' + this.name = 'multiRetrievalQAChain' + this.type = 'MultiRetrievalQAChain' + this.icon = 'chain.svg' + this.category = 'Chains' + this.description = 'QA Chain that automatically picks an appropriate vector store from multiple retrievers' + this.baseClasses = [this.type, ...getBaseClasses(MultiRetrievalQAChain)] + this.inputs = [ + { + label: 'Language Model', + name: 'model', + type: 'BaseLanguageModel' + }, + { + label: 'Vector Store Retriever', + name: 'vectorStoreRetriever', + type: 'VectorStoreRetriever', + list: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const model = nodeData.inputs?.model as BaseLanguageModel + const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as VectorStoreRetriever[] + const retrieverNames = [] + const retrieverDescriptions = [] + const retrievers = [] + + for (const vs of vectorStoreRetriever) { + retrieverNames.push(vs.name) + retrieverDescriptions.push(vs.description) + retrievers.push(vs.vectorStore.asRetriever()) + } + + const chain = MultiRetrievalQAChain.fromRetrievers(model, retrieverNames, retrieverDescriptions, retrievers, undefined, { + verbose: process.env.DEBUG === 'true' ? true : false + } as any) + + return chain + } + + async run(nodeData: INodeData, input: string): Promise { + const chain = nodeData.instance as MultiRetrievalQAChain + + const res = await chain.call({ input }) + + return res?.text + } +} + +module.exports = { nodeClass: MultiRetrievalQAChain_Chains } diff --git a/packages/components/nodes/chains/MultiRetrievalQAChain/chain.svg b/packages/components/nodes/chains/MultiRetrievalQAChain/chain.svg new file mode 100644 index 00000000..a5b32f90 --- /dev/null +++ b/packages/components/nodes/chains/MultiRetrievalQAChain/chain.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/components/nodes/retrievers/VectorStoreRetriever/VectorStoreRetriever.ts b/packages/components/nodes/retrievers/VectorStoreRetriever/VectorStoreRetriever.ts new file mode 100644 index 00000000..2ccfc995 --- /dev/null +++ b/packages/components/nodes/retrievers/VectorStoreRetriever/VectorStoreRetriever.ts @@ -0,0 +1,61 @@ +import { VectorStore } from 'langchain/vectorstores/base' +import { INode, INodeData, INodeParams, VectorStoreRetriever, VectorStoreRetrieverInput } from '../../../src/Interface' + +class VectorStoreRetriever_Retrievers implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Vector Store Retriever' + this.name = 'vectorStoreRetriever' + this.type = 'VectorStoreRetriever' + this.icon = 'vectorretriever.svg' + this.category = 'Retrievers' + this.description = 'Store vector store as retriever to be later queried by MultiRetrievalQAChain' + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Vector Store', + name: 'vectorStore', + type: 'VectorStore' + }, + { + label: 'Retriever Name', + name: 'name', + type: 'string', + placeholder: 'netflix movies' + }, + { + label: 'Retriever Description', + name: 'description', + type: 'string', + rows: 3, + description: 'Description of when to use the vector store retriever', + placeholder: 'Good for answering questions about netflix movies' + } + ] + } + + async init(nodeData: INodeData): Promise { + const name = nodeData.inputs?.name as string + const description = nodeData.inputs?.description as string + const vectorStore = nodeData.inputs?.vectorStore as VectorStore + + const obj = { + name, + description, + vectorStore + } as VectorStoreRetrieverInput + + const retriever = new VectorStoreRetriever(obj) + return retriever + } +} + +module.exports = { nodeClass: VectorStoreRetriever_Retrievers } diff --git a/packages/components/nodes/retrievers/VectorStoreRetriever/vectorretriever.svg b/packages/components/nodes/retrievers/VectorStoreRetriever/vectorretriever.svg new file mode 100644 index 00000000..da3a9f20 --- /dev/null +++ b/packages/components/nodes/retrievers/VectorStoreRetriever/vectorretriever.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index ea3d1f0d..c1411939 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -95,6 +95,7 @@ export interface IMessage { */ import { PromptTemplate as LangchainPromptTemplate, PromptTemplateInput } from 'langchain/prompts' +import { VectorStore } from 'langchain/vectorstores/base' export class PromptTemplate extends LangchainPromptTemplate { promptValues: ICommonObject @@ -124,3 +125,21 @@ export class PromptRetriever { this.systemMessage = `${fields.systemMessage}\n${fixedTemplate}` } } + +export interface VectorStoreRetrieverInput { + name: string + description: string + vectorStore: VectorStore +} + +export class VectorStoreRetriever { + name: string + description: string + vectorStore: VectorStore + + constructor(fields: VectorStoreRetrieverInput) { + this.name = fields.name + this.description = fields.description + this.vectorStore = fields.vectorStore + } +} diff --git a/packages/server/marketplaces/Multi Retrieval QA Chain.json b/packages/server/marketplaces/Multi Retrieval QA Chain.json new file mode 100644 index 00000000..48cddbf4 --- /dev/null +++ b/packages/server/marketplaces/Multi Retrieval QA Chain.json @@ -0,0 +1,859 @@ +{ + "description": "A chain that automatically picks an appropriate retriever from multiple different vector databases", + "nodes": [ + { + "width": 300, + "height": 505, + "id": "vectorStoreRetriever_0", + "position": { + "x": 712.9322670298264, + "y": 860.5462810572917 + }, + "type": "customNode", + "data": { + "id": "vectorStoreRetriever_0", + "label": "Vector Store Retriever", + "name": "vectorStoreRetriever", + "type": "VectorStoreRetriever", + "baseClasses": ["VectorStoreRetriever"], + "category": "Retrievers", + "description": "Store vector store as retriever. Used with MultiRetrievalQAChain", + "inputParams": [ + { + "label": "Retriever Name", + "name": "name", + "type": "string", + "placeholder": "netflix movies", + "id": "vectorStoreRetriever_0-input-name-string" + }, + { + "label": "Retriever Description", + "name": "description", + "type": "string", + "rows": 3, + "description": "Description of when to use the vector store retriever", + "placeholder": "Good for answering questions about netflix movies", + "id": "vectorStoreRetriever_0-input-description-string" + } + ], + "inputAnchors": [ + { + "label": "Vector Store", + "name": "vectorStore", + "type": "VectorStore", + "id": "vectorStoreRetriever_0-input-vectorStore-VectorStore" + } + ], + "inputs": { + "vectorStore": "{{supabaseExistingIndex_0.data.instance}}", + "name": "aqua teen", + "description": "Good for answering questions about Aqua Teen Hunger Force theme song" + }, + "outputAnchors": [ + { + "id": "vectorStoreRetriever_0-output-vectorStoreRetriever-VectorStoreRetriever", + "name": "vectorStoreRetriever", + "label": "VectorStoreRetriever", + "type": "VectorStoreRetriever" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 712.9322670298264, + "y": 860.5462810572917 + }, + "dragging": false + }, + { + "width": 300, + "height": 280, + "id": "multiRetrievalQAChain_0", + "position": { + "x": 1563.0150452201099, + "y": 460.78375893303934 + }, + "type": "customNode", + "data": { + "id": "multiRetrievalQAChain_0", + "label": "Multi Retrieval QA Chain", + "name": "multiRetrievalQAChain", + "type": "MultiRetrievalQAChain", + "baseClasses": ["MultiRetrievalQAChain", "MultiRouteChain", "BaseChain", "BaseLangChain"], + "category": "Chains", + "description": "QA Chain that automatically picks an appropriate vector store from multiple retrievers", + "inputParams": [], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "multiRetrievalQAChain_0-input-model-BaseLanguageModel" + }, + { + "label": "Vector Store Retriever", + "name": "vectorStoreRetriever", + "type": "VectorStoreRetriever", + "list": true, + "id": "multiRetrievalQAChain_0-input-vectorStoreRetriever-VectorStoreRetriever" + } + ], + "inputs": { + "model": "{{chatOpenAI_0.data.instance}}", + "vectorStoreRetriever": [ + "{{vectorStoreRetriever_0.data.instance}}", + "{{vectorStoreRetriever_1.data.instance}}", + "{{vectorStoreRetriever_2.data.instance}}" + ] + }, + "outputAnchors": [ + { + "id": "multiRetrievalQAChain_0-output-multiRetrievalQAChain-MultiRetrievalQAChain|MultiRouteChain|BaseChain|BaseLangChain", + "name": "multiRetrievalQAChain", + "label": "MultiRetrievalQAChain", + "type": "MultiRetrievalQAChain | MultiRouteChain | BaseChain | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1563.0150452201099, + "y": 460.78375893303934 + }, + "dragging": false + }, + { + "width": 300, + "height": 505, + "id": "vectorStoreRetriever_1", + "position": { + "x": 711.4902931206071, + "y": 315.2414600651632 + }, + "type": "customNode", + "data": { + "id": "vectorStoreRetriever_1", + "label": "Vector Store Retriever", + "name": "vectorStoreRetriever", + "type": "VectorStoreRetriever", + "baseClasses": ["VectorStoreRetriever"], + "category": "Retrievers", + "description": "Store vector store as retriever. Used with MultiRetrievalQAChain", + "inputParams": [ + { + "label": "Retriever Name", + "name": "name", + "type": "string", + "placeholder": "netflix movies", + "id": "vectorStoreRetriever_1-input-name-string" + }, + { + "label": "Retriever Description", + "name": "description", + "type": "string", + "rows": 3, + "description": "Description of when to use the vector store retriever", + "placeholder": "Good for answering questions about netflix movies", + "id": "vectorStoreRetriever_1-input-description-string" + } + ], + "inputAnchors": [ + { + "label": "Vector Store", + "name": "vectorStore", + "type": "VectorStore", + "id": "vectorStoreRetriever_1-input-vectorStore-VectorStore" + } + ], + "inputs": { + "vectorStore": "{{chromaExistingIndex_0.data.instance}}", + "name": "mst3k", + "description": "Good for answering questions about Mystery Science Theater 3000 theme song" + }, + "outputAnchors": [ + { + "id": "vectorStoreRetriever_1-output-vectorStoreRetriever-VectorStoreRetriever", + "name": "vectorStoreRetriever", + "label": "VectorStoreRetriever", + "type": "VectorStoreRetriever" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 711.4902931206071, + "y": 315.2414600651632 + }, + "dragging": false + }, + { + "width": 300, + "height": 505, + "id": "vectorStoreRetriever_2", + "position": { + "x": 706.0716220151372, + "y": -217.51566869136752 + }, + "type": "customNode", + "data": { + "id": "vectorStoreRetriever_2", + "label": "Vector Store Retriever", + "name": "vectorStoreRetriever", + "type": "VectorStoreRetriever", + "baseClasses": ["VectorStoreRetriever"], + "category": "Retrievers", + "description": "Store vector store as retriever. Used with MultiRetrievalQAChain", + "inputParams": [ + { + "label": "Retriever Name", + "name": "name", + "type": "string", + "placeholder": "netflix movies", + "id": "vectorStoreRetriever_2-input-name-string" + }, + { + "label": "Retriever Description", + "name": "description", + "type": "string", + "rows": 3, + "description": "Description of when to use the vector store retriever", + "placeholder": "Good for answering questions about netflix movies", + "id": "vectorStoreRetriever_2-input-description-string" + } + ], + "inputAnchors": [ + { + "label": "Vector Store", + "name": "vectorStore", + "type": "VectorStore", + "id": "vectorStoreRetriever_2-input-vectorStore-VectorStore" + } + ], + "inputs": { + "vectorStore": "{{pineconeExistingIndex_0.data.instance}}", + "name": "animaniacs", + "description": "Good for answering questions about Animaniacs theme song" + }, + "outputAnchors": [ + { + "id": "vectorStoreRetriever_2-output-vectorStoreRetriever-VectorStoreRetriever", + "name": "vectorStoreRetriever", + "label": "VectorStoreRetriever", + "type": "VectorStoreRetriever" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 706.0716220151372, + "y": -217.51566869136752 + }, + "dragging": false + }, + { + "width": 300, + "height": 524, + "id": "chatOpenAI_0", + "position": { + "x": 1206.027762600755, + "y": -212.35338654620222 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "BaseLangChain"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password", + "id": "chatOpenAI_0-input-openAIApiKey-password" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-0314", + "name": "gpt-4-0314" + }, + { + "label": "gpt-4-32k-0314", + "name": "gpt-4-32k-0314" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-0301", + "name": "gpt-3.5-turbo-0301" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_0-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.9, + "optional": true, + "id": "chatOpenAI_0-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-timeout-number" + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "gpt-3.5-turbo", + "temperature": 0.9, + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1206.027762600755, + "y": -212.35338654620222 + }, + "dragging": false + }, + { + "width": 300, + "height": 330, + "id": "openAIEmbeddings_0", + "position": { + "x": -254.88737984323413, + "y": 279.72801937636154 + }, + "type": "customNode", + "data": { + "id": "openAIEmbeddings_0", + "label": "OpenAI Embeddings", + "name": "openAIEmbeddings", + "type": "OpenAIEmbeddings", + "baseClasses": ["OpenAIEmbeddings", "Embeddings"], + "category": "Embeddings", + "description": "OpenAI API to generate embeddings for a given text", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password", + "id": "openAIEmbeddings_0-input-openAIApiKey-password" + }, + { + "label": "Strip New Lines", + "name": "stripNewLines", + "type": "boolean", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-stripNewLines-boolean" + }, + { + "label": "Batch Size", + "name": "batchSize", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-batchSize-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-timeout-number" + } + ], + "inputAnchors": [], + "inputs": { + "stripNewLines": "", + "batchSize": "", + "timeout": "" + }, + "outputAnchors": [ + { + "id": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "name": "openAIEmbeddings", + "label": "OpenAIEmbeddings", + "type": "OpenAIEmbeddings | Embeddings" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": -254.88737984323413, + "y": 279.72801937636154 + }, + "dragging": false + }, + { + "width": 300, + "height": 703, + "id": "pineconeExistingIndex_0", + "position": { + "x": 271.2513182410521, + "y": -410.32709109501735 + }, + "type": "customNode", + "data": { + "id": "pineconeExistingIndex_0", + "label": "Pinecone Load Existing Index", + "name": "pineconeExistingIndex", + "type": "Pinecone", + "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], + "category": "Vector Stores", + "description": "Load existing index from Pinecone (i.e: Document has been upserted)", + "inputParams": [ + { + "label": "Pinecone Api Key", + "name": "pineconeApiKey", + "type": "password", + "id": "pineconeExistingIndex_0-input-pineconeApiKey-password" + }, + { + "label": "Pinecone Environment", + "name": "pineconeEnv", + "type": "string", + "id": "pineconeExistingIndex_0-input-pineconeEnv-string" + }, + { + "label": "Pinecone Index", + "name": "pineconeIndex", + "type": "string", + "id": "pineconeExistingIndex_0-input-pineconeIndex-string" + }, + { + "label": "Pinecone Namespace", + "name": "pineconeNamespace", + "type": "string", + "placeholder": "my-first-namespace", + "optional": true, + "id": "pineconeExistingIndex_0-input-pineconeNamespace-string" + }, + { + "label": "Pinecone Metadata Filter", + "name": "pineconeMetadataFilter", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "pineconeExistingIndex_0-input-pineconeMetadataFilter-json" + } + ], + "inputAnchors": [ + { + "label": "Embeddings", + "name": "embeddings", + "type": "Embeddings", + "id": "pineconeExistingIndex_0-input-embeddings-Embeddings" + } + ], + "inputs": { + "embeddings": "{{openAIEmbeddings_0.data.instance}}", + "pineconeEnv": "", + "pineconeIndex": "", + "pineconeNamespace": "", + "pineconeMetadataFilter": "" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "pineconeExistingIndex_0-output-retriever-Pinecone|VectorStoreRetriever|BaseRetriever", + "name": "retriever", + "label": "Pinecone Retriever", + "type": "Pinecone | VectorStoreRetriever | BaseRetriever" + }, + { + "id": "pineconeExistingIndex_0-output-vectorStore-Pinecone|VectorStore", + "name": "vectorStore", + "label": "Pinecone Vector Store", + "type": "Pinecone | VectorStore" + } + ], + "default": "retriever" + } + ], + "outputs": { + "output": "vectorStore" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 271.2513182410521, + "y": -410.32709109501735 + }, + "dragging": false + }, + { + "width": 300, + "height": 454, + "id": "chromaExistingIndex_0", + "position": { + "x": 274.1430731555137, + "y": 335.15344698725556 + }, + "type": "customNode", + "data": { + "id": "chromaExistingIndex_0", + "label": "Chroma Load Existing Index", + "name": "chromaExistingIndex", + "type": "Chroma", + "baseClasses": ["Chroma", "VectorStoreRetriever", "BaseRetriever"], + "category": "Vector Stores", + "description": "Load existing index from Chroma (i.e: Document has been upserted)", + "inputParams": [ + { + "label": "Collection Name", + "name": "collectionName", + "type": "string", + "id": "chromaExistingIndex_0-input-collectionName-string" + }, + { + "label": "Chroma URL", + "name": "chromaURL", + "type": "string", + "optional": true, + "id": "chromaExistingIndex_0-input-chromaURL-string" + } + ], + "inputAnchors": [ + { + "label": "Embeddings", + "name": "embeddings", + "type": "Embeddings", + "id": "chromaExistingIndex_0-input-embeddings-Embeddings" + } + ], + "inputs": { + "embeddings": "{{openAIEmbeddings_0.data.instance}}", + "collectionName": "", + "chromaURL": "" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "chromaExistingIndex_0-output-retriever-Chroma|VectorStoreRetriever|BaseRetriever", + "name": "retriever", + "label": "Chroma Retriever", + "type": "Chroma | VectorStoreRetriever | BaseRetriever" + }, + { + "id": "chromaExistingIndex_0-output-vectorStore-Chroma|VectorStore", + "name": "vectorStore", + "label": "Chroma Vector Store", + "type": "Chroma | VectorStore" + } + ], + "default": "retriever" + } + ], + "outputs": { + "output": "vectorStore" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 274.1430731555137, + "y": 335.15344698725556 + }, + "dragging": false + }, + { + "width": 300, + "height": 703, + "id": "supabaseExistingIndex_0", + "position": { + "x": 273.7097153973373, + "y": 821.872758974335 + }, + "type": "customNode", + "data": { + "id": "supabaseExistingIndex_0", + "label": "Supabase Load Existing Index", + "name": "supabaseExistingIndex", + "type": "Supabase", + "baseClasses": ["Supabase", "VectorStoreRetriever", "BaseRetriever"], + "category": "Vector Stores", + "description": "Load existing index from Supabase (i.e: Document has been upserted)", + "inputParams": [ + { + "label": "Supabase API Key", + "name": "supabaseApiKey", + "type": "password", + "id": "supabaseExistingIndex_0-input-supabaseApiKey-password" + }, + { + "label": "Supabase Project URL", + "name": "supabaseProjUrl", + "type": "string", + "id": "supabaseExistingIndex_0-input-supabaseProjUrl-string" + }, + { + "label": "Table Name", + "name": "tableName", + "type": "string", + "id": "supabaseExistingIndex_0-input-tableName-string" + }, + { + "label": "Query Name", + "name": "queryName", + "type": "string", + "id": "supabaseExistingIndex_0-input-queryName-string" + }, + { + "label": "Supabase Metadata Filter", + "name": "supabaseMetadataFilter", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "supabaseExistingIndex_0-input-supabaseMetadataFilter-json" + } + ], + "inputAnchors": [ + { + "label": "Embeddings", + "name": "embeddings", + "type": "Embeddings", + "id": "supabaseExistingIndex_0-input-embeddings-Embeddings" + } + ], + "inputs": { + "embeddings": "{{openAIEmbeddings_0.data.instance}}", + "supabaseProjUrl": "", + "tableName": "", + "queryName": "", + "supabaseMetadataFilter": "" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "supabaseExistingIndex_0-output-retriever-Supabase|VectorStoreRetriever|BaseRetriever", + "name": "retriever", + "label": "Supabase Retriever", + "type": "Supabase | VectorStoreRetriever | BaseRetriever" + }, + { + "id": "supabaseExistingIndex_0-output-vectorStore-Supabase|VectorStore", + "name": "vectorStore", + "label": "Supabase Vector Store", + "type": "Supabase | VectorStore" + } + ], + "default": "retriever" + } + ], + "outputs": { + "output": "vectorStore" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 273.7097153973373, + "y": 821.872758974335 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "vectorStoreRetriever_0", + "sourceHandle": "vectorStoreRetriever_0-output-vectorStoreRetriever-VectorStoreRetriever", + "target": "multiRetrievalQAChain_0", + "targetHandle": "multiRetrievalQAChain_0-input-vectorStoreRetriever-VectorStoreRetriever", + "type": "buttonedge", + "id": "vectorStoreRetriever_0-vectorStoreRetriever_0-output-vectorStoreRetriever-VectorStoreRetriever-multiRetrievalQAChain_0-multiRetrievalQAChain_0-input-vectorStoreRetriever-VectorStoreRetriever", + "data": { + "label": "" + } + }, + { + "source": "vectorStoreRetriever_1", + "sourceHandle": "vectorStoreRetriever_1-output-vectorStoreRetriever-VectorStoreRetriever", + "target": "multiRetrievalQAChain_0", + "targetHandle": "multiRetrievalQAChain_0-input-vectorStoreRetriever-VectorStoreRetriever", + "type": "buttonedge", + "id": "vectorStoreRetriever_1-vectorStoreRetriever_1-output-vectorStoreRetriever-VectorStoreRetriever-multiRetrievalQAChain_0-multiRetrievalQAChain_0-input-vectorStoreRetriever-VectorStoreRetriever", + "data": { + "label": "" + } + }, + { + "source": "vectorStoreRetriever_2", + "sourceHandle": "vectorStoreRetriever_2-output-vectorStoreRetriever-VectorStoreRetriever", + "target": "multiRetrievalQAChain_0", + "targetHandle": "multiRetrievalQAChain_0-input-vectorStoreRetriever-VectorStoreRetriever", + "type": "buttonedge", + "id": "vectorStoreRetriever_2-vectorStoreRetriever_2-output-vectorStoreRetriever-VectorStoreRetriever-multiRetrievalQAChain_0-multiRetrievalQAChain_0-input-vectorStoreRetriever-VectorStoreRetriever", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain", + "target": "multiRetrievalQAChain_0", + "targetHandle": "multiRetrievalQAChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain-multiRetrievalQAChain_0-multiRetrievalQAChain_0-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "pineconeExistingIndex_0", + "sourceHandle": "pineconeExistingIndex_0-output-vectorStore-Pinecone|VectorStore", + "target": "vectorStoreRetriever_2", + "targetHandle": "vectorStoreRetriever_2-input-vectorStore-VectorStore", + "type": "buttonedge", + "id": "pineconeExistingIndex_0-pineconeExistingIndex_0-output-vectorStore-Pinecone|VectorStore-vectorStoreRetriever_2-vectorStoreRetriever_2-input-vectorStore-VectorStore", + "data": { + "label": "" + } + }, + { + "source": "openAIEmbeddings_0", + "sourceHandle": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "target": "pineconeExistingIndex_0", + "targetHandle": "pineconeExistingIndex_0-input-embeddings-Embeddings", + "type": "buttonedge", + "id": "openAIEmbeddings_0-openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings-pineconeExistingIndex_0-pineconeExistingIndex_0-input-embeddings-Embeddings", + "data": { + "label": "" + } + }, + { + "source": "chromaExistingIndex_0", + "sourceHandle": "chromaExistingIndex_0-output-vectorStore-Chroma|VectorStore", + "target": "vectorStoreRetriever_1", + "targetHandle": "vectorStoreRetriever_1-input-vectorStore-VectorStore", + "type": "buttonedge", + "id": "chromaExistingIndex_0-chromaExistingIndex_0-output-vectorStore-Chroma|VectorStore-vectorStoreRetriever_1-vectorStoreRetriever_1-input-vectorStore-VectorStore", + "data": { + "label": "" + } + }, + { + "source": "openAIEmbeddings_0", + "sourceHandle": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "target": "chromaExistingIndex_0", + "targetHandle": "chromaExistingIndex_0-input-embeddings-Embeddings", + "type": "buttonedge", + "id": "openAIEmbeddings_0-openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings-chromaExistingIndex_0-chromaExistingIndex_0-input-embeddings-Embeddings", + "data": { + "label": "" + } + }, + { + "source": "openAIEmbeddings_0", + "sourceHandle": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "target": "supabaseExistingIndex_0", + "targetHandle": "supabaseExistingIndex_0-input-embeddings-Embeddings", + "type": "buttonedge", + "id": "openAIEmbeddings_0-openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings-supabaseExistingIndex_0-supabaseExistingIndex_0-input-embeddings-Embeddings", + "data": { + "label": "" + } + }, + { + "source": "supabaseExistingIndex_0", + "sourceHandle": "supabaseExistingIndex_0-output-vectorStore-Supabase|VectorStore", + "target": "vectorStoreRetriever_0", + "targetHandle": "vectorStoreRetriever_0-input-vectorStore-VectorStore", + "type": "buttonedge", + "id": "supabaseExistingIndex_0-supabaseExistingIndex_0-output-vectorStore-Supabase|VectorStore-vectorStoreRetriever_0-vectorStoreRetriever_0-input-vectorStore-VectorStore", + "data": { + "label": "" + } + } + ] +} diff --git a/packages/server/marketplaces/Multiple VectorDB.json b/packages/server/marketplaces/Multiple VectorDB.json index 72eb3081..05223e29 100644 --- a/packages/server/marketplaces/Multiple VectorDB.json +++ b/packages/server/marketplaces/Multiple VectorDB.json @@ -1,5 +1,5 @@ { - "description": "Use the agent to choose between multiple different vector databases", + "description": "Use the agent to choose between multiple different vector databases, with the ability to use other tools", "nodes": [ { "width": 300, From e11e0358811b2c7775b24fc4e060497ebbdf8bbe Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 25 May 2023 18:20:39 +0100 Subject: [PATCH 014/398] update chroma and langchain version --- packages/components/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/package.json b/packages/components/package.json index 9c77db8e..048f87fc 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -22,14 +22,14 @@ "@supabase/supabase-js": "^2.21.0", "axios": "^0.27.2", "cheerio": "^1.0.0-rc.12", - "chromadb": "^1.3.1", + "chromadb": "^1.4.2", "cohere-ai": "^6.2.0", "d3-dsv": "2", "dotenv": "^16.0.0", "express": "^4.17.3", "form-data": "^4.0.0", "graphql": "^16.6.0", - "langchain": "^0.0.78", + "langchain": "^0.0.82", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", From 5f67ab31c3d441c892b993248535afb16ffaeace Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 25 May 2023 19:32:10 +0100 Subject: [PATCH 015/398] fix docker running locally --- Dockerfile | 1 + README.md | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Dockerfile b/Dockerfile index 315b4739..a8a4c69a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,7 @@ # Run image # docker run -d -p 3000:3000 flowise FROM node:18-alpine +RUN apk add --update libc6-compat WORKDIR /usr/src/packages diff --git a/README.md b/README.md index 545b36ba..323a8b33 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,29 @@ Drag & drop UI to build your customized LLM flow using [LangchainJS](https://git ## 🐳 Docker +### Docker Compose + 1. Go to `docker` folder at the root of the project 2. Create `.env` file and specify the `PORT` (refer to `.env.example`) 3. `docker-compose up -d` 4. Open [http://localhost:3000](http://localhost:3000) 5. You can bring the containers down by `docker-compose stop` +### Docker Image + +1. Build the image locally: + ```bash + docker build --no-cache -t flowise . + ``` +2. Run image: + ```bash + docker run -d --name flowise -p 3000:3000 flowise + ``` +3. Stop image: + ```bash + docker stop flowise + ``` + ## 👨‍💻 Developers Flowise has 3 different modules in a single mono repository. From 461a1057dfeeef476d914c887d76f864013dab61 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 25 May 2023 19:44:40 +0100 Subject: [PATCH 016/398] add gitattributes --- .gitattributes | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..c75e2532 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Declare files that will always have LF line endings on checkout. +*.cmd text eol=lf From ffdd9691e6c938542485a229cc2e26f601963863 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 25 May 2023 19:57:55 +0100 Subject: [PATCH 017/398] remove gitattributes --- .gitattributes | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index c75e2532..00000000 --- a/.gitattributes +++ /dev/null @@ -1,5 +0,0 @@ -# Set the default behavior, in case people don't have core.autocrlf set. -* text=auto - -# Declare files that will always have LF line endings on checkout. -*.cmd text eol=lf From e294d227bce670055f012922da4458e730737bdf Mon Sep 17 00:00:00 2001 From: Vijay Sai Date: Fri, 26 May 2023 13:40:06 +0530 Subject: [PATCH 018/398] feature: changes to support ApiChain --- .../nodes/chains/ApiChain/ApiChain.ts | 79 +++++++++++++++++++ .../nodes/chains/ApiChain/apichain.svg | 3 + 2 files changed, 82 insertions(+) create mode 100644 packages/components/nodes/chains/ApiChain/ApiChain.ts create mode 100644 packages/components/nodes/chains/ApiChain/apichain.svg diff --git a/packages/components/nodes/chains/ApiChain/ApiChain.ts b/packages/components/nodes/chains/ApiChain/ApiChain.ts new file mode 100644 index 00000000..96342c55 --- /dev/null +++ b/packages/components/nodes/chains/ApiChain/ApiChain.ts @@ -0,0 +1,79 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { APIChain } from 'langchain/chains' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { BaseLanguageModel } from 'langchain/base_language' +import { Document } from 'langchain/document' +import { PromptTemplate } from 'langchain/prompts' +import { OpenAI } from 'langchain' + +class ApiChain_Chains implements INode { + label: string + name: string + type: string + icon: string + category: string + baseClasses: string[] + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'API Chain' + this.name = 'apiChain' + this.type = 'ApiChain' + this.icon = 'apichain.svg' + this.category = 'Chains' + this.description = 'Chain to run queries against API' + this.baseClasses = [this.type, ...getBaseClasses(APIChain), ...getBaseClasses(OpenAI)] + this.inputs = [ + { + label: 'Language Model', + name: 'model', + type: 'BaseLanguageModel' + }, + { + label: 'Document', + name: 'document', + type: 'Document', + } + ] + } + + async init(nodeData: INodeData): Promise { + const model = nodeData.inputs?.model as BaseLanguageModel + const docs = nodeData.inputs?.document as Document[] + + const chain = await getOpenAPIChain(docs, model) + return chain + } + + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { + const model = nodeData.inputs?.model as BaseLanguageModel + const docs = nodeData.inputs?.document as Document[] + + const chain = await getOpenAPIChain(docs, model) + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const res = await chain.run(input, [handler]) + return res + } else { + const res = await chain.run(input) + return res + } + } +} + +const getOpenAPIChain = async (documents: Document[], llm: BaseLanguageModel, options: any = {}) => { + const texts = documents.map(({ pageContent }) => pageContent); + const apiResponsePrompt = new PromptTemplate({ + inputVariables: ["api_docs", "question", "api_url", "api_response"], + template: "Given this {api_response} response for {api_url}. use the given response to answer this {question}", + }); + + const chain = APIChain.fromLLMAndAPIDocs(llm, texts.toString(), { + apiResponsePrompt, + verbose: process.env.DEBUG === 'true' ? true : false, + }) + return chain +} + +module.exports = { nodeClass: ApiChain_Chains } diff --git a/packages/components/nodes/chains/ApiChain/apichain.svg b/packages/components/nodes/chains/ApiChain/apichain.svg new file mode 100644 index 00000000..ef62e168 --- /dev/null +++ b/packages/components/nodes/chains/ApiChain/apichain.svg @@ -0,0 +1,3 @@ + \ No newline at end of file From 06f933132b5878c5582ce493090e1f6d0e7f2d21 Mon Sep 17 00:00:00 2001 From: VJSai Date: Fri, 26 May 2023 13:46:33 +0530 Subject: [PATCH 019/398] Update ApiChain.ts to remove OpenAI import --- packages/components/nodes/chains/ApiChain/ApiChain.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/components/nodes/chains/ApiChain/ApiChain.ts b/packages/components/nodes/chains/ApiChain/ApiChain.ts index 96342c55..90d13661 100644 --- a/packages/components/nodes/chains/ApiChain/ApiChain.ts +++ b/packages/components/nodes/chains/ApiChain/ApiChain.ts @@ -4,7 +4,6 @@ import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { Document } from 'langchain/document' import { PromptTemplate } from 'langchain/prompts' -import { OpenAI } from 'langchain' class ApiChain_Chains implements INode { label: string @@ -23,7 +22,7 @@ class ApiChain_Chains implements INode { this.icon = 'apichain.svg' this.category = 'Chains' this.description = 'Chain to run queries against API' - this.baseClasses = [this.type, ...getBaseClasses(APIChain), ...getBaseClasses(OpenAI)] + this.baseClasses = [this.type, ...getBaseClasses(APIChain)] this.inputs = [ { label: 'Language Model', From 4a4c940dbd9f709dd99119772cacf9fa9989716f Mon Sep 17 00:00:00 2001 From: Vijay Sai Date: Fri, 26 May 2023 16:07:50 +0530 Subject: [PATCH 020/398] fixed all the linting issues --- .../nodes/chains/ApiChain/ApiChain.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/components/nodes/chains/ApiChain/ApiChain.ts b/packages/components/nodes/chains/ApiChain/ApiChain.ts index 90d13661..6fb65560 100644 --- a/packages/components/nodes/chains/ApiChain/ApiChain.ts +++ b/packages/components/nodes/chains/ApiChain/ApiChain.ts @@ -32,7 +32,7 @@ class ApiChain_Chains implements INode { { label: 'Document', name: 'document', - type: 'Document', + type: 'Document' } ] } @@ -45,7 +45,7 @@ class ApiChain_Chains implements INode { return chain } - async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as BaseLanguageModel const docs = nodeData.inputs?.document as Document[] @@ -61,16 +61,16 @@ class ApiChain_Chains implements INode { } } -const getOpenAPIChain = async (documents: Document[], llm: BaseLanguageModel, options: any = {}) => { - const texts = documents.map(({ pageContent }) => pageContent); +const getOpenAPIChain = async (documents: Document[], llm: BaseLanguageModel) => { + const texts = documents.map(({ pageContent }) => pageContent) const apiResponsePrompt = new PromptTemplate({ - inputVariables: ["api_docs", "question", "api_url", "api_response"], - template: "Given this {api_response} response for {api_url}. use the given response to answer this {question}", - }); + inputVariables: ['api_docs', 'question', 'api_url', 'api_response'], + template: 'Given this {api_response} response for {api_url}. use the given response to answer this {question}' + }) - const chain = APIChain.fromLLMAndAPIDocs(llm, texts.toString(), { - apiResponsePrompt, - verbose: process.env.DEBUG === 'true' ? true : false, + const chain = APIChain.fromLLMAndAPIDocs(llm, texts.toString(), { + apiResponsePrompt, + verbose: process.env.DEBUG === 'true' ? true : false }) return chain } From a4727c11f076d991b611112f132a8776a24899ae Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 26 May 2023 12:35:31 +0100 Subject: [PATCH 021/398] add streaming handler to MultiChains --- .../MultiPromptChain/MultiPromptChain.ts | 18 ++++++++++++------ .../MultiRetrievalQAChain.ts | 18 ++++++++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts index a7b00f46..c74e3257 100644 --- a/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts +++ b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts @@ -1,6 +1,6 @@ import { BaseLanguageModel } from 'langchain/base_language' -import { INode, INodeData, INodeParams, PromptRetriever } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams, PromptRetriever } from '../../../src/Interface' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { MultiPromptChain } from 'langchain/chains' class MultiPromptChain_Chains implements INode { @@ -56,12 +56,18 @@ class MultiPromptChain_Chains implements INode { return chain } - async run(nodeData: INodeData, input: string): Promise { + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = nodeData.instance as MultiPromptChain + const obj = { input } - const res = await chain.call({ input }) - - return res?.text + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const res = await chain.call(obj, [handler]) + return res?.text + } else { + const res = await chain.call(obj) + return res?.text + } } } diff --git a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts index 7b8778f1..214db509 100644 --- a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts +++ b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts @@ -1,6 +1,6 @@ import { BaseLanguageModel } from 'langchain/base_language' -import { INode, INodeData, INodeParams, VectorStoreRetriever } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams, VectorStoreRetriever } from '../../../src/Interface' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { MultiRetrievalQAChain } from 'langchain/chains' class MultiRetrievalQAChain_Chains implements INode { @@ -56,12 +56,18 @@ class MultiRetrievalQAChain_Chains implements INode { return chain } - async run(nodeData: INodeData, input: string): Promise { + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = nodeData.instance as MultiRetrievalQAChain + const obj = { input } - const res = await chain.call({ input }) - - return res?.text + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const res = await chain.call(obj, [handler]) + return res?.text + } else { + const res = await chain.call(obj) + return res?.text + } } } From 55ed6819a2a03dc109afeb9c2743d7928d969325 Mon Sep 17 00:00:00 2001 From: Vijay Sai Date: Fri, 26 May 2023 19:13:52 +0530 Subject: [PATCH 022/398] integrated headers functionality --- .../nodes/chains/ApiChain/ApiChain.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/chains/ApiChain/ApiChain.ts b/packages/components/nodes/chains/ApiChain/ApiChain.ts index 6fb65560..bf810340 100644 --- a/packages/components/nodes/chains/ApiChain/ApiChain.ts +++ b/packages/components/nodes/chains/ApiChain/ApiChain.ts @@ -33,6 +33,13 @@ class ApiChain_Chains implements INode { label: 'Document', name: 'document', type: 'Document' + }, + { + label: 'Headers', + name: 'headers', + type: 'json', + additionalParams: true, + optional: true } ] } @@ -40,16 +47,18 @@ class ApiChain_Chains implements INode { async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as BaseLanguageModel const docs = nodeData.inputs?.document as Document[] + const headers = nodeData.inputs?.headers as string - const chain = await getOpenAPIChain(docs, model) + const chain = await getAPIChain(docs, model, headers) return chain } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as BaseLanguageModel const docs = nodeData.inputs?.document as Document[] + const headers = nodeData.inputs?.headers as string - const chain = await getOpenAPIChain(docs, model) + const chain = await getAPIChain(docs, model, headers) if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) const res = await chain.run(input, [handler]) @@ -61,7 +70,7 @@ class ApiChain_Chains implements INode { } } -const getOpenAPIChain = async (documents: Document[], llm: BaseLanguageModel) => { +const getAPIChain = async (documents: Document[], llm: BaseLanguageModel, headers: any) => { const texts = documents.map(({ pageContent }) => pageContent) const apiResponsePrompt = new PromptTemplate({ inputVariables: ['api_docs', 'question', 'api_url', 'api_response'], @@ -70,7 +79,8 @@ const getOpenAPIChain = async (documents: Document[], llm: BaseLanguageModel) => const chain = APIChain.fromLLMAndAPIDocs(llm, texts.toString(), { apiResponsePrompt, - verbose: process.env.DEBUG === 'true' ? true : false + verbose: process.env.DEBUG === 'true' ? true : false, + headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {} }) return chain } From 3de09d5c0c4bcd92f90e3d21bb9ad39b4e2552cc Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Fri, 26 May 2023 23:51:03 +0700 Subject: [PATCH 023/398] add OpenAPI Toolkit --- .../tools/OpenApiToolkit/OpenApiToolkit.ts | 69 ++++++++++++++++++ .../nodes/tools/OpenApiToolkit/openai.png | Bin 0 -> 3991 bytes packages/components/package.json | 1 + 3 files changed, 70 insertions(+) create mode 100644 packages/components/nodes/tools/OpenApiToolkit/OpenApiToolkit.ts create mode 100644 packages/components/nodes/tools/OpenApiToolkit/openai.png diff --git a/packages/components/nodes/tools/OpenApiToolkit/OpenApiToolkit.ts b/packages/components/nodes/tools/OpenApiToolkit/OpenApiToolkit.ts new file mode 100644 index 00000000..a7dc1800 --- /dev/null +++ b/packages/components/nodes/tools/OpenApiToolkit/OpenApiToolkit.ts @@ -0,0 +1,69 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { OpenApiToolkit } from 'langchain/agents' +import { JsonSpec, JsonObject } from 'langchain/tools' +import { BaseLanguageModel } from 'langchain/base_language' +import { load } from 'js-yaml' + +class OpenApiToolkit_Tools implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'OpenApi Toolkit' + this.name = 'openApiToolkit' + this.type = 'OpenApiToolkit' + this.icon = 'openai.png' + this.category = 'Tools' + this.description = 'Load OpenAPI specification' + this.inputs = [ + { + label: 'OpenAI Api Key', + name: 'openAIApiKey', + type: 'password' + }, + { + label: 'Language Model', + name: 'model', + type: 'BaseLanguageModel' + }, + { + label: 'YAML File', + name: 'yamlFile', + type: 'file', + fileType: '.yaml' + } + ] + this.baseClasses = [this.type, 'Tool'] + } + + async init(nodeData: INodeData): Promise { + const openAIApiKey = nodeData.inputs?.openAIApiKey as string + const model = nodeData.inputs?.model as BaseLanguageModel + const yamlFileBase64 = nodeData.inputs?.yamlFile as string + + const splitDataURI = yamlFileBase64.split(',') + splitDataURI.pop() + const bf = Buffer.from(splitDataURI.pop() || '', 'base64') + const utf8String = bf.toString('utf-8') + const data = load(utf8String) as JsonObject + if (!data) { + throw new Error('Failed to load OpenAPI spec') + } + + const headers = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${openAIApiKey}` + } + const toolkit = new OpenApiToolkit(new JsonSpec(data), model, headers) + + return toolkit.tools + } +} + +module.exports = { nodeClass: OpenApiToolkit_Tools } diff --git a/packages/components/nodes/tools/OpenApiToolkit/openai.png b/packages/components/nodes/tools/OpenApiToolkit/openai.png new file mode 100644 index 0000000000000000000000000000000000000000..de08a05b28979826c4cc669c4899789763a938a1 GIT binary patch literal 3991 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 literal 0 HcmV?d00001 diff --git a/packages/components/package.json b/packages/components/package.json index 048f87fc..955cec01 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -20,6 +20,7 @@ "@huggingface/inference": "1", "@pinecone-database/pinecone": "^0.0.12", "@supabase/supabase-js": "^2.21.0", + "@types/js-yaml": "^4.0.5", "axios": "^0.27.2", "cheerio": "^1.0.0-rc.12", "chromadb": "^1.4.2", From bfa3716bef5eae3b4e600e80108a9da68a4786e6 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Fri, 26 May 2023 23:55:52 +0700 Subject: [PATCH 024/398] rename Api to API --- .../OpenAPIToolkit.ts} | 12 ++++++------ .../{OpenApiToolkit => OpenAPIToolkit}/openai.png | Bin 2 files changed, 6 insertions(+), 6 deletions(-) rename packages/components/nodes/tools/{OpenApiToolkit/OpenApiToolkit.ts => OpenAPIToolkit/OpenAPIToolkit.ts} (88%) rename packages/components/nodes/tools/{OpenApiToolkit => OpenAPIToolkit}/openai.png (100%) diff --git a/packages/components/nodes/tools/OpenApiToolkit/OpenApiToolkit.ts b/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts similarity index 88% rename from packages/components/nodes/tools/OpenApiToolkit/OpenApiToolkit.ts rename to packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts index a7dc1800..47b8db82 100644 --- a/packages/components/nodes/tools/OpenApiToolkit/OpenApiToolkit.ts +++ b/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts @@ -4,7 +4,7 @@ import { JsonSpec, JsonObject } from 'langchain/tools' import { BaseLanguageModel } from 'langchain/base_language' import { load } from 'js-yaml' -class OpenApiToolkit_Tools implements INode { +class OpenAPIToolkit_Tools implements INode { label: string name: string description: string @@ -15,15 +15,15 @@ class OpenApiToolkit_Tools implements INode { inputs: INodeParams[] constructor() { - this.label = 'OpenApi Toolkit' - this.name = 'openApiToolkit' - this.type = 'OpenApiToolkit' + this.label = 'OpenAPI Toolkit' + this.name = 'openAPIToolkit' + this.type = 'OpenAPIToolkit' this.icon = 'openai.png' this.category = 'Tools' this.description = 'Load OpenAPI specification' this.inputs = [ { - label: 'OpenAI Api Key', + label: 'OpenAI API Key', name: 'openAIApiKey', type: 'password' }, @@ -66,4 +66,4 @@ class OpenApiToolkit_Tools implements INode { } } -module.exports = { nodeClass: OpenApiToolkit_Tools } +module.exports = { nodeClass: OpenAPIToolkit_Tools } diff --git a/packages/components/nodes/tools/OpenApiToolkit/openai.png b/packages/components/nodes/tools/OpenAPIToolkit/openai.png similarity index 100% rename from packages/components/nodes/tools/OpenApiToolkit/openai.png rename to packages/components/nodes/tools/OpenAPIToolkit/openai.png From 203d6b060fb83d2f7f994e319d79157cf4dbaf26 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 26 May 2023 21:20:35 +0100 Subject: [PATCH 025/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.2.8?= =?UTF-8?q?=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 048f87fc..2cc971e2 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.7", + "version": "1.2.8", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From f220754918db52f944c88710508860b68aea495e Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 26 May 2023 21:21:05 +0100 Subject: [PATCH 026/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.2.6=20relea?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index fc2961fc..290faa3f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.5", + "version": "1.2.6", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From 5f1e27aa1699eda5b0c41909ce2a667ac40a211d Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 26 May 2023 21:21:34 +0100 Subject: [PATCH 027/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.2.7=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 50c59a30..52cb1dc2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.6", + "version": "1.2.7", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index a230f94a..4c5ed057 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.6", + "version": "1.2.7", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 2283093f187a686e59c3994b6f142783c496a675 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 27 May 2023 01:40:50 +0100 Subject: [PATCH 028/398] add get and post api chain --- .../nodes/chains/ApiChain/ApiChain.ts | 88 ---------- .../nodes/chains/ApiChain/GETApiChain.ts | 129 ++++++++++++++ .../nodes/chains/ApiChain/POSTApiChain.ts | 118 +++++++++++++ .../nodes/chains/ApiChain/postCore.ts | 162 ++++++++++++++++++ packages/components/src/utils.ts | 18 +- 5 files changed, 422 insertions(+), 93 deletions(-) delete mode 100644 packages/components/nodes/chains/ApiChain/ApiChain.ts create mode 100644 packages/components/nodes/chains/ApiChain/GETApiChain.ts create mode 100644 packages/components/nodes/chains/ApiChain/POSTApiChain.ts create mode 100644 packages/components/nodes/chains/ApiChain/postCore.ts diff --git a/packages/components/nodes/chains/ApiChain/ApiChain.ts b/packages/components/nodes/chains/ApiChain/ApiChain.ts deleted file mode 100644 index bf810340..00000000 --- a/packages/components/nodes/chains/ApiChain/ApiChain.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { APIChain } from 'langchain/chains' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' -import { BaseLanguageModel } from 'langchain/base_language' -import { Document } from 'langchain/document' -import { PromptTemplate } from 'langchain/prompts' - -class ApiChain_Chains implements INode { - label: string - name: string - type: string - icon: string - category: string - baseClasses: string[] - description: string - inputs: INodeParams[] - - constructor() { - this.label = 'API Chain' - this.name = 'apiChain' - this.type = 'ApiChain' - this.icon = 'apichain.svg' - this.category = 'Chains' - this.description = 'Chain to run queries against API' - this.baseClasses = [this.type, ...getBaseClasses(APIChain)] - this.inputs = [ - { - label: 'Language Model', - name: 'model', - type: 'BaseLanguageModel' - }, - { - label: 'Document', - name: 'document', - type: 'Document' - }, - { - label: 'Headers', - name: 'headers', - type: 'json', - additionalParams: true, - optional: true - } - ] - } - - async init(nodeData: INodeData): Promise { - const model = nodeData.inputs?.model as BaseLanguageModel - const docs = nodeData.inputs?.document as Document[] - const headers = nodeData.inputs?.headers as string - - const chain = await getAPIChain(docs, model, headers) - return chain - } - - async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const model = nodeData.inputs?.model as BaseLanguageModel - const docs = nodeData.inputs?.document as Document[] - const headers = nodeData.inputs?.headers as string - - const chain = await getAPIChain(docs, model, headers) - if (options.socketIO && options.socketIOClientId) { - const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.run(input, [handler]) - return res - } else { - const res = await chain.run(input) - return res - } - } -} - -const getAPIChain = async (documents: Document[], llm: BaseLanguageModel, headers: any) => { - const texts = documents.map(({ pageContent }) => pageContent) - const apiResponsePrompt = new PromptTemplate({ - inputVariables: ['api_docs', 'question', 'api_url', 'api_response'], - template: 'Given this {api_response} response for {api_url}. use the given response to answer this {question}' - }) - - const chain = APIChain.fromLLMAndAPIDocs(llm, texts.toString(), { - apiResponsePrompt, - verbose: process.env.DEBUG === 'true' ? true : false, - headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {} - }) - return chain -} - -module.exports = { nodeClass: ApiChain_Chains } diff --git a/packages/components/nodes/chains/ApiChain/GETApiChain.ts b/packages/components/nodes/chains/ApiChain/GETApiChain.ts new file mode 100644 index 00000000..8e657749 --- /dev/null +++ b/packages/components/nodes/chains/ApiChain/GETApiChain.ts @@ -0,0 +1,129 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { APIChain } from 'langchain/chains' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { BaseLanguageModel } from 'langchain/base_language' +import { PromptTemplate } from 'langchain/prompts' + +export const API_URL_RAW_PROMPT_TEMPLATE = `You are given the below API Documentation: +{api_docs} +Using this documentation, generate the full API url to call for answering the user question. +You should build the API url in order to get a response that is as short as possible, while still getting the necessary information to answer the question. Pay attention to deliberately exclude any unnecessary pieces of data in the API call. + +Question:{question} +API url:` + +export const API_RESPONSE_RAW_PROMPT_TEMPLATE = + 'Given this {api_response} response for {api_url}. use the given response to answer this {question}' + +class GETApiChain_Chains implements INode { + label: string + name: string + type: string + icon: string + category: string + baseClasses: string[] + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'GET API Chain' + this.name = 'getApiChain' + this.type = 'GETApiChain' + this.icon = 'apichain.svg' + this.category = 'Chains' + this.description = 'Chain to run queries against GET API' + this.baseClasses = [this.type, ...getBaseClasses(APIChain)] + this.inputs = [ + { + label: 'Language Model', + name: 'model', + type: 'BaseLanguageModel' + }, + { + label: 'API Documentation', + name: 'apiDocs', + type: 'string', + description: + 'Description of how API works. Please refer to more examples', + rows: 4 + }, + { + label: 'Headers', + name: 'headers', + type: 'json', + additionalParams: true, + optional: true + }, + { + label: 'URL Prompt', + name: 'urlPrompt', + type: 'string', + description: 'Prompt used to tell LLMs how to construct the URL. Must contains {api_docs} and {question}', + default: API_URL_RAW_PROMPT_TEMPLATE, + rows: 4, + additionalParams: true + }, + { + label: 'Answer Prompt', + name: 'ansPrompt', + type: 'string', + description: + 'Prompt used to tell LLMs how to return the API response. Must contains {api_response}, {api_url}, and {question}', + default: API_RESPONSE_RAW_PROMPT_TEMPLATE, + rows: 4, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const model = nodeData.inputs?.model as BaseLanguageModel + const apiDocs = nodeData.inputs?.apiDocs as string + const headers = nodeData.inputs?.headers as string + const urlPrompt = nodeData.inputs?.urlPrompt as string + const ansPrompt = nodeData.inputs?.ansPrompt as string + + const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt) + return chain + } + + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { + const model = nodeData.inputs?.model as BaseLanguageModel + const apiDocs = nodeData.inputs?.apiDocs as string + const headers = nodeData.inputs?.headers as string + const urlPrompt = nodeData.inputs?.urlPrompt as string + const ansPrompt = nodeData.inputs?.ansPrompt as string + + const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt) + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) + const res = await chain.run(input, [handler]) + return res + } else { + const res = await chain.run(input) + return res + } + } +} + +const getAPIChain = async (documents: string, llm: BaseLanguageModel, headers: string, urlPrompt: string, ansPrompt: string) => { + const apiUrlPrompt = new PromptTemplate({ + inputVariables: ['api_docs', 'question'], + template: urlPrompt ? urlPrompt : API_URL_RAW_PROMPT_TEMPLATE + }) + + const apiResponsePrompt = new PromptTemplate({ + inputVariables: ['api_docs', 'question', 'api_url', 'api_response'], + template: ansPrompt ? ansPrompt : API_RESPONSE_RAW_PROMPT_TEMPLATE + }) + + const chain = APIChain.fromLLMAndAPIDocs(llm, documents, { + apiUrlPrompt, + apiResponsePrompt, + verbose: process.env.DEBUG === 'true' ? true : false, + headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {} + }) + return chain +} + +module.exports = { nodeClass: GETApiChain_Chains } diff --git a/packages/components/nodes/chains/ApiChain/POSTApiChain.ts b/packages/components/nodes/chains/ApiChain/POSTApiChain.ts new file mode 100644 index 00000000..3c6ea677 --- /dev/null +++ b/packages/components/nodes/chains/ApiChain/POSTApiChain.ts @@ -0,0 +1,118 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { BaseLanguageModel } from 'langchain/base_language' +import { PromptTemplate } from 'langchain/prompts' +import { API_RESPONSE_RAW_PROMPT_TEMPLATE, API_URL_RAW_PROMPT_TEMPLATE, APIChain } from './postCore' + +class POSTApiChain_Chains implements INode { + label: string + name: string + type: string + icon: string + category: string + baseClasses: string[] + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'POST API Chain' + this.name = 'postApiChain' + this.type = 'POSTApiChain' + this.icon = 'apichain.svg' + this.category = 'Chains' + this.description = 'Chain to run queries against POST API' + this.baseClasses = [this.type, ...getBaseClasses(APIChain)] + this.inputs = [ + { + label: 'Language Model', + name: 'model', + type: 'BaseLanguageModel' + }, + { + label: 'API Documentation', + name: 'apiDocs', + type: 'string', + description: + 'Description of how API works. Please refer to more examples', + rows: 4 + }, + { + label: 'Headers', + name: 'headers', + type: 'json', + additionalParams: true, + optional: true + }, + { + label: 'URL Prompt', + name: 'urlPrompt', + type: 'string', + description: 'Prompt used to tell LLMs how to construct the URL. Must contains {api_docs} and {question}', + default: API_URL_RAW_PROMPT_TEMPLATE, + rows: 4, + additionalParams: true + }, + { + label: 'Answer Prompt', + name: 'ansPrompt', + type: 'string', + description: + 'Prompt used to tell LLMs how to return the API response. Must contains {api_response}, {api_url}, and {question}', + default: API_RESPONSE_RAW_PROMPT_TEMPLATE, + rows: 4, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const model = nodeData.inputs?.model as BaseLanguageModel + const apiDocs = nodeData.inputs?.apiDocs as string + const headers = nodeData.inputs?.headers as string + const urlPrompt = nodeData.inputs?.urlPrompt as string + const ansPrompt = nodeData.inputs?.ansPrompt as string + + const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt) + return chain + } + + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { + const model = nodeData.inputs?.model as BaseLanguageModel + const apiDocs = nodeData.inputs?.apiDocs as string + const headers = nodeData.inputs?.headers as string + const urlPrompt = nodeData.inputs?.urlPrompt as string + const ansPrompt = nodeData.inputs?.ansPrompt as string + + const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt) + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) + const res = await chain.run(input, [handler]) + return res + } else { + const res = await chain.run(input) + return res + } + } +} + +const getAPIChain = async (documents: string, llm: BaseLanguageModel, headers: string, urlPrompt: string, ansPrompt: string) => { + const apiUrlPrompt = new PromptTemplate({ + inputVariables: ['api_docs', 'question'], + template: urlPrompt ? urlPrompt : API_URL_RAW_PROMPT_TEMPLATE + }) + + const apiResponsePrompt = new PromptTemplate({ + inputVariables: ['api_docs', 'question', 'api_url_body', 'api_response'], + template: ansPrompt ? ansPrompt : API_RESPONSE_RAW_PROMPT_TEMPLATE + }) + + const chain = APIChain.fromLLMAndAPIDocs(llm, documents, { + apiUrlPrompt, + apiResponsePrompt, + verbose: process.env.DEBUG === 'true' ? true : false, + headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {} + }) + return chain +} + +module.exports = { nodeClass: POSTApiChain_Chains } diff --git a/packages/components/nodes/chains/ApiChain/postCore.ts b/packages/components/nodes/chains/ApiChain/postCore.ts new file mode 100644 index 00000000..de7215d9 --- /dev/null +++ b/packages/components/nodes/chains/ApiChain/postCore.ts @@ -0,0 +1,162 @@ +import { BaseLanguageModel } from 'langchain/base_language' +import { CallbackManagerForChainRun } from 'langchain/callbacks' +import { BaseChain, ChainInputs, LLMChain, SerializedAPIChain } from 'langchain/chains' +import { BasePromptTemplate, PromptTemplate } from 'langchain/prompts' +import { ChainValues } from 'langchain/schema' +import fetch from 'node-fetch' + +export const API_URL_RAW_PROMPT_TEMPLATE = `You are given the below API Documentation: +{api_docs} +Using this documentation, generate a json string with two keys: "url" and "data". +The value of "url" should be a string, which is the API url to call for answering the user question. +The value of "data" should be a dictionary of key-value pairs you want to POST to the url as a JSON body. +Be careful to always use double quotes for strings in the json string. +You should build the json string in order to get a response that is as short as possible, while still getting the necessary information to answer the question. Pay attention to deliberately exclude any unnecessary pieces of data in the API call. + +Question:{question} +json string:` + +export const API_RESPONSE_RAW_PROMPT_TEMPLATE = `${API_URL_RAW_PROMPT_TEMPLATE} {api_url_body} + +Here is the response from the API: + +{api_response} + +Summarize this response to answer the original question. + +Summary:` + +const defaultApiUrlPrompt = new PromptTemplate({ + inputVariables: ['api_docs', 'question'], + template: API_URL_RAW_PROMPT_TEMPLATE +}) + +const defaultApiResponsePrompt = new PromptTemplate({ + inputVariables: ['api_docs', 'question', 'api_url_body', 'api_response'], + template: API_RESPONSE_RAW_PROMPT_TEMPLATE +}) + +export interface APIChainInput extends Omit { + apiAnswerChain: LLMChain + apiRequestChain: LLMChain + apiDocs: string + inputKey?: string + headers?: Record + /** Key to use for output, defaults to `output` */ + outputKey?: string +} + +export type APIChainOptions = { + headers?: Record + apiUrlPrompt?: BasePromptTemplate + apiResponsePrompt?: BasePromptTemplate +} + +export class APIChain extends BaseChain implements APIChainInput { + apiAnswerChain: LLMChain + + apiRequestChain: LLMChain + + apiDocs: string + + headers = {} + + inputKey = 'question' + + outputKey = 'output' + + get inputKeys() { + return [this.inputKey] + } + + get outputKeys() { + return [this.outputKey] + } + + constructor(fields: APIChainInput) { + super(fields) + this.apiRequestChain = fields.apiRequestChain + this.apiAnswerChain = fields.apiAnswerChain + this.apiDocs = fields.apiDocs + this.inputKey = fields.inputKey ?? this.inputKey + this.outputKey = fields.outputKey ?? this.outputKey + this.headers = fields.headers ?? this.headers + } + + /** @ignore */ + async _call(values: ChainValues, runManager?: CallbackManagerForChainRun): Promise { + try { + const question: string = values[this.inputKey] + + const api_url_body = await this.apiRequestChain.predict({ question, api_docs: this.apiDocs }, runManager?.getChild()) + + const { url, data } = JSON.parse(api_url_body) + + const res = await fetch(url, { + method: 'POST', + headers: this.headers, + body: JSON.stringify(data) + }) + + const api_response = await res.text() + + const answer = await this.apiAnswerChain.predict( + { question, api_docs: this.apiDocs, api_url_body, api_response }, + runManager?.getChild() + ) + + return { [this.outputKey]: answer } + } catch (error) { + return { [this.outputKey]: error } + } + } + + _chainType() { + return 'api_chain' as const + } + + static async deserialize(data: SerializedAPIChain) { + const { api_request_chain, api_answer_chain, api_docs } = data + + if (!api_request_chain) { + throw new Error('LLMChain must have api_request_chain') + } + if (!api_answer_chain) { + throw new Error('LLMChain must have api_answer_chain') + } + if (!api_docs) { + throw new Error('LLMChain must have api_docs') + } + + return new APIChain({ + apiAnswerChain: await LLMChain.deserialize(api_answer_chain), + apiRequestChain: await LLMChain.deserialize(api_request_chain), + apiDocs: api_docs + }) + } + + serialize(): SerializedAPIChain { + return { + _type: this._chainType(), + api_answer_chain: this.apiAnswerChain.serialize(), + api_request_chain: this.apiRequestChain.serialize(), + api_docs: this.apiDocs + } + } + + static fromLLMAndAPIDocs( + llm: BaseLanguageModel, + apiDocs: string, + options: APIChainOptions & Omit = {} + ): APIChain { + const { apiUrlPrompt = defaultApiUrlPrompt, apiResponsePrompt = defaultApiResponsePrompt } = options + const apiRequestChain = new LLMChain({ prompt: apiUrlPrompt, llm }) + const apiAnswerChain = new LLMChain({ prompt: apiResponsePrompt, llm }) + return new this({ + apiAnswerChain, + apiRequestChain, + apiDocs, + ...options + }) + } +} diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 68c098fd..08d32bab 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -207,19 +207,27 @@ export class CustomChainHandler extends BaseCallbackHandler { isLLMStarted = false socketIO: Server socketIOClientId = '' + skipK = 0 // Skip streaming for first K numbers of handleLLMStart - constructor(socketIO: Server, socketIOClientId: string) { + constructor(socketIO: Server, socketIOClientId: string, skipK?: number) { super() this.socketIO = socketIO this.socketIOClientId = socketIOClientId + this.skipK = skipK ?? this.skipK + } + + handleLLMStart() { + if (this.skipK > 0) this.skipK -= 1 } handleLLMNewToken(token: string) { - if (!this.isLLMStarted) { - this.isLLMStarted = true - this.socketIO.to(this.socketIOClientId).emit('start', token) + if (this.skipK === 0) { + if (!this.isLLMStarted) { + this.isLLMStarted = true + this.socketIO.to(this.socketIOClientId).emit('start', token) + } + this.socketIO.to(this.socketIOClientId).emit('token', token) } - this.socketIO.to(this.socketIOClientId).emit('token', token) } handleLLMEnd() { From 015456fa166c3803e3772bcf9ef365124ed1641f Mon Sep 17 00:00:00 2001 From: VJSai Date: Sat, 27 May 2023 14:46:07 +0530 Subject: [PATCH 029/398] Update README.md with star history --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 323a8b33..74b04b2d 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,7 @@ Feel free to ask any questions, raise problems, and request new features in [dis ## 🙌 Contributing See [contributing guide](CONTRIBUTING.md). Reach out to us at [Discord](https://discord.gg/jbaHfsRVBW) if you have any questions or issues. +[![Star History Chart](https://api.star-history.com/svg?repos=FlowiseAI/Flowise&type=Timeline)](https://star-history.com/#FlowiseAI/Flowise&Date) ## 📄 License From 1e2d0bb6fef37e2d7568258ae7f9cfbf9a99fafd Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 27 May 2023 12:34:35 +0100 Subject: [PATCH 030/398] add marketplace --- packages/server/marketplaces/API Agent.json | 949 ++++++++++++++++++++ 1 file changed, 949 insertions(+) create mode 100644 packages/server/marketplaces/API Agent.json diff --git a/packages/server/marketplaces/API Agent.json b/packages/server/marketplaces/API Agent.json new file mode 100644 index 00000000..a1a42ddb --- /dev/null +++ b/packages/server/marketplaces/API Agent.json @@ -0,0 +1,949 @@ +{ + "description": "Given API docs, agent automatically decide which API to call, generating url and body request from conversation", + "nodes": [ + { + "width": 300, + "height": 459, + "id": "getApiChain_0", + "position": { + "x": 1222.6923202234623, + "y": 359.97676456347756 + }, + "type": "customNode", + "data": { + "id": "getApiChain_0", + "label": "GET API Chain", + "name": "getApiChain", + "type": "GETApiChain", + "baseClasses": ["GETApiChain", "BaseChain", "BaseLangChain"], + "category": "Chains", + "description": "Chain to run queries against GET API", + "inputParams": [ + { + "label": "API Documentation", + "name": "apiDocs", + "type": "string", + "description": "Description of how API works. Please refer to more examples", + "rows": 4, + "id": "getApiChain_0-input-apiDocs-string" + }, + { + "label": "Headers", + "name": "headers", + "type": "json", + "additionalParams": true, + "optional": true, + "id": "getApiChain_0-input-headers-json" + }, + { + "label": "URL Prompt", + "name": "urlPrompt", + "type": "string", + "description": "Prompt used to tell LLMs how to construct the URL. Must contains {api_docs} and {question}", + "default": "You are given the below API Documentation:\n{api_docs}\nUsing this documentation, generate the full API url to call for answering the user question.\nYou should build the API url in order to get a response that is as short as possible, while still getting the necessary information to answer the question. Pay attention to deliberately exclude any unnecessary pieces of data in the API call.\n\nQuestion:{question}\nAPI url:", + "rows": 4, + "additionalParams": true, + "id": "getApiChain_0-input-urlPrompt-string" + }, + { + "label": "Answer Prompt", + "name": "ansPrompt", + "type": "string", + "description": "Prompt used to tell LLMs how to return the API response. Must contains {api_response}, {api_url}, and {question}", + "default": "Given this {api_response} response for {api_url}. use the given response to answer this {question}", + "rows": 4, + "additionalParams": true, + "id": "getApiChain_0-input-ansPrompt-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "getApiChain_0-input-model-BaseLanguageModel" + } + ], + "inputs": { + "model": "{{chatOpenAI_1.data.instance}}", + "apiDocs": "BASE URL: https://api.open-meteo.com/\n\nAPI Documentation\nThe API endpoint /v1/forecast accepts a geographical coordinate, a list of weather variables and responds with a JSON hourly weather forecast for 7 days. Time always starts at 0:00 today and contains 168 hours. All URL parameters are listed below:\n\nParameter\tFormat\tRequired\tDefault\tDescription\nlatitude, longitude\tFloating point\tYes\t\tGeographical WGS84 coordinate of the location\nhourly\tString array\tNo\t\tA list of weather variables which should be returned. Values can be comma separated, or multiple &hourly= parameter in the URL can be used.\ndaily\tString array\tNo\t\tA list of daily weather variable aggregations which should be returned. Values can be comma separated, or multiple &daily= parameter in the URL can be used. If daily weather variables are specified, parameter timezone is required.\ncurrent_weather\tBool\tNo\tfalse\tInclude current weather conditions in the JSON output.\ntemperature_unit\tString\tNo\tcelsius\tIf fahrenheit is set, all temperature values are converted to Fahrenheit.\nwindspeed_unit\tString\tNo\tkmh\tOther wind speed speed units: ms, mph and kn\nprecipitation_unit\tString\tNo\tmm\tOther precipitation amount units: inch\ntimeformat\tString\tNo\tiso8601\tIf format unixtime is selected, all time values are returned in UNIX epoch time in seconds. Please note that all timestamp are in GMT+0! For daily values with unix timestamps, please apply utc_offset_seconds again to get the correct date.\ntimezone\tString\tNo\tGMT\tIf timezone is set, all timestamps are returned as local-time and data is returned starting at 00:00 local-time. Any time zone name from the time zone database is supported. If auto is set as a time zone, the coordinates will be automatically resolved to the local time zone.\npast_days\tInteger (0-2)\tNo\t0\tIf past_days is set, yesterday or the day before yesterday data are also returned.\nstart_date\nend_date\tString (yyyy-mm-dd)\tNo\t\tThe time interval to get weather data. A day must be specified as an ISO8601 date (e.g. 2022-06-30).\nmodels\tString array\tNo\tauto\tManually select one or more weather models. Per default, the best suitable weather models will be combined.\n\nHourly Parameter Definition\nThe parameter &hourly= accepts the following values. Most weather variables are given as an instantaneous value for the indicated hour. Some variables like precipitation are calculated from the preceding hour as an average or sum.\n\nVariable\tValid time\tUnit\tDescription\ntemperature_2m\tInstant\t°C (°F)\tAir temperature at 2 meters above ground\nsnowfall\tPreceding hour sum\tcm (inch)\tSnowfall amount of the preceding hour in centimeters. For the water equivalent in millimeter, divide by 7. E.g. 7 cm snow = 10 mm precipitation water equivalent\nrain\tPreceding hour sum\tmm (inch)\tRain from large scale weather systems of the preceding hour in millimeter\nshowers\tPreceding hour sum\tmm (inch)\tShowers from convective precipitation in millimeters from the preceding hour\nweathercode\tInstant\tWMO code\tWeather condition as a numeric code. Follow WMO weather interpretation codes. See table below for details.\nsnow_depth\tInstant\tmeters\tSnow depth on the ground\nfreezinglevel_height\tInstant\tmeters\tAltitude above sea level of the 0°C level\nvisibility\tInstant\tmeters\tViewing distance in meters. Influenced by low clouds, humidity and aerosols. Maximum visibility is approximately 24 km.", + "headers": "", + "urlPrompt": "You are given the below API Documentation:\n{api_docs}\nUsing this documentation, generate the full API url to call for answering the user question.\nYou should build the API url in order to get a response that is as short as possible, while still getting the necessary information to answer the question. Pay attention to deliberately exclude any unnecessary pieces of data in the API call.\n\nQuestion:{question}\nAPI url:", + "ansPrompt": "Given this {api_response} response for {api_url}. use the given response to answer this {question}" + }, + "outputAnchors": [ + { + "id": "getApiChain_0-output-getApiChain-GETApiChain|BaseChain|BaseLangChain", + "name": "getApiChain", + "label": "GETApiChain", + "type": "GETApiChain | BaseChain | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1222.6923202234623, + "y": 359.97676456347756 + }, + "dragging": false + }, + { + "width": 300, + "height": 383, + "id": "conversationalAgent_0", + "position": { + "x": 1993.8540808923876, + "y": 952.6297081192247 + }, + "type": "customNode", + "data": { + "id": "conversationalAgent_0", + "label": "Conversational Agent", + "name": "conversationalAgent", + "type": "AgentExecutor", + "baseClasses": ["AgentExecutor", "BaseChain", "BaseLangChain"], + "category": "Agents", + "description": "Conversational agent for a chat model. It will utilize chat specific prompts", + "inputParams": [ + { + "label": "System Message", + "name": "systemMessage", + "type": "string", + "rows": 4, + "optional": true, + "additionalParams": true, + "id": "conversationalAgent_0-input-systemMessage-string" + }, + { + "label": "Human Message", + "name": "humanMessage", + "type": "string", + "rows": 4, + "optional": true, + "additionalParams": true, + "id": "conversationalAgent_0-input-humanMessage-string" + } + ], + "inputAnchors": [ + { + "label": "Allowed Tools", + "name": "tools", + "type": "Tool", + "list": true, + "id": "conversationalAgent_0-input-tools-Tool" + }, + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "conversationalAgent_0-input-model-BaseLanguageModel" + }, + { + "label": "Memory", + "name": "memory", + "type": "BaseChatMemory", + "id": "conversationalAgent_0-input-memory-BaseChatMemory" + } + ], + "inputs": { + "tools": ["{{chainTool_0.data.instance}}", "{{chainTool_1.data.instance}}"], + "model": "{{chatOpenAI_0.data.instance}}", + "memory": "{{bufferMemory_0.data.instance}}", + "systemMessage": "", + "humanMessage": "" + }, + "outputAnchors": [ + { + "id": "conversationalAgent_0-output-conversationalAgent-AgentExecutor|BaseChain|BaseLangChain", + "name": "conversationalAgent", + "label": "AgentExecutor", + "type": "AgentExecutor | BaseChain | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1993.8540808923876, + "y": 952.6297081192247 + }, + "dragging": false + }, + { + "width": 300, + "height": 602, + "id": "chainTool_0", + "position": { + "x": 1600.1485877701232, + "y": 276.38970893436533 + }, + "type": "customNode", + "data": { + "id": "chainTool_0", + "label": "Chain Tool", + "name": "chainTool", + "type": "ChainTool", + "baseClasses": ["ChainTool", "DynamicTool", "Tool", "StructuredTool", "BaseLangChain"], + "category": "Tools", + "description": "Use a chain as allowed tool for agent", + "inputParams": [ + { + "label": "Chain Name", + "name": "name", + "type": "string", + "placeholder": "state-of-union-qa", + "id": "chainTool_0-input-name-string" + }, + { + "label": "Chain Description", + "name": "description", + "type": "string", + "rows": 3, + "placeholder": "State of the Union QA - useful for when you need to ask questions about the most recent state of the union address.", + "id": "chainTool_0-input-description-string" + }, + { + "label": "Return Direct", + "name": "returnDirect", + "type": "boolean", + "optional": true, + "id": "chainTool_0-input-returnDirect-boolean" + } + ], + "inputAnchors": [ + { + "label": "Base Chain", + "name": "baseChain", + "type": "BaseChain", + "id": "chainTool_0-input-baseChain-BaseChain" + } + ], + "inputs": { + "name": "weather-qa", + "description": "useful for when you need to ask question about weather", + "returnDirect": "", + "baseChain": "{{getApiChain_0.data.instance}}" + }, + "outputAnchors": [ + { + "id": "chainTool_0-output-chainTool-ChainTool|DynamicTool|Tool|StructuredTool|BaseLangChain", + "name": "chainTool", + "label": "ChainTool", + "type": "ChainTool | DynamicTool | Tool | StructuredTool | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1600.1485877701232, + "y": 276.38970893436533 + }, + "dragging": false + }, + { + "width": 300, + "height": 524, + "id": "chatOpenAI_0", + "position": { + "x": 1270.7548070814019, + "y": 1565.864417576483 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "BaseLangChain"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password", + "id": "chatOpenAI_0-input-openAIApiKey-password" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-0314", + "name": "gpt-4-0314" + }, + { + "label": "gpt-4-32k-0314", + "name": "gpt-4-32k-0314" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-0301", + "name": "gpt-3.5-turbo-0301" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_0-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.9, + "optional": true, + "id": "chatOpenAI_0-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-timeout-number" + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "gpt-3.5-turbo", + "temperature": 0.9, + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1270.7548070814019, + "y": 1565.864417576483 + }, + "dragging": false + }, + { + "width": 300, + "height": 376, + "id": "bufferMemory_0", + "position": { + "x": 1642.0644080121785, + "y": 1715.6131926891728 + }, + "type": "customNode", + "data": { + "id": "bufferMemory_0", + "label": "Buffer Memory", + "name": "bufferMemory", + "type": "BufferMemory", + "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], + "category": "Memory", + "description": "Remembers previous conversational back and forths directly", + "inputParams": [ + { + "label": "Memory Key", + "name": "memoryKey", + "type": "string", + "default": "chat_history", + "id": "bufferMemory_0-input-memoryKey-string" + }, + { + "label": "Input Key", + "name": "inputKey", + "type": "string", + "default": "input", + "id": "bufferMemory_0-input-inputKey-string" + } + ], + "inputAnchors": [], + "inputs": { + "memoryKey": "chat_history", + "inputKey": "input" + }, + "outputAnchors": [ + { + "id": "bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory", + "name": "bufferMemory", + "label": "BufferMemory", + "type": "BufferMemory | BaseChatMemory | BaseMemory" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1642.0644080121785, + "y": 1715.6131926891728 + }, + "dragging": false + }, + { + "width": 300, + "height": 524, + "id": "chatOpenAI_1", + "position": { + "x": 865.4424095725009, + "y": 350.7505181391267 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_1", + "label": "ChatOpenAI", + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "BaseLangChain"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password", + "id": "chatOpenAI_1-input-openAIApiKey-password" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-0314", + "name": "gpt-4-0314" + }, + { + "label": "gpt-4-32k-0314", + "name": "gpt-4-32k-0314" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-0301", + "name": "gpt-3.5-turbo-0301" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_1-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.9, + "optional": true, + "id": "chatOpenAI_1-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-timeout-number" + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "gpt-3.5-turbo", + "temperature": 0.9, + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 865.4424095725009, + "y": 350.7505181391267 + }, + "dragging": false + }, + { + "width": 300, + "height": 524, + "id": "chatOpenAI_2", + "position": { + "x": 587.6425146349426, + "y": 917.1494176892741 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_2", + "label": "ChatOpenAI", + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "BaseLangChain"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password", + "id": "chatOpenAI_2-input-openAIApiKey-password" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-0314", + "name": "gpt-4-0314" + }, + { + "label": "gpt-4-32k-0314", + "name": "gpt-4-32k-0314" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-0301", + "name": "gpt-3.5-turbo-0301" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_2-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.9, + "optional": true, + "id": "chatOpenAI_2-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_2-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_2-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_2-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_2-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_2-input-timeout-number" + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "gpt-3.5-turbo", + "temperature": 0.9, + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 587.6425146349426, + "y": 917.1494176892741 + }, + "dragging": false + }, + { + "width": 300, + "height": 602, + "id": "chainTool_1", + "position": { + "x": 1284.7746596034926, + "y": 895.1444797047182 + }, + "type": "customNode", + "data": { + "id": "chainTool_1", + "label": "Chain Tool", + "name": "chainTool", + "type": "ChainTool", + "baseClasses": ["ChainTool", "DynamicTool", "Tool", "StructuredTool", "BaseLangChain"], + "category": "Tools", + "description": "Use a chain as allowed tool for agent", + "inputParams": [ + { + "label": "Chain Name", + "name": "name", + "type": "string", + "placeholder": "state-of-union-qa", + "id": "chainTool_1-input-name-string" + }, + { + "label": "Chain Description", + "name": "description", + "type": "string", + "rows": 3, + "placeholder": "State of the Union QA - useful for when you need to ask questions about the most recent state of the union address.", + "id": "chainTool_1-input-description-string" + }, + { + "label": "Return Direct", + "name": "returnDirect", + "type": "boolean", + "optional": true, + "id": "chainTool_1-input-returnDirect-boolean" + } + ], + "inputAnchors": [ + { + "label": "Base Chain", + "name": "baseChain", + "type": "BaseChain", + "id": "chainTool_1-input-baseChain-BaseChain" + } + ], + "inputs": { + "name": "discord-bot", + "description": "useful for when you need to send message to Discord", + "returnDirect": "", + "baseChain": "{{postApiChain_0.data.instance}}" + }, + "outputAnchors": [ + { + "id": "chainTool_1-output-chainTool-ChainTool|DynamicTool|Tool|StructuredTool|BaseLangChain", + "name": "chainTool", + "label": "ChainTool", + "type": "ChainTool | DynamicTool | Tool | StructuredTool | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1284.7746596034926, + "y": 895.1444797047182 + }, + "dragging": false + }, + { + "width": 300, + "height": 459, + "id": "postApiChain_0", + "position": { + "x": 933.3631140153886, + "y": 974.8756002461283 + }, + "type": "customNode", + "data": { + "id": "postApiChain_0", + "label": "POST API Chain", + "name": "postApiChain", + "type": "POSTApiChain", + "baseClasses": ["POSTApiChain", "BaseChain", "BaseLangChain"], + "category": "Chains", + "description": "Chain to run queries against POST API", + "inputParams": [ + { + "label": "API Documentation", + "name": "apiDocs", + "type": "string", + "description": "Description of how API works. Please refer to more examples", + "rows": 4, + "id": "postApiChain_0-input-apiDocs-string" + }, + { + "label": "Headers", + "name": "headers", + "type": "json", + "additionalParams": true, + "optional": true, + "id": "postApiChain_0-input-headers-json" + }, + { + "label": "URL Prompt", + "name": "urlPrompt", + "type": "string", + "description": "Prompt used to tell LLMs how to construct the URL. Must contains {api_docs} and {question}", + "default": "You are given the below API Documentation:\n{api_docs}\nUsing this documentation, generate a json string with two keys: \"url\" and \"data\".\nThe value of \"url\" should be a string, which is the API url to call for answering the user question.\nThe value of \"data\" should be a dictionary of key-value pairs you want to POST to the url as a JSON body.\nBe careful to always use double quotes for strings in the json string.\nYou should build the json string in order to get a response that is as short as possible, while still getting the necessary information to answer the question. Pay attention to deliberately exclude any unnecessary pieces of data in the API call.\n\nQuestion:{question}\njson string:", + "rows": 4, + "additionalParams": true, + "id": "postApiChain_0-input-urlPrompt-string" + }, + { + "label": "Answer Prompt", + "name": "ansPrompt", + "type": "string", + "description": "Prompt used to tell LLMs how to return the API response. Must contains {api_response}, {api_url}, and {question}", + "default": "You are given the below API Documentation:\n{api_docs}\nUsing this documentation, generate a json string with two keys: \"url\" and \"data\".\nThe value of \"url\" should be a string, which is the API url to call for answering the user question.\nThe value of \"data\" should be a dictionary of key-value pairs you want to POST to the url as a JSON body.\nBe careful to always use double quotes for strings in the json string.\nYou should build the json string in order to get a response that is as short as possible, while still getting the necessary information to answer the question. Pay attention to deliberately exclude any unnecessary pieces of data in the API call.\n\nQuestion:{question}\njson string: {api_url_body}\n\nHere is the response from the API:\n\n{api_response}\n\nSummarize this response to answer the original question.\n\nSummary:", + "rows": 4, + "additionalParams": true, + "id": "postApiChain_0-input-ansPrompt-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "postApiChain_0-input-model-BaseLanguageModel" + } + ], + "inputs": { + "model": "{{chatOpenAI_2.data.instance}}", + "apiDocs": "API documentation:\nEndpoint: https://eog776prcv6dg0j.m.pipedream.net\n\nThis API is for sending Discord message\n\nQuery body table:\nmessage | string | Message to send | required\n\nResponse schema (string):\nresult | string", + "headers": "", + "urlPrompt": "You are given the below API Documentation:\n{api_docs}\nUsing this documentation, generate a json string with two keys: \"url\" and \"data\".\nThe value of \"url\" should be a string, which is the API url to call for answering the user question.\nThe value of \"data\" should be a dictionary of key-value pairs you want to POST to the url as a JSON body.\nBe careful to always use double quotes for strings in the json string.\nYou should build the json string in order to get a response that is as short as possible, while still getting the necessary information to answer the question. Pay attention to deliberately exclude any unnecessary pieces of data in the API call.\n\nQuestion:{question}\njson string:", + "ansPrompt": "You are given the below API Documentation:\n{api_docs}\nUsing this documentation, generate a json string with two keys: \"url\" and \"data\".\nThe value of \"url\" should be a string, which is the API url to call for answering the user question.\nThe value of \"data\" should be a dictionary of key-value pairs you want to POST to the url as a JSON body.\nBe careful to always use double quotes for strings in the json string.\nYou should build the json string in order to get a response that is as short as possible, while still getting the necessary information to answer the question. Pay attention to deliberately exclude any unnecessary pieces of data in the API call.\n\nQuestion:{question}\njson string: {api_url_body}\n\nHere is the response from the API:\n\n{api_response}\n\nSummarize this response to answer the original question.\n\nSummary:" + }, + "outputAnchors": [ + { + "id": "postApiChain_0-output-postApiChain-POSTApiChain|BaseChain|BaseLangChain", + "name": "postApiChain", + "label": "POSTApiChain", + "type": "POSTApiChain | BaseChain | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 933.3631140153886, + "y": 974.8756002461283 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "getApiChain_0", + "sourceHandle": "getApiChain_0-output-getApiChain-GETApiChain|BaseChain|BaseLangChain", + "target": "chainTool_0", + "targetHandle": "chainTool_0-input-baseChain-BaseChain", + "type": "buttonedge", + "id": "getApiChain_0-getApiChain_0-output-getApiChain-GETApiChain|BaseChain|BaseLangChain-chainTool_0-chainTool_0-input-baseChain-BaseChain", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain", + "target": "conversationalAgent_0", + "targetHandle": "conversationalAgent_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain-conversationalAgent_0-conversationalAgent_0-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "bufferMemory_0", + "sourceHandle": "bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory", + "target": "conversationalAgent_0", + "targetHandle": "conversationalAgent_0-input-memory-BaseChatMemory", + "type": "buttonedge", + "id": "bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-conversationalAgent_0-conversationalAgent_0-input-memory-BaseChatMemory", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_1", + "sourceHandle": "chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain", + "target": "getApiChain_0", + "targetHandle": "getApiChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_1-chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain-getApiChain_0-getApiChain_0-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_2", + "sourceHandle": "chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain", + "target": "postApiChain_0", + "targetHandle": "postApiChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_2-chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain-postApiChain_0-postApiChain_0-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "postApiChain_0", + "sourceHandle": "postApiChain_0-output-postApiChain-POSTApiChain|BaseChain|BaseLangChain", + "target": "chainTool_1", + "targetHandle": "chainTool_1-input-baseChain-BaseChain", + "type": "buttonedge", + "id": "postApiChain_0-postApiChain_0-output-postApiChain-POSTApiChain|BaseChain|BaseLangChain-chainTool_1-chainTool_1-input-baseChain-BaseChain", + "data": { + "label": "" + } + }, + { + "source": "chainTool_0", + "sourceHandle": "chainTool_0-output-chainTool-ChainTool|DynamicTool|Tool|StructuredTool|BaseLangChain", + "target": "conversationalAgent_0", + "targetHandle": "conversationalAgent_0-input-tools-Tool", + "type": "buttonedge", + "id": "chainTool_0-chainTool_0-output-chainTool-ChainTool|DynamicTool|Tool|StructuredTool|BaseLangChain-conversationalAgent_0-conversationalAgent_0-input-tools-Tool", + "data": { + "label": "" + } + }, + { + "source": "chainTool_1", + "sourceHandle": "chainTool_1-output-chainTool-ChainTool|DynamicTool|Tool|StructuredTool|BaseLangChain", + "target": "conversationalAgent_0", + "targetHandle": "conversationalAgent_0-input-tools-Tool", + "type": "buttonedge", + "id": "chainTool_1-chainTool_1-output-chainTool-ChainTool|DynamicTool|Tool|StructuredTool|BaseLangChain-conversationalAgent_0-conversationalAgent_0-input-tools-Tool", + "data": { + "label": "" + } + } + ] +} From b8bee40c1bf7c57d726cfb43c8a3683cd4ee8668 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Sat, 27 May 2023 19:18:30 +0700 Subject: [PATCH 031/398] fix wrong logo --- .../tools/OpenAPIToolkit/OpenAPIToolkit.ts | 2 +- .../nodes/tools/OpenAPIToolkit/openai.png | Bin 3991 -> 0 bytes .../nodes/tools/OpenAPIToolkit/openapi.png | Bin 0 -> 25114 bytes 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 packages/components/nodes/tools/OpenAPIToolkit/openai.png create mode 100644 packages/components/nodes/tools/OpenAPIToolkit/openapi.png diff --git a/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts b/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts index 47b8db82..d6168061 100644 --- a/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts +++ b/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts @@ -18,7 +18,7 @@ class OpenAPIToolkit_Tools implements INode { this.label = 'OpenAPI Toolkit' this.name = 'openAPIToolkit' this.type = 'OpenAPIToolkit' - this.icon = 'openai.png' + this.icon = 'openapi.png' this.category = 'Tools' this.description = 'Load OpenAPI specification' this.inputs = [ diff --git a/packages/components/nodes/tools/OpenAPIToolkit/openai.png b/packages/components/nodes/tools/OpenAPIToolkit/openai.png deleted file mode 100644 index de08a05b28979826c4cc669c4899789763a938a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3991 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 diff --git a/packages/components/nodes/tools/OpenAPIToolkit/openapi.png b/packages/components/nodes/tools/OpenAPIToolkit/openapi.png new file mode 100644 index 0000000000000000000000000000000000000000..457c2e4050c8eef06588f25c0acc67a6059ebe01 GIT binary patch literal 25114 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 Date: Sat, 27 May 2023 13:28:21 +0100 Subject: [PATCH 032/398] fix conversational chain output response --- .../nodes/chains/ConversationChain/ConversationChain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index 4e39ae6d..843e05fc 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -96,7 +96,7 @@ class ConversationChain_Chains implements INode { return res?.response } else { const res = await chain.call({ input }) - return res?.text + return res?.response } } } From 626e1b29f828a7ff4fe1dd73a8b85b0be5929cfc Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 27 May 2023 14:32:41 +0100 Subject: [PATCH 033/398] add authrorization to npx and docker installation --- Dockerfile | 5 +++++ README.md | 14 ++++++++++++++ docker/.env.example | 4 +++- docker/README.md | 24 ++++++++++++++++++++++++ packages/server/src/commands/start.ts | 10 +++++++++- 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 docker/README.md diff --git a/Dockerfile b/Dockerfile index a8a4c69a..8d4518ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,12 @@ # Build local monorepo image # docker build --no-cache -t flowise . + # Run image # docker run -d -p 3000:3000 flowise + +# Run image with authorization +# docker run -d -e USERNAME=user -e PASSWORD=1234 -p 3000:3000 flowise + FROM node:18-alpine RUN apk add --update libc6-compat diff --git a/README.md b/README.md index 74b04b2d..067a6de9 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,12 @@ Drag & drop UI to build your customized LLM flow using [LangchainJS](https://git npx flowise start ``` + With username & password + + ```bash + npx flowise start --USERNAME=user --PASSWORD=1234 + ``` + 3. Open [http://localhost:3000](http://localhost:3000) ## 🐳 Docker @@ -38,9 +44,17 @@ Drag & drop UI to build your customized LLM flow using [LangchainJS](https://git docker build --no-cache -t flowise . ``` 2. Run image: + ```bash docker run -d --name flowise -p 3000:3000 flowise ``` + + With username & password + + ```bash + docker run -d -e USERNAME=user -e PASSWORD=1234 --name flowise -p 3000:3000 flowise + ``` + 3. Stop image: ```bash docker stop flowise diff --git a/docker/.env.example b/docker/.env.example index c0c68b1c..2f0e5571 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -1 +1,3 @@ -PORT=3000 \ No newline at end of file +PORT=3000 +USERNAME=user +PASSWORD=1234 \ No newline at end of file diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..57f747d3 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,24 @@ +# Flowise Docker Hub Image + +Starts Flowise from [DockerHub Image](https://hub.docker.com/repository/docker/flowiseai/flowise/general) + +## Usage + +1. Create `.env` file and specify the `PORT` (refer to `.env.example`) +2. `docker-compose up -d` +3. Open [http://localhost:3000](http://localhost:3000) +4. You can bring the containers down by `docker-compose stop` + +## With Authrorization + +1. Create `.env` file and specify the `PORT`, `USERNAME`, and `PASSWORD` (refer to `.env.example`) +2. Pass `USERNAME` and `PASSWORD` to the `docker-compose.yml` file: + ``` + environment: + - PORT=${PORT} + - USERNAME=${USERNAME} + - PASSWORD=${PASSWORD} + ``` +3. `docker-compose up -d` +4. Open [http://localhost:3000](http://localhost:3000) +5. You can bring the containers down by `docker-compose stop` diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index 9c9e5591..96456f26 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -1,4 +1,4 @@ -import { Command } from '@oclif/core' +import { Command, Flags } from '@oclif/core' import path from 'path' import * as Server from '../index' import * as DataSource from '../DataSource' @@ -14,6 +14,10 @@ let processExitCode = EXIT_CODE.SUCCESS export default class Start extends Command { static args = [] + static flags = { + USERNAME: Flags.string(), + PASSWORD: Flags.string() + } async stopProcess() { console.info('Shutting down Flowise...') @@ -43,6 +47,10 @@ export default class Start extends Command { console.error('uncaughtException: ', err) }) + const { flags } = await this.parse(Start) + if (flags.USERNAME) process.env.USERNAME = flags.USERNAME + if (flags.PASSWORD) process.env.PASSWORD = flags.PASSWORD + await (async () => { try { this.log('Starting Flowise...') From 90cba7e8f1d9f9bdfc1f7406d05ef2e9661e2588 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 27 May 2023 18:04:48 +0100 Subject: [PATCH 034/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.2.9?= =?UTF-8?q?=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 27e0a59c..27ecc5cb 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.8", + "version": "1.2.9", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From 8a50fee6b15d7f536e2f4690f41bfaaa784fc95c Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 27 May 2023 18:05:31 +0100 Subject: [PATCH 035/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.2.7=20relea?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 290faa3f..b782ca67 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.6", + "version": "1.2.7", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From dc4fe13b12288dccf44ec99f9b4c309ad3a4c304 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 27 May 2023 18:05:51 +0100 Subject: [PATCH 036/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.2.8=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 52cb1dc2..08454ee3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.7", + "version": "1.2.8", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index 4c5ed057..4bf0a250 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.7", + "version": "1.2.8", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 0f0d887f787b17f3340840a6cb61ffff28410826 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 27 May 2023 20:23:18 +0100 Subject: [PATCH 037/398] change USERNAME and PASSWORD to FLOWISE_USERNAME and FLOWISE_PASSWORD to prevent conflict with machine env variables --- Dockerfile | 3 --- README.md | 14 ++++---------- docker/.env.example | 4 ++-- docker/README.md | 8 ++++---- docker/docker-compose.yml | 2 ++ packages/components/src/index.ts | 2 +- packages/server/.env.example | 4 ++-- packages/server/README.md | 6 +++--- packages/server/package.json | 3 +-- packages/server/src/commands/start.ts | 10 +++++----- packages/server/src/index.ts | 6 +++--- packages/ui/src/api/client.js | 4 ++-- 12 files changed, 29 insertions(+), 37 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8d4518ea..77eaa9d6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,9 +4,6 @@ # Run image # docker run -d -p 3000:3000 flowise -# Run image with authorization -# docker run -d -e USERNAME=user -e PASSWORD=1234 -p 3000:3000 flowise - FROM node:18-alpine RUN apk add --update libc6-compat diff --git a/README.md b/README.md index 067a6de9..dff890ff 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Drag & drop UI to build your customized LLM flow using [LangchainJS](https://git With username & password ```bash - npx flowise start --USERNAME=user --PASSWORD=1234 + npx flowise start --FLOWISE_USERNAME=user --FLOWISE_PASSWORD=1234 ``` 3. Open [http://localhost:3000](http://localhost:3000) @@ -49,12 +49,6 @@ Drag & drop UI to build your customized LLM flow using [LangchainJS](https://git docker run -d --name flowise -p 3000:3000 flowise ``` - With username & password - - ```bash - docker run -d -e USERNAME=user -e PASSWORD=1234 --name flowise -p 3000:3000 flowise - ``` - 3. Stop image: ```bash docker stop flowise @@ -119,11 +113,11 @@ Flowise has 3 different modules in a single mono repository. ## 🔒 Authentication -To enable app level authentication, add `USERNAME` and `PASSWORD` to the `.env` file in `packages/server`: +To enable app level authentication, add `FLOWISE_USERNAME` and `FLOWISE_PASSWORD` to the `.env` file in `packages/server`: ``` -USERNAME=user -PASSWORD=1234 +FLOWISE_USERNAME=user +FLOWISE_PASSWORD=1234 ``` ## 📖 Documentation diff --git a/docker/.env.example b/docker/.env.example index 2f0e5571..f3211196 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -1,3 +1,3 @@ PORT=3000 -USERNAME=user -PASSWORD=1234 \ No newline at end of file +# FLOWISE_USERNAME=user +# FLOWISE_PASSWORD=1234 diff --git a/docker/README.md b/docker/README.md index 57f747d3..7f991a04 100644 --- a/docker/README.md +++ b/docker/README.md @@ -11,13 +11,13 @@ Starts Flowise from [DockerHub Image](https://hub.docker.com/repository/docker/f ## With Authrorization -1. Create `.env` file and specify the `PORT`, `USERNAME`, and `PASSWORD` (refer to `.env.example`) -2. Pass `USERNAME` and `PASSWORD` to the `docker-compose.yml` file: +1. Create `.env` file and specify the `PORT`, `FLOWISE_USERNAME`, and `FLOWISE_PASSWORD` (refer to `.env.example`) +2. Pass `FLOWISE_USERNAME` and `FLOWISE_PASSWORD` to the `docker-compose.yml` file: ``` environment: - PORT=${PORT} - - USERNAME=${USERNAME} - - PASSWORD=${PASSWORD} + - FLOWISE_USERNAME=${FLOWISE_USERNAME} + - FLOWISE_PASSWORD=${FLOWISE_PASSWORD} ``` 3. `docker-compose up -d` 4. Open [http://localhost:3000](http://localhost:3000) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 7d142cb8..c776f96e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -6,6 +6,8 @@ services: restart: always environment: - PORT=${PORT} + - FLOWISE_USERNAME=${FLOWISE_USERNAME} + - FLOWISE_PASSWORD=${FLOWISE_PASSWORD} ports: - '${PORT}:${PORT}' volumes: diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index d04f5bf6..ae2e380e 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -2,7 +2,7 @@ import dotenv from 'dotenv' import path from 'path' const envPath = path.join(__dirname, '..', '..', '.env') -dotenv.config({ path: envPath }) +dotenv.config({ path: envPath, override: true }) export * from './Interface' export * from './utils' diff --git a/packages/server/.env.example b/packages/server/.env.example index 2131b8d1..fd82c096 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -1,4 +1,4 @@ PORT=3000 -# USERNAME=user -# PASSWORD=1234 +# FLOWISE_USERNAME=user +# FLOWISE_PASSWORD=1234 # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/packages/server/README.md b/packages/server/README.md index 1915863b..2cdf41d1 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -22,11 +22,11 @@ Drag & drop UI to build your customized LLM flow using [LangchainJS](https://git ## 🔒 Authentication -To enable app level authentication, add `USERNAME` and `PASSWORD` to the `.env` file: +To enable app level authentication, add `FLOWISE_USERNAME` and `FLOWISE_PASSWORD` to the `.env` file: ``` -USERNAME=user -PASSWORD=1234 +FLOWISE_USERNAME=user +FLOWISE_PASSWORD=1234 ``` ## 📖 Documentation diff --git a/packages/server/package.json b/packages/server/package.json index 4bf0a250..f68db123 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -13,8 +13,7 @@ "dist", "npm-shrinkwrap.json", "oclif.manifest.json", - "oauth2.html", - ".env" + "oauth2.html" ], "oclif": { "bin": "flowise", diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index 96456f26..a3ee561a 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -4,7 +4,7 @@ import * as Server from '../index' import * as DataSource from '../DataSource' import dotenv from 'dotenv' -dotenv.config({ path: path.join(__dirname, '..', '..', '.env') }) +dotenv.config({ path: path.join(__dirname, '..', '..', '.env'), override: true }) enum EXIT_CODE { SUCCESS = 0, @@ -15,8 +15,8 @@ let processExitCode = EXIT_CODE.SUCCESS export default class Start extends Command { static args = [] static flags = { - USERNAME: Flags.string(), - PASSWORD: Flags.string() + FLOWISE_USERNAME: Flags.string(), + FLOWISE_PASSWORD: Flags.string() } async stopProcess() { @@ -48,8 +48,8 @@ export default class Start extends Command { }) const { flags } = await this.parse(Start) - if (flags.USERNAME) process.env.USERNAME = flags.USERNAME - if (flags.PASSWORD) process.env.PASSWORD = flags.PASSWORD + if (flags.FLOWISE_USERNAME) process.env.FLOWISE_USERNAME = flags.FLOWISE_USERNAME + if (flags.FLOWISE_PASSWORD) process.env.FLOWISE_PASSWORD = flags.FLOWISE_PASSWORD await (async () => { try { diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 59dfe3cf..548c27e9 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -83,9 +83,9 @@ export class App { // Allow access from * this.app.use(cors()) - if (process.env.USERNAME && process.env.PASSWORD) { - const username = process.env.USERNAME.toLocaleLowerCase() - const password = process.env.PASSWORD.toLocaleLowerCase() + if (process.env.FLOWISE_USERNAME && process.env.FLOWISE_PASSWORD) { + const username = process.env.FLOWISE_USERNAME + const password = process.env.FLOWISE_PASSWORD const basicAuthMiddleware = basicAuth({ users: { [username]: password } }) diff --git a/packages/ui/src/api/client.js b/packages/ui/src/api/client.js index cafdf0b3..8235bde4 100644 --- a/packages/ui/src/api/client.js +++ b/packages/ui/src/api/client.js @@ -14,8 +14,8 @@ apiClient.interceptors.request.use(function (config) { if (username && password) { config.auth = { - username: username.toLocaleLowerCase(), - password: password.toLocaleLowerCase() + username, + password } } From d7abdf7187fd0132e240732617542084545f5c8a Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 27 May 2023 20:59:45 +0100 Subject: [PATCH 038/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.2.1?= =?UTF-8?q?1=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 27ecc5cb..bf149bfd 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.9", + "version": "1.2.11", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From d87808e811770b4dd7c9f45fa458f389a97e6ad9 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 27 May 2023 21:00:28 +0100 Subject: [PATCH 039/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.2.9=20relea?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index b782ca67..5ffad0da 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.7", + "version": "1.2.9", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From 0dbd9e1127a9f2006783ac4151821ef20f3329a5 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 27 May 2023 21:00:55 +0100 Subject: [PATCH 040/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.2.10=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 08454ee3..44a50e60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.8", + "version": "1.2.10", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index f68db123..9710c16f 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.8", + "version": "1.2.10", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From cc42702cd093ebe9d31c499cf340e0605f450ec9 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 28 May 2023 12:59:41 +0100 Subject: [PATCH 041/398] add faiss --- .../Faiss_Existing/Faiss_Existing.ts | 70 +++++++++++++++ .../vectorstores/Faiss_Existing/faiss.svg | 10 +++ .../vectorstores/Faiss_Upsert/Faiss_Upsert.ts | 85 +++++++++++++++++++ .../nodes/vectorstores/Faiss_Upsert/faiss.svg | 10 +++ packages/components/package.json | 1 + packages/server/src/index.ts | 5 +- packages/server/src/utils/index.ts | 30 ++++++- 7 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts create mode 100644 packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg create mode 100644 packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts create mode 100644 packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg diff --git a/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts b/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts new file mode 100644 index 00000000..8916a734 --- /dev/null +++ b/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts @@ -0,0 +1,70 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { FaissStore } from 'langchain/vectorstores/faiss' +import { Embeddings } from 'langchain/embeddings/base' +import { getBaseClasses } from '../../../src/utils' + +class Faiss_Existing_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Faiss Load Existing Index' + this.name = 'faissExistingIndex' + this.type = 'Faiss' + this.icon = 'faiss.svg' + this.category = 'Vector Stores' + this.description = 'Load existing index from Faiss (i.e: Document has been upserted)' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Base Path to load', + name: 'basePath', + description: 'Path to load faiss.index file', + placeholder: `C:\\Users\\User\\Desktop`, + type: 'string' + } + ] + this.outputs = [ + { + label: 'Faiss Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Faiss Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(FaissStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const embeddings = nodeData.inputs?.embeddings as Embeddings + const basePath = nodeData.inputs?.basePath as string + const output = nodeData.outputs?.output as string + + const vectorStore = await FaissStore.load(basePath, embeddings) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever() + return retriever + } else if (output === 'vectorStore') { + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: Faiss_Existing_VectorStores } diff --git a/packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg b/packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg new file mode 100644 index 00000000..5fbe9832 --- /dev/null +++ b/packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts b/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts new file mode 100644 index 00000000..2db6a038 --- /dev/null +++ b/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts @@ -0,0 +1,85 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { getBaseClasses } from '../../../src/utils' +import { FaissStore } from 'langchain/vectorstores/faiss' + +class FaissUpsert_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Faiss Upsert Document' + this.name = 'faissUpsert' + this.type = 'Faiss' + this.icon = 'faiss.svg' + this.category = 'Vector Stores' + this.description = 'Upsert documents to Faiss' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Document', + name: 'document', + type: 'Document', + list: true + }, + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Base Path to store', + name: 'basePath', + description: 'Path to store faiss.index file', + placeholder: `C:\\Users\\User\\Desktop`, + type: 'string' + } + ] + this.outputs = [ + { + label: 'Faiss Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Faiss Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(FaissStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string + const basePath = nodeData.inputs?.basePath as string + + const flattenDocs = docs && docs.length ? docs.flat() : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + finalDocs.push(new Document(flattenDocs[i])) + } + + const vectorStore = await FaissStore.fromDocuments(finalDocs, embeddings) + await vectorStore.save(basePath) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever() + return retriever + } else if (output === 'vectorStore') { + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: FaissUpsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg b/packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg new file mode 100644 index 00000000..5fbe9832 --- /dev/null +++ b/packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index bf149bfd..76cf6377 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -28,6 +28,7 @@ "d3-dsv": "2", "dotenv": "^16.0.0", "express": "^4.17.3", + "faiss-node": "^0.2.0", "form-data": "^4.0.0", "graphql": "^16.6.0", "langchain": "^0.0.82", diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 548c27e9..92cd12d0 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -34,7 +34,8 @@ import { findAvailableConfigs, isSameOverrideConfig, replaceAllAPIKeys, - isFlowValidForStream + isFlowValidForStream, + isVectorStoreFaiss } from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' @@ -634,6 +635,8 @@ export class App { const nodeModule = await import(nodeInstanceFilePath) const nodeInstance = new nodeModule.nodeClass() + isStreamValid = isStreamValid && !isVectorStoreFaiss(nodeToExecuteData) + const result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 982a82d0..18473c51 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -14,7 +14,7 @@ import { INodeData, IOverrideConfig } from '../Interface' -import { cloneDeep, get } from 'lodash' +import { cloneDeep, get, omit, merge } from 'lodash' import { ICommonObject, getInputVariables } from 'flowise-components' import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' @@ -317,6 +317,25 @@ export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowN return returnVal } +/** + * Temporarily disable streaming if vectorStore is Faiss + * @param {INodeData} flowNodeData + * @returns {boolean} + */ +export const isVectorStoreFaiss = (flowNodeData: INodeData) => { + if (flowNodeData.inputs && flowNodeData.inputs.vectorStoreRetriever) { + const vectorStoreRetriever = flowNodeData.inputs.vectorStoreRetriever + if (typeof vectorStoreRetriever === 'string' && vectorStoreRetriever.includes('faiss')) return true + if ( + typeof vectorStoreRetriever === 'object' && + vectorStoreRetriever.vectorStore && + vectorStoreRetriever.vectorStore.constructor.name === 'FaissStore' + ) + return true + } + return false +} + /** * Loop through each inputs and resolve variable if neccessary * @param {INodeData} reactFlowNodeData @@ -325,7 +344,12 @@ export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowN * @returns {INodeData} */ export const resolveVariables = (reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[], question: string): INodeData => { - const flowNodeData = cloneDeep(reactFlowNodeData) + let flowNodeData = cloneDeep(reactFlowNodeData) + if (reactFlowNodeData.instance && isVectorStoreFaiss(reactFlowNodeData)) { + // omit and merge because cloneDeep of instance gives "Illegal invocation" Exception + const flowNodeDataWithoutInstance = cloneDeep(omit(reactFlowNodeData, ['instance'])) + flowNodeData = merge(flowNodeDataWithoutInstance, { instance: reactFlowNodeData.instance }) + } const types = 'inputs' const getParamValues = (paramsObj: ICommonObject) => { @@ -633,5 +657,5 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod } } - return isChatOrLLMsExist && endingNodeData.category === 'Chains' + return isChatOrLLMsExist && endingNodeData.category === 'Chains' && !isVectorStoreFaiss(endingNodeData) } From c7a0b5a0bbdadb8d26e7c161220f01e83857b9a1 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 28 May 2023 19:06:31 +0100 Subject: [PATCH 042/398] update README to include docs and deploy --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dff890ff..fb312514 100644 --- a/README.md +++ b/README.md @@ -122,14 +122,20 @@ FLOWISE_PASSWORD=1234 ## 📖 Documentation -Coming soon - -## 💻 Cloud Hosted - -Coming soon +[Flowise Docs](https://docs.flowiseai.com/) ## 🌐 Self Host +### [Railway](https://docs.flowiseai.com/deployment/railway) + +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/YK7J0v) + +### [Render](https://docs.flowiseai.com/deployment/render) + +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) + +## 💻 Cloud Hosted + Coming soon ## 🙋 Support From 5a1c1397a363adeb1c459b0bdafe6664934442c5 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 29 May 2023 00:34:53 +0100 Subject: [PATCH 043/398] add notionDB --- .../Notion/{Notion.ts => NotionDB.ts} | 49 +++++++++++++------ packages/components/package.json | 2 +- 2 files changed, 35 insertions(+), 16 deletions(-) rename packages/components/nodes/documentloaders/Notion/{Notion.ts => NotionDB.ts} (54%) diff --git a/packages/components/nodes/documentloaders/Notion/Notion.ts b/packages/components/nodes/documentloaders/Notion/NotionDB.ts similarity index 54% rename from packages/components/nodes/documentloaders/Notion/Notion.ts rename to packages/components/nodes/documentloaders/Notion/NotionDB.ts index f5bfcb2a..e1eb182a 100644 --- a/packages/components/nodes/documentloaders/Notion/Notion.ts +++ b/packages/components/nodes/documentloaders/Notion/NotionDB.ts @@ -1,8 +1,8 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' -import { NotionLoader } from 'langchain/document_loaders/fs/notion' +import { NotionDBLoader } from 'langchain/document_loaders/web/notiondb' -class Notion_DocumentLoaders implements INode { +class NotionDB_DocumentLoaders implements INode { label: string name: string description: string @@ -13,27 +13,40 @@ class Notion_DocumentLoaders implements INode { inputs: INodeParams[] constructor() { - this.label = 'Notion Folder' - this.name = 'notionFolder' + this.label = 'Notion Database' + this.name = 'notionDB' this.type = 'Document' this.icon = 'notion.png' this.category = 'Document Loaders' - this.description = `Load data from Notion folder` + this.description = `Load data from Notion Database` this.baseClasses = [this.type] this.inputs = [ - { - label: 'Notion Folder', - name: 'notionFolder', - type: 'string', - description: 'Get folder path', - placeholder: 'Paste folder path' - }, { label: 'Text Splitter', name: 'textSplitter', type: 'TextSplitter', optional: true }, + { + label: 'Notion Database Id', + name: 'databaseId', + type: 'string', + description: + 'If your URL looks like - https://www.notion.so/?v=, then is the database ID' + }, + { + label: 'Notion Integration Token', + name: 'notionIntegrationToken', + type: 'string', + description: + 'You can find integration token here' + }, + { + label: 'Page Size Limit', + name: 'pageSizeLimit', + type: 'number', + default: 10 + }, { label: 'Metadata', name: 'metadata', @@ -46,10 +59,16 @@ class Notion_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter - const notionFolder = nodeData.inputs?.notionFolder as string + const databaseId = nodeData.inputs?.databaseId as string + const notionIntegrationToken = nodeData.inputs?.notionIntegrationToken as string + const pageSizeLimit = nodeData.inputs?.pageSizeLimit as string const metadata = nodeData.inputs?.metadata - const loader = new NotionLoader(notionFolder) + const loader = new NotionDBLoader({ + pageSizeLimit: pageSizeLimit ? parseInt(pageSizeLimit, 10) : 10, + databaseId, + notionIntegrationToken + }) let docs = [] if (textSplitter) { @@ -78,4 +97,4 @@ class Notion_DocumentLoaders implements INode { } } -module.exports = { nodeClass: Notion_DocumentLoaders } +module.exports = { nodeClass: NotionDB_DocumentLoaders } diff --git a/packages/components/package.json b/packages/components/package.json index bf149bfd..b76e729c 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -30,7 +30,7 @@ "express": "^4.17.3", "form-data": "^4.0.0", "graphql": "^16.6.0", - "langchain": "^0.0.82", + "langchain": "^0.0.84", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", From 10c4f916de4100f86eee2e80b9a52e50d25ebf7b Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Mon, 29 May 2023 21:29:24 +0700 Subject: [PATCH 044/398] modify string to password --- .../{Notion => NotionDB}/NotionDB.ts | 11 +-- .../{Notion => NotionDB}/notion.png | Bin .../NotionFolder/NotionFolder.ts | 81 ++++++++++++++++++ .../documentloaders/NotionFolder/notion.png | Bin 0 -> 11406 bytes 4 files changed, 87 insertions(+), 5 deletions(-) rename packages/components/nodes/documentloaders/{Notion => NotionDB}/NotionDB.ts (93%) rename packages/components/nodes/documentloaders/{Notion => NotionDB}/notion.png (100%) create mode 100644 packages/components/nodes/documentloaders/NotionFolder/NotionFolder.ts create mode 100644 packages/components/nodes/documentloaders/NotionFolder/notion.png diff --git a/packages/components/nodes/documentloaders/Notion/NotionDB.ts b/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts similarity index 93% rename from packages/components/nodes/documentloaders/Notion/NotionDB.ts rename to packages/components/nodes/documentloaders/NotionDB/NotionDB.ts index e1eb182a..10bb93c4 100644 --- a/packages/components/nodes/documentloaders/Notion/NotionDB.ts +++ b/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts @@ -1,6 +1,6 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' -import { NotionDBLoader } from 'langchain/document_loaders/web/notiondb' +import { NotionDBLoader, NotionDBLoaderParams } from 'langchain/document_loaders/web/notiondb' class NotionDB_DocumentLoaders implements INode { label: string @@ -37,7 +37,7 @@ class NotionDB_DocumentLoaders implements INode { { label: 'Notion Integration Token', name: 'notionIntegrationToken', - type: 'string', + type: 'password', description: 'You can find integration token here' }, @@ -64,13 +64,14 @@ class NotionDB_DocumentLoaders implements INode { const pageSizeLimit = nodeData.inputs?.pageSizeLimit as string const metadata = nodeData.inputs?.metadata - const loader = new NotionDBLoader({ + const obj: NotionDBLoaderParams = { pageSizeLimit: pageSizeLimit ? parseInt(pageSizeLimit, 10) : 10, databaseId, notionIntegrationToken - }) - let docs = [] + } + const loader = new NotionDBLoader(obj) + let docs = [] if (textSplitter) { docs = await loader.loadAndSplit(textSplitter) } else { diff --git a/packages/components/nodes/documentloaders/Notion/notion.png b/packages/components/nodes/documentloaders/NotionDB/notion.png similarity index 100% rename from packages/components/nodes/documentloaders/Notion/notion.png rename to packages/components/nodes/documentloaders/NotionDB/notion.png diff --git a/packages/components/nodes/documentloaders/NotionFolder/NotionFolder.ts b/packages/components/nodes/documentloaders/NotionFolder/NotionFolder.ts new file mode 100644 index 00000000..d7c72a05 --- /dev/null +++ b/packages/components/nodes/documentloaders/NotionFolder/NotionFolder.ts @@ -0,0 +1,81 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { TextSplitter } from 'langchain/text_splitter' +import { NotionLoader } from 'langchain/document_loaders/fs/notion' + +class NotionFolder_DocumentLoaders implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Notion Folder' + this.name = 'notionFolder' + this.type = 'Document' + this.icon = 'notion.png' + this.category = 'Document Loaders' + this.description = `Load data from Notion folder` + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Notion Folder', + name: 'notionFolder', + type: 'string', + description: 'Get folder path', + placeholder: 'Paste folder path' + }, + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const textSplitter = nodeData.inputs?.textSplitter as TextSplitter + const notionFolder = nodeData.inputs?.notionFolder as string + const metadata = nodeData.inputs?.metadata + + const loader = new NotionLoader(notionFolder) + let docs = [] + + if (textSplitter) { + docs = await loader.loadAndSplit(textSplitter) + } else { + docs = await loader.load() + } + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + let finaldocs = [] + for (const doc of docs) { + const newdoc = { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + finaldocs.push(newdoc) + } + return finaldocs + } + + return docs + } +} + +module.exports = { nodeClass: NotionFolder_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/NotionFolder/notion.png b/packages/components/nodes/documentloaders/NotionFolder/notion.png new file mode 100644 index 0000000000000000000000000000000000000000..391051679c8cc33e7e52891593147283bf93dcb0 GIT binary patch literal 11406 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 Date: Mon, 29 May 2023 22:00:01 +0700 Subject: [PATCH 045/398] add bathSize into AzureOpenAIEmbedding --- .../AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts index 355877e5..7fe61e62 100644 --- a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts +++ b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts @@ -55,6 +55,14 @@ class AzureOpenAIEmbedding_Embeddings implements INode { ], default: '2023-03-15-preview' }, + { + label: 'Batch Size', + name: 'batchSize', + type: 'number', + default: '1', + optional: true, + additionalParams: true + }, { label: 'Timeout', name: 'timeout', @@ -70,6 +78,7 @@ class AzureOpenAIEmbedding_Embeddings implements INode { const azureOpenAIApiInstanceName = nodeData.inputs?.azureOpenAIApiInstanceName as string const azureOpenAIApiDeploymentName = nodeData.inputs?.azureOpenAIApiDeploymentName as string const azureOpenAIApiVersion = nodeData.inputs?.azureOpenAIApiVersion as string + const batchSize = nodeData.inputs?.batchSize as string const timeout = nodeData.inputs?.timeout as string const obj: Partial & Partial = { @@ -79,6 +88,7 @@ class AzureOpenAIEmbedding_Embeddings implements INode { azureOpenAIApiVersion } + if (batchSize) obj.batchSize = parseInt(batchSize, 10) if (timeout) obj.timeout = parseInt(timeout, 10) const model = new OpenAIEmbeddings(obj) From b6308e2a5d4c58e2df254382badaa78859358207 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 29 May 2023 18:56:20 +0100 Subject: [PATCH 046/398] update README to include AWS and DigitalOcean deployment --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index fb312514..fff3feff 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,10 @@ FLOWISE_PASSWORD=1234 [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) +### [AWS](https://docs.flowiseai.com/deployment/aws) + +### [DigitalOcean](https://docs.flowiseai.com/deployment/digital-ocean) + ## 💻 Cloud Hosted Coming soon From ac7521521833a937e25b8334de585dd38aa54bb2 Mon Sep 17 00:00:00 2001 From: Neeraj Bansal Date: Tue, 30 May 2023 23:12:45 +0530 Subject: [PATCH 047/398] added confluence document loader and html-to-text dependency --- .../documentloaders/Confluence/Confluence.ts | 120 ++++++++++++++++++ .../documentloaders/Confluence/confluence.png | Bin 0 -> 14323 bytes packages/components/package.json | 3 +- 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 packages/components/nodes/documentloaders/Confluence/Confluence.ts create mode 100644 packages/components/nodes/documentloaders/Confluence/confluence.png diff --git a/packages/components/nodes/documentloaders/Confluence/Confluence.ts b/packages/components/nodes/documentloaders/Confluence/Confluence.ts new file mode 100644 index 00000000..c2f3abc0 --- /dev/null +++ b/packages/components/nodes/documentloaders/Confluence/Confluence.ts @@ -0,0 +1,120 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { TextSplitter } from 'langchain/text_splitter' +// import { GithubRepoLoader, GithubRepoLoaderParams } from 'langchain/document_loaders/web/github' +import { ConfluencePagesLoader, ConfluencePagesLoaderParams } from "langchain/document_loaders/web/confluence"; + +class Confluence_DocumentLoaders implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Confluence' + this.name = 'confluence' + this.type = 'Document' + this.icon = 'confluence.png' + this.category = 'Document Loaders' + this.description = `Load data from a Confluence Document` + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + label: 'Username', + name: 'username', + type: 'string', + placeholder: '', + }, + { + label: 'Access Token', + name: 'accessToken', + type: 'password', + placeholder: '', + }, + { + label: 'Base URL', + name: 'baseUrl', + type: 'string', + placeholder: "https://example.atlassian.net/wiki" + }, + { + label: 'Space Key', + name: 'spaceKey', + type: 'string', + placeholder: '~EXAMPLE362906de5d343d49dcdbae5dEXAMPLE' + }, + { + label: 'Limit', + name: 'limit', + type: 'number', + default: 0, + optional: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const username = nodeData.inputs?.username as string + const accessToken = nodeData.inputs?.accessToken as string + const spaceKey = nodeData.inputs?.spaceKey as string + const baseUrl = nodeData.inputs?.baseUrl as string + const limit = nodeData.inputs?.limit as number + const textSplitter = nodeData.inputs?.textSplitter as TextSplitter + const metadata = nodeData.inputs?.metadata + + const options: ConfluencePagesLoaderParams = { + username, + accessToken, + baseUrl, + spaceKey, + limit + } + + const loader = new ConfluencePagesLoader(options) + + console.log('loader') + let docs = [] + + if (textSplitter) { + docs = await loader.loadAndSplit(textSplitter) + } else { + docs = await loader.load() + } + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + let finaldocs = [] + for (const doc of docs) { + const newdoc = { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + finaldocs.push(newdoc) + } + return finaldocs + } + + return docs + } +} + +module.exports = { nodeClass: Confluence_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/Confluence/confluence.png b/packages/components/nodes/documentloaders/Confluence/confluence.png new file mode 100644 index 0000000000000000000000000000000000000000..3cbb7b3dc2492867afc54c6ddca6ae2219b91aec GIT binary patch literal 14323 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 cf7!K;{8S{rDi{+x&m7 Date: Tue, 30 May 2023 23:22:23 +0530 Subject: [PATCH 048/398] removed ununsed comments --- .../components/nodes/documentloaders/Confluence/Confluence.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/components/nodes/documentloaders/Confluence/Confluence.ts b/packages/components/nodes/documentloaders/Confluence/Confluence.ts index c2f3abc0..7d8dd340 100644 --- a/packages/components/nodes/documentloaders/Confluence/Confluence.ts +++ b/packages/components/nodes/documentloaders/Confluence/Confluence.ts @@ -1,6 +1,5 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' -// import { GithubRepoLoader, GithubRepoLoaderParams } from 'langchain/document_loaders/web/github' import { ConfluencePagesLoader, ConfluencePagesLoaderParams } from "langchain/document_loaders/web/confluence"; class Confluence_DocumentLoaders implements INode { @@ -88,7 +87,6 @@ class Confluence_DocumentLoaders implements INode { const loader = new ConfluencePagesLoader(options) - console.log('loader') let docs = [] if (textSplitter) { From 791af9310edc16168542a2aab1d4fe5e54a7cc95 Mon Sep 17 00:00:00 2001 From: Neeraj Bansal Date: Wed, 31 May 2023 01:20:32 +0530 Subject: [PATCH 049/398] lint fix --- .../nodes/documentloaders/Confluence/Confluence.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/documentloaders/Confluence/Confluence.ts b/packages/components/nodes/documentloaders/Confluence/Confluence.ts index 7d8dd340..9a69be14 100644 --- a/packages/components/nodes/documentloaders/Confluence/Confluence.ts +++ b/packages/components/nodes/documentloaders/Confluence/Confluence.ts @@ -1,6 +1,6 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' -import { ConfluencePagesLoader, ConfluencePagesLoaderParams } from "langchain/document_loaders/web/confluence"; +import { ConfluencePagesLoader, ConfluencePagesLoaderParams } from 'langchain/document_loaders/web/confluence' class Confluence_DocumentLoaders implements INode { label: string @@ -31,19 +31,19 @@ class Confluence_DocumentLoaders implements INode { label: 'Username', name: 'username', type: 'string', - placeholder: '', + placeholder: '' }, { label: 'Access Token', name: 'accessToken', type: 'password', - placeholder: '', + placeholder: '' }, { label: 'Base URL', name: 'baseUrl', type: 'string', - placeholder: "https://example.atlassian.net/wiki" + placeholder: 'https://example.atlassian.net/wiki' }, { label: 'Space Key', From 0e6e5476cc2695a06467257b75436cda314b6fbf Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Wed, 31 May 2023 14:29:59 +0100 Subject: [PATCH 050/398] Revert "Feature/Add faiss" --- .../Faiss_Existing/Faiss_Existing.ts | 70 --------------- .../vectorstores/Faiss_Existing/faiss.svg | 10 --- .../vectorstores/Faiss_Upsert/Faiss_Upsert.ts | 85 ------------------- .../nodes/vectorstores/Faiss_Upsert/faiss.svg | 10 --- packages/components/package.json | 1 - packages/server/src/index.ts | 5 +- packages/server/src/utils/index.ts | 30 +------ 7 files changed, 4 insertions(+), 207 deletions(-) delete mode 100644 packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts delete mode 100644 packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg delete mode 100644 packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts delete mode 100644 packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg diff --git a/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts b/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts deleted file mode 100644 index 8916a734..00000000 --- a/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' -import { FaissStore } from 'langchain/vectorstores/faiss' -import { Embeddings } from 'langchain/embeddings/base' -import { getBaseClasses } from '../../../src/utils' - -class Faiss_Existing_VectorStores implements INode { - label: string - name: string - description: string - type: string - icon: string - category: string - baseClasses: string[] - inputs: INodeParams[] - outputs: INodeOutputsValue[] - - constructor() { - this.label = 'Faiss Load Existing Index' - this.name = 'faissExistingIndex' - this.type = 'Faiss' - this.icon = 'faiss.svg' - this.category = 'Vector Stores' - this.description = 'Load existing index from Faiss (i.e: Document has been upserted)' - this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] - this.inputs = [ - { - label: 'Embeddings', - name: 'embeddings', - type: 'Embeddings' - }, - { - label: 'Base Path to load', - name: 'basePath', - description: 'Path to load faiss.index file', - placeholder: `C:\\Users\\User\\Desktop`, - type: 'string' - } - ] - this.outputs = [ - { - label: 'Faiss Retriever', - name: 'retriever', - baseClasses: this.baseClasses - }, - { - label: 'Faiss Vector Store', - name: 'vectorStore', - baseClasses: [this.type, ...getBaseClasses(FaissStore)] - } - ] - } - - async init(nodeData: INodeData): Promise { - const embeddings = nodeData.inputs?.embeddings as Embeddings - const basePath = nodeData.inputs?.basePath as string - const output = nodeData.outputs?.output as string - - const vectorStore = await FaissStore.load(basePath, embeddings) - - if (output === 'retriever') { - const retriever = vectorStore.asRetriever() - return retriever - } else if (output === 'vectorStore') { - return vectorStore - } - return vectorStore - } -} - -module.exports = { nodeClass: Faiss_Existing_VectorStores } diff --git a/packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg b/packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg deleted file mode 100644 index 5fbe9832..00000000 --- a/packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts b/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts deleted file mode 100644 index 2db6a038..00000000 --- a/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' -import { Embeddings } from 'langchain/embeddings/base' -import { Document } from 'langchain/document' -import { getBaseClasses } from '../../../src/utils' -import { FaissStore } from 'langchain/vectorstores/faiss' - -class FaissUpsert_VectorStores implements INode { - label: string - name: string - description: string - type: string - icon: string - category: string - baseClasses: string[] - inputs: INodeParams[] - outputs: INodeOutputsValue[] - - constructor() { - this.label = 'Faiss Upsert Document' - this.name = 'faissUpsert' - this.type = 'Faiss' - this.icon = 'faiss.svg' - this.category = 'Vector Stores' - this.description = 'Upsert documents to Faiss' - this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] - this.inputs = [ - { - label: 'Document', - name: 'document', - type: 'Document', - list: true - }, - { - label: 'Embeddings', - name: 'embeddings', - type: 'Embeddings' - }, - { - label: 'Base Path to store', - name: 'basePath', - description: 'Path to store faiss.index file', - placeholder: `C:\\Users\\User\\Desktop`, - type: 'string' - } - ] - this.outputs = [ - { - label: 'Faiss Retriever', - name: 'retriever', - baseClasses: this.baseClasses - }, - { - label: 'Faiss Vector Store', - name: 'vectorStore', - baseClasses: [this.type, ...getBaseClasses(FaissStore)] - } - ] - } - - async init(nodeData: INodeData): Promise { - const docs = nodeData.inputs?.document as Document[] - const embeddings = nodeData.inputs?.embeddings as Embeddings - const output = nodeData.outputs?.output as string - const basePath = nodeData.inputs?.basePath as string - - const flattenDocs = docs && docs.length ? docs.flat() : [] - const finalDocs = [] - for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) - } - - const vectorStore = await FaissStore.fromDocuments(finalDocs, embeddings) - await vectorStore.save(basePath) - - if (output === 'retriever') { - const retriever = vectorStore.asRetriever() - return retriever - } else if (output === 'vectorStore') { - return vectorStore - } - return vectorStore - } -} - -module.exports = { nodeClass: FaissUpsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg b/packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg deleted file mode 100644 index 5fbe9832..00000000 --- a/packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index 65eedd51..8f8e68bc 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -28,7 +28,6 @@ "d3-dsv": "2", "dotenv": "^16.0.0", "express": "^4.17.3", - "faiss-node": "^0.2.0", "form-data": "^4.0.0", "graphql": "^16.6.0", "langchain": "^0.0.82", diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 92cd12d0..548c27e9 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -34,8 +34,7 @@ import { findAvailableConfigs, isSameOverrideConfig, replaceAllAPIKeys, - isFlowValidForStream, - isVectorStoreFaiss + isFlowValidForStream } from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' @@ -635,8 +634,6 @@ export class App { const nodeModule = await import(nodeInstanceFilePath) const nodeInstance = new nodeModule.nodeClass() - isStreamValid = isStreamValid && !isVectorStoreFaiss(nodeToExecuteData) - const result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 18473c51..982a82d0 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -14,7 +14,7 @@ import { INodeData, IOverrideConfig } from '../Interface' -import { cloneDeep, get, omit, merge } from 'lodash' +import { cloneDeep, get } from 'lodash' import { ICommonObject, getInputVariables } from 'flowise-components' import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' @@ -317,25 +317,6 @@ export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowN return returnVal } -/** - * Temporarily disable streaming if vectorStore is Faiss - * @param {INodeData} flowNodeData - * @returns {boolean} - */ -export const isVectorStoreFaiss = (flowNodeData: INodeData) => { - if (flowNodeData.inputs && flowNodeData.inputs.vectorStoreRetriever) { - const vectorStoreRetriever = flowNodeData.inputs.vectorStoreRetriever - if (typeof vectorStoreRetriever === 'string' && vectorStoreRetriever.includes('faiss')) return true - if ( - typeof vectorStoreRetriever === 'object' && - vectorStoreRetriever.vectorStore && - vectorStoreRetriever.vectorStore.constructor.name === 'FaissStore' - ) - return true - } - return false -} - /** * Loop through each inputs and resolve variable if neccessary * @param {INodeData} reactFlowNodeData @@ -344,12 +325,7 @@ export const isVectorStoreFaiss = (flowNodeData: INodeData) => { * @returns {INodeData} */ export const resolveVariables = (reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[], question: string): INodeData => { - let flowNodeData = cloneDeep(reactFlowNodeData) - if (reactFlowNodeData.instance && isVectorStoreFaiss(reactFlowNodeData)) { - // omit and merge because cloneDeep of instance gives "Illegal invocation" Exception - const flowNodeDataWithoutInstance = cloneDeep(omit(reactFlowNodeData, ['instance'])) - flowNodeData = merge(flowNodeDataWithoutInstance, { instance: reactFlowNodeData.instance }) - } + const flowNodeData = cloneDeep(reactFlowNodeData) const types = 'inputs' const getParamValues = (paramsObj: ICommonObject) => { @@ -657,5 +633,5 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod } } - return isChatOrLLMsExist && endingNodeData.category === 'Chains' && !isVectorStoreFaiss(endingNodeData) + return isChatOrLLMsExist && endingNodeData.category === 'Chains' } From 13cc192e37745bb8ebc17ccd590af03a337069db Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 31 May 2023 15:03:15 +0100 Subject: [PATCH 051/398] add GCP deployment --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fff3feff..a3241cad 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,8 @@ FLOWISE_PASSWORD=1234 ### [DigitalOcean](https://docs.flowiseai.com/deployment/digital-ocean) +### [GCP](https://docs.flowiseai.com/deployment/gcp) + ## 💻 Cloud Hosted Coming soon From 0adfa3ec90a1711d561e519ed8523d20e9f148ae Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Thu, 1 Jun 2023 01:57:38 +0700 Subject: [PATCH 052/398] modify description --- packages/components/nodes/documentloaders/NotionDB/NotionDB.ts | 2 +- .../nodes/documentloaders/NotionFolder/NotionFolder.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts b/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts index 10bb93c4..71e5e507 100644 --- a/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts +++ b/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts @@ -18,7 +18,7 @@ class NotionDB_DocumentLoaders implements INode { this.type = 'Document' this.icon = 'notion.png' this.category = 'Document Loaders' - this.description = `Load data from Notion Database` + this.description = 'Load data from Notion Database ID' this.baseClasses = [this.type] this.inputs = [ { diff --git a/packages/components/nodes/documentloaders/NotionFolder/NotionFolder.ts b/packages/components/nodes/documentloaders/NotionFolder/NotionFolder.ts index d7c72a05..11b8165b 100644 --- a/packages/components/nodes/documentloaders/NotionFolder/NotionFolder.ts +++ b/packages/components/nodes/documentloaders/NotionFolder/NotionFolder.ts @@ -18,7 +18,7 @@ class NotionFolder_DocumentLoaders implements INode { this.type = 'Document' this.icon = 'notion.png' this.category = 'Document Loaders' - this.description = `Load data from Notion folder` + this.description = 'Load data from the exported and unzipped Notion folder' this.baseClasses = [this.type] this.inputs = [ { From ff80a7134daddcb409412778724b4b2e0608d54c Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 1 Jun 2023 12:32:57 +0100 Subject: [PATCH 053/398] add nodejs version --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a3241cad..05221f47 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ Drag & drop UI to build your customized LLM flow using [LangchainJS](https://git ## ⚡Quick Start +Download and Install [NodeJS](https://nodejs.org/en/download) >= 18.15.0 + 1. Install Flowise ```bash npm install -g flowise From eb27485913e1ea0b2fbf091bcc49a93c58fc0fde Mon Sep 17 00:00:00 2001 From: Matthew Ratzke Date: Sun, 4 Jun 2023 12:58:05 -0600 Subject: [PATCH 054/398] update github options --- .../nodes/documentloaders/Github/Github.ts | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/components/nodes/documentloaders/Github/Github.ts b/packages/components/nodes/documentloaders/Github/Github.ts index 552790ab..dd0eb4e3 100644 --- a/packages/components/nodes/documentloaders/Github/Github.ts +++ b/packages/components/nodes/documentloaders/Github/Github.ts @@ -40,6 +40,12 @@ class Github_DocumentLoaders implements INode { placeholder: '', optional: true }, + { + label: 'Recursive', + name: 'recursive', + type: 'boolean', + optional: true + }, { label: 'Text Splitter', name: 'textSplitter', @@ -59,41 +65,37 @@ class Github_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const repoLink = nodeData.inputs?.repoLink as string const branch = nodeData.inputs?.branch as string + const recursive = nodeData.inputs?.recursive as boolean const accessToken = nodeData.inputs?.accessToken as string const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata const options: GithubRepoLoaderParams = { branch, - recursive: false, - unknown: 'warn' + recursive, + unknown: 'warn', } if (accessToken) options.accessToken = accessToken + console.log('🤖[GithubRepoLoader]: Initializing!'); + const loader = new GithubRepoLoader(repoLink, options) - let docs = [] - - if (textSplitter) { - docs = await loader.loadAndSplit(textSplitter) - } else { - docs = await loader.load() - } + const docs = textSplitter ? await loader.loadAndSplit(textSplitter) : await loader.load() + console.log('🤖[GithubRepoLoader]: Documents Loaded!'); + if (metadata) { const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) - let finaldocs = [] - for (const doc of docs) { - const newdoc = { + return docs.map(doc => { + return { ...doc, metadata: { ...doc.metadata, ...parsedMetadata } } - finaldocs.push(newdoc) - } - return finaldocs + }) } return docs From 4063b3ab3bbabc377e5f53f554134ac407110f0e Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 5 Jun 2023 00:49:22 +0100 Subject: [PATCH 055/398] add embed code customization --- .../src/ui-component/dialog/APICodeDialog.js | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/packages/ui/src/ui-component/dialog/APICodeDialog.js b/packages/ui/src/ui-component/dialog/APICodeDialog.js index 94001528..55aeca75 100644 --- a/packages/ui/src/ui-component/dialog/APICodeDialog.js +++ b/packages/ui/src/ui-component/dialog/APICodeDialog.js @@ -129,6 +129,51 @@ const embedCode = (chatflowid) => { ` } +const embedCodeCustomization = (chatflowid) => { + return `` +} + const APICodeDialog = ({ show, dialogProps, onCancel }) => { const portalElement = document.getElementById('portal') const navigate = useNavigate() @@ -140,6 +185,7 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { const [chatflowApiKeyId, setChatflowApiKeyId] = useState('') const [selectedApiKey, setSelectedApiKey] = useState({}) const [checkboxVal, setCheckbox] = useState(false) + const [embedChatCheckboxVal, setEmbedChatCheckbox] = useState(false) const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys) const updateChatflowApi = useApi(chatflowsApi.updateChatflow) @@ -152,6 +198,10 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { } } + const onCheckBoxEmbedChatChanged = (newVal) => { + setEmbedChatCheckbox(newVal) + } + const onApiKeySelected = (keyValue) => { if (keyValue === 'addnewkey') { navigate('/apikey') @@ -588,6 +638,22 @@ query({ /> )} + {value === 0 && ( + + )} + {value === 0 && embedChatCheckboxVal && ( + + )} ))} From d98854d85e13b8c3cbda5a0b766d97512c0e7807 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 5 Jun 2023 11:02:22 +0100 Subject: [PATCH 056/398] add faiss --- .../Faiss_Existing/Faiss_Existing.ts | 70 +++++++++++++++ .../vectorstores/Faiss_Existing/faiss.svg | 10 +++ .../vectorstores/Faiss_Upsert/Faiss_Upsert.ts | 85 +++++++++++++++++++ .../nodes/vectorstores/Faiss_Upsert/faiss.svg | 10 +++ packages/components/package.json | 1 + packages/server/src/index.ts | 4 +- packages/server/src/utils/index.ts | 30 ++++++- 7 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts create mode 100644 packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg create mode 100644 packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts create mode 100644 packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg diff --git a/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts b/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts new file mode 100644 index 00000000..8916a734 --- /dev/null +++ b/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts @@ -0,0 +1,70 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { FaissStore } from 'langchain/vectorstores/faiss' +import { Embeddings } from 'langchain/embeddings/base' +import { getBaseClasses } from '../../../src/utils' + +class Faiss_Existing_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Faiss Load Existing Index' + this.name = 'faissExistingIndex' + this.type = 'Faiss' + this.icon = 'faiss.svg' + this.category = 'Vector Stores' + this.description = 'Load existing index from Faiss (i.e: Document has been upserted)' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Base Path to load', + name: 'basePath', + description: 'Path to load faiss.index file', + placeholder: `C:\\Users\\User\\Desktop`, + type: 'string' + } + ] + this.outputs = [ + { + label: 'Faiss Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Faiss Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(FaissStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const embeddings = nodeData.inputs?.embeddings as Embeddings + const basePath = nodeData.inputs?.basePath as string + const output = nodeData.outputs?.output as string + + const vectorStore = await FaissStore.load(basePath, embeddings) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever() + return retriever + } else if (output === 'vectorStore') { + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: Faiss_Existing_VectorStores } diff --git a/packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg b/packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg new file mode 100644 index 00000000..5fbe9832 --- /dev/null +++ b/packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts b/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts new file mode 100644 index 00000000..2db6a038 --- /dev/null +++ b/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts @@ -0,0 +1,85 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { getBaseClasses } from '../../../src/utils' +import { FaissStore } from 'langchain/vectorstores/faiss' + +class FaissUpsert_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Faiss Upsert Document' + this.name = 'faissUpsert' + this.type = 'Faiss' + this.icon = 'faiss.svg' + this.category = 'Vector Stores' + this.description = 'Upsert documents to Faiss' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Document', + name: 'document', + type: 'Document', + list: true + }, + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Base Path to store', + name: 'basePath', + description: 'Path to store faiss.index file', + placeholder: `C:\\Users\\User\\Desktop`, + type: 'string' + } + ] + this.outputs = [ + { + label: 'Faiss Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Faiss Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(FaissStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string + const basePath = nodeData.inputs?.basePath as string + + const flattenDocs = docs && docs.length ? docs.flat() : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + finalDocs.push(new Document(flattenDocs[i])) + } + + const vectorStore = await FaissStore.fromDocuments(finalDocs, embeddings) + await vectorStore.save(basePath) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever() + return retriever + } else if (output === 'vectorStore') { + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: FaissUpsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg b/packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg new file mode 100644 index 00000000..5fbe9832 --- /dev/null +++ b/packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index 762af123..161792b2 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -28,6 +28,7 @@ "d3-dsv": "2", "dotenv": "^16.0.0", "express": "^4.17.3", + "faiss-node": "^0.2.1", "form-data": "^4.0.0", "graphql": "^16.6.0", "langchain": "^0.0.84", diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 548c27e9..0e030734 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -34,7 +34,8 @@ import { findAvailableConfigs, isSameOverrideConfig, replaceAllAPIKeys, - isFlowValidForStream + isFlowValidForStream, + isVectorStoreFaiss } from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' @@ -634,6 +635,7 @@ export class App { const nodeModule = await import(nodeInstanceFilePath) const nodeInstance = new nodeModule.nodeClass() + isStreamValid = isStreamValid && !isVectorStoreFaiss(nodeToExecuteData) const result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 982a82d0..18473c51 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -14,7 +14,7 @@ import { INodeData, IOverrideConfig } from '../Interface' -import { cloneDeep, get } from 'lodash' +import { cloneDeep, get, omit, merge } from 'lodash' import { ICommonObject, getInputVariables } from 'flowise-components' import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' @@ -317,6 +317,25 @@ export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowN return returnVal } +/** + * Temporarily disable streaming if vectorStore is Faiss + * @param {INodeData} flowNodeData + * @returns {boolean} + */ +export const isVectorStoreFaiss = (flowNodeData: INodeData) => { + if (flowNodeData.inputs && flowNodeData.inputs.vectorStoreRetriever) { + const vectorStoreRetriever = flowNodeData.inputs.vectorStoreRetriever + if (typeof vectorStoreRetriever === 'string' && vectorStoreRetriever.includes('faiss')) return true + if ( + typeof vectorStoreRetriever === 'object' && + vectorStoreRetriever.vectorStore && + vectorStoreRetriever.vectorStore.constructor.name === 'FaissStore' + ) + return true + } + return false +} + /** * Loop through each inputs and resolve variable if neccessary * @param {INodeData} reactFlowNodeData @@ -325,7 +344,12 @@ export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowN * @returns {INodeData} */ export const resolveVariables = (reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[], question: string): INodeData => { - const flowNodeData = cloneDeep(reactFlowNodeData) + let flowNodeData = cloneDeep(reactFlowNodeData) + if (reactFlowNodeData.instance && isVectorStoreFaiss(reactFlowNodeData)) { + // omit and merge because cloneDeep of instance gives "Illegal invocation" Exception + const flowNodeDataWithoutInstance = cloneDeep(omit(reactFlowNodeData, ['instance'])) + flowNodeData = merge(flowNodeDataWithoutInstance, { instance: reactFlowNodeData.instance }) + } const types = 'inputs' const getParamValues = (paramsObj: ICommonObject) => { @@ -633,5 +657,5 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod } } - return isChatOrLLMsExist && endingNodeData.category === 'Chains' + return isChatOrLLMsExist && endingNodeData.category === 'Chains' && !isVectorStoreFaiss(endingNodeData) } From ab00214ec2718e6d2fb5689d86d8342a95c0d07e Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 5 Jun 2023 12:47:55 +0100 Subject: [PATCH 057/398] update api code dialog for embed chat --- .../src/ui-component/dialog/APICodeDialog.js | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/packages/ui/src/ui-component/dialog/APICodeDialog.js b/packages/ui/src/ui-component/dialog/APICodeDialog.js index 55aeca75..e2e7438d 100644 --- a/packages/ui/src/ui-component/dialog/APICodeDialog.js +++ b/packages/ui/src/ui-component/dialog/APICodeDialog.js @@ -121,7 +121,7 @@ const getConfigExamplesForCurl = (configData, bodyType) => { const embedCode = (chatflowid) => { return `` } @@ -605,7 +605,18 @@ query({ {value === 0 && ( <> - Paste this anywhere in the {``} tag of your html file + Paste this anywhere in the {``} tag of your html file. +

+ You can also specify a  + + version + + : {`https://cdn.jsdelivr.net/npm/flowise-embed@/dist/web.js`} +

@@ -648,7 +659,7 @@ query({ {value === 0 && embedChatCheckboxVal && ( Date: Mon, 5 Jun 2023 21:32:02 +0900 Subject: [PATCH 058/398] CI: add docker build test --- .github/workflows/test_docker_build.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/test_docker_build.yml diff --git a/.github/workflows/test_docker_build.yml b/.github/workflows/test_docker_build.yml new file mode 100644 index 00000000..b54063c6 --- /dev/null +++ b/.github/workflows/test_docker_build.yml @@ -0,0 +1,22 @@ +name: Test Docker Build + +on: + push: + branches: + - master + + pull_request: + branches: + - "*" + +jobs: + build: + strategy: + matrix: + platform: [macos-latest, windows-latest, ubuntu-latest] + runs-on: ${{ matrix.platform }} + + steps: + - uses: actions/checkout@v3 + + - run: docker build --no-cache -t flowise . From 2cc6e054268824937b0258fe8c3cf070ec524f91 Mon Sep 17 00:00:00 2001 From: ewfian Date: Mon, 5 Jun 2023 21:34:43 +0900 Subject: [PATCH 059/398] fix branch name --- .github/workflows/main.yml | 2 +- .github/workflows/test_docker_build.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a89846a6..d087fb93 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ name: Node CI on: push: branches: - - master + - main pull_request: branches: diff --git a/.github/workflows/test_docker_build.yml b/.github/workflows/test_docker_build.yml index b54063c6..4d824cea 100644 --- a/.github/workflows/test_docker_build.yml +++ b/.github/workflows/test_docker_build.yml @@ -3,7 +3,7 @@ name: Test Docker Build on: push: branches: - - master + - main pull_request: branches: From 1b8ca053802a361f0700bef625a9d8008fe4e689 Mon Sep 17 00:00:00 2001 From: ewfian Date: Mon, 5 Jun 2023 21:40:32 +0900 Subject: [PATCH 060/398] fix build failed --- .github/workflows/test_docker_build.yml | 1 + Dockerfile | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_docker_build.yml b/.github/workflows/test_docker_build.yml index 4d824cea..bde36098 100644 --- a/.github/workflows/test_docker_build.yml +++ b/.github/workflows/test_docker_build.yml @@ -12,6 +12,7 @@ on: jobs: build: strategy: + fail-fast: false matrix: platform: [macos-latest, windows-latest, ubuntu-latest] runs-on: ${{ matrix.platform }} diff --git a/Dockerfile b/Dockerfile index 77eaa9d6..5f70a301 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,9 +9,10 @@ RUN apk add --update libc6-compat WORKDIR /usr/src/packages -# Copy root package.json and lockfile +# Copy root package.json COPY package.json ./ -COPY yarn.lock ./ +# Conditonal copy lockfile +COPY yarn.loc[k] ./ # Copy components package.json COPY packages/components/package.json ./packages/components/package.json From 7e8557fa5cfac5b967d9abab08d1d12670e22ca8 Mon Sep 17 00:00:00 2001 From: ewfian Date: Mon, 5 Jun 2023 21:48:41 +0900 Subject: [PATCH 061/398] fix build failed --- .github/workflows/test_docker_build.yml | 6 +----- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_docker_build.yml b/.github/workflows/test_docker_build.yml index bde36098..a698c247 100644 --- a/.github/workflows/test_docker_build.yml +++ b/.github/workflows/test_docker_build.yml @@ -11,11 +11,7 @@ on: jobs: build: - strategy: - fail-fast: false - matrix: - platform: [macos-latest, windows-latest, ubuntu-latest] - runs-on: ${{ matrix.platform }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/Dockerfile b/Dockerfile index 5f70a301..76508b7d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ WORKDIR /usr/src/packages # Copy root package.json COPY package.json ./ # Conditonal copy lockfile -COPY yarn.loc[k] ./ +COPY ./yarn.loc[k] ./ # Copy components package.json COPY packages/components/package.json ./packages/components/package.json From 4e51f2a008fc87a39d5506ce1bb3f5e08f683d20 Mon Sep 17 00:00:00 2001 From: ewfian Date: Mon, 5 Jun 2023 21:50:33 +0900 Subject: [PATCH 062/398] fix build failed --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 76508b7d..6ff3ef15 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ WORKDIR /usr/src/packages # Copy root package.json COPY package.json ./ # Conditonal copy lockfile -COPY ./yarn.loc[k] ./ +COPY yarn.* ./ # Copy components package.json COPY packages/components/package.json ./packages/components/package.json From aa8996a98214d3c636c28034c61659d082b2f7cb Mon Sep 17 00:00:00 2001 From: ewfian Date: Mon, 5 Jun 2023 22:04:18 +0900 Subject: [PATCH 063/398] fix build failed --- Dockerfile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6ff3ef15..3fc40769 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,10 +9,8 @@ RUN apk add --update libc6-compat WORKDIR /usr/src/packages -# Copy root package.json -COPY package.json ./ -# Conditonal copy lockfile -COPY yarn.* ./ +# Copy root package.json and lockfile +COPY package.json yarn.loc[k] ./ # Copy components package.json COPY packages/components/package.json ./packages/components/package.json From ae3387cedaafb0700115546b370e86eed8cf27f0 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Mon, 5 Jun 2023 21:44:37 +0700 Subject: [PATCH 064/398] add returnJSONStr --- .../prompts/PromptTemplate/PromptTemplate.ts | 7 +-- packages/components/src/utils.ts | 52 +++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts index f976d64c..cfa2c488 100644 --- a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts +++ b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts @@ -1,5 +1,5 @@ import { ICommonObject, INode, INodeData, INodeParams, PromptTemplate } from '../../../src/Interface' -import { getBaseClasses, getInputVariables } from '../../../src/utils' +import { getBaseClasses, getInputVariables, returnJSONStr } from '../../../src/utils' import { PromptTemplateInput } from 'langchain/prompts' class PromptTemplate_Prompts implements INode { @@ -46,11 +46,12 @@ class PromptTemplate_Prompts implements INode { async init(nodeData: INodeData): Promise { const template = nodeData.inputs?.template as string - const promptValuesStr = nodeData.inputs?.promptValues as string + let promptValuesStr = nodeData.inputs?.promptValues as string let promptValues: ICommonObject = {} if (promptValuesStr) { - promptValues = JSON.parse(promptValuesStr.replace(/\s/g, '')) + promptValuesStr = promptValuesStr.replace(/\s/g, '') + promptValues = JSON.parse(returnJSONStr(promptValuesStr)) } const inputVariables = getInputVariables(template) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 08d32bab..05ff2e85 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -234,3 +234,55 @@ export class CustomChainHandler extends BaseCallbackHandler { this.socketIO.to(this.socketIOClientId).emit('end') } } + +export const returnJSONStr = (jsonStr: string): string => { + let jsonStrArray = jsonStr.split(':') + // jsonStrArray = jsonStrArray.split('"') + console.log(`jsonStrArray: ${JSON.stringify(jsonStrArray)} length: ${jsonStrArray.length}`) + + let wholeString = '' + for (let i = 0; i < jsonStrArray.length; i++) { + // console.log(`element: ${jsonStrArray[i]}`) + if (jsonStrArray[i].includes(',') && jsonStrArray[i + 1] !== undefined) { + const splitValueAndTitle = jsonStrArray[i].split(',') + const value = splitValueAndTitle[0] + const newTitle = splitValueAndTitle[1] + wholeString += handleEscapeDoubleQuote(value) + ',' + newTitle + ':' + } else { + wholeString += wholeString === '' ? jsonStrArray[i] + ':' : handleEscapeDoubleQuote(jsonStrArray[i]) + } + } + console.log(`wholeString: ${wholeString}`) + return wholeString +} + +const handleEscapeDoubleQuote = (value: string): string => { + console.log(`value: ${value}`) + let newValue = '' + if (value.includes('"')) { + const valueArray = value.split('"') + for (let i = 0; i < valueArray.length; i++) { + if ((i + 1) % 2 !== 0) { + switch (valueArray[i]) { + case '': + console.log(`nothing`) + newValue += '"' + break + case '}': + console.log(`}`) + newValue += '"}' + break + default: + console.log(`default`) + newValue += '\\"' + valueArray[i] + '\\"' + } + } else { + newValue += valueArray[i] + } + + console.log(`valueArray[i]: ${valueArray[i]}`) + console.log(`newValue: ${newValue}`) + } + } + return newValue === '' ? value : newValue +} From f7dd021b38b030167a53a355550018c302a1fba7 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Mon, 5 Jun 2023 21:59:42 +0700 Subject: [PATCH 065/398] remove console.log --- packages/components/src/utils.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 05ff2e85..22a09d34 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -237,12 +237,9 @@ export class CustomChainHandler extends BaseCallbackHandler { export const returnJSONStr = (jsonStr: string): string => { let jsonStrArray = jsonStr.split(':') - // jsonStrArray = jsonStrArray.split('"') - console.log(`jsonStrArray: ${JSON.stringify(jsonStrArray)} length: ${jsonStrArray.length}`) let wholeString = '' for (let i = 0; i < jsonStrArray.length; i++) { - // console.log(`element: ${jsonStrArray[i]}`) if (jsonStrArray[i].includes(',') && jsonStrArray[i + 1] !== undefined) { const splitValueAndTitle = jsonStrArray[i].split(',') const value = splitValueAndTitle[0] @@ -252,12 +249,10 @@ export const returnJSONStr = (jsonStr: string): string => { wholeString += wholeString === '' ? jsonStrArray[i] + ':' : handleEscapeDoubleQuote(jsonStrArray[i]) } } - console.log(`wholeString: ${wholeString}`) return wholeString } const handleEscapeDoubleQuote = (value: string): string => { - console.log(`value: ${value}`) let newValue = '' if (value.includes('"')) { const valueArray = value.split('"') @@ -265,23 +260,17 @@ const handleEscapeDoubleQuote = (value: string): string => { if ((i + 1) % 2 !== 0) { switch (valueArray[i]) { case '': - console.log(`nothing`) newValue += '"' break case '}': - console.log(`}`) newValue += '"}' break default: - console.log(`default`) newValue += '\\"' + valueArray[i] + '\\"' } } else { newValue += valueArray[i] } - - console.log(`valueArray[i]: ${valueArray[i]}`) - console.log(`newValue: ${newValue}`) } } return newValue === '' ? value : newValue From 3d1b34bf3df14ab1eab8d6aae9b4d6088a55fb39 Mon Sep 17 00:00:00 2001 From: Matthew Ratzke Date: Mon, 5 Jun 2023 12:33:45 -0600 Subject: [PATCH 066/398] added more logging --- packages/components/nodes/documentloaders/Github/Github.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/nodes/documentloaders/Github/Github.ts b/packages/components/nodes/documentloaders/Github/Github.ts index dd0eb4e3..64c821e8 100644 --- a/packages/components/nodes/documentloaders/Github/Github.ts +++ b/packages/components/nodes/documentloaders/Github/Github.ts @@ -81,6 +81,7 @@ class Github_DocumentLoaders implements INode { console.log('🤖[GithubRepoLoader]: Initializing!'); const loader = new GithubRepoLoader(repoLink, options) + console.log('🤖[GithubRepoLoader]: Loading documents from ' + repoLink); const docs = textSplitter ? await loader.loadAndSplit(textSplitter) : await loader.load() console.log('🤖[GithubRepoLoader]: Documents Loaded!'); From 971de6b1edefb1c611fab2cc21fa78112d59a460 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 5 Jun 2023 21:18:40 +0100 Subject: [PATCH 067/398] Add python3 make g++ Issue - https://github.com/FlowiseAI/Flowise/issues/241 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3fc40769..fc76cd00 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ # docker run -d -p 3000:3000 flowise FROM node:18-alpine -RUN apk add --update libc6-compat +RUN apk add --update libc6-compat python3 make g++ WORKDIR /usr/src/packages From 96dc0f538646507345b6a409dbecee385670bb6a Mon Sep 17 00:00:00 2001 From: Yared Tsegaye Date: Tue, 6 Jun 2023 03:26:43 +0300 Subject: [PATCH 068/398] fix: fixed undefined class initialization --- packages/server/src/NodesPool.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/server/src/NodesPool.ts b/packages/server/src/NodesPool.ts index b17164e0..1ee506ea 100644 --- a/packages/server/src/NodesPool.ts +++ b/packages/server/src/NodesPool.ts @@ -19,7 +19,8 @@ export class NodesPool { nodeFiles.map(async (file) => { if (file.endsWith('.js')) { const nodeModule = await require(file) - try { + + if (nodeModule.nodeClass) { const newNodeInstance = new nodeModule.nodeClass() newNodeInstance.filePath = file @@ -37,8 +38,6 @@ export class NodesPool { const nodeIconAbsolutePath = `${filePath.join('/')}/${newNodeInstance.icon}` this.componentNodes[newNodeInstance.name].icon = nodeIconAbsolutePath } - } catch (e) { - // console.error(e); } } }) From 1a35bad434357f7f91367113203a3c36f546e96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=AE=E7=AC=91?= Date: Tue, 6 Jun 2023 16:50:11 +0800 Subject: [PATCH 069/398] feat: support basePath --- .../nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts | 12 +++++++++++- .../embeddings/OpenAIEmbedding/OpenAIEmbedding.ts | 10 +++++++++- packages/components/nodes/llms/OpenAI/OpenAI.ts | 12 +++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts index 7d2098ec..f07fce60 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts @@ -96,6 +96,13 @@ class ChatOpenAI_ChatModels implements INode { type: 'number', optional: true, additionalParams: true + }, + { + label: 'BasePath', + name: 'basepath', + type: 'string', + optional: true, + additionalParams: true } ] } @@ -110,6 +117,7 @@ class ChatOpenAI_ChatModels implements INode { const presencePenalty = nodeData.inputs?.presencePenalty as string const timeout = nodeData.inputs?.timeout as string const streaming = nodeData.inputs?.streaming as boolean + const basePath = nodeData.inputs?.basepath as string const obj: Partial & { openAIApiKey?: string } = { temperature: parseInt(temperature, 10), @@ -124,7 +132,9 @@ class ChatOpenAI_ChatModels implements INode { if (presencePenalty) obj.presencePenalty = parseInt(presencePenalty, 10) if (timeout) obj.timeout = parseInt(timeout, 10) - const model = new ChatOpenAI(obj) + const model = new ChatOpenAI(obj, { + basePath + }) return model } } diff --git a/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding.ts b/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding.ts index 3ccfab82..0fd08973 100644 --- a/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding.ts +++ b/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding.ts @@ -46,6 +46,13 @@ class OpenAIEmbedding_Embeddings implements INode { type: 'number', optional: true, additionalParams: true + }, + { + label: 'BasePath', + name: 'basepath', + type: 'string', + optional: true, + additionalParams: true } ] } @@ -55,6 +62,7 @@ class OpenAIEmbedding_Embeddings implements INode { const stripNewLines = nodeData.inputs?.stripNewLines as boolean const batchSize = nodeData.inputs?.batchSize as string const timeout = nodeData.inputs?.timeout as string + const basePath = nodeData.inputs?.basepath as string const obj: Partial & { openAIApiKey?: string } = { openAIApiKey @@ -64,7 +72,7 @@ class OpenAIEmbedding_Embeddings implements INode { if (batchSize) obj.batchSize = parseInt(batchSize, 10) if (timeout) obj.timeout = parseInt(timeout, 10) - const model = new OpenAIEmbeddings(obj) + const model = new OpenAIEmbeddings(obj, { basePath }) return model } } diff --git a/packages/components/nodes/llms/OpenAI/OpenAI.ts b/packages/components/nodes/llms/OpenAI/OpenAI.ts index 48b1bc84..fb7e5b6b 100644 --- a/packages/components/nodes/llms/OpenAI/OpenAI.ts +++ b/packages/components/nodes/llms/OpenAI/OpenAI.ts @@ -106,6 +106,13 @@ class OpenAI_LLMs implements INode { type: 'number', optional: true, additionalParams: true + }, + { + label: 'BasePath', + name: 'basepath', + type: 'string', + optional: true, + additionalParams: true } ] } @@ -122,6 +129,7 @@ class OpenAI_LLMs implements INode { const batchSize = nodeData.inputs?.batchSize as string const bestOf = nodeData.inputs?.bestOf as string const streaming = nodeData.inputs?.streaming as boolean + const basePath = nodeData.inputs?.basepath as string const obj: Partial & { openAIApiKey?: string } = { temperature: parseInt(temperature, 10), @@ -138,7 +146,9 @@ class OpenAI_LLMs implements INode { if (batchSize) obj.batchSize = parseInt(batchSize, 10) if (bestOf) obj.bestOf = parseInt(bestOf, 10) - const model = new OpenAI(obj) + const model = new OpenAI(obj, { + basePath + }) return model } } From b071790a5a5e607e7d2a97c394eeeeb19144b99e Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 8 Jun 2023 23:51:34 +0100 Subject: [PATCH 070/398] add return source documents functioanality --- .../ConversationalRetrievalQAChain.ts | 87 ++++++-- packages/components/package.json | 2 +- packages/components/src/Interface.ts | 2 +- packages/components/src/utils.ts | 11 +- packages/server/src/Interface.ts | 1 + packages/server/src/entity/ChatMessage.ts | 3 + packages/server/src/index.ts | 2 +- .../MainLayout/Header/ProfileSection/index.js | 17 +- .../ui/src/ui-component/dialog/AboutDialog.js | 85 ++++++++ .../ui-component/dialog/SourceDocDialog.js | 57 +++++ .../ui/src/views/chatmessage/ChatMessage.js | 195 +++++++++++------- 11 files changed, 371 insertions(+), 91 deletions(-) create mode 100644 packages/ui/src/ui-component/dialog/AboutDialog.js create mode 100644 packages/ui/src/ui-component/dialog/SourceDocDialog.js diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 659e1e2e..3b7e1413 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -2,7 +2,9 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { ConversationalRetrievalQAChain } from 'langchain/chains' -import { BaseRetriever } from 'langchain/schema' +import { AIChatMessage, BaseRetriever, HumanChatMessage } from 'langchain/schema' +import { BaseChatMemory, BufferMemory, ChatMessageHistory } from 'langchain/memory' +import { PromptTemplate } from 'langchain/prompts' const default_qa_template = `Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. @@ -47,6 +49,12 @@ class ConversationalRetrievalQAChain_Chains implements INode { name: 'vectorStoreRetriever', type: 'BaseRetriever' }, + { + label: 'Return Source Documents', + name: 'returnSourceDocuments', + type: 'boolean', + optional: true + }, { label: 'System Message', name: 'systemMessagePrompt', @@ -56,6 +64,31 @@ class ConversationalRetrievalQAChain_Chains implements INode { optional: true, placeholder: 'I want you to act as a document that I am having a conversation with. Your name is "AI Assistant". You will provide me with answers from the given info. If the answer is not included, say exactly "Hmm, I am not sure." and stop after that. Refuse to answer any question not about the info. Never break character.' + }, + { + label: 'Chain Option', + name: 'chainOption', + type: 'options', + options: [ + { + label: 'MapReduceDocumentsChain', + name: 'map_reduce', + description: + 'Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time' + }, + { + label: 'RefineDocumentsChain', + name: 'refine', + description: 'Suitable for QA tasks over a large number of documents.' + }, + { + label: 'StuffDocumentsChain', + name: 'stuff', + description: 'Suitable for QA tasks over a small number of documents.' + } + ], + additionalParams: true, + optional: true } ] } @@ -64,44 +97,64 @@ class ConversationalRetrievalQAChain_Chains implements INode { const model = nodeData.inputs?.model as BaseLanguageModel const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as BaseRetriever const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string + const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean + const chainOption = nodeData.inputs?.chainOption as string - const chain = ConversationalRetrievalQAChain.fromLLM(model, vectorStoreRetriever, { + const obj: any = { verbose: process.env.DEBUG === 'true' ? true : false, - qaTemplate: systemMessagePrompt ? `${systemMessagePrompt}\n${qa_template}` : default_qa_template - }) + qaChainOptions: { + type: 'stuff', + prompt: PromptTemplate.fromTemplate(systemMessagePrompt ? `${systemMessagePrompt}\n${qa_template}` : default_qa_template) + }, + memory: new BufferMemory({ + memoryKey: 'chat_history', + inputKey: 'question', + outputKey: 'text', + returnMessages: true + }) + } + if (returnSourceDocuments) obj.returnSourceDocuments = returnSourceDocuments + if (chainOption) obj.qaChainOptions = { ...obj.qaChainOptions, type: chainOption } + + const chain = ConversationalRetrievalQAChain.fromLLM(model, vectorStoreRetriever, obj) return chain } - async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = nodeData.instance as ConversationalRetrievalQAChain + const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean let model = nodeData.inputs?.model // Temporary fix: https://github.com/hwchase17/langchainjs/issues/754 model.streaming = false chain.questionGeneratorChain.llm = model - let chatHistory = '' + const obj = { question: input } - if (options && options.chatHistory) { + if (chain.memory && options && options.chatHistory) { + const chatHistory = [] const histories: IMessage[] = options.chatHistory - chatHistory = histories - .map((item) => { - return item.message - }) - .join('') - } + const memory = chain.memory as BaseChatMemory - const obj = { - question: input, - chat_history: chatHistory ? chatHistory : [] + for (const message of histories) { + if (message.type === 'apiMessage') { + chatHistory.push(new AIChatMessage(message.message)) + } else if (message.type === 'userMessage') { + chatHistory.push(new HumanChatMessage(message.message)) + } + } + memory.chatHistory = new ChatMessageHistory(chatHistory) + chain.memory = memory } if (options.socketIO && options.socketIOClientId) { - const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, undefined, returnSourceDocuments) const res = await chain.call(obj, [handler]) + if (res.text && res.sourceDocuments) return res return res?.text } else { const res = await chain.call(obj) + if (res.text && res.sourceDocuments) return res return res?.text } } diff --git a/packages/components/package.json b/packages/components/package.json index 161792b2..46b441bd 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -31,7 +31,7 @@ "faiss-node": "^0.2.1", "form-data": "^4.0.0", "graphql": "^16.6.0", - "langchain": "^0.0.84", + "langchain": "^0.0.91", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index c1411939..bd94cca8 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -75,7 +75,7 @@ export interface INode extends INodeProperties { inputs?: INodeParams[] output?: INodeOutputsValue[] init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise - run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise + run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise } export interface INodeData extends INodeProperties { diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 08d32bab..3e801512 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -4,6 +4,7 @@ import * as fs from 'fs' import * as path from 'path' import { BaseCallbackHandler } from 'langchain/callbacks' import { Server } from 'socket.io' +import { ChainValues } from 'langchain/dist/schema' 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 @@ -208,12 +209,14 @@ export class CustomChainHandler extends BaseCallbackHandler { socketIO: Server socketIOClientId = '' skipK = 0 // Skip streaming for first K numbers of handleLLMStart + returnSourceDocuments = false - constructor(socketIO: Server, socketIOClientId: string, skipK?: number) { + constructor(socketIO: Server, socketIOClientId: string, skipK?: number, returnSourceDocuments?: boolean) { super() this.socketIO = socketIO this.socketIOClientId = socketIOClientId this.skipK = skipK ?? this.skipK + this.returnSourceDocuments = returnSourceDocuments ?? this.returnSourceDocuments } handleLLMStart() { @@ -233,4 +236,10 @@ export class CustomChainHandler extends BaseCallbackHandler { handleLLMEnd() { this.socketIO.to(this.socketIOClientId).emit('end') } + + handleChainEnd(outputs: ChainValues): void | Promise { + if (this.returnSourceDocuments) { + this.socketIO.to(this.socketIOClientId).emit('sourceDocuments', outputs?.sourceDocuments) + } + } } diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 0dede024..b6876df3 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -21,6 +21,7 @@ export interface IChatMessage { content: string chatflowid: string createdDate: Date + sourceDocuments: string } export interface IComponentNodes { diff --git a/packages/server/src/entity/ChatMessage.ts b/packages/server/src/entity/ChatMessage.ts index 3380c86c..236dc5f9 100644 --- a/packages/server/src/entity/ChatMessage.ts +++ b/packages/server/src/entity/ChatMessage.ts @@ -17,6 +17,9 @@ export class ChatMessage implements IChatMessage { @Column() content: string + @Column({ nullable: true }) + sourceDocuments: string + @CreateDateColumn() createdDate: Date } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 0e030734..3a0c64cd 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -90,7 +90,7 @@ export class App { const basicAuthMiddleware = basicAuth({ users: { [username]: password } }) - const whitelistURLs = ['/api/v1/prediction/', '/api/v1/node-icon/'] + const whitelistURLs = ['/api/v1/prediction/', '/api/v1/node-icon/', '/api/v1/chatflows-streaming'] this.app.use((req, res, next) => { if (req.url.includes('/api/v1/')) { whitelistURLs.some((url) => req.url.includes(url)) ? next() : basicAuthMiddleware(req, res, next) diff --git a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js index f6f6a730..41de3dd4 100644 --- a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js +++ b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js @@ -27,9 +27,10 @@ import PerfectScrollbar from 'react-perfect-scrollbar' import MainCard from 'ui-component/cards/MainCard' import Transitions from 'ui-component/extended/Transitions' import { BackdropLoader } from 'ui-component/loading/BackdropLoader' +import AboutDialog from 'ui-component/dialog/AboutDialog' // assets -import { IconLogout, IconSettings, IconFileExport, IconFileDownload } from '@tabler/icons' +import { IconLogout, IconSettings, IconFileExport, IconFileDownload, IconInfoCircle } from '@tabler/icons' // API import databaseApi from 'api/database' @@ -49,6 +50,7 @@ const ProfileSection = ({ username, handleLogout }) => { const [open, setOpen] = useState(false) const [loading, setLoading] = useState(false) + const [aboutDialogOpen, setAboutDialogOpen] = useState(false) const anchorRef = useRef(null) const uploadRef = useRef(null) @@ -215,6 +217,18 @@ const ProfileSection = ({ username, handleLogout }) => { Export Database} /> + { + setOpen(false) + setAboutDialogOpen(true) + }} + > + + + + About Flowise} /> + {localStorage.getItem('username') && localStorage.getItem('password') && ( {
handleFileUpload(e)} /> + setAboutDialogOpen(false)} /> ) } diff --git a/packages/ui/src/ui-component/dialog/AboutDialog.js b/packages/ui/src/ui-component/dialog/AboutDialog.js new file mode 100644 index 00000000..54c077d1 --- /dev/null +++ b/packages/ui/src/ui-component/dialog/AboutDialog.js @@ -0,0 +1,85 @@ +import { createPortal } from 'react-dom' +import { useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import { Dialog, DialogContent, DialogTitle, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Paper } from '@mui/material' +import moment from 'moment' +import axios from 'axios' + +const fetchLatestVer = async ({ api }) => { + let apiReturn = await axios + .get(api) + .then(async function (response) { + return response.data + }) + .catch(function (error) { + console.error(error) + }) + return apiReturn +} + +const AboutDialog = ({ show, onCancel }) => { + const portalElement = document.getElementById('portal') + + const [data, setData] = useState({}) + + useEffect(() => { + if (show) { + const fetchData = async (api) => { + let response = await fetchLatestVer({ api }) + setData(response) + } + + fetchData('https://api.github.com/repos/FlowiseAI/Flowise/releases/latest') + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [show]) + + const component = show ? ( + + + Flowise Version + + + {data && ( + + + + + Latest Version + Published At + + + + + + + {data.name} + + + {moment(data.published_at).fromNow()} + + +
+
+ )} +
+
+ ) : null + + return createPortal(component, portalElement) +} + +AboutDialog.propTypes = { + show: PropTypes.bool, + onCancel: PropTypes.func +} + +export default AboutDialog diff --git a/packages/ui/src/ui-component/dialog/SourceDocDialog.js b/packages/ui/src/ui-component/dialog/SourceDocDialog.js new file mode 100644 index 00000000..a088a6c4 --- /dev/null +++ b/packages/ui/src/ui-component/dialog/SourceDocDialog.js @@ -0,0 +1,57 @@ +import { createPortal } from 'react-dom' +import { useState, useEffect } from 'react' +import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' +import { Dialog, DialogContent, DialogTitle } from '@mui/material' +import ReactJson from 'react-json-view' + +const SourceDocDialog = ({ show, dialogProps, onCancel }) => { + const portalElement = document.getElementById('portal') + const customization = useSelector((state) => state.customization) + + const [data, setData] = useState({}) + + useEffect(() => { + if (dialogProps.data) setData(dialogProps.data) + + return () => { + setData({}) + } + }, [dialogProps]) + + const component = show ? ( + + + Source Document + + + + + + ) : null + + return createPortal(component, portalElement) +} + +SourceDocDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func +} + +export default SourceDocDialog diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index e894f46b..65bad212 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -7,13 +7,14 @@ import rehypeMathjax from 'rehype-mathjax' import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' -import { CircularProgress, OutlinedInput, Divider, InputAdornment, IconButton, Box } from '@mui/material' +import { CircularProgress, OutlinedInput, Divider, InputAdornment, IconButton, Box, Chip } from '@mui/material' import { useTheme } from '@mui/material/styles' import { IconSend } from '@tabler/icons' // project import import { CodeBlock } from 'ui-component/markdown/CodeBlock' import { MemoizedReactMarkdown } from 'ui-component/markdown/MemoizedReactMarkdown' +import SourceDocDialog from 'ui-component/dialog/SourceDocDialog' import './ChatMessage.css' // api @@ -43,11 +44,18 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { ]) const [socketIOClientId, setSocketIOClientId] = useState('') const [isChatFlowAvailableToStream, setIsChatFlowAvailableToStream] = useState(false) + const [sourceDialogOpen, setSourceDialogOpen] = useState(false) + const [sourceDialogProps, setSourceDialogProps] = useState({}) const inputRef = useRef(null) const getChatmessageApi = useApi(chatmessageApi.getChatmessageFromChatflow) const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming) + const onSourceDialogClick = (data) => { + setSourceDialogProps({ data }) + setSourceDialogOpen(true) + } + const scrollToBottom = () => { if (ps.current) { ps.current.scrollTo({ top: maxScroll }) @@ -56,13 +64,14 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { const onChange = useCallback((e) => setUserInput(e.target.value), [setUserInput]) - const addChatMessage = async (message, type) => { + const addChatMessage = async (message, type, sourceDocuments) => { try { const newChatMessageBody = { role: type, content: message, chatflowid: chatflowid } + if (sourceDocuments) newChatMessageBody.sourceDocuments = JSON.stringify(sourceDocuments) await chatmessageApi.createNewChatmessage(chatflowid, newChatMessageBody) } catch (error) { console.error(error) @@ -78,6 +87,15 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { }) } + const updateLastMessageSourceDocuments = (sourceDocuments) => { + setMessages((prevMessages) => { + let allMessages = [...cloneDeep(prevMessages)] + if (allMessages[allMessages.length - 1].type === 'userMessage') return allMessages + allMessages[allMessages.length - 1].sourceDocuments = sourceDocuments + return allMessages + }) + } + // Handle errors const handleError = (message = 'Oops! There seems to be an error. Please try again.') => { message = message.replace(`Unable to parse JSON response from chat agent.\n\n`, '') @@ -114,8 +132,20 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { if (response.data) { const data = response.data - if (!isChatFlowAvailableToStream) setMessages((prevMessages) => [...prevMessages, { message: data, type: 'apiMessage' }]) - addChatMessage(data, 'apiMessage') + if (typeof data === 'object' && data.text && data.sourceDocuments) { + if (!isChatFlowAvailableToStream) { + setMessages((prevMessages) => [ + ...prevMessages, + { message: data.text, sourceDocuments: data.sourceDocuments, type: 'apiMessage' } + ]) + } + addChatMessage(data.text, 'apiMessage', data.sourceDocuments) + } else { + if (!isChatFlowAvailableToStream) { + setMessages((prevMessages) => [...prevMessages, { message: data, type: 'apiMessage' }]) + } + addChatMessage(data, 'apiMessage') + } setLoading(false) setUserInput('') setTimeout(() => { @@ -146,10 +176,12 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { if (getChatmessageApi.data) { const loadedMessages = [] for (const message of getChatmessageApi.data) { - loadedMessages.push({ + const obj = { message: message.content, type: message.role - }) + } + if (message.sourceDocuments) obj.sourceDocuments = JSON.parse(message.sourceDocuments) + loadedMessages.push(obj) } setMessages((prevMessages) => [...prevMessages, ...loadedMessages]) } @@ -196,6 +228,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { setMessages((prevMessages) => [...prevMessages, { message: '', type: 'apiMessage' }]) }) + socket.on('sourceDocuments', updateLastMessageSourceDocuments) + socket.on('token', updateLastMessage) } @@ -225,69 +259,91 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { messages.map((message, index) => { return ( // The latest message sent by the user will be animated while waiting for a response - - {/* Display the correct icon depending on the message type */} - {message.type === 'apiMessage' ? ( - AI - ) : ( - Me - )} -
- {/* Messages are being rendered in Markdown format */} - - ) : ( - - {children} - - ) - } - }} - > - {message.message} - -
-
+ <> + + {/* Display the correct icon depending on the message type */} + {message.type === 'apiMessage' ? ( + AI + ) : ( + Me + )} +
+
+ {/* Messages are being rendered in Markdown format */} + + ) : ( + + {children} + + ) + } + }} + > + {message.message} + +
+ {message.sourceDocuments && ( +
+ {message.sourceDocuments.map((source, index) => { + return ( + onSourceDialogClick(source)} + /> + ) + })} +
+ )} +
+
+ ) })} @@ -328,6 +384,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { + setSourceDialogOpen(false)} /> ) } From 4da845745c5eeb516b1606faf27ccc7b15dfbe39 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 9 Jun 2023 14:35:14 +0100 Subject: [PATCH 071/398] added fix to get input variables from prompt --- packages/components/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 210ca649..de026a35 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -132,7 +132,7 @@ export const getInputVariables = (paramValue: string): string[] => { const variableStack = [] const inputVariables = [] let startIdx = 0 - const endIdx = returnVal.length - 1 + const endIdx = returnVal.length while (startIdx < endIdx) { const substr = returnVal.substring(startIdx, startIdx + 1) From 0b00a02ffb79dab9d43404db4c9401443d059b9f Mon Sep 17 00:00:00 2001 From: Govind Kumar Date: Fri, 9 Jun 2023 19:13:51 +0530 Subject: [PATCH 072/398] added line break support in user chat input box --- packages/ui/src/views/chatmessage/ChatMessage.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index e894f46b..dc8a9cae 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -308,8 +308,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { placeholder={loading ? 'Waiting for response...' : 'Type your question...'} value={userInput} onChange={onChange} + multiline={true} endAdornment={ - + {loading ? (
From 24681234e8b4af68e426d08bd4a39ba796a33681 Mon Sep 17 00:00:00 2001 From: Govind Kumar Date: Fri, 9 Jun 2023 19:47:42 +0530 Subject: [PATCH 073/398] changed padding to 15px --- packages/ui/src/views/chatmessage/ChatMessage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index dc8a9cae..feb5549c 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -310,7 +310,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { onChange={onChange} multiline={true} endAdornment={ - + {loading ? (
From 7a04b42f94334a40255952b341ca57d3b742c5a8 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 9 Jun 2023 17:47:15 +0100 Subject: [PATCH 074/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.2.1?= =?UTF-8?q?2=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 46b441bd..3e3d58b6 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.11", + "version": "1.2.12", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From 625db721aebc0c35b44de94294066b538341ffb7 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 9 Jun 2023 17:48:09 +0100 Subject: [PATCH 075/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.2.10=20rele?= =?UTF-8?q?ase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 5ffad0da..0727fc87 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.9", + "version": "1.2.10", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From 66bfd536a1114abd244faf1dfceb7afe88bef851 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 9 Jun 2023 17:48:43 +0100 Subject: [PATCH 076/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.2.11=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 44a50e60..b077c7ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.10", + "version": "1.2.11", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index 9710c16f..cdfb11c1 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.10", + "version": "1.2.11", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From ffebe7c769eed2f2f09badae08626afa11d0709b Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 10 Jun 2023 01:34:37 +0100 Subject: [PATCH 077/398] change flat to flatten from lodash --- packages/components/nodes/agents/AutoGPT/AutoGPT.ts | 3 ++- .../nodes/agents/ConversationalAgent/ConversationalAgent.ts | 3 ++- .../components/nodes/agents/MRKLAgentChat/MRKLAgentChat.ts | 3 ++- packages/components/nodes/agents/MRKLAgentLLM/MRKLAgentLLM.ts | 3 ++- .../nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts | 3 ++- .../components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts | 3 ++- .../nodes/vectorstores/InMemory/InMemoryVectorStore.ts | 3 ++- .../nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts | 3 ++- .../nodes/vectorstores/Supabase_Upsert/Supabase_Upsert.ts | 3 ++- .../nodes/vectorstores/Weaviate_Upsert/Weaviate_Upsert.ts | 3 ++- 10 files changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/components/nodes/agents/AutoGPT/AutoGPT.ts b/packages/components/nodes/agents/AutoGPT/AutoGPT.ts index 4775507b..ca118500 100644 --- a/packages/components/nodes/agents/AutoGPT/AutoGPT.ts +++ b/packages/components/nodes/agents/AutoGPT/AutoGPT.ts @@ -3,6 +3,7 @@ import { BaseChatModel } from 'langchain/chat_models/base' import { AutoGPT } from 'langchain/experimental/autogpt' import { Tool } from 'langchain/tools' import { VectorStoreRetriever } from 'langchain/vectorstores/base' +import { flatten } from 'lodash' class AutoGPT_Agents implements INode { label: string @@ -67,7 +68,7 @@ class AutoGPT_Agents implements INode { const model = nodeData.inputs?.model as BaseChatModel const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as VectorStoreRetriever let tools = nodeData.inputs?.tools as Tool[] - tools = tools.flat() + tools = flatten(tools) const aiName = (nodeData.inputs?.aiName as string) || 'AutoGPT' const aiRole = (nodeData.inputs?.aiRole as string) || 'Assistant' const maxLoop = nodeData.inputs?.maxLoop as string diff --git a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts index d2106e18..363b3907 100644 --- a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts +++ b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts @@ -5,6 +5,7 @@ import { BaseChatMemory, ChatMessageHistory } from 'langchain/memory' import { getBaseClasses } from '../../../src/utils' import { AIChatMessage, HumanChatMessage } from 'langchain/schema' import { BaseLanguageModel } from 'langchain/base_language' +import { flatten } from 'lodash' class ConversationalAgent_Agents implements INode { label: string @@ -63,7 +64,7 @@ class ConversationalAgent_Agents implements INode { async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as BaseLanguageModel let tools = nodeData.inputs?.tools as Tool[] - tools = tools.flat() + tools = flatten(tools) const memory = nodeData.inputs?.memory as BaseChatMemory const humanMessage = nodeData.inputs?.humanMessage as string const systemMessage = nodeData.inputs?.systemMessage as string diff --git a/packages/components/nodes/agents/MRKLAgentChat/MRKLAgentChat.ts b/packages/components/nodes/agents/MRKLAgentChat/MRKLAgentChat.ts index 34b36fc1..d2a52d6c 100644 --- a/packages/components/nodes/agents/MRKLAgentChat/MRKLAgentChat.ts +++ b/packages/components/nodes/agents/MRKLAgentChat/MRKLAgentChat.ts @@ -3,6 +3,7 @@ import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/age import { getBaseClasses } from '../../../src/utils' import { Tool } from 'langchain/tools' import { BaseLanguageModel } from 'langchain/base_language' +import { flatten } from 'lodash' class MRKLAgentChat_Agents implements INode { label: string @@ -40,7 +41,7 @@ class MRKLAgentChat_Agents implements INode { async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as BaseLanguageModel let tools = nodeData.inputs?.tools as Tool[] - tools = tools.flat() + tools = flatten(tools) const executor = await initializeAgentExecutorWithOptions(tools, model, { agentType: 'chat-zero-shot-react-description', verbose: process.env.DEBUG === 'true' ? true : false diff --git a/packages/components/nodes/agents/MRKLAgentLLM/MRKLAgentLLM.ts b/packages/components/nodes/agents/MRKLAgentLLM/MRKLAgentLLM.ts index 20246ffa..eb685531 100644 --- a/packages/components/nodes/agents/MRKLAgentLLM/MRKLAgentLLM.ts +++ b/packages/components/nodes/agents/MRKLAgentLLM/MRKLAgentLLM.ts @@ -3,6 +3,7 @@ import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/age import { Tool } from 'langchain/tools' import { getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' +import { flatten } from 'lodash' class MRKLAgentLLM_Agents implements INode { label: string @@ -40,7 +41,7 @@ class MRKLAgentLLM_Agents implements INode { async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as BaseLanguageModel let tools = nodeData.inputs?.tools as Tool[] - tools = tools.flat() + tools = flatten(tools) const executor = await initializeAgentExecutorWithOptions(tools, model, { agentType: 'zero-shot-react-description', diff --git a/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts b/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts index fb7c404e..87cce2c4 100644 --- a/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts +++ b/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts @@ -3,6 +3,7 @@ import { Chroma } from 'langchain/vectorstores/chroma' import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' import { getBaseClasses } from '../../../src/utils' +import { flatten } from 'lodash' class ChromaUpsert_VectorStores implements INode { label: string @@ -68,7 +69,7 @@ class ChromaUpsert_VectorStores implements INode { const chromaURL = nodeData.inputs?.chromaURL as string const output = nodeData.outputs?.output as string - const flattenDocs = docs && docs.length ? docs.flat() : [] + const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { finalDocs.push(new Document(flattenDocs[i])) diff --git a/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts b/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts index 2db6a038..c94a3076 100644 --- a/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts +++ b/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts @@ -3,6 +3,7 @@ import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' import { getBaseClasses } from '../../../src/utils' import { FaissStore } from 'langchain/vectorstores/faiss' +import { flatten } from 'lodash' class FaissUpsert_VectorStores implements INode { label: string @@ -63,7 +64,7 @@ class FaissUpsert_VectorStores implements INode { const output = nodeData.outputs?.output as string const basePath = nodeData.inputs?.basePath as string - const flattenDocs = docs && docs.length ? docs.flat() : [] + const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { finalDocs.push(new Document(flattenDocs[i])) diff --git a/packages/components/nodes/vectorstores/InMemory/InMemoryVectorStore.ts b/packages/components/nodes/vectorstores/InMemory/InMemoryVectorStore.ts index 8d89b2ef..5efae4f4 100644 --- a/packages/components/nodes/vectorstores/InMemory/InMemoryVectorStore.ts +++ b/packages/components/nodes/vectorstores/InMemory/InMemoryVectorStore.ts @@ -3,6 +3,7 @@ import { MemoryVectorStore } from 'langchain/vectorstores/memory' import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' import { getBaseClasses } from '../../../src/utils' +import { flatten } from 'lodash' class InMemoryVectorStore_VectorStores implements INode { label: string @@ -55,7 +56,7 @@ class InMemoryVectorStore_VectorStores implements INode { const embeddings = nodeData.inputs?.embeddings as Embeddings const output = nodeData.outputs?.output as string - const flattenDocs = docs && docs.length ? docs.flat() : [] + const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { finalDocs.push(new Document(flattenDocs[i])) diff --git a/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts b/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts index d89174d3..6af660c1 100644 --- a/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts +++ b/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts @@ -4,6 +4,7 @@ import { PineconeLibArgs, PineconeStore } from 'langchain/vectorstores/pinecone' import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' import { getBaseClasses } from '../../../src/utils' +import { flatten } from 'lodash' class PineconeUpsert_VectorStores implements INode { label: string @@ -90,7 +91,7 @@ class PineconeUpsert_VectorStores implements INode { const pineconeIndex = client.Index(index) - const flattenDocs = docs && docs.length ? docs.flat() : [] + const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { finalDocs.push(new Document(flattenDocs[i])) diff --git a/packages/components/nodes/vectorstores/Supabase_Upsert/Supabase_Upsert.ts b/packages/components/nodes/vectorstores/Supabase_Upsert/Supabase_Upsert.ts index 0a8af6fd..18c65789 100644 --- a/packages/components/nodes/vectorstores/Supabase_Upsert/Supabase_Upsert.ts +++ b/packages/components/nodes/vectorstores/Supabase_Upsert/Supabase_Upsert.ts @@ -4,6 +4,7 @@ import { Document } from 'langchain/document' import { getBaseClasses } from '../../../src/utils' import { SupabaseVectorStore } from 'langchain/vectorstores/supabase' import { createClient } from '@supabase/supabase-js' +import { flatten } from 'lodash' class SupabaseUpsert_VectorStores implements INode { label: string @@ -82,7 +83,7 @@ class SupabaseUpsert_VectorStores implements INode { const client = createClient(supabaseProjUrl, supabaseApiKey) - const flattenDocs = docs && docs.length ? docs.flat() : [] + const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { finalDocs.push(new Document(flattenDocs[i])) diff --git a/packages/components/nodes/vectorstores/Weaviate_Upsert/Weaviate_Upsert.ts b/packages/components/nodes/vectorstores/Weaviate_Upsert/Weaviate_Upsert.ts index 0528d249..a05f97d2 100644 --- a/packages/components/nodes/vectorstores/Weaviate_Upsert/Weaviate_Upsert.ts +++ b/packages/components/nodes/vectorstores/Weaviate_Upsert/Weaviate_Upsert.ts @@ -4,6 +4,7 @@ import { Document } from 'langchain/document' import { getBaseClasses } from '../../../src/utils' import { WeaviateLibArgs, WeaviateStore } from 'langchain/vectorstores/weaviate' import weaviate, { WeaviateClient, ApiKey } from 'weaviate-ts-client' +import { flatten } from 'lodash' class WeaviateUpsert_VectorStores implements INode { label: string @@ -122,7 +123,7 @@ class WeaviateUpsert_VectorStores implements INode { const client: WeaviateClient = weaviate.client(clientConfig) - const flattenDocs = docs && docs.length ? docs.flat() : [] + const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { finalDocs.push(new Document(flattenDocs[i])) From 011f6afcb319eb9ce477e0dd2155de8096e0c540 Mon Sep 17 00:00:00 2001 From: Jeffrey-Wang Date: Sat, 10 Jun 2023 12:09:22 +0800 Subject: [PATCH 078/398] feat: support zep memory --- .../nodes/memory/ZepMemory/ZepMemory.ts | 136 ++++++++++++++++++ .../nodes/memory/ZepMemory/memory.svg | 8 ++ packages/components/package.json | 1 + 3 files changed, 145 insertions(+) create mode 100644 packages/components/nodes/memory/ZepMemory/ZepMemory.ts create mode 100644 packages/components/nodes/memory/ZepMemory/memory.svg diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts new file mode 100644 index 00000000..f833b4e3 --- /dev/null +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -0,0 +1,136 @@ +import { SystemChatMessage } from 'langchain/schema' +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' + +class ZepMemory_Memory implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Zep Memory' + this.name = 'ZepMemory' + this.type = 'ZepMemory' + this.icon = 'memory.svg' + this.category = 'Memory' + this.description = 'Summarizes the conversation and stores the memory in zep server' + this.baseClasses = [this.type, ...getBaseClasses(ZepMemory)] + this.inputs = [ + { + label: 'Base URL', + name: 'baseURL', + type: 'string', + default: 'http://127.0.0.1:8000' + }, + { + label: 'Session Id', + name: 'sessionId', + type: 'string', + placeholder: 'unique and manually change in every conversion!', + default: '' + }, + { + label: 'Auto Summary', + name: 'autoSummary', + type: 'boolean', + default: true + }, + { + label: 'Auto Summary Template', + name: 'autoSummaryTemplate', + type: 'string', + default: 'This is the summary of the following conversation:\n{summary}', + additionalParams: true + }, + { + label: 'AI Prefix', + name: 'aiPrefix', + type: 'string', + default: 'ai', + additionalParams: true + }, + { + label: 'Human Prefix', + name: 'humanPrefix', + type: 'string', + default: 'human', + additionalParams: true + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history', + additionalParams: true + }, + { + label: 'Input Key', + name: 'inputKey', + type: 'string', + default: 'input', + additionalParams: true + }, + { + label: 'Output Key', + name: 'outputKey', + type: 'string', + default: 'text', + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const baseURL = nodeData.inputs?.baseURL as string + const sessionId = nodeData.inputs?.sessionId as string + const aiPrefix = nodeData.inputs?.aiPrefix as string + const humanPrefix = nodeData.inputs?.humanPrefix as string + const memoryKey = nodeData.inputs?.memoryKey as string + const inputKey = nodeData.inputs?.inputKey as string + const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string + const autoSummary = nodeData.inputs?.autoSummary as boolean + + const obj: ZepMemoryInput = { + baseURL, + sessionId, + aiPrefix, + humanPrefix, + returnMessages: true, + memoryKey, + inputKey + } + + let zep = new ZepMemory(obj) + + // hack to support summary + let tmpFunc = zep.loadMemoryVariables + zep.loadMemoryVariables = async (values) => { + let data = await tmpFunc.bind(zep, values)() + if (autoSummary && zep.returnMessages && data[zep.memoryKey] && data[zep.memoryKey].length) { + const memory = await zep.zepClient.getMemory(zep.sessionId, 10) + if (memory?.summary) { + let summary = autoSummaryTemplate.replace(/{summary}/g, memory.summary.content) + // eslint-disable-next-line no-console + console.log('[ZepMemory] auto summary:', summary) + data[zep.memoryKey].unshift(new SystemChatMessage(summary)) + } + } + // for langchain zep memory compatibility, or we will get "Missing value for input variable chat_history" + if (data instanceof Array) { + data = { + [zep.memoryKey]: data + } + } + return data + } + return zep + } +} + +module.exports = { nodeClass: ZepMemory_Memory } diff --git a/packages/components/nodes/memory/ZepMemory/memory.svg b/packages/components/nodes/memory/ZepMemory/memory.svg new file mode 100644 index 00000000..ca8e17da --- /dev/null +++ b/packages/components/nodes/memory/ZepMemory/memory.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index 3e3d58b6..a778ea8f 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -17,6 +17,7 @@ "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "@dqbd/tiktoken": "^1.0.7", + "@getzep/zep-js": "^0.3.1", "@huggingface/inference": "1", "@pinecone-database/pinecone": "^0.0.12", "@supabase/supabase-js": "^2.21.0", From b7501a9baae6bc84a0efa6b84b79290de1b1e28c Mon Sep 17 00:00:00 2001 From: Matthew Ratzke Date: Sat, 10 Jun 2023 08:59:19 -0600 Subject: [PATCH 079/398] lint fixes --- .../components/nodes/documentloaders/Github/Github.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/components/nodes/documentloaders/Github/Github.ts b/packages/components/nodes/documentloaders/Github/Github.ts index 64c821e8..209e6b40 100644 --- a/packages/components/nodes/documentloaders/Github/Github.ts +++ b/packages/components/nodes/documentloaders/Github/Github.ts @@ -69,26 +69,21 @@ class Github_DocumentLoaders implements INode { const accessToken = nodeData.inputs?.accessToken as string const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - + const options: GithubRepoLoaderParams = { branch, recursive, - unknown: 'warn', + unknown: 'warn' } if (accessToken) options.accessToken = accessToken - console.log('🤖[GithubRepoLoader]: Initializing!'); - const loader = new GithubRepoLoader(repoLink, options) - console.log('🤖[GithubRepoLoader]: Loading documents from ' + repoLink); const docs = textSplitter ? await loader.loadAndSplit(textSplitter) : await loader.load() - console.log('🤖[GithubRepoLoader]: Documents Loaded!'); - if (metadata) { const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) - return docs.map(doc => { + return docs.map((doc) => { return { ...doc, metadata: { From d11cb5f4b425b8faf36f07319b87e65e9055c2e7 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 10 Jun 2023 17:21:27 +0100 Subject: [PATCH 080/398] add top K to vector stores --- .../nodes/agents/BabyAGI/BabyAGI.ts | 3 +- .../components/nodes/agents/BabyAGI/core.ts | 12 +- .../MultiRetrievalQAChain.ts | 2 +- .../chains/VectorDBQAChain/VectorDBQAChain.ts | 5 +- .../Chroma_Existing/Chroma_Existing.ts | 14 +- .../Chroma_Upsert/Chroma_Upsert.ts | 14 +- .../Faiss_Existing/Faiss_Existing.ts | 14 +- .../vectorstores/Faiss_Upsert/Faiss_Upsert.ts | 14 +- .../InMemory/InMemoryVectorStore.ts | 13 +- .../Pinecone_Existing/Pinecone_Existing.ts | 16 +- .../Pinecone_Upsert/Pinecone_Upsert.ts | 15 +- .../Supabase_Existing/Supabase_Exisiting.ts | 14 +- .../Supabase_Upsert/Supabase_Upsert.ts | 14 +- .../Weaviate_Existing/Weaviate_Existing.ts | 15 +- .../Weaviate_Upsert/Weaviate_Upsert.ts | 15 +- packages/server/marketplaces/API Agent.json | 24 + packages/server/marketplaces/Antonym.json | 8 + packages/server/marketplaces/AutoGPT.json | 27 + packages/server/marketplaces/BabyAGI.json | 27 + .../server/marketplaces/ChatGPTPlugin.json | 8 + .../marketplaces/Conversational Agent.json | 8 + .../Conversational Retrieval QA Chain.json | 59 +++ .../server/marketplaces/Github Repo QnA.json | 59 +++ packages/server/marketplaces/MRKLAgent.json | 8 + .../marketplaces/Metadata Filter Load.json | 59 +++ .../marketplaces/Metadata Filter Upsert.json | 59 +++ .../marketplaces/Multi Prompt Chain.json | 8 + .../Multi Retrieval QA Chain.json | 73 ++- .../marketplaces/Multiple VectorDB.json | 476 ++++++++++-------- .../server/marketplaces/Prompt Chaining.json | 16 + .../server/marketplaces/SQL DB Chain.json | 8 + .../Simple Conversation Chain.json | 8 + .../server/marketplaces/Simple LLM Chain.json | 8 + packages/server/marketplaces/Translator.json | 8 + packages/server/marketplaces/WebBrowser.json | 24 + packages/server/marketplaces/Zapier NLA.json | 8 + 36 files changed, 925 insertions(+), 238 deletions(-) diff --git a/packages/components/nodes/agents/BabyAGI/BabyAGI.ts b/packages/components/nodes/agents/BabyAGI/BabyAGI.ts index 5112be0e..91af1469 100644 --- a/packages/components/nodes/agents/BabyAGI/BabyAGI.ts +++ b/packages/components/nodes/agents/BabyAGI/BabyAGI.ts @@ -45,8 +45,9 @@ class BabyAGI_Agents implements INode { const model = nodeData.inputs?.model as BaseChatModel const vectorStore = nodeData.inputs?.vectorStore as VectorStore const taskLoop = nodeData.inputs?.taskLoop as string + const k = (vectorStore as any)?.k ?? 4 - const babyAgi = BabyAGI.fromLLM(model, vectorStore, parseInt(taskLoop, 10)) + const babyAgi = BabyAGI.fromLLM(model, vectorStore, parseInt(taskLoop, 10), k) return babyAgi } diff --git a/packages/components/nodes/agents/BabyAGI/core.ts b/packages/components/nodes/agents/BabyAGI/core.ts index 76889b52..444aa3eb 100644 --- a/packages/components/nodes/agents/BabyAGI/core.ts +++ b/packages/components/nodes/agents/BabyAGI/core.ts @@ -154,18 +154,22 @@ export class BabyAGI { maxIterations = 3 + topK = 4 + constructor( taskCreationChain: TaskCreationChain, taskPrioritizationChain: TaskPrioritizationChain, executionChain: ExecutionChain, vectorStore: VectorStore, - maxIterations: number + maxIterations: number, + topK: number ) { this.taskCreationChain = taskCreationChain this.taskPrioritizationChain = taskPrioritizationChain this.executionChain = executionChain this.vectorStore = vectorStore this.maxIterations = maxIterations + this.topK = topK } addTask(task: Task) { @@ -219,7 +223,7 @@ export class BabyAGI { this.printNextTask(task) // Step 2: Execute the task - const result = await executeTask(this.vectorStore, this.executionChain, objective, task.task_name) + const result = await executeTask(this.vectorStore, this.executionChain, objective, task.task_name, this.topK) const thisTaskId = task.task_id finalResult = result this.printTaskResult(result) @@ -257,10 +261,10 @@ export class BabyAGI { return finalResult } - static fromLLM(llm: BaseChatModel, vectorstore: VectorStore, maxIterations = 3): BabyAGI { + static fromLLM(llm: BaseChatModel, vectorstore: VectorStore, maxIterations = 3, topK = 4): BabyAGI { const taskCreationChain = TaskCreationChain.from_llm(llm) const taskPrioritizationChain = TaskPrioritizationChain.from_llm(llm) const executionChain = ExecutionChain.from_llm(llm) - return new BabyAGI(taskCreationChain, taskPrioritizationChain, executionChain, vectorstore, maxIterations) + return new BabyAGI(taskCreationChain, taskPrioritizationChain, executionChain, vectorstore, maxIterations, topK) } } diff --git a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts index 214db509..b17125c2 100644 --- a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts +++ b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts @@ -46,7 +46,7 @@ class MultiRetrievalQAChain_Chains implements INode { for (const vs of vectorStoreRetriever) { retrieverNames.push(vs.name) retrieverDescriptions.push(vs.description) - retrievers.push(vs.vectorStore.asRetriever()) + retrievers.push(vs.vectorStore.asRetriever((vs.vectorStore as any).k ?? 4)) } const chain = MultiRetrievalQAChain.fromRetrievers(model, retrieverNames, retrieverDescriptions, retrievers, undefined, { diff --git a/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts b/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts index f752d60c..5850ed7b 100644 --- a/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts +++ b/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts @@ -40,7 +40,10 @@ class VectorDBQAChain_Chains implements INode { const model = nodeData.inputs?.model as BaseLanguageModel const vectorStore = nodeData.inputs?.vectorStore as VectorStore - const chain = VectorDBQAChain.fromLLM(model, vectorStore, { verbose: process.env.DEBUG === 'true' ? true : false }) + const chain = VectorDBQAChain.fromLLM(model, vectorStore, { + k: (vectorStore as any)?.k ?? 4, + verbose: process.env.DEBUG === 'true' ? true : false + }) return chain } diff --git a/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts b/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts index fbaa5cbb..3ce93e87 100644 --- a/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts +++ b/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts @@ -38,6 +38,15 @@ class Chroma_Existing_VectorStores implements INode { name: 'chromaURL', type: 'string', optional: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true } ] this.outputs = [ @@ -59,6 +68,8 @@ class Chroma_Existing_VectorStores implements INode { const embeddings = nodeData.inputs?.embeddings as Embeddings const chromaURL = nodeData.inputs?.chromaURL as string const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 const obj: { collectionName: string @@ -69,9 +80,10 @@ class Chroma_Existing_VectorStores implements INode { const vectorStore = await Chroma.fromExistingCollection(embeddings, obj) if (output === 'retriever') { - const retriever = vectorStore.asRetriever() + const retriever = vectorStore.asRetriever(k) return retriever } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k return vectorStore } return vectorStore diff --git a/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts b/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts index 87cce2c4..f32fbb67 100644 --- a/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts +++ b/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts @@ -46,6 +46,15 @@ class ChromaUpsert_VectorStores implements INode { name: 'chromaURL', type: 'string', optional: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true } ] this.outputs = [ @@ -68,6 +77,8 @@ class ChromaUpsert_VectorStores implements INode { const embeddings = nodeData.inputs?.embeddings as Embeddings const chromaURL = nodeData.inputs?.chromaURL as string const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] @@ -84,9 +95,10 @@ class ChromaUpsert_VectorStores implements INode { const vectorStore = await Chroma.fromDocuments(finalDocs, embeddings, obj) if (output === 'retriever') { - const retriever = vectorStore.asRetriever() + const retriever = vectorStore.asRetriever(k) return retriever } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k return vectorStore } return vectorStore diff --git a/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts b/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts index 8916a734..6dd18594 100644 --- a/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts +++ b/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts @@ -34,6 +34,15 @@ class Faiss_Existing_VectorStores implements INode { description: 'Path to load faiss.index file', placeholder: `C:\\Users\\User\\Desktop`, type: 'string' + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true } ] this.outputs = [ @@ -54,13 +63,16 @@ class Faiss_Existing_VectorStores implements INode { const embeddings = nodeData.inputs?.embeddings as Embeddings const basePath = nodeData.inputs?.basePath as string const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 const vectorStore = await FaissStore.load(basePath, embeddings) if (output === 'retriever') { - const retriever = vectorStore.asRetriever() + const retriever = vectorStore.asRetriever(k) return retriever } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k return vectorStore } return vectorStore diff --git a/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts b/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts index c94a3076..5e5f9028 100644 --- a/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts +++ b/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts @@ -42,6 +42,15 @@ class FaissUpsert_VectorStores implements INode { description: 'Path to store faiss.index file', placeholder: `C:\\Users\\User\\Desktop`, type: 'string' + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true } ] this.outputs = [ @@ -63,6 +72,8 @@ class FaissUpsert_VectorStores implements INode { const embeddings = nodeData.inputs?.embeddings as Embeddings const output = nodeData.outputs?.output as string const basePath = nodeData.inputs?.basePath as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] @@ -74,9 +85,10 @@ class FaissUpsert_VectorStores implements INode { await vectorStore.save(basePath) if (output === 'retriever') { - const retriever = vectorStore.asRetriever() + const retriever = vectorStore.asRetriever(k) return retriever } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k return vectorStore } return vectorStore diff --git a/packages/components/nodes/vectorstores/InMemory/InMemoryVectorStore.ts b/packages/components/nodes/vectorstores/InMemory/InMemoryVectorStore.ts index 5efae4f4..32a785a5 100644 --- a/packages/components/nodes/vectorstores/InMemory/InMemoryVectorStore.ts +++ b/packages/components/nodes/vectorstores/InMemory/InMemoryVectorStore.ts @@ -35,6 +35,14 @@ class InMemoryVectorStore_VectorStores implements INode { label: 'Embeddings', name: 'embeddings', type: 'Embeddings' + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + optional: true } ] this.outputs = [ @@ -55,6 +63,8 @@ class InMemoryVectorStore_VectorStores implements INode { const docs = nodeData.inputs?.document as Document[] const embeddings = nodeData.inputs?.embeddings as Embeddings const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] @@ -65,9 +75,10 @@ class InMemoryVectorStore_VectorStores implements INode { const vectorStore = await MemoryVectorStore.fromDocuments(finalDocs, embeddings) if (output === 'retriever') { - const retriever = vectorStore.asRetriever() + const retriever = vectorStore.asRetriever(k) return retriever } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k return vectorStore } return vectorStore diff --git a/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts b/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts index 04706ed0..e57da396 100644 --- a/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts +++ b/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts @@ -49,6 +49,7 @@ class Pinecone_Existing_VectorStores implements INode { name: 'pineconeNamespace', type: 'string', placeholder: 'my-first-namespace', + additionalParams: true, optional: true }, { @@ -57,6 +58,15 @@ class Pinecone_Existing_VectorStores implements INode { type: 'json', optional: true, additionalParams: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true } ] this.outputs = [ @@ -79,9 +89,10 @@ class Pinecone_Existing_VectorStores implements INode { const index = nodeData.inputs?.pineconeIndex as string const pineconeNamespace = nodeData.inputs?.pineconeNamespace as string const pineconeMetadataFilter = nodeData.inputs?.pineconeMetadataFilter - const embeddings = nodeData.inputs?.embeddings as Embeddings const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 const client = new PineconeClient() await client.init({ @@ -104,9 +115,10 @@ class Pinecone_Existing_VectorStores implements INode { const vectorStore = await PineconeStore.fromExistingIndex(embeddings, obj) if (output === 'retriever') { - const retriever = vectorStore.asRetriever() + const retriever = vectorStore.asRetriever(k) return retriever } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k return vectorStore } return vectorStore diff --git a/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts b/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts index 6af660c1..ad1767c2 100644 --- a/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts +++ b/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts @@ -57,6 +57,16 @@ class PineconeUpsert_VectorStores implements INode { name: 'pineconeNamespace', type: 'string', placeholder: 'my-first-namespace', + additionalParams: true, + optional: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, optional: true } ] @@ -82,6 +92,8 @@ class PineconeUpsert_VectorStores implements INode { const docs = nodeData.inputs?.document as Document[] const embeddings = nodeData.inputs?.embeddings as Embeddings const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 const client = new PineconeClient() await client.init({ @@ -106,9 +118,10 @@ class PineconeUpsert_VectorStores implements INode { const vectorStore = await PineconeStore.fromDocuments(finalDocs, embeddings, obj) if (output === 'retriever') { - const retriever = vectorStore.asRetriever() + const retriever = vectorStore.asRetriever(k) return retriever } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k return vectorStore } return vectorStore diff --git a/packages/components/nodes/vectorstores/Supabase_Existing/Supabase_Exisiting.ts b/packages/components/nodes/vectorstores/Supabase_Existing/Supabase_Exisiting.ts index f97b1887..173660ca 100644 --- a/packages/components/nodes/vectorstores/Supabase_Existing/Supabase_Exisiting.ts +++ b/packages/components/nodes/vectorstores/Supabase_Existing/Supabase_Exisiting.ts @@ -55,6 +55,15 @@ class Supabase_Existing_VectorStores implements INode { type: 'json', optional: true, additionalParams: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true } ] this.outputs = [ @@ -79,6 +88,8 @@ class Supabase_Existing_VectorStores implements INode { const embeddings = nodeData.inputs?.embeddings as Embeddings const supabaseMetadataFilter = nodeData.inputs?.supabaseMetadataFilter const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 const client = createClient(supabaseProjUrl, supabaseApiKey) @@ -96,9 +107,10 @@ class Supabase_Existing_VectorStores implements INode { const vectorStore = await SupabaseVectorStore.fromExistingIndex(embeddings, obj) if (output === 'retriever') { - const retriever = vectorStore.asRetriever() + const retriever = vectorStore.asRetriever(k) return retriever } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k return vectorStore } return vectorStore diff --git a/packages/components/nodes/vectorstores/Supabase_Upsert/Supabase_Upsert.ts b/packages/components/nodes/vectorstores/Supabase_Upsert/Supabase_Upsert.ts index 18c65789..69997a56 100644 --- a/packages/components/nodes/vectorstores/Supabase_Upsert/Supabase_Upsert.ts +++ b/packages/components/nodes/vectorstores/Supabase_Upsert/Supabase_Upsert.ts @@ -56,6 +56,15 @@ class SupabaseUpsert_VectorStores implements INode { label: 'Query Name', name: 'queryName', type: 'string' + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true } ] this.outputs = [ @@ -80,6 +89,8 @@ class SupabaseUpsert_VectorStores implements INode { const docs = nodeData.inputs?.document as Document[] const embeddings = nodeData.inputs?.embeddings as Embeddings const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 const client = createClient(supabaseProjUrl, supabaseApiKey) @@ -96,9 +107,10 @@ class SupabaseUpsert_VectorStores implements INode { }) if (output === 'retriever') { - const retriever = vectorStore.asRetriever() + const retriever = vectorStore.asRetriever(k) return retriever } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k return vectorStore } return vectorStore diff --git a/packages/components/nodes/vectorstores/Weaviate_Existing/Weaviate_Existing.ts b/packages/components/nodes/vectorstores/Weaviate_Existing/Weaviate_Existing.ts index ba0ab502..595691bd 100644 --- a/packages/components/nodes/vectorstores/Weaviate_Existing/Weaviate_Existing.ts +++ b/packages/components/nodes/vectorstores/Weaviate_Existing/Weaviate_Existing.ts @@ -79,6 +79,15 @@ class Weaviate_Existing_VectorStores implements INode { placeholder: `["foo"]`, optional: true, additionalParams: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true } ] this.outputs = [ @@ -102,9 +111,10 @@ class Weaviate_Existing_VectorStores implements INode { const weaviateApiKey = nodeData.inputs?.weaviateApiKey as string const weaviateTextKey = nodeData.inputs?.weaviateTextKey as string const weaviateMetadataKeys = nodeData.inputs?.weaviateMetadataKeys as string - const embeddings = nodeData.inputs?.embeddings as Embeddings const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 const clientConfig: any = { scheme: weaviateScheme, @@ -125,9 +135,10 @@ class Weaviate_Existing_VectorStores implements INode { const vectorStore = await WeaviateStore.fromExistingIndex(embeddings, obj) if (output === 'retriever') { - const retriever = vectorStore.asRetriever() + const retriever = vectorStore.asRetriever(k) return retriever } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k return vectorStore } return vectorStore diff --git a/packages/components/nodes/vectorstores/Weaviate_Upsert/Weaviate_Upsert.ts b/packages/components/nodes/vectorstores/Weaviate_Upsert/Weaviate_Upsert.ts index a05f97d2..06137426 100644 --- a/packages/components/nodes/vectorstores/Weaviate_Upsert/Weaviate_Upsert.ts +++ b/packages/components/nodes/vectorstores/Weaviate_Upsert/Weaviate_Upsert.ts @@ -87,6 +87,15 @@ class WeaviateUpsert_VectorStores implements INode { placeholder: `["foo"]`, optional: true, additionalParams: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true } ] this.outputs = [ @@ -110,10 +119,11 @@ class WeaviateUpsert_VectorStores implements INode { const weaviateApiKey = nodeData.inputs?.weaviateApiKey as string const weaviateTextKey = nodeData.inputs?.weaviateTextKey as string const weaviateMetadataKeys = nodeData.inputs?.weaviateMetadataKeys as string - const docs = nodeData.inputs?.document as Document[] const embeddings = nodeData.inputs?.embeddings as Embeddings const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 const clientConfig: any = { scheme: weaviateScheme, @@ -140,9 +150,10 @@ class WeaviateUpsert_VectorStores implements INode { const vectorStore = await WeaviateStore.fromDocuments(finalDocs, embeddings, obj) if (output === 'retriever') { - const retriever = vectorStore.asRetriever() + const retriever = vectorStore.asRetriever(k) return retriever } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k return vectorStore } return vectorStore diff --git a/packages/server/marketplaces/API Agent.json b/packages/server/marketplaces/API Agent.json index a1a42ddb..20e270af 100644 --- a/packages/server/marketplaces/API Agent.json +++ b/packages/server/marketplaces/API Agent.json @@ -346,6 +346,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" } ], "inputAnchors": [], @@ -533,6 +541,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-basepath-string" } ], "inputAnchors": [], @@ -664,6 +680,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_2-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_2-input-basepath-string" } ], "inputAnchors": [], diff --git a/packages/server/marketplaces/Antonym.json b/packages/server/marketplaces/Antonym.json index 507015b3..2e21fd22 100644 --- a/packages/server/marketplaces/Antonym.json +++ b/packages/server/marketplaces/Antonym.json @@ -276,6 +276,14 @@ "optional": true, "additionalParams": true, "id": "openAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAI_1-input-basepath-string" } ], "inputAnchors": [], diff --git a/packages/server/marketplaces/AutoGPT.json b/packages/server/marketplaces/AutoGPT.json index 1ec20277..4fd1cfdb 100644 --- a/packages/server/marketplaces/AutoGPT.json +++ b/packages/server/marketplaces/AutoGPT.json @@ -192,6 +192,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-basepath-string" } ], "inputAnchors": [], @@ -412,6 +420,14 @@ "optional": true, "additionalParams": true, "id": "openAIEmbeddings_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-basepath-string" } ], "inputAnchors": [], @@ -480,6 +496,7 @@ "type": "string", "placeholder": "my-first-namespace", "optional": true, + "additionalParams": true, "id": "pineconeExistingIndex_1-input-pineconeNamespace-string" }, { @@ -489,6 +506,16 @@ "optional": true, "additionalParams": true, "id": "pineconeExistingIndex_1-input-pineconeMetadataFilter-json" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pineconeExistingIndex_1-input-topK-number" } ], "inputAnchors": [ diff --git a/packages/server/marketplaces/BabyAGI.json b/packages/server/marketplaces/BabyAGI.json index 6cb40519..797b574f 100644 --- a/packages/server/marketplaces/BabyAGI.json +++ b/packages/server/marketplaces/BabyAGI.json @@ -48,6 +48,14 @@ "optional": true, "additionalParams": true, "id": "openAIEmbeddings_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_1-input-basepath-string" } ], "inputAnchors": [], @@ -116,6 +124,7 @@ "type": "string", "placeholder": "my-first-namespace", "optional": true, + "additionalParams": true, "id": "pineconeExistingIndex_1-input-pineconeNamespace-string" }, { @@ -125,6 +134,16 @@ "optional": true, "additionalParams": true, "id": "pineconeExistingIndex_1-input-pineconeMetadataFilter-json" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pineconeExistingIndex_1-input-topK-number" } ], "inputAnchors": [ @@ -276,6 +295,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-basepath-string" } ], "inputAnchors": [], diff --git a/packages/server/marketplaces/ChatGPTPlugin.json b/packages/server/marketplaces/ChatGPTPlugin.json index 4eec2ccc..648c94b7 100644 --- a/packages/server/marketplaces/ChatGPTPlugin.json +++ b/packages/server/marketplaces/ChatGPTPlugin.json @@ -355,6 +355,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" } ], "inputAnchors": [], diff --git a/packages/server/marketplaces/Conversational Agent.json b/packages/server/marketplaces/Conversational Agent.json index b47b73f0..635455ce 100644 --- a/packages/server/marketplaces/Conversational Agent.json +++ b/packages/server/marketplaces/Conversational Agent.json @@ -102,6 +102,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-basepath-string" } ], "inputAnchors": [], diff --git a/packages/server/marketplaces/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/Conversational Retrieval QA Chain.json index 584a175a..4d470ab2 100644 --- a/packages/server/marketplaces/Conversational Retrieval QA Chain.json +++ b/packages/server/marketplaces/Conversational Retrieval QA Chain.json @@ -169,6 +169,14 @@ "optional": true, "additionalParams": true, "id": "openAIEmbeddings_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_1-input-basepath-string" } ], "inputAnchors": [], @@ -237,7 +245,18 @@ "type": "string", "placeholder": "my-first-namespace", "optional": true, + "additionalParams": true, "id": "pineconeUpsert_1-input-pineconeNamespace-string" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pineconeUpsert_1-input-topK-number" } ], "inputAnchors": [ @@ -397,6 +416,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" } ], "inputAnchors": [], @@ -445,6 +472,13 @@ "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ + { + "label": "Return Source Documents", + "name": "returnSourceDocuments", + "type": "boolean", + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" + }, { "label": "System Message", "name": "systemMessagePrompt", @@ -454,6 +488,31 @@ "optional": true, "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + }, + { + "label": "Chain Option", + "name": "chainOption", + "type": "options", + "options": [ + { + "label": "MapReduceDocumentsChain", + "name": "map_reduce", + "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" + }, + { + "label": "RefineDocumentsChain", + "name": "refine", + "description": "Suitable for QA tasks over a large number of documents." + }, + { + "label": "StuffDocumentsChain", + "name": "stuff", + "description": "Suitable for QA tasks over a small number of documents." + } + ], + "additionalParams": true, + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-chainOption-options" } ], "inputAnchors": [ diff --git a/packages/server/marketplaces/Github Repo QnA.json b/packages/server/marketplaces/Github Repo QnA.json index debc2aad..a9294eca 100644 --- a/packages/server/marketplaces/Github Repo QnA.json +++ b/packages/server/marketplaces/Github Repo QnA.json @@ -186,6 +186,14 @@ "optional": true, "additionalParams": true, "id": "openAIEmbeddings_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_1-input-basepath-string" } ], "inputAnchors": [], @@ -254,7 +262,18 @@ "type": "string", "placeholder": "my-first-namespace", "optional": true, + "additionalParams": true, "id": "pineconeUpsert_1-input-pineconeNamespace-string" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pineconeUpsert_1-input-topK-number" } ], "inputAnchors": [ @@ -414,6 +433,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" } ], "inputAnchors": [], @@ -462,6 +489,13 @@ "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ + { + "label": "Return Source Documents", + "name": "returnSourceDocuments", + "type": "boolean", + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" + }, { "label": "System Message", "name": "systemMessagePrompt", @@ -471,6 +505,31 @@ "optional": true, "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + }, + { + "label": "Chain Option", + "name": "chainOption", + "type": "options", + "options": [ + { + "label": "MapReduceDocumentsChain", + "name": "map_reduce", + "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" + }, + { + "label": "RefineDocumentsChain", + "name": "refine", + "description": "Suitable for QA tasks over a large number of documents." + }, + { + "label": "StuffDocumentsChain", + "name": "stuff", + "description": "Suitable for QA tasks over a small number of documents." + } + ], + "additionalParams": true, + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-chainOption-options" } ], "inputAnchors": [ diff --git a/packages/server/marketplaces/MRKLAgent.json b/packages/server/marketplaces/MRKLAgent.json index c0790a26..257123e0 100644 --- a/packages/server/marketplaces/MRKLAgent.json +++ b/packages/server/marketplaces/MRKLAgent.json @@ -197,6 +197,14 @@ "optional": true, "additionalParams": true, "id": "openAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAI_1-input-basepath-string" } ], "inputAnchors": [], diff --git a/packages/server/marketplaces/Metadata Filter Load.json b/packages/server/marketplaces/Metadata Filter Load.json index c496b199..dfc6d6fb 100644 --- a/packages/server/marketplaces/Metadata Filter Load.json +++ b/packages/server/marketplaces/Metadata Filter Load.json @@ -114,6 +114,14 @@ "optional": true, "additionalParams": true, "id": "openAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAI_1-input-basepath-string" } ], "inputAnchors": [], @@ -193,6 +201,14 @@ "optional": true, "additionalParams": true, "id": "openAIEmbeddings_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_1-input-basepath-string" } ], "inputAnchors": [], @@ -261,6 +277,7 @@ "type": "string", "placeholder": "my-first-namespace", "optional": true, + "additionalParams": true, "id": "pineconeExistingIndex_0-input-pineconeNamespace-string" }, { @@ -270,6 +287,16 @@ "optional": true, "additionalParams": true, "id": "pineconeExistingIndex_0-input-pineconeMetadataFilter-json" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pineconeExistingIndex_0-input-topK-number" } ], "inputAnchors": [ @@ -339,6 +366,13 @@ "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ + { + "label": "Return Source Documents", + "name": "returnSourceDocuments", + "type": "boolean", + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" + }, { "label": "System Message", "name": "systemMessagePrompt", @@ -348,6 +382,31 @@ "optional": true, "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + }, + { + "label": "Chain Option", + "name": "chainOption", + "type": "options", + "options": [ + { + "label": "MapReduceDocumentsChain", + "name": "map_reduce", + "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" + }, + { + "label": "RefineDocumentsChain", + "name": "refine", + "description": "Suitable for QA tasks over a large number of documents." + }, + { + "label": "StuffDocumentsChain", + "name": "stuff", + "description": "Suitable for QA tasks over a small number of documents." + } + ], + "additionalParams": true, + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-chainOption-options" } ], "inputAnchors": [ diff --git a/packages/server/marketplaces/Metadata Filter Upsert.json b/packages/server/marketplaces/Metadata Filter Upsert.json index d0335f16..87336654 100644 --- a/packages/server/marketplaces/Metadata Filter Upsert.json +++ b/packages/server/marketplaces/Metadata Filter Upsert.json @@ -171,6 +171,14 @@ "optional": true, "additionalParams": true, "id": "openAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAI_1-input-basepath-string" } ], "inputAnchors": [], @@ -250,6 +258,14 @@ "optional": true, "additionalParams": true, "id": "openAIEmbeddings_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_1-input-basepath-string" } ], "inputAnchors": [], @@ -466,7 +482,18 @@ "type": "string", "placeholder": "my-first-namespace", "optional": true, + "additionalParams": true, "id": "pineconeUpsert_0-input-pineconeNamespace-string" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pineconeUpsert_0-input-topK-number" } ], "inputAnchors": [ @@ -543,6 +570,13 @@ "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ + { + "label": "Return Source Documents", + "name": "returnSourceDocuments", + "type": "boolean", + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" + }, { "label": "System Message", "name": "systemMessagePrompt", @@ -552,6 +586,31 @@ "optional": true, "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + }, + { + "label": "Chain Option", + "name": "chainOption", + "type": "options", + "options": [ + { + "label": "MapReduceDocumentsChain", + "name": "map_reduce", + "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" + }, + { + "label": "RefineDocumentsChain", + "name": "refine", + "description": "Suitable for QA tasks over a large number of documents." + }, + { + "label": "StuffDocumentsChain", + "name": "stuff", + "description": "Suitable for QA tasks over a small number of documents." + } + ], + "additionalParams": true, + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-chainOption-options" } ], "inputAnchors": [ diff --git a/packages/server/marketplaces/Multi Prompt Chain.json b/packages/server/marketplaces/Multi Prompt Chain.json index fa99cd91..a9c41a76 100644 --- a/packages/server/marketplaces/Multi Prompt Chain.json +++ b/packages/server/marketplaces/Multi Prompt Chain.json @@ -362,6 +362,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" } ], "inputAnchors": [], diff --git a/packages/server/marketplaces/Multi Retrieval QA Chain.json b/packages/server/marketplaces/Multi Retrieval QA Chain.json index 48cddbf4..8f2ca89e 100644 --- a/packages/server/marketplaces/Multi Retrieval QA Chain.json +++ b/packages/server/marketplaces/Multi Retrieval QA Chain.json @@ -3,7 +3,7 @@ "nodes": [ { "width": 300, - "height": 505, + "height": 504, "id": "vectorStoreRetriever_0", "position": { "x": 712.9322670298264, @@ -69,7 +69,7 @@ }, { "width": 300, - "height": 280, + "height": 279, "id": "multiRetrievalQAChain_0", "position": { "x": 1563.0150452201099, @@ -128,7 +128,7 @@ }, { "width": 300, - "height": 505, + "height": 504, "id": "vectorStoreRetriever_1", "position": { "x": 711.4902931206071, @@ -194,7 +194,7 @@ }, { "width": 300, - "height": 505, + "height": 504, "id": "vectorStoreRetriever_2", "position": { "x": 706.0716220151372, @@ -260,7 +260,7 @@ }, { "width": 300, - "height": 524, + "height": 523, "id": "chatOpenAI_0", "position": { "x": 1206.027762600755, @@ -359,6 +359,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" } ], "inputAnchors": [], @@ -391,7 +399,7 @@ }, { "width": 300, - "height": 330, + "height": 329, "id": "openAIEmbeddings_0", "position": { "x": -254.88737984323413, @@ -436,6 +444,14 @@ "optional": true, "additionalParams": true, "id": "openAIEmbeddings_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-basepath-string" } ], "inputAnchors": [], @@ -464,7 +480,7 @@ }, { "width": 300, - "height": 703, + "height": 603, "id": "pineconeExistingIndex_0", "position": { "x": 271.2513182410521, @@ -504,6 +520,7 @@ "type": "string", "placeholder": "my-first-namespace", "optional": true, + "additionalParams": true, "id": "pineconeExistingIndex_0-input-pineconeNamespace-string" }, { @@ -513,6 +530,16 @@ "optional": true, "additionalParams": true, "id": "pineconeExistingIndex_0-input-pineconeMetadataFilter-json" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pineconeExistingIndex_0-input-topK-number" } ], "inputAnchors": [ @@ -566,11 +593,11 @@ }, { "width": 300, - "height": 454, + "height": 505, "id": "chromaExistingIndex_0", "position": { - "x": 274.1430731555137, - "y": 335.15344698725556 + "x": 269.2940530300552, + "y": 262.41814510537796 }, "type": "customNode", "data": { @@ -594,6 +621,16 @@ "type": "string", "optional": true, "id": "chromaExistingIndex_0-input-chromaURL-string" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "chromaExistingIndex_0-input-topK-number" } ], "inputAnchors": [ @@ -638,14 +675,14 @@ }, "selected": false, "positionAbsolute": { - "x": 274.1430731555137, - "y": 335.15344698725556 + "x": 269.2940530300552, + "y": 262.41814510537796 }, "dragging": false }, { "width": 300, - "height": 703, + "height": 702, "id": "supabaseExistingIndex_0", "position": { "x": 273.7097153973373, @@ -692,6 +729,16 @@ "optional": true, "additionalParams": true, "id": "supabaseExistingIndex_0-input-supabaseMetadataFilter-json" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "supabaseExistingIndex_0-input-topK-number" } ], "inputAnchors": [ diff --git a/packages/server/marketplaces/Multiple VectorDB.json b/packages/server/marketplaces/Multiple VectorDB.json index 05223e29..05f7ca5e 100644 --- a/packages/server/marketplaces/Multiple VectorDB.json +++ b/packages/server/marketplaces/Multiple VectorDB.json @@ -3,7 +3,7 @@ "nodes": [ { "width": 300, - "height": 330, + "height": 329, "id": "openAIEmbeddings_2", "position": { "x": 155.07832615625986, @@ -48,6 +48,14 @@ "optional": true, "additionalParams": true, "id": "openAIEmbeddings_2-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_2-input-basepath-string" } ], "inputAnchors": [], @@ -76,11 +84,11 @@ }, { "width": 300, - "height": 355, + "height": 505, "id": "chromaExistingIndex_1", "position": { "x": 522.8177328694987, - "y": -548.8355398674973 + "y": -723.8834555183237 }, "type": "customNode", "data": { @@ -97,6 +105,23 @@ "name": "collectionName", "type": "string", "id": "chromaExistingIndex_1-input-collectionName-string" + }, + { + "label": "Chroma URL", + "name": "chromaURL", + "type": "string", + "optional": true, + "id": "chromaExistingIndex_1-input-chromaURL-string" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "chromaExistingIndex_1-input-topK-number" } ], "inputAnchors": [ @@ -134,24 +159,24 @@ } ], "outputs": { - "output": "vectorStore" + "output": "retriever" }, "selected": false }, "positionAbsolute": { "x": 522.8177328694987, - "y": -548.8355398674973 + "y": -723.8834555183237 }, "selected": false, "dragging": false }, { "width": 300, - "height": 524, + "height": 523, "id": "openAI_3", "position": { - "x": 512.7434966474709, - "y": -1107.9938317347255 + "x": 527.7101375911075, + "y": -1290.6752949922043 }, "type": "customNode", "data": { @@ -258,6 +283,14 @@ "optional": true, "additionalParams": true, "id": "openAI_3-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAI_3-input-basepath-string" } ], "inputAnchors": [], @@ -284,69 +317,15 @@ "selected": false }, "positionAbsolute": { - "x": 512.7434966474709, - "y": -1107.9938317347255 + "x": 527.7101375911075, + "y": -1290.6752949922043 }, "selected": false, "dragging": false }, { "width": 300, - "height": 280, - "id": "vectorDBQAChain_2", - "position": { - "x": 880.7795222381183, - "y": -823.6550506138045 - }, - "type": "customNode", - "data": { - "id": "vectorDBQAChain_2", - "label": "VectorDB QA Chain", - "name": "vectorDBQAChain", - "type": "VectorDBQAChain", - "baseClasses": ["VectorDBQAChain", "BaseChain", "BaseLangChain"], - "category": "Chains", - "description": "QA chain for vector databases", - "inputParams": [], - "inputAnchors": [ - { - "label": "Language Model", - "name": "model", - "type": "BaseLanguageModel", - "id": "vectorDBQAChain_2-input-model-BaseLanguageModel" - }, - { - "label": "Vector Store", - "name": "vectorStore", - "type": "VectorStore", - "id": "vectorDBQAChain_2-input-vectorStore-VectorStore" - } - ], - "inputs": { - "model": "{{openAI_3.data.instance}}", - "vectorStore": "{{chromaExistingIndex_1.data.instance}}" - }, - "outputAnchors": [ - { - "id": "vectorDBQAChain_2-output-vectorDBQAChain-VectorDBQAChain|BaseChain|BaseLangChain", - "name": "vectorDBQAChain", - "label": "VectorDBQAChain", - "type": "VectorDBQAChain | BaseChain | BaseLangChain" - } - ], - "outputs": {}, - "selected": false - }, - "positionAbsolute": { - "x": 880.7795222381183, - "y": -823.6550506138045 - }, - "selected": false, - "dragging": false - }, - { - "width": 300, - "height": 602, + "height": 601, "id": "chainTool_2", "position": { "x": 1251.240972921597, @@ -397,7 +376,7 @@ "name": "ai-paper-qa", "description": "AI Paper QA - useful for when you need to ask questions about the AI-Generated Content paper.", "returnDirect": "", - "baseChain": "{{vectorDBQAChain_2.data.instance}}" + "baseChain": "{{retrievalQAChain_0.data.instance}}" }, "outputAnchors": [ { @@ -419,7 +398,7 @@ }, { "width": 300, - "height": 143, + "height": 142, "id": "calculator_1", "position": { "x": 1649.5389102641816, @@ -457,7 +436,7 @@ }, { "width": 300, - "height": 278, + "height": 277, "id": "serpAPI_0", "position": { "x": 1654.5273488033688, @@ -502,7 +481,7 @@ }, { "width": 300, - "height": 330, + "height": 329, "id": "openAIEmbeddings_3", "position": { "x": 163.902196956619, @@ -547,6 +526,14 @@ "optional": true, "additionalParams": true, "id": "openAIEmbeddings_3-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_3-input-basepath-string" } ], "inputAnchors": [], @@ -575,7 +562,7 @@ }, { "width": 300, - "height": 524, + "height": 523, "id": "openAI_4", "position": { "x": 529.8870809493459, @@ -686,6 +673,14 @@ "optional": true, "additionalParams": true, "id": "openAI_4-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAI_4-input-basepath-string" } ], "inputAnchors": [], @@ -720,11 +715,11 @@ }, { "width": 300, - "height": 703, + "height": 603, "id": "pineconeExistingIndex_1", "position": { - "x": 539.4840212380209, - "y": 452.3690065882661 + "x": 525.6644489497978, + "y": 420.1233379157454 }, "type": "customNode", "data": { @@ -760,6 +755,7 @@ "type": "string", "placeholder": "my-first-namespace", "optional": true, + "additionalParams": true, "id": "pineconeExistingIndex_1-input-pineconeNamespace-string" }, { @@ -769,6 +765,16 @@ "optional": true, "additionalParams": true, "id": "pineconeExistingIndex_1-input-pineconeMetadataFilter-json" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pineconeExistingIndex_1-input-topK-number" } ], "inputAnchors": [ @@ -808,78 +814,24 @@ } ], "outputs": { - "output": "vectorStore" + "output": "retriever" }, "selected": false }, "selected": false, "dragging": false, "positionAbsolute": { - "x": 539.4840212380209, - "y": 452.3690065882661 + "x": 525.6644489497978, + "y": 420.1233379157454 } }, { "width": 300, - "height": 280, - "id": "vectorDBQAChain_3", - "position": { - "x": 896.3238465010572, - "y": 173.57643605877104 - }, - "type": "customNode", - "data": { - "id": "vectorDBQAChain_3", - "label": "VectorDB QA Chain", - "name": "vectorDBQAChain", - "type": "VectorDBQAChain", - "baseClasses": ["VectorDBQAChain", "BaseChain", "BaseLangChain"], - "category": "Chains", - "description": "QA chain for vector databases", - "inputParams": [], - "inputAnchors": [ - { - "label": "Language Model", - "name": "model", - "type": "BaseLanguageModel", - "id": "vectorDBQAChain_3-input-model-BaseLanguageModel" - }, - { - "label": "Vector Store", - "name": "vectorStore", - "type": "VectorStore", - "id": "vectorDBQAChain_3-input-vectorStore-VectorStore" - } - ], - "inputs": { - "model": "{{openAI_4.data.instance}}", - "vectorStore": "{{pineconeExistingIndex_1.data.instance}}" - }, - "outputAnchors": [ - { - "id": "vectorDBQAChain_3-output-vectorDBQAChain-VectorDBQAChain|BaseChain|BaseLangChain", - "name": "vectorDBQAChain", - "label": "VectorDBQAChain", - "type": "VectorDBQAChain | BaseChain | BaseLangChain" - } - ], - "outputs": {}, - "selected": false - }, - "positionAbsolute": { - "x": 896.3238465010572, - "y": 173.57643605877104 - }, - "selected": false, - "dragging": false - }, - { - "width": 300, - "height": 602, + "height": 601, "id": "chainTool_3", "position": { - "x": 1260.8044270644157, - "y": -244.7000095631508 + "x": 1267.7142132085273, + "y": -85.7749282485849 }, "type": "customNode", "data": { @@ -926,7 +878,7 @@ "name": "state-of-union-qa", "description": "State of the Union QA - useful for when you need to ask questions about the most recent state of the union address.", "returnDirect": "", - "baseChain": "{{vectorDBQAChain_3.data.instance}}" + "baseChain": "{{retrievalQAChain_1.data.instance}}" }, "outputAnchors": [ { @@ -942,13 +894,13 @@ "selected": false, "dragging": false, "positionAbsolute": { - "x": 1260.8044270644157, - "y": -244.7000095631508 + "x": 1267.7142132085273, + "y": -85.7749282485849 } }, { "width": 300, - "height": 524, + "height": 523, "id": "openAI_5", "position": { "x": 1683.95439713088, @@ -1059,6 +1011,14 @@ "optional": true, "additionalParams": true, "id": "openAI_5-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAI_5-input-basepath-string" } ], "inputAnchors": [], @@ -1093,7 +1053,7 @@ }, { "width": 300, - "height": 280, + "height": 279, "id": "mrklAgentLLM_0", "position": { "x": 2061.891333395338, @@ -1150,6 +1110,114 @@ "y": -140.0694021759809 }, "dragging": false + }, + { + "width": 300, + "height": 279, + "id": "retrievalQAChain_0", + "position": { + "x": 898.1253096948574, + "y": -859.1174013418433 + }, + "type": "customNode", + "data": { + "id": "retrievalQAChain_0", + "label": "Retrieval QA Chain", + "name": "retrievalQAChain", + "type": "RetrievalQAChain", + "baseClasses": ["RetrievalQAChain", "BaseChain", "BaseLangChain"], + "category": "Chains", + "description": "QA chain to answer a question based on the retrieved documents", + "inputParams": [], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "retrievalQAChain_0-input-model-BaseLanguageModel" + }, + { + "label": "Vector Store Retriever", + "name": "vectorStoreRetriever", + "type": "BaseRetriever", + "id": "retrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + } + ], + "inputs": { + "model": "{{openAI_3.data.instance}}", + "vectorStoreRetriever": "{{chromaExistingIndex_1.data.instance}}" + }, + "outputAnchors": [ + { + "id": "retrievalQAChain_0-output-retrievalQAChain-RetrievalQAChain|BaseChain|BaseLangChain", + "name": "retrievalQAChain", + "label": "RetrievalQAChain", + "type": "RetrievalQAChain | BaseChain | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 898.1253096948574, + "y": -859.1174013418433 + }, + "dragging": false + }, + { + "width": 300, + "height": 279, + "id": "retrievalQAChain_1", + "position": { + "x": 895.4349543765911, + "y": 166.60331503487222 + }, + "type": "customNode", + "data": { + "id": "retrievalQAChain_1", + "label": "Retrieval QA Chain", + "name": "retrievalQAChain", + "type": "RetrievalQAChain", + "baseClasses": ["RetrievalQAChain", "BaseChain", "BaseLangChain"], + "category": "Chains", + "description": "QA chain to answer a question based on the retrieved documents", + "inputParams": [], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "retrievalQAChain_1-input-model-BaseLanguageModel" + }, + { + "label": "Vector Store Retriever", + "name": "vectorStoreRetriever", + "type": "BaseRetriever", + "id": "retrievalQAChain_1-input-vectorStoreRetriever-BaseRetriever" + } + ], + "inputs": { + "model": "{{openAI_4.data.instance}}", + "vectorStoreRetriever": "{{pineconeExistingIndex_1.data.instance}}" + }, + "outputAnchors": [ + { + "id": "retrievalQAChain_1-output-retrievalQAChain-RetrievalQAChain|BaseChain|BaseLangChain", + "name": "retrievalQAChain", + "label": "RetrievalQAChain", + "type": "RetrievalQAChain | BaseChain | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 895.4349543765911, + "y": 166.60331503487222 + }, + "dragging": false } ], "edges": [ @@ -1164,50 +1232,6 @@ "label": "" } }, - { - "source": "chromaExistingIndex_1", - "sourceHandle": "chromaExistingIndex_1-output-vectorStore-Chroma|VectorStore", - "target": "vectorDBQAChain_2", - "targetHandle": "vectorDBQAChain_2-input-vectorStore-VectorStore", - "type": "buttonedge", - "id": "chromaExistingIndex_1-chromaExistingIndex_1-output-vectorStore-Chroma|VectorStore-vectorDBQAChain_2-vectorDBQAChain_2-input-vectorStore-VectorStore", - "data": { - "label": "" - } - }, - { - "source": "openAI_3", - "sourceHandle": "openAI_3-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain", - "target": "vectorDBQAChain_2", - "targetHandle": "vectorDBQAChain_2-input-model-BaseLanguageModel", - "type": "buttonedge", - "id": "openAI_3-openAI_3-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain-vectorDBQAChain_2-vectorDBQAChain_2-input-model-BaseLanguageModel", - "data": { - "label": "" - } - }, - { - "source": "vectorDBQAChain_2", - "sourceHandle": "vectorDBQAChain_2-output-vectorDBQAChain-VectorDBQAChain|BaseChain|BaseLangChain", - "target": "chainTool_2", - "targetHandle": "chainTool_2-input-baseChain-BaseChain", - "type": "buttonedge", - "id": "vectorDBQAChain_2-vectorDBQAChain_2-output-vectorDBQAChain-VectorDBQAChain|BaseChain|BaseLangChain-chainTool_2-chainTool_2-input-baseChain-BaseChain", - "data": { - "label": "" - } - }, - { - "source": "openAI_4", - "sourceHandle": "openAI_4-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain", - "target": "vectorDBQAChain_3", - "targetHandle": "vectorDBQAChain_3-input-model-BaseLanguageModel", - "type": "buttonedge", - "id": "openAI_4-openAI_4-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain-vectorDBQAChain_3-vectorDBQAChain_3-input-model-BaseLanguageModel", - "data": { - "label": "" - } - }, { "source": "openAIEmbeddings_3", "sourceHandle": "openAIEmbeddings_3-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", @@ -1219,28 +1243,6 @@ "label": "" } }, - { - "source": "vectorDBQAChain_3", - "sourceHandle": "vectorDBQAChain_3-output-vectorDBQAChain-VectorDBQAChain|BaseChain|BaseLangChain", - "target": "chainTool_3", - "targetHandle": "chainTool_3-input-baseChain-BaseChain", - "type": "buttonedge", - "id": "vectorDBQAChain_3-vectorDBQAChain_3-output-vectorDBQAChain-VectorDBQAChain|BaseChain|BaseLangChain-chainTool_3-chainTool_3-input-baseChain-BaseChain", - "data": { - "label": "" - } - }, - { - "source": "pineconeExistingIndex_1", - "sourceHandle": "pineconeExistingIndex_1-output-vectorStore-Pinecone|VectorStore", - "target": "vectorDBQAChain_3", - "targetHandle": "vectorDBQAChain_3-input-vectorStore-VectorStore", - "type": "buttonedge", - "id": "pineconeExistingIndex_1-pineconeExistingIndex_1-output-vectorStore-Pinecone|VectorStore-vectorDBQAChain_3-vectorDBQAChain_3-input-vectorStore-VectorStore", - "data": { - "label": "" - } - }, { "source": "serpAPI_0", "sourceHandle": "serpAPI_0-output-serpAPI-SerpAPI|Tool|StructuredTool|BaseLangChain", @@ -1295,6 +1297,72 @@ "data": { "label": "" } + }, + { + "source": "openAI_3", + "sourceHandle": "openAI_3-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain", + "target": "retrievalQAChain_0", + "targetHandle": "retrievalQAChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "openAI_3-openAI_3-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain-retrievalQAChain_0-retrievalQAChain_0-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "chromaExistingIndex_1", + "sourceHandle": "chromaExistingIndex_1-output-retriever-Chroma|VectorStoreRetriever|BaseRetriever", + "target": "retrievalQAChain_0", + "targetHandle": "retrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", + "type": "buttonedge", + "id": "chromaExistingIndex_1-chromaExistingIndex_1-output-retriever-Chroma|VectorStoreRetriever|BaseRetriever-retrievalQAChain_0-retrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", + "data": { + "label": "" + } + }, + { + "source": "retrievalQAChain_0", + "sourceHandle": "retrievalQAChain_0-output-retrievalQAChain-RetrievalQAChain|BaseChain|BaseLangChain", + "target": "chainTool_2", + "targetHandle": "chainTool_2-input-baseChain-BaseChain", + "type": "buttonedge", + "id": "retrievalQAChain_0-retrievalQAChain_0-output-retrievalQAChain-RetrievalQAChain|BaseChain|BaseLangChain-chainTool_2-chainTool_2-input-baseChain-BaseChain", + "data": { + "label": "" + } + }, + { + "source": "openAI_4", + "sourceHandle": "openAI_4-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain", + "target": "retrievalQAChain_1", + "targetHandle": "retrievalQAChain_1-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "openAI_4-openAI_4-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain-retrievalQAChain_1-retrievalQAChain_1-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "pineconeExistingIndex_1", + "sourceHandle": "pineconeExistingIndex_1-output-retriever-Pinecone|VectorStoreRetriever|BaseRetriever", + "target": "retrievalQAChain_1", + "targetHandle": "retrievalQAChain_1-input-vectorStoreRetriever-BaseRetriever", + "type": "buttonedge", + "id": "pineconeExistingIndex_1-pineconeExistingIndex_1-output-retriever-Pinecone|VectorStoreRetriever|BaseRetriever-retrievalQAChain_1-retrievalQAChain_1-input-vectorStoreRetriever-BaseRetriever", + "data": { + "label": "" + } + }, + { + "source": "retrievalQAChain_1", + "sourceHandle": "retrievalQAChain_1-output-retrievalQAChain-RetrievalQAChain|BaseChain|BaseLangChain", + "target": "chainTool_3", + "targetHandle": "chainTool_3-input-baseChain-BaseChain", + "type": "buttonedge", + "id": "retrievalQAChain_1-retrievalQAChain_1-output-retrievalQAChain-RetrievalQAChain|BaseChain|BaseLangChain-chainTool_3-chainTool_3-input-baseChain-BaseChain", + "data": { + "label": "" + } } ] } diff --git a/packages/server/marketplaces/Prompt Chaining.json b/packages/server/marketplaces/Prompt Chaining.json index 69f9370e..33a64081 100644 --- a/packages/server/marketplaces/Prompt Chaining.json +++ b/packages/server/marketplaces/Prompt Chaining.json @@ -114,6 +114,14 @@ "optional": true, "additionalParams": true, "id": "openAI_2-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAI_2-input-basepath-string" } ], "inputAnchors": [], @@ -461,6 +469,14 @@ "optional": true, "additionalParams": true, "id": "openAI_3-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAI_3-input-basepath-string" } ], "inputAnchors": [], diff --git a/packages/server/marketplaces/SQL DB Chain.json b/packages/server/marketplaces/SQL DB Chain.json index 90f8814c..e7826aa2 100644 --- a/packages/server/marketplaces/SQL DB Chain.json +++ b/packages/server/marketplaces/SQL DB Chain.json @@ -114,6 +114,14 @@ "optional": true, "additionalParams": true, "id": "openAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAI_1-input-basepath-string" } ], "inputAnchors": [], diff --git a/packages/server/marketplaces/Simple Conversation Chain.json b/packages/server/marketplaces/Simple Conversation Chain.json index bb1a5fff..f3decc85 100644 --- a/packages/server/marketplaces/Simple Conversation Chain.json +++ b/packages/server/marketplaces/Simple Conversation Chain.json @@ -102,6 +102,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" } ], "inputAnchors": [], diff --git a/packages/server/marketplaces/Simple LLM Chain.json b/packages/server/marketplaces/Simple LLM Chain.json index 26a3a2ee..c9d354bc 100644 --- a/packages/server/marketplaces/Simple LLM Chain.json +++ b/packages/server/marketplaces/Simple LLM Chain.json @@ -114,6 +114,14 @@ "optional": true, "additionalParams": true, "id": "openAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAI_1-input-basepath-string" } ], "inputAnchors": [], diff --git a/packages/server/marketplaces/Translator.json b/packages/server/marketplaces/Translator.json index d9bc4f25..6e36f943 100644 --- a/packages/server/marketplaces/Translator.json +++ b/packages/server/marketplaces/Translator.json @@ -172,6 +172,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-basepath-string" } ], "inputAnchors": [], diff --git a/packages/server/marketplaces/WebBrowser.json b/packages/server/marketplaces/WebBrowser.json index bd367161..7d7ff357 100644 --- a/packages/server/marketplaces/WebBrowser.json +++ b/packages/server/marketplaces/WebBrowser.json @@ -102,6 +102,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" } ], "inputAnchors": [], @@ -235,6 +243,14 @@ "optional": true, "additionalParams": true, "id": "openAIEmbeddings_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-basepath-string" } ], "inputAnchors": [], @@ -362,6 +378,14 @@ "optional": true, "additionalParams": true, "id": "chatOpenAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-basepath-string" } ], "inputAnchors": [], diff --git a/packages/server/marketplaces/Zapier NLA.json b/packages/server/marketplaces/Zapier NLA.json index eafd8f23..19b30107 100644 --- a/packages/server/marketplaces/Zapier NLA.json +++ b/packages/server/marketplaces/Zapier NLA.json @@ -159,6 +159,14 @@ "optional": true, "additionalParams": true, "id": "openAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAI_0-input-basepath-string" } ], "inputAnchors": [], From 0d41f6a4bfc2688ea62868a1dcb83251398e6548 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sat, 10 Jun 2023 17:30:28 +0100 Subject: [PATCH 081/398] Update Github.ts lint fix --- packages/components/nodes/documentloaders/Github/Github.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/documentloaders/Github/Github.ts b/packages/components/nodes/documentloaders/Github/Github.ts index 209e6b40..bbaad3cb 100644 --- a/packages/components/nodes/documentloaders/Github/Github.ts +++ b/packages/components/nodes/documentloaders/Github/Github.ts @@ -69,7 +69,7 @@ class Github_DocumentLoaders implements INode { const accessToken = nodeData.inputs?.accessToken as string const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - + const options: GithubRepoLoaderParams = { branch, recursive, From 6522b3a602f35a28a67a0bb2d2230c06b7903dca Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 10 Jun 2023 17:46:27 +0100 Subject: [PATCH 082/398] add PORT and EXECUTION_MODE flags --- packages/server/src/commands/start.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index a3ee561a..94b8d995 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -16,7 +16,9 @@ export default class Start extends Command { static args = [] static flags = { FLOWISE_USERNAME: Flags.string(), - FLOWISE_PASSWORD: Flags.string() + FLOWISE_PASSWORD: Flags.string(), + PORT: Flags.string(), + EXECUTION_MODE: Flags.string() } async stopProcess() { @@ -50,6 +52,8 @@ export default class Start extends Command { const { flags } = await this.parse(Start) if (flags.FLOWISE_USERNAME) process.env.FLOWISE_USERNAME = flags.FLOWISE_USERNAME if (flags.FLOWISE_PASSWORD) process.env.FLOWISE_PASSWORD = flags.FLOWISE_PASSWORD + if (flags.PORT) process.env.PORT = flags.PORT + if (flags.EXECUTION_MODE) process.env.EXECUTION_MODE = flags.EXECUTION_MODE await (async () => { try { From f68637150f636064a4c52c2219fa18c5437fa9a4 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 11 Jun 2023 01:29:03 +0100 Subject: [PATCH 083/398] add localai embeddings --- .../LocalAIEmbedding/LocalAIEmbedding.ts | 53 ++ .../embeddings/LocalAIEmbedding/localai.png | Bin 0 -> 144086 bytes packages/server/marketplaces/Local QnA.json | 521 ++++++++++++++++++ 3 files changed, 574 insertions(+) create mode 100644 packages/components/nodes/embeddings/LocalAIEmbedding/LocalAIEmbedding.ts create mode 100644 packages/components/nodes/embeddings/LocalAIEmbedding/localai.png create mode 100644 packages/server/marketplaces/Local QnA.json diff --git a/packages/components/nodes/embeddings/LocalAIEmbedding/LocalAIEmbedding.ts b/packages/components/nodes/embeddings/LocalAIEmbedding/LocalAIEmbedding.ts new file mode 100644 index 00000000..7fb2a798 --- /dev/null +++ b/packages/components/nodes/embeddings/LocalAIEmbedding/LocalAIEmbedding.ts @@ -0,0 +1,53 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { OpenAIEmbeddings, OpenAIEmbeddingsParams } from 'langchain/embeddings/openai' + +class LocalAIEmbedding_Embeddings implements INode { + label: string + name: string + type: string + icon: string + category: string + description: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'LocalAI Embeddings' + this.name = 'localAIEmbeddings' + this.type = 'LocalAI Embeddings' + this.icon = 'localai.png' + this.category = 'Embeddings' + this.description = 'Use local embeddings models like llama.cpp' + this.baseClasses = [this.type, 'Embeddings'] + this.inputs = [ + { + label: 'Base Path', + name: 'basePath', + type: 'string', + placeholder: 'http://localhost:8080/v1' + }, + { + label: 'Model Name', + name: 'modelName', + type: 'string', + placeholder: 'text-embedding-ada-002' + } + ] + } + + async init(nodeData: INodeData): Promise { + const modelName = nodeData.inputs?.modelName as string + const basePath = nodeData.inputs?.basePath as string + + const obj: Partial & { openAIApiKey?: string } = { + modelName, + openAIApiKey: 'sk-' + } + + const model = new OpenAIEmbeddings(obj, { basePath }) + + return model + } +} + +module.exports = { nodeClass: LocalAIEmbedding_Embeddings } diff --git a/packages/components/nodes/embeddings/LocalAIEmbedding/localai.png b/packages/components/nodes/embeddings/LocalAIEmbedding/localai.png new file mode 100644 index 0000000000000000000000000000000000000000..321403973dae17c99fbf0435440e36abd81e2f6b GIT binary patch literal 144086 zcmV)XK&`)tP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 Date: Sun, 11 Jun 2023 13:14:53 +0800 Subject: [PATCH 084/398] feat: unique chat id --- .../components/nodes/memory/ZepMemory/ZepMemory.ts | 12 +++--------- packages/server/src/ChildProcess.ts | 1 + packages/server/src/index.ts | 11 +++++++++++ packages/server/src/utils/index.ts | 3 ++- packages/ui/src/views/chatmessage/ChatMessage.js | 3 ++- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index f833b4e3..95b83bbb 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -2,6 +2,7 @@ import { SystemChatMessage } from 'langchain/schema' import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' +import { ICommonObject } from '../../../src' class ZepMemory_Memory implements INode { label: string @@ -28,13 +29,6 @@ class ZepMemory_Memory implements INode { type: 'string', default: 'http://127.0.0.1:8000' }, - { - label: 'Session Id', - name: 'sessionId', - type: 'string', - placeholder: 'unique and manually change in every conversion!', - default: '' - }, { label: 'Auto Summary', name: 'autoSummary', @@ -86,15 +80,15 @@ class ZepMemory_Memory implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const baseURL = nodeData.inputs?.baseURL as string - const sessionId = nodeData.inputs?.sessionId as string const aiPrefix = nodeData.inputs?.aiPrefix as string const humanPrefix = nodeData.inputs?.humanPrefix as string const memoryKey = nodeData.inputs?.memoryKey as string const inputKey = nodeData.inputs?.inputKey as string const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string const autoSummary = nodeData.inputs?.autoSummary as boolean + const sessionId = options?.chatId as string const obj: ZepMemoryInput = { baseURL, diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index 483379d0..e5f95170 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -83,6 +83,7 @@ export class ChildProcess { depthQueue, componentNodes, incomingInput.question, + '', incomingInput?.overrideConfig ) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 3a0c64cd..5e91a30a 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -506,6 +506,16 @@ export class App { }) if (!chatflow) return res.status(404).send(`Chatflow ${chatflowid} not found`) + // first chatmessage id as the unique chat id + const firstChatMessage = await this.AppDataSource.getRepository(ChatMessage) + .createQueryBuilder('cm') + .select('cm.id') + .where('chatflowid = :chatflowid', { chatflowid }) + .orderBy('cm.createdDate', 'ASC') + .getOne() + if (!firstChatMessage) return res.status(500).send(`Chatflow ${chatflowid} first message not found`) + const chatId = firstChatMessage.id + if (!isInternal) { await this.validateKey(req, res, chatflow) } @@ -618,6 +628,7 @@ export class App { depthQueue, this.nodesPool.componentNodes, incomingInput.question, + chatId, incomingInput?.overrideConfig ) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 18473c51..b993b958 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -182,6 +182,7 @@ export const buildLangchain = async ( depthQueue: IDepthQueue, componentNodes: IComponentNodes, question: string, + chatId: string, overrideConfig?: ICommonObject ) => { const flowNodes = cloneDeep(reactFlowNodes) @@ -214,7 +215,7 @@ export const buildLangchain = async ( if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig) const reactFlowNodeData: INodeData = resolveVariables(flowNodeData, flowNodes, question) - flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question) + flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, { chatId }) } catch (e: any) { console.error(e) throw new Error(e) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 077419f1..2f5415d0 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -118,7 +118,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { setLoading(true) setMessages((prevMessages) => [...prevMessages, { message: userInput, type: 'userMessage' }]) - addChatMessage(userInput, 'userMessage') + // waiting for first chatmessage uploaded, the first chatmessage id will be chatId for every components + await addChatMessage(userInput, 'userMessage') // Send user question and history to API try { From 6d9ed45201a21243ca0d1399f275a02aa55931eb Mon Sep 17 00:00:00 2001 From: Vikram Singh Date: Sun, 11 Jun 2023 18:44:16 +0530 Subject: [PATCH 085/398] resolved chat horizontal scrolling --- packages/ui/src/views/chatmessage/ChatMessage.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.css b/packages/ui/src/views/chatmessage/ChatMessage.css index 9086fb13..ef7f1e93 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.css +++ b/packages/ui/src/views/chatmessage/ChatMessage.css @@ -2,6 +2,7 @@ width: 100%; height: 100%; overflow-y: scroll; + overflow-x: hidden; border-radius: 0.5rem; } @@ -75,6 +76,9 @@ } .markdownanswer a { + display: block; + margin-right: 2.5rem; + word-wrap: break-word; color: #16bed7; font-weight: 500; } From fd9d6fcb0356b2a93df4874e910afce6a7482f0b Mon Sep 17 00:00:00 2001 From: Jeffrey-Wang Date: Sun, 11 Jun 2023 23:30:26 +0800 Subject: [PATCH 086/398] fix: zep memory --- .../nodes/memory/ZepMemory/ZepMemory.ts | 14 +++++++++-- packages/server/src/ChildProcess.ts | 4 +++- packages/server/src/index.ts | 23 +++++++++++-------- .../ui/src/views/chatmessage/ChatMessage.js | 2 +- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 95b83bbb..cf1d8e58 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -35,6 +35,14 @@ class ZepMemory_Memory implements INode { type: 'boolean', default: true }, + { + label: 'Session Id', + name: 'sessionId', + type: 'string', + placeholder: 'if empty, chatId will be used automatically', + default: '', + additionalParams: true + }, { label: 'Auto Summary Template', name: 'autoSummaryTemplate', @@ -88,11 +96,13 @@ class ZepMemory_Memory implements INode { const inputKey = nodeData.inputs?.inputKey as string const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string const autoSummary = nodeData.inputs?.autoSummary as boolean - const sessionId = options?.chatId as string + const sessionId = nodeData.inputs?.sessionId as string + + const chatId = options?.chatId as string const obj: ZepMemoryInput = { baseURL, - sessionId, + sessionId: sessionId ? sessionId : chatId, aiPrefix, humanPrefix, returnMessages: true, diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index e5f95170..07b52909 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -1,5 +1,6 @@ import { IChildProcessMessage, IReactFlowNode, IReactFlowObject, IRunChatflowMessageValue, INodeData } from './Interface' import { buildLangchain, constructGraphs, getEndingNode, getStartingNodes, resolveVariables } from './utils' +import { getChatId } from './index' export class ChildProcess { /** @@ -76,6 +77,7 @@ export class ChildProcess { const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) /*** BFS to traverse from Starting Nodes to Ending Node ***/ + const chatId = await getChatId(chatflow.id) const reactFlowNodes = await buildLangchain( startingNodeIds, nodes, @@ -83,7 +85,7 @@ export class ChildProcess { depthQueue, componentNodes, incomingInput.question, - '', + chatId, incomingInput?.overrideConfig ) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 5e91a30a..78be231a 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -506,15 +506,8 @@ export class App { }) if (!chatflow) return res.status(404).send(`Chatflow ${chatflowid} not found`) - // first chatmessage id as the unique chat id - const firstChatMessage = await this.AppDataSource.getRepository(ChatMessage) - .createQueryBuilder('cm') - .select('cm.id') - .where('chatflowid = :chatflowid', { chatflowid }) - .orderBy('cm.createdDate', 'ASC') - .getOne() - if (!firstChatMessage) return res.status(500).send(`Chatflow ${chatflowid} first message not found`) - const chatId = firstChatMessage.id + const chatId = await getChatId(chatflow.id) + if (!chatId) return res.status(500).send(`Chatflow ${chatflowid} first message not found`) if (!isInternal) { await this.validateKey(req, res, chatflow) @@ -672,6 +665,18 @@ export class App { } } +export async function getChatId(chatflowid: string) { + // first chatmessage id as the unique chat id + const firstChatMessage = await getDataSource() + .getRepository(ChatMessage) + .createQueryBuilder('cm') + .select('cm.id') + .where('chatflowid = :chatflowid', { chatflowid }) + .orderBy('cm.createdDate', 'ASC') + .getOne() + return firstChatMessage ? firstChatMessage.id : '' +} + let serverApp: App | undefined export async function start(): Promise { diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 2f5415d0..5021cd9b 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -118,7 +118,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { setLoading(true) setMessages((prevMessages) => [...prevMessages, { message: userInput, type: 'userMessage' }]) - // waiting for first chatmessage uploaded, the first chatmessage id will be chatId for every components + // waiting for first chatmessage saved, the first chatmessage will be used in sendMessageAndGetPrediction await addChatMessage(userInput, 'userMessage') // Send user question and history to API From 9d664329266b2125090332296be53bb6b6a3b86a Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 12 Jun 2023 09:43:48 +0100 Subject: [PATCH 087/398] update HuggingFace nodes --- .../ChatHuggingFace/ChatHuggingFace.ts | 103 +++++++ .../ChatHuggingFace/huggingface.png | Bin 0 -> 120391 bytes .../HuggingFaceInference.ts | 59 +++- .../marketplaces/HuggingFace LLM Chain.json | 274 ++++++++++++++++++ 4 files changed, 433 insertions(+), 3 deletions(-) create mode 100644 packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts create mode 100644 packages/components/nodes/chatmodels/ChatHuggingFace/huggingface.png create mode 100644 packages/server/marketplaces/HuggingFace LLM Chain.json diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts new file mode 100644 index 00000000..3252a61a --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts @@ -0,0 +1,103 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { HFInput, HuggingFaceInference } from 'langchain/llms/hf' + +class ChatHuggingFace_ChatModels implements INode { + label: string + name: string + type: string + icon: string + category: string + description: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'ChatHuggingFace' + this.name = 'chatHuggingFace' + this.type = 'ChatHuggingFace' + this.icon = 'huggingface.png' + this.category = 'Chat Models' + this.description = 'Wrapper around HuggingFace large language models' + this.baseClasses = [this.type, 'BaseChatModel', ...getBaseClasses(HuggingFaceInference)] + this.inputs = [ + { + label: 'Model', + name: 'model', + type: 'string', + placeholder: 'gpt2' + }, + { + label: 'HuggingFace Api Key', + name: 'apiKey', + type: 'password' + }, + { + label: 'Temperature', + name: 'temperature', + type: 'number', + description: 'Temperature parameter may not apply to certain model. Please check available model parameters', + optional: true, + additionalParams: true + }, + { + label: 'Max Tokens', + name: 'maxTokens', + type: 'number', + description: 'Max Tokens parameter may not apply to certain model. Please check available model parameters', + optional: true, + additionalParams: true + }, + { + label: 'Top Probability', + name: 'topP', + type: 'number', + description: 'Top Probability parameter may not apply to certain model. Please check available model parameters', + optional: true, + additionalParams: true + }, + { + label: 'Top K', + name: 'hfTopK', + type: 'number', + description: 'Top K parameter may not apply to certain model. Please check available model parameters', + optional: true, + additionalParams: true + }, + { + label: 'Frequency Penalty', + name: 'frequencyPenalty', + type: 'number', + description: 'Frequency Penalty parameter may not apply to certain model. Please check available model parameters', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const model = nodeData.inputs?.model as string + const apiKey = nodeData.inputs?.apiKey as string + const temperature = nodeData.inputs?.temperature as string + const maxTokens = nodeData.inputs?.maxTokens as string + const topP = nodeData.inputs?.topP as string + const hfTopK = nodeData.inputs?.hfTopK as string + const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string + + const obj: Partial = { + model, + apiKey + } + + if (temperature) obj.temperature = parseInt(temperature, 10) + if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) + if (topP) obj.topP = parseInt(topP, 10) + if (hfTopK) obj.topK = parseInt(hfTopK, 10) + if (frequencyPenalty) obj.frequencyPenalty = parseInt(frequencyPenalty, 10) + + const huggingFace = new HuggingFaceInference(obj) + return huggingFace + } +} + +module.exports = { nodeClass: ChatHuggingFace_ChatModels } diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/huggingface.png b/packages/components/nodes/chatmodels/ChatHuggingFace/huggingface.png new file mode 100644 index 0000000000000000000000000000000000000000..f8f202a46300c21e41bd782da959e43df5c73fd1 GIT binary patch literal 120391 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 { const model = nodeData.inputs?.model as string const apiKey = nodeData.inputs?.apiKey as string + const temperature = nodeData.inputs?.temperature as string + const maxTokens = nodeData.inputs?.maxTokens as string + const topP = nodeData.inputs?.topP as string + const hfTopK = nodeData.inputs?.hfTopK as string + const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string - const huggingFace = new HuggingFaceInference({ + const obj: Partial = { model, apiKey - }) + } + + if (temperature) obj.temperature = parseInt(temperature, 10) + if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) + if (topP) obj.topP = parseInt(topP, 10) + if (hfTopK) obj.topK = parseInt(hfTopK, 10) + if (frequencyPenalty) obj.frequencyPenalty = parseInt(frequencyPenalty, 10) + + const huggingFace = new HuggingFaceInference(obj) return huggingFace } } diff --git a/packages/server/marketplaces/HuggingFace LLM Chain.json b/packages/server/marketplaces/HuggingFace LLM Chain.json new file mode 100644 index 00000000..9d3492c6 --- /dev/null +++ b/packages/server/marketplaces/HuggingFace LLM Chain.json @@ -0,0 +1,274 @@ +{ + "description": "Simple LLM Chain using HuggingFace Inference API on falcon-7b-instruct model", + "nodes": [ + { + "width": 300, + "height": 532, + "id": "promptTemplate_1", + "position": { + "x": 514.5434056794296, + "y": 507.47798128037107 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_1", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_1-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "string", + "rows": 4, + "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_1-input-promptValues-string" + } + ], + "inputAnchors": [], + "inputs": { + "template": "Question: {question}\n\nAnswer: Let's think step by step.", + "promptValues": "" + }, + "outputAnchors": [ + { + "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 514.5434056794296, + "y": 507.47798128037107 + }, + "dragging": false + }, + { + "width": 300, + "height": 405, + "id": "llmChain_1", + "position": { + "x": 970.9254258940236, + "y": 320.56761595884564 + }, + "type": "customNode", + "data": { + "id": "llmChain_1", + "label": "LLM Chain", + "name": "llmChain", + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain", "BaseLangChain"], + "category": "Chains", + "description": "Chain to run queries against LLMs", + "inputParams": [ + { + "label": "Chain Name", + "name": "chainName", + "type": "string", + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_1-input-chainName-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "llmChain_1-input-model-BaseLanguageModel" + }, + { + "label": "Prompt", + "name": "prompt", + "type": "BasePromptTemplate", + "id": "llmChain_1-input-prompt-BasePromptTemplate" + } + ], + "inputs": { + "model": "{{huggingFaceInference_LLMs_0.data.instance}}", + "prompt": "{{promptTemplate_1.data.instance}}", + "chainName": "" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_1-output-llmChain-LLMChain|BaseChain|BaseLangChain", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain | BaseLangChain" + }, + { + "id": "llmChain_1-output-outputPrediction-string", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string" + } + ], + "default": "llmChain" + } + ], + "outputs": { + "output": "llmChain" + }, + "selected": false + }, + "positionAbsolute": { + "x": 970.9254258940236, + "y": 320.56761595884564 + }, + "selected": false, + "dragging": false + }, + { + "width": 300, + "height": 427, + "id": "huggingFaceInference_LLMs_0", + "position": { + "x": 503.5630827259226, + "y": 50.79125094823999 + }, + "type": "customNode", + "data": { + "id": "huggingFaceInference_LLMs_0", + "label": "HuggingFace Inference", + "name": "huggingFaceInference_LLMs", + "type": "HuggingFaceInference", + "baseClasses": ["HuggingFaceInference", "LLM", "BaseLLM", "BaseLanguageModel", "BaseLangChain"], + "category": "LLMs", + "description": "Wrapper around HuggingFace large language models", + "inputParams": [ + { + "label": "Model", + "name": "model", + "type": "string", + "placeholder": "gpt2", + "id": "huggingFaceInference_LLMs_0-input-model-string" + }, + { + "label": "HuggingFace Api Key", + "name": "apiKey", + "type": "password", + "id": "huggingFaceInference_LLMs_0-input-apiKey-password" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "description": "Temperature parameter may not apply to certain model. Please check available model parameters", + "optional": true, + "additionalParams": true, + "id": "huggingFaceInference_LLMs_0-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "description": "Max Tokens parameter may not apply to certain model. Please check available model parameters", + "optional": true, + "additionalParams": true, + "id": "huggingFaceInference_LLMs_0-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "description": "Top Probability parameter may not apply to certain model. Please check available model parameters", + "optional": true, + "additionalParams": true, + "id": "huggingFaceInference_LLMs_0-input-topP-number" + }, + { + "label": "Top K", + "name": "hfTopK", + "type": "number", + "description": "Top K parameter may not apply to certain model. Please check available model parameters", + "optional": true, + "additionalParams": true, + "id": "huggingFaceInference_LLMs_0-input-hfTopK-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "description": "Frequency Penalty parameter may not apply to certain model. Please check available model parameters", + "optional": true, + "additionalParams": true, + "id": "huggingFaceInference_LLMs_0-input-frequencyPenalty-number" + } + ], + "inputAnchors": [], + "inputs": { + "model": "tiiuae/falcon-7b-instruct", + "temperature": "", + "maxTokens": "200", + "topP": "", + "hfTopK": "10", + "frequencyPenalty": "" + }, + "outputAnchors": [ + { + "id": "huggingFaceInference_LLMs_0-output-huggingFaceInference_LLMs-HuggingFaceInference|LLM|BaseLLM|BaseLanguageModel|BaseLangChain", + "name": "huggingFaceInference_LLMs", + "label": "HuggingFaceInference", + "type": "HuggingFaceInference | LLM | BaseLLM | BaseLanguageModel | BaseLangChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 503.5630827259226, + "y": 50.79125094823999 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "promptTemplate_1", + "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_1-promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } + }, + { + "source": "huggingFaceInference_LLMs_0", + "sourceHandle": "huggingFaceInference_LLMs_0-output-huggingFaceInference_LLMs-HuggingFaceInference|LLM|BaseLLM|BaseLanguageModel|BaseLangChain", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "huggingFaceInference_LLMs_0-huggingFaceInference_LLMs_0-output-huggingFaceInference_LLMs-HuggingFaceInference|LLM|BaseLLM|BaseLanguageModel|BaseLangChain-llmChain_1-llmChain_1-input-model-BaseLanguageModel", + "data": { + "label": "" + } + } + ] +} From fe6737a6cb4f7118c8d26f7d57001a85f17a36f0 Mon Sep 17 00:00:00 2001 From: Jeffrey-Wang Date: Mon, 12 Jun 2023 21:52:46 +0800 Subject: [PATCH 088/398] fix: childprocess chatId. --- packages/components/nodes/memory/ZepMemory/ZepMemory.ts | 2 +- packages/server/src/ChildProcess.ts | 4 +--- packages/server/src/Interface.ts | 1 + packages/server/src/index.ts | 7 +++++++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index cf1d8e58..1fb6d9ff 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -39,7 +39,7 @@ class ZepMemory_Memory implements INode { label: 'Session Id', name: 'sessionId', type: 'string', - placeholder: 'if empty, chatId will be used automatically', + description: 'if empty, chatId will be used automatically', default: '', additionalParams: true }, diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index 07b52909..08847a52 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -1,6 +1,5 @@ import { IChildProcessMessage, IReactFlowNode, IReactFlowObject, IRunChatflowMessageValue, INodeData } from './Interface' import { buildLangchain, constructGraphs, getEndingNode, getStartingNodes, resolveVariables } from './utils' -import { getChatId } from './index' export class ChildProcess { /** @@ -24,7 +23,7 @@ export class ChildProcess { await sendToParentProcess('start', '_') // Create a Queue and add our initial node in it - const { endingNodeData, chatflow, incomingInput, componentNodes } = messageValue + const { endingNodeData, chatflow, chatId, incomingInput, componentNodes } = messageValue let nodeToExecuteData: INodeData let addToChatFlowPool: any = {} @@ -77,7 +76,6 @@ export class ChildProcess { const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) /*** BFS to traverse from Starting Nodes to Ending Node ***/ - const chatId = await getChatId(chatflow.id) const reactFlowNodes = await buildLangchain( startingNodeIds, nodes, diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index b6876df3..2c1fe406 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -143,6 +143,7 @@ export interface IDatabaseExport { export interface IRunChatflowMessageValue { chatflow: IChatFlow + chatId: string incomingInput: IncomingInput componentNodes: IComponentNodes endingNodeData?: INodeData diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 78be231a..9379a922 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -441,9 +441,11 @@ export class App { if (!fs.existsSync(childpath)) childpath = 'ChildProcess.ts' const childProcess = fork(childpath, [], { signal }) + const chatId = await getChatId(chatflow.id) const value = { chatflow, + chatId, incomingInput, componentNodes: cloneDeep(this.nodesPool.componentNodes), endingNodeData @@ -665,6 +667,11 @@ export class App { } } +/** + * Get first chat message id + * @param {string} chatflowid + * @returns {string} + */ export async function getChatId(chatflowid: string) { // first chatmessage id as the unique chat id const firstChatMessage = await getDataSource() From a5c408dbe8d58cd1fa262177a489367c639ae469 Mon Sep 17 00:00:00 2001 From: Jeffrey-Wang Date: Mon, 12 Jun 2023 23:45:41 +0800 Subject: [PATCH 089/398] fix: remove useless query --- packages/server/src/index.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 9379a922..b248f22c 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -432,7 +432,7 @@ export class App { * @param {IncomingInput} incomingInput * @param {INodeData} endingNodeData */ - async startChildProcess(chatflow: ChatFlow, incomingInput: IncomingInput, endingNodeData?: INodeData) { + async startChildProcess(chatflow: ChatFlow, chatId: string, incomingInput: IncomingInput, endingNodeData?: INodeData) { try { const controller = new AbortController() const { signal } = controller @@ -441,7 +441,6 @@ export class App { if (!fs.existsSync(childpath)) childpath = 'ChildProcess.ts' const childProcess = fork(childpath, [], { signal }) - const chatId = await getChatId(chatflow.id) const value = { chatflow, @@ -562,7 +561,7 @@ export class App { if (isRebuildNeeded()) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData try { - const result = await this.startChildProcess(chatflow, incomingInput, nodeToExecuteData) + const result = await this.startChildProcess(chatflow, chatId, incomingInput, nodeToExecuteData) return res.json(result) } catch (error) { @@ -570,7 +569,7 @@ export class App { } } else { try { - const result = await this.startChildProcess(chatflow, incomingInput) + const result = await this.startChildProcess(chatflow, chatId, incomingInput) return res.json(result) } catch (error) { return res.status(500).send(error) From 243eca348dfc1ba8fa5f89b0b3d43fa59632e51e Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 12 Jun 2023 22:41:45 +0100 Subject: [PATCH 090/398] disable stream when in child mode --- packages/server/src/utils/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index b993b958..3a176673 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -658,5 +658,10 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod } } - return isChatOrLLMsExist && endingNodeData.category === 'Chains' && !isVectorStoreFaiss(endingNodeData) + return ( + isChatOrLLMsExist && + endingNodeData.category === 'Chains' && + !isVectorStoreFaiss(endingNodeData) && + process.env.EXECUTION_MODE !== 'child' + ) } From 4fd73b86ed9ca198a560b821bb4cabf1ac1bdb8f Mon Sep 17 00:00:00 2001 From: atilgner Date: Tue, 13 Jun 2023 13:13:49 -0700 Subject: [PATCH 091/398] feat: added puppeteer loader node --- .../documentloaders/Puppeteer/Puppeteer.ts | 122 ++++++++++++++++++ .../documentloaders/Puppeteer/puppeteer.svg | 14 ++ packages/components/package.json | 5 +- 3 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts create mode 100644 packages/components/nodes/documentloaders/Puppeteer/puppeteer.svg diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts new file mode 100644 index 00000000..1331c736 --- /dev/null +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -0,0 +1,122 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { TextSplitter } from 'langchain/text_splitter' +import { PuppeteerWebBaseLoader } from 'langchain/document_loaders/web/puppeteer' +import { test } from 'linkifyjs' +import { getAvailableURLs } from '../../../src' + +class Puppeteer_DocumentLoaders implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Puppeteer Web Scraper' + this.name = 'puppeteerWebScraper' + this.type = 'Document' + this.icon = 'puppeteer.svg' + this.category = 'Document Loaders' + this.description = `Load data from webpages` + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'URL', + name: 'url', + type: 'string' + }, + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + label: 'Web Scrape for Relative Links', + name: 'webScrape', + type: 'boolean', + optional: true, + additionalParams: true + }, + { + label: 'Web Scrape Links Limit', + name: 'limit', + type: 'number', + default: 10, + optional: true, + additionalParams: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const textSplitter = nodeData.inputs?.textSplitter as TextSplitter + const metadata = nodeData.inputs?.metadata + const webScrape = nodeData.inputs?.webScrape as boolean + let limit = nodeData.inputs?.limit as string + + let url = nodeData.inputs?.url as string + url = url.trim() + if (!test(url)) { + throw new Error('Invalid URL') + } + + const puppeteerLoader = async (url: string): Promise => { + let docs = [] + const loader = new PuppeteerWebBaseLoader(url) + if (textSplitter) { + docs = await loader.loadAndSplit(textSplitter) + } else { + docs = await loader.load() + } + return docs + } + + let availableUrls: string[] + let docs = [] + if (webScrape) { + if (!limit) limit = '10' + availableUrls = await getAvailableURLs(url, parseInt(limit)) + for (let i = 0; i < availableUrls.length; i++) { + try { + docs.push(...(await puppeteerLoader(availableUrls[i]))) + } catch (error) { + console.error('Error loading url with puppeteer. URL: ', availableUrls[i], 'Error: ', error) + continue + } + } + } else { + docs = await puppeteerLoader(url) + } + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + let finaldocs = [] + for (const doc of docs) { + const newdoc = { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + finaldocs.push(newdoc) + } + return finaldocs + } + + return docs + } +} + +module.exports = { nodeClass: Puppeteer_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/Puppeteer/puppeteer.svg b/packages/components/nodes/documentloaders/Puppeteer/puppeteer.svg new file mode 100644 index 00000000..8477fc52 --- /dev/null +++ b/packages/components/nodes/documentloaders/Puppeteer/puppeteer.svg @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index a778ea8f..3bcac0d9 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -32,15 +32,16 @@ "faiss-node": "^0.2.1", "form-data": "^4.0.0", "graphql": "^16.6.0", + "html-to-text": "^9.0.5", "langchain": "^0.0.91", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", "node-fetch": "^2.6.11", "pdf-parse": "^1.1.1", + "puppeteer": "^20.7.1", "weaviate-ts-client": "^1.1.0", - "ws": "^8.9.0", - "html-to-text": "^9.0.5" + "ws": "^8.9.0" }, "devDependencies": { "@types/gulp": "4.0.9", From 86ab30510c3838075f8c49fc565245c9307a63c0 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 14 Jun 2023 01:05:37 +0100 Subject: [PATCH 092/398] add openai function calling support --- .../OpenAIFunctionAgent.ts | 69 ++++ .../agents/OpenAIFunctionAgent/openai.png | Bin 0 -> 3991 bytes .../nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts | 8 + .../nodes/tools/ZapierNLA/ZapierNLA.ts | 4 +- packages/components/package.json | 2 +- packages/server/marketplaces/API Agent.json | 24 ++ packages/server/marketplaces/AutoGPT.json | 8 + packages/server/marketplaces/BabyAGI.json | 8 + .../server/marketplaces/ChatGPTPlugin.json | 8 + .../marketplaces/Conversational Agent.json | 8 + .../Conversational Retrieval QA Chain.json | 8 + .../server/marketplaces/Github Repo QnA.json | 8 + .../marketplaces/Multi Prompt Chain.json | 8 + .../Multi Retrieval QA Chain.json | 8 + .../server/marketplaces/OpenAI Agent.json | 327 ++++++++++++++++++ .../Simple Conversation Chain.json | 8 + packages/server/marketplaces/Translator.json | 8 + packages/server/marketplaces/WebBrowser.json | 16 + packages/server/src/utils/index.ts | 2 +- .../ui/src/views/canvas/NodeInputHandler.js | 1 + 20 files changed, 529 insertions(+), 4 deletions(-) create mode 100644 packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts create mode 100644 packages/components/nodes/agents/OpenAIFunctionAgent/openai.png create mode 100644 packages/server/marketplaces/OpenAI Agent.json diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts new file mode 100644 index 00000000..9efe602f --- /dev/null +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -0,0 +1,69 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' +import { Tool } from 'langchain/tools' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { BaseLanguageModel } from 'langchain/base_language' +import { flatten } from 'lodash' + +class OpenAIFunctionAgent_Agents implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'OpenAI Function Agent' + this.name = 'openAIFunctionAgent' + this.type = 'AgentExecutor' + this.category = 'Agents' + this.icon = 'openai.png' + this.description = `An agent that uses OpenAI's Function Calling functionality to pick the tool and args to call` + this.baseClasses = [this.type, ...getBaseClasses(AgentExecutor)] + this.inputs = [ + { + label: 'Allowed Tools', + name: 'tools', + type: 'Tool', + list: true + }, + { + label: 'OpenAI Chat Model', + name: 'model', + description: + 'Only works with gpt-3.5-turbo-0613 and gpt-4-0613. Refer docs for more info', + type: 'BaseChatModel' + } + ] + } + + async init(nodeData: INodeData): Promise { + const model = nodeData.inputs?.model as BaseLanguageModel + let tools = nodeData.inputs?.tools as Tool[] + tools = flatten(tools) + + const executor = await initializeAgentExecutorWithOptions(tools, model, { + agentType: 'openai-functions', + verbose: process.env.DEBUG === 'true' ? true : false + }) + return executor + } + + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { + const executor = nodeData.instance as AgentExecutor + + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const result = await executor.run(input, [handler]) + return result + } else { + const result = await executor.run(input) + return result + } + } +} + +module.exports = { nodeClass: OpenAIFunctionAgent_Agents } diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/openai.png b/packages/components/nodes/agents/OpenAIFunctionAgent/openai.png new file mode 100644 index 0000000000000000000000000000000000000000..de08a05b28979826c4cc669c4899789763a938a1 GIT binary patch literal 3991 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 literal 0 HcmV?d00001 diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts index f07fce60..7ddb17af 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts @@ -43,6 +43,10 @@ class ChatOpenAI_ChatModels implements INode { label: 'gpt-4-32k-0314', name: 'gpt-4-32k-0314' }, + { + label: 'gpt-4-0613', + name: 'gpt-4-0613' + }, { label: 'gpt-3.5-turbo', name: 'gpt-3.5-turbo' @@ -50,6 +54,10 @@ class ChatOpenAI_ChatModels implements INode { { label: 'gpt-3.5-turbo-0301', name: 'gpt-3.5-turbo-0301' + }, + { + label: 'gpt-3.5-turbo-0613', + name: 'gpt-3.5-turbo-0613' } ], default: 'gpt-3.5-turbo', diff --git a/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts b/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts index 849f5946..d16e32e6 100644 --- a/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts +++ b/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts @@ -1,4 +1,4 @@ -import { ZapierNLAWrapper, ZapiterNLAWrapperParams } from 'langchain/tools' +import { ZapierNLAWrapper, ZapierNLAWrapperParams } from 'langchain/tools' import { INode, INodeData, INodeParams } from '../../../src/Interface' import { ZapierToolKit } from 'langchain/agents' @@ -32,7 +32,7 @@ class ZapierNLA_Tools implements INode { async init(nodeData: INodeData): Promise { const apiKey = nodeData.inputs?.apiKey as string - const obj: Partial = { + const obj: Partial = { apiKey } const zapier = new ZapierNLAWrapper(obj) diff --git a/packages/components/package.json b/packages/components/package.json index a778ea8f..2b4afb5a 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -32,7 +32,7 @@ "faiss-node": "^0.2.1", "form-data": "^4.0.0", "graphql": "^16.6.0", - "langchain": "^0.0.91", + "langchain": "^0.0.94", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", diff --git a/packages/server/marketplaces/API Agent.json b/packages/server/marketplaces/API Agent.json index 20e270af..ce4b7fb0 100644 --- a/packages/server/marketplaces/API Agent.json +++ b/packages/server/marketplaces/API Agent.json @@ -286,6 +286,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -293,6 +297,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", @@ -481,6 +489,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -488,6 +500,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", @@ -620,6 +636,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -627,6 +647,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/AutoGPT.json b/packages/server/marketplaces/AutoGPT.json index 4fd1cfdb..4bb68722 100644 --- a/packages/server/marketplaces/AutoGPT.json +++ b/packages/server/marketplaces/AutoGPT.json @@ -132,6 +132,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -139,6 +143,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/BabyAGI.json b/packages/server/marketplaces/BabyAGI.json index 797b574f..aefb9902 100644 --- a/packages/server/marketplaces/BabyAGI.json +++ b/packages/server/marketplaces/BabyAGI.json @@ -235,6 +235,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -242,6 +246,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/ChatGPTPlugin.json b/packages/server/marketplaces/ChatGPTPlugin.json index 648c94b7..4d5d1869 100644 --- a/packages/server/marketplaces/ChatGPTPlugin.json +++ b/packages/server/marketplaces/ChatGPTPlugin.json @@ -295,6 +295,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -302,6 +306,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/Conversational Agent.json b/packages/server/marketplaces/Conversational Agent.json index 635455ce..d6d61e86 100644 --- a/packages/server/marketplaces/Conversational Agent.json +++ b/packages/server/marketplaces/Conversational Agent.json @@ -42,6 +42,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -49,6 +53,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/Conversational Retrieval QA Chain.json index 4d470ab2..4d8b2881 100644 --- a/packages/server/marketplaces/Conversational Retrieval QA Chain.json +++ b/packages/server/marketplaces/Conversational Retrieval QA Chain.json @@ -356,6 +356,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -363,6 +367,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/Github Repo QnA.json b/packages/server/marketplaces/Github Repo QnA.json index a9294eca..852c9741 100644 --- a/packages/server/marketplaces/Github Repo QnA.json +++ b/packages/server/marketplaces/Github Repo QnA.json @@ -373,6 +373,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -380,6 +384,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/Multi Prompt Chain.json b/packages/server/marketplaces/Multi Prompt Chain.json index a9c41a76..d4aa4550 100644 --- a/packages/server/marketplaces/Multi Prompt Chain.json +++ b/packages/server/marketplaces/Multi Prompt Chain.json @@ -302,6 +302,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -309,6 +313,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/Multi Retrieval QA Chain.json b/packages/server/marketplaces/Multi Retrieval QA Chain.json index 8f2ca89e..cdb468d1 100644 --- a/packages/server/marketplaces/Multi Retrieval QA Chain.json +++ b/packages/server/marketplaces/Multi Retrieval QA Chain.json @@ -299,6 +299,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -306,6 +310,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/OpenAI Agent.json b/packages/server/marketplaces/OpenAI Agent.json new file mode 100644 index 00000000..77192eb1 --- /dev/null +++ b/packages/server/marketplaces/OpenAI Agent.json @@ -0,0 +1,327 @@ +{ + "description": "An agent that uses OpenAI's Function Calling functionality to pick the tool and args to call", + "nodes": [ + { + "width": 300, + "height": 524, + "id": "chatOpenAI_0", + "position": { + "x": 373.8366297840716, + "y": 448.58765780622326 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "BaseLangChain", "Serializable"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "OpenAI Api Key", + "name": "openAIApiKey", + "type": "password", + "id": "chatOpenAI_0-input-openAIApiKey-password" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-0314", + "name": "gpt-4-0314" + }, + { + "label": "gpt-4-32k-0314", + "name": "gpt-4-32k-0314" + }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-0301", + "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_0-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.9, + "optional": true, + "id": "chatOpenAI_0-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "gpt-3.5-turbo-0613", + "temperature": 0.9, + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain|Serializable", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | BaseLangChain | Serializable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 373.8366297840716, + "y": 448.58765780622326 + }, + "dragging": false + }, + { + "width": 300, + "height": 280, + "id": "openAIFunctionAgent_0", + "position": { + "x": 1084.5405852317417, + "y": 384.4653768834282 + }, + "type": "customNode", + "data": { + "id": "openAIFunctionAgent_0", + "label": "OpenAI Function Agent", + "name": "openAIFunctionAgent", + "type": "AgentExecutor", + "baseClasses": ["AgentExecutor", "BaseChain", "BaseLangChain", "Serializable"], + "category": "Agents", + "description": "An agent that uses OpenAI's Function Calling functionality to pick the tool and args to call", + "inputParams": [], + "inputAnchors": [ + { + "label": "Allowed Tools", + "name": "tools", + "type": "Tool", + "list": true, + "id": "openAIFunctionAgent_0-input-tools-Tool" + }, + { + "label": "OpenAI Chat Model", + "name": "model", + "description": "Only works with gpt-3.5-turbo-0613 and gpt-4-0613. Refer docs for more info", + "type": "BaseChatModel", + "id": "openAIFunctionAgent_0-input-model-BaseChatModel" + } + ], + "inputs": { + "tools": ["{{calculator_0.data.instance}}", "{{serper_0.data.instance}}"], + "model": "{{chatOpenAI_0.data.instance}}" + }, + "outputAnchors": [ + { + "id": "openAIFunctionAgent_0-output-openAIFunctionAgent-AgentExecutor|BaseChain|BaseLangChain|Serializable", + "name": "openAIFunctionAgent", + "label": "AgentExecutor", + "type": "AgentExecutor | BaseChain | BaseLangChain | Serializable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1084.5405852317417, + "y": 384.4653768834282 + }, + "dragging": false + }, + { + "width": 300, + "height": 278, + "id": "serper_0", + "position": { + "x": 691.7580226065319, + "y": 34.00444633899792 + }, + "type": "customNode", + "data": { + "id": "serper_0", + "label": "Serper", + "name": "serper", + "type": "Serper", + "baseClasses": ["Serper", "Tool", "StructuredTool", "BaseLangChain", "Serializable"], + "category": "Tools", + "description": "Wrapper around Serper.dev - Google Search API", + "inputParams": [ + { + "label": "Serper Api Key", + "name": "apiKey", + "type": "password", + "id": "serper_0-input-apiKey-password" + } + ], + "inputAnchors": [], + "inputs": {}, + "outputAnchors": [ + { + "id": "serper_0-output-serper-Serper|Tool|StructuredTool|BaseLangChain|Serializable", + "name": "serper", + "label": "Serper", + "type": "Serper | Tool | StructuredTool | BaseLangChain | Serializable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 691.7580226065319, + "y": 34.00444633899792 + }, + "dragging": false + }, + { + "width": 300, + "height": 143, + "id": "calculator_0", + "position": { + "x": 341.63347110886497, + "y": 261.6753474034481 + }, + "type": "customNode", + "data": { + "id": "calculator_0", + "label": "Calculator", + "name": "calculator", + "type": "Calculator", + "baseClasses": ["Calculator", "Tool", "StructuredTool", "BaseLangChain", "Serializable"], + "category": "Tools", + "description": "Perform calculations on response", + "inputParams": [], + "inputAnchors": [], + "inputs": {}, + "outputAnchors": [ + { + "id": "calculator_0-output-calculator-Calculator|Tool|StructuredTool|BaseLangChain|Serializable", + "name": "calculator", + "label": "Calculator", + "type": "Calculator | Tool | StructuredTool | BaseLangChain | Serializable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 341.63347110886497, + "y": 261.6753474034481 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain|Serializable", + "target": "openAIFunctionAgent_0", + "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain|Serializable-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-BaseChatModel", + "data": { + "label": "" + } + }, + { + "source": "calculator_0", + "sourceHandle": "calculator_0-output-calculator-Calculator|Tool|StructuredTool|BaseLangChain|Serializable", + "target": "openAIFunctionAgent_0", + "targetHandle": "openAIFunctionAgent_0-input-tools-Tool", + "type": "buttonedge", + "id": "calculator_0-calculator_0-output-calculator-Calculator|Tool|StructuredTool|BaseLangChain|Serializable-openAIFunctionAgent_0-openAIFunctionAgent_0-input-tools-Tool", + "data": { + "label": "" + } + }, + { + "source": "serper_0", + "sourceHandle": "serper_0-output-serper-Serper|Tool|StructuredTool|BaseLangChain|Serializable", + "target": "openAIFunctionAgent_0", + "targetHandle": "openAIFunctionAgent_0-input-tools-Tool", + "type": "buttonedge", + "id": "serper_0-serper_0-output-serper-Serper|Tool|StructuredTool|BaseLangChain|Serializable-openAIFunctionAgent_0-openAIFunctionAgent_0-input-tools-Tool", + "data": { + "label": "" + } + } + ] +} diff --git a/packages/server/marketplaces/Simple Conversation Chain.json b/packages/server/marketplaces/Simple Conversation Chain.json index f3decc85..93cf06d7 100644 --- a/packages/server/marketplaces/Simple Conversation Chain.json +++ b/packages/server/marketplaces/Simple Conversation Chain.json @@ -42,6 +42,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -49,6 +53,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/Translator.json b/packages/server/marketplaces/Translator.json index 6e36f943..5a57a8f5 100644 --- a/packages/server/marketplaces/Translator.json +++ b/packages/server/marketplaces/Translator.json @@ -112,6 +112,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -119,6 +123,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/WebBrowser.json b/packages/server/marketplaces/WebBrowser.json index 7d7ff357..d533c75f 100644 --- a/packages/server/marketplaces/WebBrowser.json +++ b/packages/server/marketplaces/WebBrowser.json @@ -42,6 +42,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -49,6 +53,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", @@ -318,6 +326,10 @@ "label": "gpt-4-32k-0314", "name": "gpt-4-32k-0314" }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" @@ -325,6 +337,10 @@ { "label": "gpt-3.5-turbo-0301", "name": "gpt-3.5-turbo-0301" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 3a176673..8e19bf5b 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -660,7 +660,7 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod return ( isChatOrLLMsExist && - endingNodeData.category === 'Chains' && + (endingNodeData.category === 'Chains' || endingNodeData.name === 'openAIFunctionAgent') && !isVectorStoreFaiss(endingNodeData) && process.env.EXECUTION_MODE !== 'child' ) diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 1dc656e8..d58f7a66 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -95,6 +95,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA {inputAnchor.label} {!inputAnchor.optional &&  *} + {inputAnchor.description && } From 67dad03493380e5a30f9d5865593b183e23344c8 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 14 Jun 2023 01:32:09 +0100 Subject: [PATCH 093/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.2.1?= =?UTF-8?q?3=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 2b4afb5a..207d3e89 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.12", + "version": "1.2.13", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From 539e00ff2551a5ee694e43a245c212f5f2ecbe38 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 14 Jun 2023 01:33:20 +0100 Subject: [PATCH 094/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.2.11=20rele?= =?UTF-8?q?ase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 0727fc87..af9ac5e0 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.10", + "version": "1.2.11", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From 92cd760b16d633c4e4eb87628f16bdc22bd85240 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 14 Jun 2023 01:33:38 +0100 Subject: [PATCH 095/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.2.12=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b077c7ec..fc08e450 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.11", + "version": "1.2.12", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index cdfb11c1..e7a8bc61 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.11", + "version": "1.2.12", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 8f4b5ba3f62463a0fa1c8a23f98b6b2242d2665f Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 14 Jun 2023 12:34:25 +0100 Subject: [PATCH 096/398] pdf loader add legacy option --- .../components/nodes/documentloaders/Pdf/Pdf.ts | 16 ++++++++++++++-- packages/components/package.json | 5 +++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/documentloaders/Pdf/Pdf.ts b/packages/components/nodes/documentloaders/Pdf/Pdf.ts index bc36f8cb..5bee0e65 100644 --- a/packages/components/nodes/documentloaders/Pdf/Pdf.ts +++ b/packages/components/nodes/documentloaders/Pdf/Pdf.ts @@ -49,6 +49,13 @@ class Pdf_DocumentLoaders implements INode { ], default: 'perPage' }, + { + label: 'Use Legacy Build', + name: 'legacyBuild', + type: 'boolean', + optional: true, + additionalParams: true + }, { label: 'Metadata', name: 'metadata', @@ -64,6 +71,7 @@ class Pdf_DocumentLoaders implements INode { const pdfFileBase64 = nodeData.inputs?.pdfFile as string const usage = nodeData.inputs?.usage as string const metadata = nodeData.inputs?.metadata + const legacyBuild = nodeData.inputs?.legacyBuild as boolean let alldocs = [] let files: string[] = [] @@ -82,7 +90,8 @@ class Pdf_DocumentLoaders implements INode { const loader = new PDFLoader(new Blob([bf]), { splitPages: false, // @ts-ignore - pdfjs: () => import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js') + pdfjs: () => + legacyBuild ? import('pdfjs-dist/legacy/build/pdf.js') : import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js') }) if (textSplitter) { const docs = await loader.loadAndSplit(textSplitter) @@ -93,7 +102,10 @@ class Pdf_DocumentLoaders implements INode { } } else { // @ts-ignore - const loader = new PDFLoader(new Blob([bf]), { pdfjs: () => import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js') }) + const loader = new PDFLoader(new Blob([bf]), { + pdfjs: () => + legacyBuild ? import('pdfjs-dist/legacy/build/pdf.js') : import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js') + }) if (textSplitter) { const docs = await loader.loadAndSplit(textSplitter) alldocs.push(...docs) diff --git a/packages/components/package.json b/packages/components/package.json index 207d3e89..07275b08 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -32,15 +32,16 @@ "faiss-node": "^0.2.1", "form-data": "^4.0.0", "graphql": "^16.6.0", + "html-to-text": "^9.0.5", "langchain": "^0.0.94", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", "node-fetch": "^2.6.11", "pdf-parse": "^1.1.1", + "pdfjs-dist": "^3.7.107", "weaviate-ts-client": "^1.1.0", - "ws": "^8.9.0", - "html-to-text": "^9.0.5" + "ws": "^8.9.0" }, "devDependencies": { "@types/gulp": "4.0.9", From 97ea2c405e0965180a7aa1b259855191c5bb5c5c Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 14 Jun 2023 13:30:07 +0100 Subject: [PATCH 097/398] yarn lint fix --- packages/components/nodes/documentloaders/Pdf/Pdf.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/documentloaders/Pdf/Pdf.ts b/packages/components/nodes/documentloaders/Pdf/Pdf.ts index 5bee0e65..ddb7edb8 100644 --- a/packages/components/nodes/documentloaders/Pdf/Pdf.ts +++ b/packages/components/nodes/documentloaders/Pdf/Pdf.ts @@ -89,8 +89,8 @@ class Pdf_DocumentLoaders implements INode { if (usage === 'perFile') { const loader = new PDFLoader(new Blob([bf]), { splitPages: false, - // @ts-ignore pdfjs: () => + // @ts-ignore legacyBuild ? import('pdfjs-dist/legacy/build/pdf.js') : import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js') }) if (textSplitter) { @@ -101,9 +101,9 @@ class Pdf_DocumentLoaders implements INode { alldocs.push(...docs) } } else { - // @ts-ignore const loader = new PDFLoader(new Blob([bf]), { pdfjs: () => + // @ts-ignore legacyBuild ? import('pdfjs-dist/legacy/build/pdf.js') : import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js') }) if (textSplitter) { From 74939c187a583bd434bb14cfa75b2497ec0f8a60 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 14 Jun 2023 13:39:38 +0100 Subject: [PATCH 098/398] add fixes --- .../nodes/chains/MultiPromptChain/MultiPromptChain.ts | 2 +- .../nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts index c74e3257..659641f8 100644 --- a/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts +++ b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts @@ -61,7 +61,7 @@ class MultiPromptChain_Chains implements INode { const obj = { input } if (options.socketIO && options.socketIOClientId) { - const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) const res = await chain.call(obj, [handler]) return res?.text } else { diff --git a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts index b17125c2..b18ac867 100644 --- a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts +++ b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts @@ -61,7 +61,7 @@ class MultiRetrievalQAChain_Chains implements INode { const obj = { input } if (options.socketIO && options.socketIOClientId) { - const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) const res = await chain.call(obj, [handler]) return res?.text } else { From e50c065acad8a552832180b12e63af687f715efc Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 14 Jun 2023 15:47:33 +0100 Subject: [PATCH 099/398] update multi chains --- .../MultiPromptChain/MultiPromptChain.ts | 9 ++++--- .../MultiRetrievalQAChain.ts | 26 ++++++++++++++----- .../Multi Retrieval QA Chain.json | 9 ++++++- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts index 659641f8..189f41f7 100644 --- a/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts +++ b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts @@ -49,9 +49,12 @@ class MultiPromptChain_Chains implements INode { promptTemplates.push(prompt.systemMessage) } - const chain = MultiPromptChain.fromPrompts(model, promptNames, promptDescriptions, promptTemplates, undefined, { - verbose: process.env.DEBUG === 'true' ? true : false - } as any) + const chain = MultiPromptChain.fromLLMAndPrompts(model, { + promptNames, + promptDescriptions, + promptTemplates, + llmChainOpts: { verbose: process.env.DEBUG === 'true' ? true : false } + }) return chain } diff --git a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts index b18ac867..b3575a93 100644 --- a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts +++ b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts @@ -32,6 +32,12 @@ class MultiRetrievalQAChain_Chains implements INode { name: 'vectorStoreRetriever', type: 'VectorStoreRetriever', list: true + }, + { + label: 'Return Source Documents', + name: 'returnSourceDocuments', + type: 'boolean', + optional: true } ] } @@ -39,6 +45,8 @@ class MultiRetrievalQAChain_Chains implements INode { async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as BaseLanguageModel const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as VectorStoreRetriever[] + const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean + const retrieverNames = [] const retrieverDescriptions = [] const retrievers = [] @@ -49,23 +57,29 @@ class MultiRetrievalQAChain_Chains implements INode { retrievers.push(vs.vectorStore.asRetriever((vs.vectorStore as any).k ?? 4)) } - const chain = MultiRetrievalQAChain.fromRetrievers(model, retrieverNames, retrieverDescriptions, retrievers, undefined, { - verbose: process.env.DEBUG === 'true' ? true : false - } as any) - + const chain = MultiRetrievalQAChain.fromLLMAndRetrievers(model, { + retrieverNames, + retrieverDescriptions, + retrievers, + retrievalQAChainOpts: { verbose: process.env.DEBUG === 'true' ? true : false, returnSourceDocuments } + }) return chain } - async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = nodeData.instance as MultiRetrievalQAChain + const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean + const obj = { input } if (options.socketIO && options.socketIOClientId) { - const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2, returnSourceDocuments) const res = await chain.call(obj, [handler]) + if (res.text && res.sourceDocuments) return res return res?.text } else { const res = await chain.call(obj) + if (res.text && res.sourceDocuments) return res return res?.text } } diff --git a/packages/server/marketplaces/Multi Retrieval QA Chain.json b/packages/server/marketplaces/Multi Retrieval QA Chain.json index cdb468d1..f3cd1fcc 100644 --- a/packages/server/marketplaces/Multi Retrieval QA Chain.json +++ b/packages/server/marketplaces/Multi Retrieval QA Chain.json @@ -84,7 +84,14 @@ "baseClasses": ["MultiRetrievalQAChain", "MultiRouteChain", "BaseChain", "BaseLangChain"], "category": "Chains", "description": "QA Chain that automatically picks an appropriate vector store from multiple retrievers", - "inputParams": [], + "inputParams": [ + { + "label": "Return Source Documents", + "name": "returnSourceDocuments", + "type": "boolean", + "optional": true + } + ], "inputAnchors": [ { "label": "Language Model", From 8fa04285094cd8c7683f8851dc73481a6c8adb77 Mon Sep 17 00:00:00 2001 From: Govind Kumar Date: Wed, 14 Jun 2023 20:29:38 +0530 Subject: [PATCH 100/398] Feature: Subtitles Loader Integration --- .../documentloaders/Subtitles/Subtitles.ts | 95 +++++++++++++++++++ .../Subtitles/subtitlesFile.svg | 1 + packages/components/package.json | 5 +- 3 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 packages/components/nodes/documentloaders/Subtitles/Subtitles.ts create mode 100644 packages/components/nodes/documentloaders/Subtitles/subtitlesFile.svg diff --git a/packages/components/nodes/documentloaders/Subtitles/Subtitles.ts b/packages/components/nodes/documentloaders/Subtitles/Subtitles.ts new file mode 100644 index 00000000..0f60e151 --- /dev/null +++ b/packages/components/nodes/documentloaders/Subtitles/Subtitles.ts @@ -0,0 +1,95 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { TextSplitter } from 'langchain/text_splitter' +import { SRTLoader } from 'langchain/document_loaders/fs/srt' + +class Subtitles_DocumentLoaders implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Subtitles File' + this.name = 'subtitlesFile' + this.type = 'Document' + this.icon = 'subtitlesFile.svg' + this.category = 'Document Loaders' + this.description = `Load data from subtitles files` + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Subtitles File', + name: 'subtitlesFile', + type: 'file', + fileType: '.srt' + }, + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const textSplitter = nodeData.inputs?.textSplitter as TextSplitter + const subtitlesFileBase64 = nodeData.inputs?.subtitlesFile as string + const metadata = nodeData.inputs?.metadata + + let alldocs = [] + let files: string[] = [] + + if (subtitlesFileBase64.startsWith('[') && subtitlesFileBase64.endsWith(']')) { + files = JSON.parse(subtitlesFileBase64) + } else { + files = [subtitlesFileBase64] + } + + for (const file of files) { + const splitDataURI = file.split(',') + splitDataURI.pop() + const bf = Buffer.from(splitDataURI.pop() || '', 'base64') + const blob = new Blob([bf]) + const loader = new SRTLoader(blob) + + if (textSplitter) { + const docs = await loader.loadAndSplit(textSplitter) + alldocs.push(...docs) + } else { + const docs = await loader.load() + alldocs.push(...docs) + } + } + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + let finaldocs = [] + for (const doc of alldocs) { + const newdoc = { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + finaldocs.push(newdoc) + } + return finaldocs + } + return alldocs + } +} + +module.exports = { nodeClass: Subtitles_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/Subtitles/subtitlesFile.svg b/packages/components/nodes/documentloaders/Subtitles/subtitlesFile.svg new file mode 100644 index 00000000..a6ee925b --- /dev/null +++ b/packages/components/nodes/documentloaders/Subtitles/subtitlesFile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index 207d3e89..30a6b407 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -32,15 +32,16 @@ "faiss-node": "^0.2.1", "form-data": "^4.0.0", "graphql": "^16.6.0", + "html-to-text": "^9.0.5", "langchain": "^0.0.94", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", "node-fetch": "^2.6.11", "pdf-parse": "^1.1.1", + "srt-parser-2": "^1.2.3", "weaviate-ts-client": "^1.1.0", - "ws": "^8.9.0", - "html-to-text": "^9.0.5" + "ws": "^8.9.0" }, "devDependencies": { "@types/gulp": "4.0.9", From 1bb65e78670bb56223035bb6d0cb35f2d1ab4ca7 Mon Sep 17 00:00:00 2001 From: disflyer Date: Wed, 14 Jun 2023 22:20:17 +0800 Subject: [PATCH 101/398] feat: add playwright nodes --- .../documentloaders/Playwright/Playwright.ts | 117 ++++++++++++++++++ .../documentloaders/Playwright/playwright.svg | 9 ++ packages/components/package.json | 1 + 3 files changed, 127 insertions(+) create mode 100644 packages/components/nodes/documentloaders/Playwright/Playwright.ts create mode 100644 packages/components/nodes/documentloaders/Playwright/playwright.svg diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts new file mode 100644 index 00000000..6b7790af --- /dev/null +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -0,0 +1,117 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { TextSplitter } from 'langchain/text_splitter' +import { PlaywrightWebBaseLoader } from 'langchain/document_loaders/web/playwright' +import { test } from 'linkifyjs' +import { getAvailableURLs } from '../../../src' + +class Playwright_DocumentLoaders implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Playwright Web Scraper' + this.name = 'playwrightWebScraper' + this.type = 'Document' + this.icon = 'playwright.svg' + this.category = 'Document Loaders' + this.description = `Load data from webpages` + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'URL', + name: 'url', + type: 'string' + }, + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + label: 'Web Scrap for Relative Links', + name: 'webScrap', + type: 'boolean', + optional: true, + additionalParams: true + }, + { + label: 'Web Scrap Links Limit', + name: 'limit', + type: 'number', + default: 10, + optional: true, + additionalParams: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const textSplitter = nodeData.inputs?.textSplitter as TextSplitter + const metadata = nodeData.inputs?.metadata + const webScrap = nodeData.inputs?.webScrap as boolean + let limit = nodeData.inputs?.limit as string + + let url = nodeData.inputs?.url as string + url = url.trim() + if (!test(url)) { + throw new Error('Invalid URL') + } + + const playwrightLoader = async (url: string): Promise => { + let docs = [] + const loader = new PlaywrightWebBaseLoader(url) + if (textSplitter) { + docs = await loader.loadAndSplit(textSplitter) + } else { + docs = await loader.load() + } + return docs + } + + let availableUrls: string[] + let docs = [] + if (webScrap) { + if (!limit) limit = '10' + availableUrls = await getAvailableURLs(url, parseInt(limit)) + for (let i = 0; i < availableUrls.length; i++) { + docs.push(...(await playwrightLoader(availableUrls[i]))) + } + } else { + docs = await playwrightLoader(url) + } + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + let finaldocs = [] + for (const doc of docs) { + const newdoc = { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + finaldocs.push(newdoc) + } + return finaldocs + } + + return docs + } +} + +module.exports = { nodeClass: Playwright_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/Playwright/playwright.svg b/packages/components/nodes/documentloaders/Playwright/playwright.svg new file mode 100644 index 00000000..0992832d --- /dev/null +++ b/packages/components/nodes/documentloaders/Playwright/playwright.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index 207d3e89..cbea948a 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -38,6 +38,7 @@ "moment": "^2.29.3", "node-fetch": "^2.6.11", "pdf-parse": "^1.1.1", + "playwright": "^1.35.0", "weaviate-ts-client": "^1.1.0", "ws": "^8.9.0", "html-to-text": "^9.0.5" From a05e9921fa1b7a9b108d7007a652a537894f6912 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 14 Jun 2023 19:38:30 +0100 Subject: [PATCH 102/398] add packages to Dockerfile --- Dockerfile | 2 ++ docker/Dockerfile | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index fc76cd00..e9470c31 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,8 @@ FROM node:18-alpine RUN apk add --update libc6-compat python3 make g++ +# needed for pdfjs-dist +RUN apk add --no-cache build-base cairo-dev pango-dev WORKDIR /usr/src/packages diff --git a/docker/Dockerfile b/docker/Dockerfile index e4bf704a..15c4e0ac 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,6 +4,8 @@ USER root RUN apk add --no-cache git RUN apk add --no-cache python3 py3-pip make g++ +# needed for pdfjs-dist +RUN apk add --no-cache build-base cairo-dev pango-dev # You can install a specific version like: flowise@1.0.0 RUN npm install -g flowise From 2e43b233a5adfd81fb8a11e7f57f8eca63a9af9c Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 14 Jun 2023 19:48:44 +0100 Subject: [PATCH 103/398] add 16k model --- .../nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts | 28 ++++--- packages/server/marketplaces/API Agent.json | 84 +++++++++++-------- packages/server/marketplaces/AutoGPT.json | 28 ++++--- packages/server/marketplaces/BabyAGI.json | 28 ++++--- .../server/marketplaces/ChatGPTPlugin.json | 28 ++++--- .../marketplaces/Conversational Agent.json | 28 ++++--- .../Conversational Retrieval QA Chain.json | 28 ++++--- .../server/marketplaces/Github Repo QnA.json | 28 ++++--- .../marketplaces/Multi Prompt Chain.json | 28 ++++--- .../Multi Retrieval QA Chain.json | 28 ++++--- .../server/marketplaces/OpenAI Agent.json | 28 ++++--- .../Simple Conversation Chain.json | 28 ++++--- packages/server/marketplaces/Translator.json | 28 ++++--- packages/server/marketplaces/WebBrowser.json | 56 +++++++------ 14 files changed, 272 insertions(+), 204 deletions(-) diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts index 7ddb17af..26f54db8 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts @@ -35,29 +35,33 @@ class ChatOpenAI_ChatModels implements INode { label: 'gpt-4', name: 'gpt-4' }, - { - label: 'gpt-4-0314', - name: 'gpt-4-0314' - }, - { - label: 'gpt-4-32k-0314', - name: 'gpt-4-32k-0314' - }, { label: 'gpt-4-0613', name: 'gpt-4-0613' }, + { + label: 'gpt-4-32k', + name: 'gpt-4-32k' + }, + { + label: 'gpt-4-32k-0613', + name: 'gpt-4-32k-0613' + }, { label: 'gpt-3.5-turbo', name: 'gpt-3.5-turbo' }, - { - label: 'gpt-3.5-turbo-0301', - name: 'gpt-3.5-turbo-0301' - }, { label: 'gpt-3.5-turbo-0613', name: 'gpt-3.5-turbo-0613' + }, + { + label: 'gpt-3.5-turbo-16k', + name: 'gpt-3.5-turbo-16k' + }, + { + label: 'gpt-3.5-turbo-16k-0613', + name: 'gpt-3.5-turbo-16k-0613' } ], default: 'gpt-3.5-turbo', diff --git a/packages/server/marketplaces/API Agent.json b/packages/server/marketplaces/API Agent.json index ce4b7fb0..8ca79926 100644 --- a/packages/server/marketplaces/API Agent.json +++ b/packages/server/marketplaces/API Agent.json @@ -278,29 +278,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", @@ -481,29 +485,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", @@ -628,29 +636,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/AutoGPT.json b/packages/server/marketplaces/AutoGPT.json index 4bb68722..47926272 100644 --- a/packages/server/marketplaces/AutoGPT.json +++ b/packages/server/marketplaces/AutoGPT.json @@ -124,29 +124,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/BabyAGI.json b/packages/server/marketplaces/BabyAGI.json index aefb9902..572d73f1 100644 --- a/packages/server/marketplaces/BabyAGI.json +++ b/packages/server/marketplaces/BabyAGI.json @@ -227,29 +227,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/ChatGPTPlugin.json b/packages/server/marketplaces/ChatGPTPlugin.json index 4d5d1869..76964a09 100644 --- a/packages/server/marketplaces/ChatGPTPlugin.json +++ b/packages/server/marketplaces/ChatGPTPlugin.json @@ -287,29 +287,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/Conversational Agent.json b/packages/server/marketplaces/Conversational Agent.json index d6d61e86..b0295fd6 100644 --- a/packages/server/marketplaces/Conversational Agent.json +++ b/packages/server/marketplaces/Conversational Agent.json @@ -34,29 +34,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/Conversational Retrieval QA Chain.json index 4d8b2881..ed190cdc 100644 --- a/packages/server/marketplaces/Conversational Retrieval QA Chain.json +++ b/packages/server/marketplaces/Conversational Retrieval QA Chain.json @@ -348,29 +348,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/Github Repo QnA.json b/packages/server/marketplaces/Github Repo QnA.json index 852c9741..92867957 100644 --- a/packages/server/marketplaces/Github Repo QnA.json +++ b/packages/server/marketplaces/Github Repo QnA.json @@ -365,29 +365,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/Multi Prompt Chain.json b/packages/server/marketplaces/Multi Prompt Chain.json index d4aa4550..339476e7 100644 --- a/packages/server/marketplaces/Multi Prompt Chain.json +++ b/packages/server/marketplaces/Multi Prompt Chain.json @@ -294,29 +294,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/Multi Retrieval QA Chain.json b/packages/server/marketplaces/Multi Retrieval QA Chain.json index f3cd1fcc..04df2fee 100644 --- a/packages/server/marketplaces/Multi Retrieval QA Chain.json +++ b/packages/server/marketplaces/Multi Retrieval QA Chain.json @@ -298,29 +298,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/OpenAI Agent.json b/packages/server/marketplaces/OpenAI Agent.json index 77192eb1..7e685546 100644 --- a/packages/server/marketplaces/OpenAI Agent.json +++ b/packages/server/marketplaces/OpenAI Agent.json @@ -34,29 +34,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/Simple Conversation Chain.json b/packages/server/marketplaces/Simple Conversation Chain.json index 93cf06d7..f7e654db 100644 --- a/packages/server/marketplaces/Simple Conversation Chain.json +++ b/packages/server/marketplaces/Simple Conversation Chain.json @@ -34,29 +34,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/Translator.json b/packages/server/marketplaces/Translator.json index 5a57a8f5..fda400e2 100644 --- a/packages/server/marketplaces/Translator.json +++ b/packages/server/marketplaces/Translator.json @@ -104,29 +104,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", diff --git a/packages/server/marketplaces/WebBrowser.json b/packages/server/marketplaces/WebBrowser.json index d533c75f..f87fe07e 100644 --- a/packages/server/marketplaces/WebBrowser.json +++ b/packages/server/marketplaces/WebBrowser.json @@ -34,29 +34,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", @@ -318,29 +322,33 @@ "label": "gpt-4", "name": "gpt-4" }, - { - "label": "gpt-4-0314", - "name": "gpt-4-0314" - }, - { - "label": "gpt-4-32k-0314", - "name": "gpt-4-32k-0314" - }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { - "label": "gpt-3.5-turbo-0301", - "name": "gpt-3.5-turbo-0301" - }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", From 919d04930616c12065621f5acb01c7f4c33bb359 Mon Sep 17 00:00:00 2001 From: atilgner Date: Wed, 14 Jun 2023 12:09:31 -0700 Subject: [PATCH 104/398] fix: accidentally deleted html to text --- packages/components/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 0aa161f8..b0a58161 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -40,7 +40,8 @@ "pdf-parse": "^1.1.1", "puppeteer": "^20.7.1", "weaviate-ts-client": "^1.1.0", - "ws": "^8.9.0" + "ws": "^8.9.0", + "html-to-text": "^9.0.5" }, "devDependencies": { "@types/gulp": "4.0.9", From b16b8c62655392dba1912d75e653c3949ac7dd82 Mon Sep 17 00:00:00 2001 From: Vikram Segta Date: Thu, 15 Jun 2023 01:16:51 +0530 Subject: [PATCH 105/398] bug/Chat multiline fix --- packages/ui/src/views/chatmessage/ChatMessage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 5021cd9b..f73a2ba0 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -366,6 +366,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { value={userInput} onChange={onChange} multiline={true} + maxRows={2} endAdornment={ From 74993dbca85f2697dac20620ea217e582dd432e1 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Wed, 14 Jun 2023 23:38:25 +0100 Subject: [PATCH 106/398] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b7318507..13531280 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -23,9 +23,13 @@ A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. -**Setup** +**Flow** +If applicable, add exported flow in order to help replicating the problem. -- OS: [e.g. iOS, Windows, Linux] +**Setup** +- Installation [e.g. docker, `npx flowise start`, `yarn start`] +- Flowise Version [e.g. 1.2.11] +- OS: [e.g. macOS, Windows, Linux] - Browser [e.g. chrome, safari] **Additional context** From 17318ad7bbeda768ea0b6a47dab59292f36a0888 Mon Sep 17 00:00:00 2001 From: Pravesh-mansharamani Date: Thu, 15 Jun 2023 07:27:07 +0530 Subject: [PATCH 107/398] Added the LatexTextSplitter --- .../LatexTextSplitter/LatexTextSplitter.ts | 52 +++++++++++++++++++ .../LatexTextSplitter/latexTextSplitter.svg | 6 +++ 2 files changed, 58 insertions(+) create mode 100644 packages/components/nodes/textsplitters/LatexTextSplitter/LatexTextSplitter.ts create mode 100644 packages/components/nodes/textsplitters/LatexTextSplitter/latexTextSplitter.svg diff --git a/packages/components/nodes/textsplitters/LatexTextSplitter/LatexTextSplitter.ts b/packages/components/nodes/textsplitters/LatexTextSplitter/LatexTextSplitter.ts new file mode 100644 index 00000000..d6568d07 --- /dev/null +++ b/packages/components/nodes/textsplitters/LatexTextSplitter/LatexTextSplitter.ts @@ -0,0 +1,52 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { RecursiveCharacterTextSplitter, RecursiveCharacterTextSplitterParams } from 'langchain/text_splitter' + +class LatexTextSplitter_TextSplitters implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + constructor() { + this.label = 'Latex Text Splitter' + this.name = 'latexTextSplitter' + this.type = 'LatexTextSplitter' + this.icon = 'latexTextSplitter.svg' + this.category = 'Text Splitters' + this.description = `Split documents along Latex headings, headlines, enumerations and more.` + this.baseClasses = [this.type, ...getBaseClasses(RecursiveCharacterTextSplitter)] + this.inputs = [ + { + label: 'Chunk Size', + name: 'chunkSize', + type: 'number', + default: 1000, + optional: true + }, + { + label: 'Chunk Overlap', + name: 'chunkOverlap', + type: 'number', + optional: true + } + ] + } + async init(nodeData: INodeData): Promise { + const chunkSize = nodeData.inputs?.chunkSize as string + const chunkOverlap = nodeData.inputs?.chunkOverlap as string + + const obj = {} as RecursiveCharacterTextSplitterParams + + if (chunkSize) obj.chunkSize = parseInt(chunkSize, 10) + if (chunkOverlap) obj.chunkOverlap = parseInt(chunkOverlap, 10) + + const splitter = RecursiveCharacterTextSplitter.fromLanguage('latex', obj) + + return splitter + } +} +module.exports = { nodeClass: LatexTextSplitter_TextSplitters } diff --git a/packages/components/nodes/textsplitters/LatexTextSplitter/latexTextSplitter.svg b/packages/components/nodes/textsplitters/LatexTextSplitter/latexTextSplitter.svg new file mode 100644 index 00000000..ae9d89be --- /dev/null +++ b/packages/components/nodes/textsplitters/LatexTextSplitter/latexTextSplitter.svg @@ -0,0 +1,6 @@ + + + + + + From 92bbc938c914b8c4717b179ad9bfa5df2e43c010 Mon Sep 17 00:00:00 2001 From: Vikram Segta Date: Thu, 15 Jun 2023 10:41:06 +0530 Subject: [PATCH 108/398] bug/Chat input fix --- packages/ui/src/views/chatmessage/ChatExpandDialog.js | 2 +- packages/ui/src/views/chatmessage/ChatMessage.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/views/chatmessage/ChatExpandDialog.js b/packages/ui/src/views/chatmessage/ChatExpandDialog.js index aa5cd504..1b2037a8 100644 --- a/packages/ui/src/views/chatmessage/ChatExpandDialog.js +++ b/packages/ui/src/views/chatmessage/ChatExpandDialog.js @@ -43,7 +43,7 @@ const ChatExpandDialog = ({ show, dialogProps, onClear, onCancel }) => { )}

- + diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index f73a2ba0..f37abe64 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -366,7 +366,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { value={userInput} onChange={onChange} multiline={true} - maxRows={2} + maxRows={isDialog ? 7 : 2} endAdornment={ From b499d30774b5158a9ab9eaece35af027f14e4182 Mon Sep 17 00:00:00 2001 From: Pravesh-mansharamani Date: Thu, 15 Jun 2023 20:52:49 +0530 Subject: [PATCH 109/398] Added the Figma docs loader --- .../nodes/documentloaders/Figma/Figma.ts | 81 ++++++++++++++++++ .../nodes/documentloaders/Figma/figma.png | Bin 0 -> 176029 bytes 2 files changed, 81 insertions(+) create mode 100644 packages/components/nodes/documentloaders/Figma/Figma.ts create mode 100644 packages/components/nodes/documentloaders/Figma/figma.png diff --git a/packages/components/nodes/documentloaders/Figma/Figma.ts b/packages/components/nodes/documentloaders/Figma/Figma.ts new file mode 100644 index 00000000..480c834d --- /dev/null +++ b/packages/components/nodes/documentloaders/Figma/Figma.ts @@ -0,0 +1,81 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface'; +import { FigmaFileLoader, FigmaLoaderParams } from 'langchain/document_loaders/web/figma'; + +class Figma_DocumentLoaders implements INode { + label: string; + name: string; + description: string; + type: string; + icon: string; + category: string; + baseClasses: string[]; + inputs: INodeParams[]; + + constructor() { + this.label = 'Figma'; + this.name = 'figma'; + this.type = 'Document'; + this.icon = 'figma.png'; + this.category = 'Document Loaders'; + this.description = 'Load data from a Figma file'; + this.baseClasses = [this.type]; + this.inputs = [ + { + label: 'Access Token', + name: 'accessToken', + type: 'password', + placeholder: '', + }, + { + label: 'File Key', + name: 'fileKey', + type: 'string', + placeholder: 'key', + }, + { + label: 'Node IDs', + name: 'nodeIds', + type: 'string', + placeholder: '0, 1, 2', + }, + { + label: 'Recursive', + name: 'recursive', + type: 'boolean', + optional: true + }, + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ]; + } + + async init(nodeData: INodeData): Promise { + const accessToken = nodeData.inputs?.accessToken as string; + const nodeIds = (nodeData.inputs?.nodeIds as string)?.split(',') || []; + const fileKey = nodeData.inputs?.fileKey as string; + + const options: FigmaLoaderParams = { + accessToken, + nodeIds, + fileKey, + }; + + const loader = new FigmaFileLoader(options); + const docs = await loader.load(); + + return docs; + } +} + +module.exports = { nodeClass: Figma_DocumentLoaders }; diff --git a/packages/components/nodes/documentloaders/Figma/figma.png b/packages/components/nodes/documentloaders/Figma/figma.png new file mode 100644 index 0000000000000000000000000000000000000000..72372ddff0e6eec27448d24b10c46c987edf317d GIT binary patch literal 176029 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

?1A{3edV5r?d-vCR~^xf@Hu&y&T1 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 V5p|!MT;T}c;(o Date: Thu, 15 Jun 2023 21:00:19 +0530 Subject: [PATCH 110/398] fixed the prettier error --- .../nodes/documentloaders/Figma/Figma.ts | 144 +++++++++--------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/packages/components/nodes/documentloaders/Figma/Figma.ts b/packages/components/nodes/documentloaders/Figma/Figma.ts index 480c834d..388c4ee0 100644 --- a/packages/components/nodes/documentloaders/Figma/Figma.ts +++ b/packages/components/nodes/documentloaders/Figma/Figma.ts @@ -1,81 +1,81 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface'; -import { FigmaFileLoader, FigmaLoaderParams } from 'langchain/document_loaders/web/figma'; +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { FigmaFileLoader, FigmaLoaderParams } from 'langchain/document_loaders/web/figma' class Figma_DocumentLoaders implements INode { - label: string; - name: string; - description: string; - type: string; - icon: string; - category: string; - baseClasses: string[]; - inputs: INodeParams[]; + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] - constructor() { - this.label = 'Figma'; - this.name = 'figma'; - this.type = 'Document'; - this.icon = 'figma.png'; - this.category = 'Document Loaders'; - this.description = 'Load data from a Figma file'; - this.baseClasses = [this.type]; - this.inputs = [ - { - label: 'Access Token', - name: 'accessToken', - type: 'password', - placeholder: '', - }, - { - label: 'File Key', - name: 'fileKey', - type: 'string', - placeholder: 'key', - }, - { - label: 'Node IDs', - name: 'nodeIds', - type: 'string', - placeholder: '0, 1, 2', - }, - { - label: 'Recursive', - name: 'recursive', - type: 'boolean', - optional: true - }, - { - label: 'Text Splitter', - name: 'textSplitter', - type: 'TextSplitter', - optional: true - }, - { - label: 'Metadata', - name: 'metadata', - type: 'json', - optional: true, - additionalParams: true - } - ]; - } + constructor() { + this.label = 'Figma' + this.name = 'figma' + this.type = 'Document' + this.icon = 'figma.png' + this.category = 'Document Loaders' + this.description = 'Load data from a Figma file' + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Access Token', + name: 'accessToken', + type: 'password', + placeholder: '' + }, + { + label: 'File Key', + name: 'fileKey', + type: 'string', + placeholder: 'key' + }, + { + label: 'Node IDs', + name: 'nodeIds', + type: 'string', + placeholder: '0, 1, 2' + }, + { + label: 'Recursive', + name: 'recursive', + type: 'boolean', + optional: true + }, + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } - async init(nodeData: INodeData): Promise { - const accessToken = nodeData.inputs?.accessToken as string; - const nodeIds = (nodeData.inputs?.nodeIds as string)?.split(',') || []; - const fileKey = nodeData.inputs?.fileKey as string; + async init(nodeData: INodeData): Promise { + const accessToken = nodeData.inputs?.accessToken as string + const nodeIds = (nodeData.inputs?.nodeIds as string)?.split(',') || [] + const fileKey = nodeData.inputs?.fileKey as string - const options: FigmaLoaderParams = { - accessToken, - nodeIds, - fileKey, - }; + const options: FigmaLoaderParams = { + accessToken, + nodeIds, + fileKey + } - const loader = new FigmaFileLoader(options); - const docs = await loader.load(); + const loader = new FigmaFileLoader(options) + const docs = await loader.load() - return docs; - } + return docs + } } -module.exports = { nodeClass: Figma_DocumentLoaders }; +module.exports = { nodeClass: Figma_DocumentLoaders } From c2b35f6c9534513bbd043fd7a05188c8e103fb14 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Fri, 16 Jun 2023 16:57:54 +0700 Subject: [PATCH 111/398] AzureOpenAIEmbedding modify user input --- .../AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts index 7fe61e62..2a211622 100644 --- a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts +++ b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts @@ -42,18 +42,10 @@ class AzureOpenAIEmbedding_Embeddings implements INode { { label: 'Azure OpenAI Api Version', name: 'azureOpenAIApiVersion', - type: 'options', - options: [ - { - label: '2023-03-15-preview', - name: '2023-03-15-preview' - }, - { - label: '2022-12-01', - name: '2022-12-01' - } - ], - default: '2023-03-15-preview' + type: 'string', + placeholder: 'YOUR-API-VERSION', + description: + 'Description of Supported API Versions. Please refer examples' }, { label: 'Batch Size', From 95d3dd3fafaf3782a21004ab34237856ef01a7f5 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 16 Jun 2023 15:17:12 +0100 Subject: [PATCH 112/398] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b8e2e8a5..13531280 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -27,7 +27,6 @@ If applicable, add screenshots to help explain your problem. If applicable, add exported flow in order to help replicating the problem. **Setup** - - Installation [e.g. docker, `npx flowise start`, `yarn start`] - Flowise Version [e.g. 1.2.11] - OS: [e.g. macOS, Windows, Linux] From 1c4880f665cbc5fc5e14272d1c4560ceb2690a58 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 16 Jun 2023 15:39:49 +0100 Subject: [PATCH 113/398] change latext splitter to code splitter --- .../CodeTextSplitter/CodeTextSplitter.ts | 128 ++++++++++++++++++ .../CodeTextSplitter/codeTextSplitter.svg | 8 ++ .../LatexTextSplitter/LatexTextSplitter.ts | 52 ------- .../LatexTextSplitter/latexTextSplitter.svg | 6 - 4 files changed, 136 insertions(+), 58 deletions(-) create mode 100644 packages/components/nodes/textsplitters/CodeTextSplitter/CodeTextSplitter.ts create mode 100644 packages/components/nodes/textsplitters/CodeTextSplitter/codeTextSplitter.svg delete mode 100644 packages/components/nodes/textsplitters/LatexTextSplitter/LatexTextSplitter.ts delete mode 100644 packages/components/nodes/textsplitters/LatexTextSplitter/latexTextSplitter.svg diff --git a/packages/components/nodes/textsplitters/CodeTextSplitter/CodeTextSplitter.ts b/packages/components/nodes/textsplitters/CodeTextSplitter/CodeTextSplitter.ts new file mode 100644 index 00000000..b14655b8 --- /dev/null +++ b/packages/components/nodes/textsplitters/CodeTextSplitter/CodeTextSplitter.ts @@ -0,0 +1,128 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { + RecursiveCharacterTextSplitter, + RecursiveCharacterTextSplitterParams, + SupportedTextSplitterLanguage +} from 'langchain/text_splitter' + +class CodeTextSplitter_TextSplitters implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + constructor() { + this.label = 'Code Text Splitter' + this.name = 'codeTextSplitter' + this.type = 'CodeTextSplitter' + this.icon = 'codeTextSplitter.svg' + this.category = 'Text Splitters' + this.description = `Split documents based on language-specific syntax` + this.baseClasses = [this.type, ...getBaseClasses(RecursiveCharacterTextSplitter)] + this.inputs = [ + { + label: 'Language', + name: 'language', + type: 'options', + options: [ + { + label: 'cpp', + name: 'cpp' + }, + { + label: 'go', + name: 'go' + }, + { + label: 'java', + name: 'java' + }, + { + label: 'js', + name: 'js' + }, + { + label: 'php', + name: 'php' + }, + { + label: 'proto', + name: 'proto' + }, + { + label: 'python', + name: 'python' + }, + { + label: 'rst', + name: 'rst' + }, + { + label: 'ruby', + name: 'ruby' + }, + { + label: 'rust', + name: 'rust' + }, + { + label: 'scala', + name: 'scala' + }, + { + label: 'swift', + name: 'swift' + }, + { + label: 'markdown', + name: 'markdown' + }, + { + label: 'latex', + name: 'latex' + }, + { + label: 'html', + name: 'html' + }, + { + label: 'sol', + name: 'sol' + } + ] + }, + { + label: 'Chunk Size', + name: 'chunkSize', + type: 'number', + default: 1000, + optional: true + }, + { + label: 'Chunk Overlap', + name: 'chunkOverlap', + type: 'number', + optional: true + } + ] + } + async init(nodeData: INodeData): Promise { + const chunkSize = nodeData.inputs?.chunkSize as string + const chunkOverlap = nodeData.inputs?.chunkOverlap as string + const language = nodeData.inputs?.language as SupportedTextSplitterLanguage + + const obj = {} as RecursiveCharacterTextSplitterParams + + if (chunkSize) obj.chunkSize = parseInt(chunkSize, 10) + if (chunkOverlap) obj.chunkOverlap = parseInt(chunkOverlap, 10) + + const splitter = RecursiveCharacterTextSplitter.fromLanguage(language, obj) + + return splitter + } +} +module.exports = { nodeClass: CodeTextSplitter_TextSplitters } diff --git a/packages/components/nodes/textsplitters/CodeTextSplitter/codeTextSplitter.svg b/packages/components/nodes/textsplitters/CodeTextSplitter/codeTextSplitter.svg new file mode 100644 index 00000000..d3b3d188 --- /dev/null +++ b/packages/components/nodes/textsplitters/CodeTextSplitter/codeTextSplitter.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/components/nodes/textsplitters/LatexTextSplitter/LatexTextSplitter.ts b/packages/components/nodes/textsplitters/LatexTextSplitter/LatexTextSplitter.ts deleted file mode 100644 index d6568d07..00000000 --- a/packages/components/nodes/textsplitters/LatexTextSplitter/LatexTextSplitter.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' -import { RecursiveCharacterTextSplitter, RecursiveCharacterTextSplitterParams } from 'langchain/text_splitter' - -class LatexTextSplitter_TextSplitters implements INode { - label: string - name: string - description: string - type: string - icon: string - category: string - baseClasses: string[] - inputs: INodeParams[] - constructor() { - this.label = 'Latex Text Splitter' - this.name = 'latexTextSplitter' - this.type = 'LatexTextSplitter' - this.icon = 'latexTextSplitter.svg' - this.category = 'Text Splitters' - this.description = `Split documents along Latex headings, headlines, enumerations and more.` - this.baseClasses = [this.type, ...getBaseClasses(RecursiveCharacterTextSplitter)] - this.inputs = [ - { - label: 'Chunk Size', - name: 'chunkSize', - type: 'number', - default: 1000, - optional: true - }, - { - label: 'Chunk Overlap', - name: 'chunkOverlap', - type: 'number', - optional: true - } - ] - } - async init(nodeData: INodeData): Promise { - const chunkSize = nodeData.inputs?.chunkSize as string - const chunkOverlap = nodeData.inputs?.chunkOverlap as string - - const obj = {} as RecursiveCharacterTextSplitterParams - - if (chunkSize) obj.chunkSize = parseInt(chunkSize, 10) - if (chunkOverlap) obj.chunkOverlap = parseInt(chunkOverlap, 10) - - const splitter = RecursiveCharacterTextSplitter.fromLanguage('latex', obj) - - return splitter - } -} -module.exports = { nodeClass: LatexTextSplitter_TextSplitters } diff --git a/packages/components/nodes/textsplitters/LatexTextSplitter/latexTextSplitter.svg b/packages/components/nodes/textsplitters/LatexTextSplitter/latexTextSplitter.svg deleted file mode 100644 index ae9d89be..00000000 --- a/packages/components/nodes/textsplitters/LatexTextSplitter/latexTextSplitter.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - From 5e35918021a79d5b19d011192bfe13cc5dffd5ed Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 16 Jun 2023 23:16:33 +0100 Subject: [PATCH 114/398] add PUPPETEER_SKIP_DOWNLOAD --- .github/workflows/main.yml | 3 ++- .github/workflows/test_docker_build.yml | 5 +++-- Dockerfile | 1 + docker/Dockerfile | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d087fb93..759f195f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,8 @@ jobs: platform: [ubuntu-latest] node-version: [18.15.0] runs-on: ${{ matrix.platform }} - + env: + PUPPETEER_SKIP_DOWNLOAD: true steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} diff --git a/.github/workflows/test_docker_build.yml b/.github/workflows/test_docker_build.yml index a698c247..a27cf22d 100644 --- a/.github/workflows/test_docker_build.yml +++ b/.github/workflows/test_docker_build.yml @@ -7,12 +7,13 @@ on: pull_request: branches: - - "*" + - '*' jobs: build: runs-on: ubuntu-latest - + env: + PUPPETEER_SKIP_DOWNLOAD: true steps: - uses: actions/checkout@v3 diff --git a/Dockerfile b/Dockerfile index e9470c31..fe01ed8d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ FROM node:18-alpine RUN apk add --update libc6-compat python3 make g++ # needed for pdfjs-dist RUN apk add --no-cache build-base cairo-dev pango-dev +ENV PUPPETEER_SKIP_DOWNLOAD=true WORKDIR /usr/src/packages diff --git a/docker/Dockerfile b/docker/Dockerfile index 15c4e0ac..2203af11 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,6 +6,7 @@ RUN apk add --no-cache git RUN apk add --no-cache python3 py3-pip make g++ # needed for pdfjs-dist RUN apk add --no-cache build-base cairo-dev pango-dev +ENV PUPPETEER_SKIP_DOWNLOAD=true # You can install a specific version like: flowise@1.0.0 RUN npm install -g flowise From d10dce0ccb24f6a41cfb59eb2a14f026d16a5b04 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 17 Jun 2023 09:57:26 +0100 Subject: [PATCH 115/398] add first chat message not found from API call --- packages/server/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index b248f22c..40bd75cd 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -507,8 +507,8 @@ export class App { }) if (!chatflow) return res.status(404).send(`Chatflow ${chatflowid} not found`) - const chatId = await getChatId(chatflow.id) - if (!chatId) return res.status(500).send(`Chatflow ${chatflowid} first message not found`) + let chatId = await getChatId(chatflow.id) + if (!chatId) chatId = Date.now().toString() if (!isInternal) { await this.validateKey(req, res, chatflow) From 567a61ac10460215128fc5166324726ac393486d Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 17 Jun 2023 23:35:41 +0100 Subject: [PATCH 116/398] add fontsize and chatflowconfig customization --- .../ui/src/ui-component/dialog/APICodeDialog.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/ui/src/ui-component/dialog/APICodeDialog.js b/packages/ui/src/ui-component/dialog/APICodeDialog.js index e2e7438d..e64f4bf8 100644 --- a/packages/ui/src/ui-component/dialog/APICodeDialog.js +++ b/packages/ui/src/ui-component/dialog/APICodeDialog.js @@ -135,6 +135,9 @@ const embedCodeCustomization = (chatflowid) => { Chatbot.init({ chatflowid: "${chatflowid}", apiHost: "${baseURL}", + chatflowConfig: { + // topK: 2 + }, theme: { button: { backgroundColor: "#3B81F6", @@ -149,6 +152,7 @@ const embedCodeCustomization = (chatflowid) => { backgroundColor: "#ffffff", height: 700, width: 400, + fontSize: 16, poweredByTextColor: "#303235", botMessage: { backgroundColor: "#f7f8ff", @@ -189,6 +193,7 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys) const updateChatflowApi = useApi(chatflowsApi.updateChatflow) + const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming) const getConfigApi = useApi(configApi.getConfig) const onCheckBoxChanged = (newVal) => { @@ -553,6 +558,7 @@ query({ useEffect(() => { if (show) { getAllAPIKeysApi.request() + getIsChatflowStreamingApi.request(dialogProps.chatflowid) } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -665,6 +671,15 @@ query({ wrapLines /> )} + {value !== 0 && getIsChatflowStreamingApi.data?.isStreaming && ( +

+ Read  + + here + +  on how to stream response back to application +

+ )} ))} From 974855b2b75e45ba500b7c8900842b3cc7f70c06 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 18 Jun 2023 12:43:19 +0100 Subject: [PATCH 117/398] update contributing md --- CONTRIBUTING.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a09051f3..03211d51 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ Flowise has 3 different modules in a single mono repository. #### Prerequisite -- Install Yarn +- Install [Yarn v1](https://classic.yarnpkg.com/en/docs/install) ```bash npm i -g yarn ``` @@ -84,7 +84,11 @@ Flowise has 3 different modules in a single mono repository. yarn start ``` -9. For development, run +9. For development: + + - Create `.env` file and specify the `PORT` (refer to `.env.example`) in `packages/ui` + - Create `.env` file and specify the `PORT` (refer to `.env.example`) in `packages/server` + - Run ```bash yarn dev From c0e88d483fea049c23db7bb2544abd6d7432903f Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 18 Jun 2023 14:14:26 +0100 Subject: [PATCH 118/398] update README --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 05221f47..dbce8f3b 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Flowise has 3 different modules in a single mono repository. ### Prerequisite -- Install Yarn +- Install [Yarn v1](https://classic.yarnpkg.com/en/docs/install) ```bash npm i -g yarn ``` @@ -107,9 +107,13 @@ Flowise has 3 different modules in a single mono repository. 6. For development build: - ```bash - yarn dev - ``` + - Create `.env` file and specify the `PORT` (refer to `.env.example`) in `packages/ui` + - Create `.env` file and specify the `PORT` (refer to `.env.example`) in `packages/server` + - Run + + ```bash + yarn dev + ``` Any code changes will reload the app automatically on [http://localhost:8080](http://localhost:8080) @@ -138,6 +142,8 @@ FLOWISE_PASSWORD=1234 ### [AWS](https://docs.flowiseai.com/deployment/aws) +### [Azure](https://docs.flowiseai.com/deployment/azure) + ### [DigitalOcean](https://docs.flowiseai.com/deployment/digital-ocean) ### [GCP](https://docs.flowiseai.com/deployment/gcp) From 1e53f2e137fff2398895888ee73e7161c26209f5 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sun, 18 Jun 2023 22:44:29 +0100 Subject: [PATCH 119/398] Create FUNDING.yml --- .github/FUNDING.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..6ba7e31e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: [ Flowise ] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] From 8c880199cdb6956bdb16db0d41c5de53aecd1322 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sun, 18 Jun 2023 22:53:22 +0100 Subject: [PATCH 120/398] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 6ba7e31e..cd443a19 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: [ Flowise ] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username From 70da39629c84328ad864075011f8ba119c1645f2 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 21 Jun 2023 18:31:53 +0100 Subject: [PATCH 121/398] add custom tool --- .../OpenAIFunctionAgent.ts | 32 +- .../nodes/tools/ChainTool/chaintool.svg | 8 +- .../nodes/tools/CustomTool/CustomTool.ts | 108 +++++ .../components/nodes/tools/CustomTool/core.ts | 78 +++ .../nodes/tools/CustomTool/customtool.svg | 4 + packages/components/package.json | 3 +- packages/components/src/Interface.ts | 22 +- packages/components/src/utils.ts | 31 +- packages/server/src/ChildProcess.ts | 26 +- packages/server/src/DataSource.ts | 3 +- packages/server/src/Interface.ts | 11 + packages/server/src/entity/Tool.ts | 30 ++ packages/server/src/index.ts | 87 +++- packages/server/src/utils/index.ts | 14 +- packages/ui/package.json | 1 + packages/ui/src/api/tools.js | 19 + packages/ui/src/assets/images/tools_empty.svg | 1 + packages/ui/src/menu-items/dashboard.js | 12 +- packages/ui/src/routes/MainRoutes.js | 7 + .../ui/src/ui-component/cards/ItemCard.js | 46 +- .../ui-component/dropdown/AsyncDropdown.js | 147 ++++++ .../src/ui-component/editor/DarkCodeEditor.js | 1 + .../ui-component/editor/LightCodeEditor.js | 1 + packages/ui/src/ui-component/grid/Grid.js | 37 ++ packages/ui/src/utils/genericHelper.js | 21 +- .../ui/src/views/canvas/NodeInputHandler.js | 79 ++- packages/ui/src/views/tools/ToolDialog.js | 448 ++++++++++++++++++ packages/ui/src/views/tools/index.js | 112 +++++ 28 files changed, 1346 insertions(+), 43 deletions(-) create mode 100644 packages/components/nodes/tools/CustomTool/CustomTool.ts create mode 100644 packages/components/nodes/tools/CustomTool/core.ts create mode 100644 packages/components/nodes/tools/CustomTool/customtool.svg create mode 100644 packages/server/src/entity/Tool.ts create mode 100644 packages/ui/src/api/tools.js create mode 100644 packages/ui/src/assets/images/tools_empty.svg create mode 100644 packages/ui/src/ui-component/dropdown/AsyncDropdown.js create mode 100644 packages/ui/src/ui-component/grid/Grid.js create mode 100644 packages/ui/src/views/tools/ToolDialog.js create mode 100644 packages/ui/src/views/tools/index.js diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index 9efe602f..4c740874 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -1,9 +1,10 @@ -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' -import { Tool } from 'langchain/tools' import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { flatten } from 'lodash' +import { BaseChatMemory, ChatMessageHistory } from 'langchain/memory' +import { AIChatMessage, HumanChatMessage } from 'langchain/schema' class OpenAIFunctionAgent_Agents implements INode { label: string @@ -30,6 +31,11 @@ class OpenAIFunctionAgent_Agents implements INode { type: 'Tool', list: true }, + { + label: 'Memory', + name: 'memory', + type: 'BaseChatMemory' + }, { label: 'OpenAI Chat Model', name: 'model', @@ -42,18 +48,38 @@ class OpenAIFunctionAgent_Agents implements INode { async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as BaseLanguageModel - let tools = nodeData.inputs?.tools as Tool[] + const memory = nodeData.inputs?.memory as BaseChatMemory + + let tools = nodeData.inputs?.tools tools = flatten(tools) const executor = await initializeAgentExecutorWithOptions(tools, model, { agentType: 'openai-functions', verbose: process.env.DEBUG === 'true' ? true : false }) + if (memory) executor.memory = memory + return executor } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const executor = nodeData.instance as AgentExecutor + const memory = nodeData.inputs?.memory as BaseChatMemory + + if (options && options.chatHistory) { + const chatHistory = [] + const histories: IMessage[] = options.chatHistory + + for (const message of histories) { + if (message.type === 'apiMessage') { + chatHistory.push(new AIChatMessage(message.message)) + } else if (message.type === 'userMessage') { + chatHistory.push(new HumanChatMessage(message.message)) + } + } + memory.chatHistory = new ChatMessageHistory(chatHistory) + executor.memory = memory + } if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) diff --git a/packages/components/nodes/tools/ChainTool/chaintool.svg b/packages/components/nodes/tools/ChainTool/chaintool.svg index c5bd0fbc..ab76749b 100644 --- a/packages/components/nodes/tools/ChainTool/chaintool.svg +++ b/packages/components/nodes/tools/ChainTool/chaintool.svg @@ -1,4 +1,8 @@ - + - + + + + + \ No newline at end of file diff --git a/packages/components/nodes/tools/CustomTool/CustomTool.ts b/packages/components/nodes/tools/CustomTool/CustomTool.ts new file mode 100644 index 00000000..768e9092 --- /dev/null +++ b/packages/components/nodes/tools/CustomTool/CustomTool.ts @@ -0,0 +1,108 @@ +import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { DynamicStructuredTool } from './core' +import { z } from 'zod' +import { DataSource } from 'typeorm' + +class CustomTool_Tools implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Custom Tool' + this.name = 'customTool' + this.type = 'CustomTool' + this.icon = 'customtool.svg' + this.category = 'Tools' + this.description = `Use custom tool you've created in Flowise within chatflow` + this.inputs = [ + { + label: 'Select Tool', + name: 'selectedTool', + type: 'asyncOptions', + loadMethod: 'listTools' + } + ] + this.baseClasses = [this.type, 'Tool', ...getBaseClasses(DynamicStructuredTool)] + } + + //@ts-ignore + loadMethods = { + async listTools(nodeData: INodeData, options: ICommonObject): Promise { + const returnData: INodeOptionsValue[] = [] + + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + + if (appDataSource === undefined || !appDataSource) { + return returnData + } + + const tools = await appDataSource.getRepository(databaseEntities['Tool']).find() + + for (let i = 0; i < tools.length; i += 1) { + const data = { + label: tools[i].name, + name: tools[i].id, + description: tools[i].description + } as INodeOptionsValue + returnData.push(data) + } + return returnData + } + } + + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + const selectedToolId = nodeData.inputs?.selectedTool as string + + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + + try { + const tool = await appDataSource.getRepository(databaseEntities['Tool']).findOneBy({ + id: selectedToolId + }) + + if (!tool) throw new Error(`Tool ${selectedToolId} not found`) + const obj = { + name: tool.name, + description: tool.description, + schema: z.object(convertSchemaToZod(tool.schema)), + code: tool.func + } + return new DynamicStructuredTool(obj) + } catch (e) { + throw new Error(e) + } + } +} + +const convertSchemaToZod = (schema: string) => { + try { + const parsedSchema = JSON.parse(schema) + const zodObj: any = {} + for (const sch of parsedSchema) { + if (sch.type === 'string') { + if (sch.required) z.string({ required_error: `${sch.property} required` }).describe(sch.description) + zodObj[sch.property] = z.string().describe(sch.description) + } else if (sch.type === 'number') { + if (sch.required) z.number({ required_error: `${sch.property} required` }).describe(sch.description) + zodObj[sch.property] = z.number().describe(sch.description) + } else if (sch.type === 'boolean') { + if (sch.required) z.boolean({ required_error: `${sch.property} required` }).describe(sch.description) + zodObj[sch.property] = z.boolean().describe(sch.description) + } + } + return zodObj + } catch (e) { + throw new Error(e) + } +} + +module.exports = { nodeClass: CustomTool_Tools } diff --git a/packages/components/nodes/tools/CustomTool/core.ts b/packages/components/nodes/tools/CustomTool/core.ts new file mode 100644 index 00000000..0d3d7bcd --- /dev/null +++ b/packages/components/nodes/tools/CustomTool/core.ts @@ -0,0 +1,78 @@ +import { z } from 'zod' +import { CallbackManagerForToolRun } from 'langchain/callbacks' +import { StructuredTool, ToolParams } from 'langchain/tools' +import { NodeVM } from 'vm2' +import { availableDependencies } from '../../../src/utils' + +export interface BaseDynamicToolInput extends ToolParams { + name: string + description: string + code: string + returnDirect?: boolean +} + +export interface DynamicStructuredToolInput< + // eslint-disable-next-line + T extends z.ZodObject = z.ZodObject +> extends BaseDynamicToolInput { + func?: (input: z.infer, runManager?: CallbackManagerForToolRun) => Promise + schema: T +} + +export class DynamicStructuredTool< + // eslint-disable-next-line + T extends z.ZodObject = z.ZodObject +> extends StructuredTool { + name: string + + description: string + + code: string + + func: DynamicStructuredToolInput['func'] + + schema: T + + constructor(fields: DynamicStructuredToolInput) { + super(fields) + this.name = fields.name + this.description = fields.description + this.code = fields.code + this.func = fields.func + this.returnDirect = fields.returnDirect ?? this.returnDirect + this.schema = fields.schema + } + + protected async _call(arg: z.output): Promise { + let sandbox: any = {} + if (typeof arg === 'object' && Object.keys(arg).length) { + for (const item in arg) { + sandbox[`$${item}`] = arg[item] + } + } + + const options = { + console: 'inherit', + sandbox, + require: { + external: false as boolean | { modules: string[] }, + builtin: ['*'] + } + } as any + + const external = JSON.stringify(availableDependencies) + if (external) { + const deps = JSON.parse(external) + if (deps && deps.length) { + options.require.external = { + modules: deps + } + } + } + + const vm = new NodeVM(options) + const response = await vm.run(`module.exports = async function() {${this.code}}()`, __dirname) + + return response + } +} diff --git a/packages/components/nodes/tools/CustomTool/customtool.svg b/packages/components/nodes/tools/CustomTool/customtool.svg new file mode 100644 index 00000000..c5bd0fbc --- /dev/null +++ b/packages/components/nodes/tools/CustomTool/customtool.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index 738c7752..ec97d4d7 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -33,7 +33,7 @@ "form-data": "^4.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "^0.0.94", + "langchain": "^0.0.96", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", @@ -43,6 +43,7 @@ "playwright": "^1.35.0", "puppeteer": "^20.7.1", "srt-parser-2": "^1.2.3", + "vm2": "^3.9.19", "weaviate-ts-client": "^1.1.0", "ws": "^8.9.0" }, diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index bd94cca8..f8a6fd58 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -2,7 +2,18 @@ * Types */ -export type NodeParamsType = 'options' | 'string' | 'number' | 'boolean' | 'password' | 'json' | 'code' | 'date' | 'file' | 'folder' +export type NodeParamsType = + | 'asyncOptions' + | 'options' + | 'string' + | 'number' + | 'boolean' + | 'password' + | 'json' + | 'code' + | 'date' + | 'file' + | 'folder' export type CommonType = string | number | boolean | undefined | null @@ -16,6 +27,10 @@ export interface ICommonObject { [key: string]: any | CommonType | ICommonObject | CommonType[] | ICommonObject[] } +export type IDatabaseEntity = { + [key: string]: any +} + export interface IAttachment { content: string contentType: string @@ -50,6 +65,7 @@ export interface INodeParams { placeholder?: string fileType?: string additionalParams?: boolean + loadMethod?: string } export interface INodeExecutionData { @@ -74,6 +90,9 @@ export interface INodeProperties { export interface INode extends INodeProperties { inputs?: INodeParams[] output?: INodeOutputsValue[] + loadMethods?: { + [key: string]: (nodeData: INodeData, options?: ICommonObject) => Promise + } init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise } @@ -83,6 +102,7 @@ export interface INodeData extends INodeProperties { inputs?: ICommonObject outputs?: ICommonObject instance?: any + loadMethod?: string // method to load async options } export interface IMessage { diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index de026a35..ad8d28dc 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -18,6 +18,7 @@ export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is no */ export const getBaseClasses = (targetClass: any) => { const baseClasses: string[] = [] + const skipClassNames = ['BaseLangChain', 'Serializable'] if (targetClass instanceof Function) { let baseClass = targetClass @@ -26,7 +27,7 @@ export const getBaseClasses = (targetClass: any) => { const newBaseClass = Object.getPrototypeOf(baseClass) if (newBaseClass && newBaseClass !== Object && newBaseClass.name) { baseClass = newBaseClass - baseClasses.push(baseClass.name) + if (!skipClassNames.includes(baseClass.name)) baseClasses.push(baseClass.name) } else { break } @@ -284,3 +285,31 @@ const handleEscapeDoubleQuote = (value: string): string => { } return newValue === '' ? value : newValue } + +export const availableDependencies = [ + '@dqbd/tiktoken', + '@getzep/zep-js', + '@huggingface/inference', + '@pinecone-database/pinecone', + '@supabase/supabase-js', + 'axios', + 'cheerio', + 'chromadb', + 'cohere-ai', + 'd3-dsv', + 'form-data', + 'graphql', + 'html-to-text', + 'langchain', + 'linkifyjs', + 'mammoth', + 'moment', + 'node-fetch', + 'pdf-parse', + 'pdfjs-dist', + 'playwright', + 'puppeteer', + 'srt-parser-2', + 'typeorm', + 'weaviate-ts-client' +] diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index 08847a52..95f7368a 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -1,5 +1,10 @@ +import path from 'path' import { IChildProcessMessage, IReactFlowNode, IReactFlowObject, IRunChatflowMessageValue, INodeData } from './Interface' -import { buildLangchain, constructGraphs, getEndingNode, getStartingNodes, resolveVariables } from './utils' +import { buildLangchain, constructGraphs, getEndingNode, getStartingNodes, getUserHome, resolveVariables } from './utils' +import { DataSource } from 'typeorm' +import { ChatFlow } from './entity/ChatFlow' +import { ChatMessage } from './entity/ChatMessage' +import { Tool } from './entity/Tool' export class ChildProcess { /** @@ -22,6 +27,8 @@ export class ChildProcess { await sendToParentProcess('start', '_') + const childAppDataSource = await initDB() + // Create a Queue and add our initial node in it const { endingNodeData, chatflow, chatId, incomingInput, componentNodes } = messageValue @@ -84,6 +91,7 @@ export class ChildProcess { componentNodes, incomingInput.question, chatId, + childAppDataSource, incomingInput?.overrideConfig ) @@ -115,6 +123,22 @@ export class ChildProcess { } } +/** + * Initalize DB in child process + * @returns {DataSource} + */ +async function initDB() { + const homePath = path.join(getUserHome(), '.flowise') + const childAppDataSource = new DataSource({ + type: 'sqlite', + database: path.resolve(homePath, 'database.sqlite'), + synchronize: true, + entities: [ChatFlow, ChatMessage, Tool], + migrations: [] + }) + return await childAppDataSource.initialize() +} + /** * Send data back to parent process * @param {string} key Key of message diff --git a/packages/server/src/DataSource.ts b/packages/server/src/DataSource.ts index 76c8e144..2ec8104a 100644 --- a/packages/server/src/DataSource.ts +++ b/packages/server/src/DataSource.ts @@ -3,6 +3,7 @@ import path from 'path' import { DataSource } from 'typeorm' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' +import { Tool } from './entity/Tool' import { getUserHome } from './utils' let appDataSource: DataSource @@ -14,7 +15,7 @@ export const init = async (): Promise => { type: 'sqlite', database: path.resolve(homePath, 'database.sqlite'), synchronize: true, - entities: [ChatFlow, ChatMessage], + entities: [ChatFlow, ChatMessage, Tool], migrations: [] }) } diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 2c1fe406..1eafcae6 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -24,6 +24,17 @@ export interface IChatMessage { sourceDocuments: string } +export interface ITool { + id: string + name: string + description: string + color: string + schema: string + func: string + updatedDate: Date + createdDate: Date +} + export interface IComponentNodes { [key: string]: INode } diff --git a/packages/server/src/entity/Tool.ts b/packages/server/src/entity/Tool.ts new file mode 100644 index 00000000..d547374c --- /dev/null +++ b/packages/server/src/entity/Tool.ts @@ -0,0 +1,30 @@ +/* eslint-disable */ +import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm' +import { ITool } from '../Interface' + +@Entity() +export class Tool implements ITool { + @PrimaryGeneratedColumn('uuid') + id: string + + @Column() + name: string + + @Column() + description: string + + @Column() + color: string + + @Column({ nullable: true }) + schema: string + + @Column({ nullable: true }) + func: string + + @CreateDateColumn() + createdDate: Date + + @UpdateDateColumn() + updatedDate: Date +} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 40bd75cd..65bfef23 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -35,7 +35,8 @@ import { isSameOverrideConfig, replaceAllAPIKeys, isFlowValidForStream, - isVectorStoreFaiss + isVectorStoreFaiss, + databaseEntities } from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' @@ -43,8 +44,9 @@ import { NodesPool } from './NodesPool' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' import { ChatflowPool } from './ChatflowPool' -import { ICommonObject } from 'flowise-components' +import { ICommonObject, INodeOptionsValue } from 'flowise-components' import { fork } from 'child_process' +import { Tool } from './entity/Tool' export class App { app: express.Application @@ -142,6 +144,29 @@ export class App { } }) + // load async options + this.app.post('/api/v1/node-load-method/:name', async (req: Request, res: Response) => { + const nodeData: INodeData = req.body + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, req.params.name)) { + try { + const nodeInstance = this.nodesPool.componentNodes[req.params.name] + const methodName = nodeData.loadMethod || '' + + const returnOptions: INodeOptionsValue[] = await nodeInstance.loadMethods![methodName]!.call(nodeInstance, nodeData, { + appDataSource: this.AppDataSource, + databaseEntities: databaseEntities + }) + + return res.json(returnOptions) + } catch (error) { + return res.json([]) + } + } else { + res.status(404).send(`Node ${req.params.name} not found`) + return + } + }) + // ---------------------------------------- // Chatflows // ---------------------------------------- @@ -257,6 +282,63 @@ export class App { return res.json(results) }) + // ---------------------------------------- + // Tools + // ---------------------------------------- + + // Get all tools + this.app.get('/api/v1/tools', async (req: Request, res: Response) => { + const tools = await this.AppDataSource.getRepository(Tool).find() + return res.json(tools) + }) + + // Get specific tool + this.app.get('/api/v1/tools/:id', async (req: Request, res: Response) => { + const tool = await this.AppDataSource.getRepository(Tool).findOneBy({ + id: req.params.id + }) + return res.json(tool) + }) + + // Add tool + this.app.post('/api/v1/tools', async (req: Request, res: Response) => { + const body = req.body + const newTool = new Tool() + Object.assign(newTool, body) + + const tool = this.AppDataSource.getRepository(Tool).create(newTool) + const results = await this.AppDataSource.getRepository(Tool).save(tool) + + return res.json(results) + }) + + // Update tool + this.app.put('/api/v1/tools/:id', async (req: Request, res: Response) => { + const tool = await this.AppDataSource.getRepository(Tool).findOneBy({ + id: req.params.id + }) + + if (!tool) { + res.status(404).send(`Tool ${req.params.id} not found`) + return + } + + const body = req.body + const updateTool = new Tool() + Object.assign(updateTool, body) + + this.AppDataSource.getRepository(Tool).merge(tool, updateTool) + const result = await this.AppDataSource.getRepository(Tool).save(tool) + + return res.json(result) + }) + + // Delete tool + this.app.delete('/api/v1/tools/:id', async (req: Request, res: Response) => { + const results = await this.AppDataSource.getRepository(Tool).delete({ id: req.params.id }) + return res.json(results) + }) + // ---------------------------------------- // Configuration // ---------------------------------------- @@ -623,6 +705,7 @@ export class App { this.nodesPool.componentNodes, incomingInput.question, chatId, + this.AppDataSource, incomingInput?.overrideConfig ) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 8e19bf5b..e3005c7b 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -15,10 +15,15 @@ import { IOverrideConfig } from '../Interface' import { cloneDeep, get, omit, merge } from 'lodash' -import { ICommonObject, getInputVariables } from 'flowise-components' +import { ICommonObject, getInputVariables, IDatabaseEntity } from 'flowise-components' import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' +import { ChatFlow } from '../entity/ChatFlow' +import { ChatMessage } from '../entity/ChatMessage' +import { Tool } from '../entity/Tool' +import { DataSource } from 'typeorm' const QUESTION_VAR_PREFIX = 'question' +export const databaseEntities: IDatabaseEntity = { ChatFlow: ChatFlow, ChatMessage: ChatMessage, Tool: Tool } /** * Returns the home folder path of the user if @@ -183,6 +188,7 @@ export const buildLangchain = async ( componentNodes: IComponentNodes, question: string, chatId: string, + appDataSource: DataSource, overrideConfig?: ICommonObject ) => { const flowNodes = cloneDeep(reactFlowNodes) @@ -215,7 +221,11 @@ export const buildLangchain = async ( if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig) const reactFlowNodeData: INodeData = resolveVariables(flowNodeData, flowNodes, question) - flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, { chatId }) + flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, { + chatId, + appDataSource, + databaseEntities + }) } catch (e: any) { console.error(e) throw new Error(e) diff --git a/packages/ui/package.json b/packages/ui/package.json index af9ac5e0..1d7bc490 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.10.6", "@mui/icons-material": "^5.0.3", "@mui/material": "^5.11.12", + "@mui/x-data-grid": "^6.8.0", "@tabler/icons": "^1.39.1", "clsx": "^1.1.1", "formik": "^2.2.6", diff --git a/packages/ui/src/api/tools.js b/packages/ui/src/api/tools.js new file mode 100644 index 00000000..77992a2a --- /dev/null +++ b/packages/ui/src/api/tools.js @@ -0,0 +1,19 @@ +import client from './client' + +const getAllTools = () => client.get('/tools') + +const getSpecificTool = (id) => client.get(`/tools/${id}`) + +const createNewTool = (body) => client.post(`/tools`, body) + +const updateTool = (id, body) => client.put(`/tools/${id}`, body) + +const deleteTool = (id) => client.delete(`/tools/${id}`) + +export default { + getAllTools, + getSpecificTool, + createNewTool, + updateTool, + deleteTool +} diff --git a/packages/ui/src/assets/images/tools_empty.svg b/packages/ui/src/assets/images/tools_empty.svg new file mode 100644 index 00000000..9a2a2a77 --- /dev/null +++ b/packages/ui/src/assets/images/tools_empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/menu-items/dashboard.js b/packages/ui/src/menu-items/dashboard.js index f1cd5062..948b4e4a 100644 --- a/packages/ui/src/menu-items/dashboard.js +++ b/packages/ui/src/menu-items/dashboard.js @@ -1,8 +1,8 @@ // assets -import { IconHierarchy, IconBuildingStore, IconKey } from '@tabler/icons' +import { IconHierarchy, IconBuildingStore, IconKey, IconTool } from '@tabler/icons' // constant -const icons = { IconHierarchy, IconBuildingStore, IconKey } +const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool } // ==============================|| DASHBOARD MENU ITEMS ||============================== // @@ -27,6 +27,14 @@ const dashboard = { icon: icons.IconBuildingStore, breadcrumbs: true }, + { + id: 'tools', + title: 'Tools', + type: 'item', + url: '/tools', + icon: icons.IconTool, + breadcrumbs: true + }, { id: 'apikey', title: 'API Keys', diff --git a/packages/ui/src/routes/MainRoutes.js b/packages/ui/src/routes/MainRoutes.js index 5353e41a..28e60287 100644 --- a/packages/ui/src/routes/MainRoutes.js +++ b/packages/ui/src/routes/MainRoutes.js @@ -13,6 +13,9 @@ const Marketplaces = Loadable(lazy(() => import('views/marketplaces'))) // apikey routing const APIKey = Loadable(lazy(() => import('views/apikey'))) +// apikey routing +const Tools = Loadable(lazy(() => import('views/tools'))) + // ==============================|| MAIN ROUTING ||============================== // const MainRoutes = { @@ -34,6 +37,10 @@ const MainRoutes = { { path: '/apikey', element: + }, + { + path: '/tools', + element: } ] } diff --git a/packages/ui/src/ui-component/cards/ItemCard.js b/packages/ui/src/ui-component/cards/ItemCard.js index 506947ce..345a88d5 100644 --- a/packages/ui/src/ui-component/cards/ItemCard.js +++ b/packages/ui/src/ui-component/cards/ItemCard.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types' // material-ui -import { styled, useTheme } from '@mui/material/styles' -import { Box, Grid, Chip, Typography } from '@mui/material' +import { styled } from '@mui/material/styles' +import { Box, Grid, Typography } from '@mui/material' // project imports import MainCard from 'ui-component/cards/MainCard' @@ -27,20 +27,7 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({ // ===========================|| CONTRACT CARD ||=========================== // -const ItemCard = ({ isLoading, data, images, onClick }) => { - const theme = useTheme() - - const chipSX = { - height: 24, - padding: '0 6px' - } - - const activeChatflowSX = { - ...chipSX, - color: 'white', - backgroundColor: theme.palette.success.dark - } - +const ItemCard = ({ isLoading, data, images, color, onClick }) => { return ( <> {isLoading ? ( @@ -49,7 +36,24 @@ const ItemCard = ({ isLoading, data, images, onClick }) => { -
+
+ {color && ( +
+ )} @@ -61,13 +65,6 @@ const ItemCard = ({ isLoading, data, images, onClick }) => { {data.description} )} - - {data.deployed && ( - - - - )} - {images && (
{ + const loadMethod = nodeData.inputParams.find((param) => param.name === name)?.loadMethod + const username = localStorage.getItem('username') + const password = localStorage.getItem('password') + + let lists = await axios + .post( + `${baseURL}/api/v1/node-load-method/${nodeData.name}`, + { ...nodeData, loadMethod }, + { auth: username && password ? { username, password } : undefined } + ) + .then(async function (response) { + return response.data + }) + .catch(function (error) { + console.error(error) + }) + return lists +} + +export const AsyncDropdown = ({ + name, + nodeData, + value, + onSelect, + isCreateNewOption, + onCreateNew, + disabled = false, + disableClearable = false +}) => { + const customization = useSelector((state) => state.customization) + + const [open, setOpen] = useState(false) + const [options, setOptions] = useState([]) + const [loading, setLoading] = useState(false) + const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value) + const getDefaultOptionValue = () => '' + const addNewOption = [{ label: '- Create New -', name: '-create-' }] + let [internalValue, setInternalValue] = useState(value ?? 'choose an option') + + useEffect(() => { + setLoading(true) + ;(async () => { + const fetchData = async () => { + let response = await fetchList({ name, nodeData }) + if (isCreateNewOption) setOptions([...response, ...addNewOption]) + else setOptions([...response]) + setLoading(false) + } + fetchData() + })() + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return ( + <> + { + setOpen(true) + }} + onClose={() => { + setOpen(false) + }} + options={options} + value={findMatchingOptions(options, internalValue) || getDefaultOptionValue()} + onChange={(e, selection) => { + const value = selection ? selection.name : '' + if (isCreateNewOption && value === '-create-') { + onCreateNew() + } else { + setInternalValue(value) + onSelect(value) + } + }} + PopperComponent={StyledPopper} + loading={loading} + renderInput={(params) => ( + + {loading ? : null} + {params.InputProps.endAdornment} + + ) + }} + /> + )} + renderOption={(props, option) => ( + +
+ {option.label} + {option.description && ( + {option.description} + )} +
+
+ )} + /> + + ) +} + +AsyncDropdown.propTypes = { + name: PropTypes.string, + nodeData: PropTypes.object, + value: PropTypes.string, + onSelect: PropTypes.func, + onCreateNew: PropTypes.func, + disabled: PropTypes.bool, + disableClearable: PropTypes.bool, + isCreateNewOption: PropTypes.bool +} diff --git a/packages/ui/src/ui-component/editor/DarkCodeEditor.js b/packages/ui/src/ui-component/editor/DarkCodeEditor.js index 3925f4a6..bf0719dd 100644 --- a/packages/ui/src/ui-component/editor/DarkCodeEditor.js +++ b/packages/ui/src/ui-component/editor/DarkCodeEditor.js @@ -21,6 +21,7 @@ export const DarkCodeEditor = ({ value, placeholder, disabled = false, type, sty onValueChange={onValueChange} onMouseUp={onMouseUp} onBlur={onBlur} + tabSize={4} style={{ ...style, background: theme.palette.codeEditor.main diff --git a/packages/ui/src/ui-component/editor/LightCodeEditor.js b/packages/ui/src/ui-component/editor/LightCodeEditor.js index 86f7057d..14dcbf29 100644 --- a/packages/ui/src/ui-component/editor/LightCodeEditor.js +++ b/packages/ui/src/ui-component/editor/LightCodeEditor.js @@ -21,6 +21,7 @@ export const LightCodeEditor = ({ value, placeholder, disabled = false, type, st onValueChange={onValueChange} onMouseUp={onMouseUp} onBlur={onBlur} + tabSize={4} style={{ ...style, background: theme.palette.card.main diff --git a/packages/ui/src/ui-component/grid/Grid.js b/packages/ui/src/ui-component/grid/Grid.js new file mode 100644 index 00000000..2049f56c --- /dev/null +++ b/packages/ui/src/ui-component/grid/Grid.js @@ -0,0 +1,37 @@ +import PropTypes from 'prop-types' +import { DataGrid } from '@mui/x-data-grid' +import { IconPlus } from '@tabler/icons' +import { Button } from '@mui/material' + +export const Grid = ({ columns, rows, style, onRowUpdate, addNewRow }) => { + const handleProcessRowUpdate = (newRow) => { + onRowUpdate(newRow) + return newRow + } + + return ( + <> + + {rows && columns && ( +
+ console.error(error)} + rows={rows} + columns={columns} + /> +
+ )} + + ) +} + +Grid.propTypes = { + rows: PropTypes.array, + columns: PropTypes.array, + style: PropTypes.any, + addNewRow: PropTypes.func, + onRowUpdate: PropTypes.func +} diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index fac83225..03f891ec 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -39,7 +39,7 @@ export const initNode = (nodeData, newNodeId) => { const incoming = nodeData.inputs ? nodeData.inputs.length : 0 const outgoing = 1 - const whitelistTypes = ['options', 'string', 'number', 'boolean', 'password', 'json', 'code', 'date', 'file', 'folder'] + const whitelistTypes = ['asyncOptions', 'options', 'string', 'number', 'boolean', 'password', 'json', 'code', 'date', 'file', 'folder'] for (let i = 0; i < incoming; i += 1) { const newInput = { @@ -334,3 +334,22 @@ export const throttle = (func, limit) => { } } } + +export const generateRandomGradient = () => { + function randomColor() { + var color = 'rgb(' + for (var i = 0; i < 3; i++) { + var random = Math.floor(Math.random() * 256) + color += random + if (i < 2) { + color += ',' + } + } + color += ')' + return color + } + + var gradient = 'linear-gradient(' + randomColor() + ', ' + randomColor() + ')' + + return gradient +} diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index d58f7a66..31a8a37d 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -7,10 +7,11 @@ import { useSelector } from 'react-redux' import { useTheme, styled } from '@mui/material/styles' import { Box, Typography, Tooltip, IconButton } from '@mui/material' import { tooltipClasses } from '@mui/material/Tooltip' -import { IconArrowsMaximize } from '@tabler/icons' +import { IconArrowsMaximize, IconEdit } from '@tabler/icons' // project import import { Dropdown } from 'ui-component/dropdown/Dropdown' +import { AsyncDropdown } from 'ui-component/dropdown/AsyncDropdown' import { Input } from 'ui-component/input/Input' import { File } from 'ui-component/file/File' import { SwitchInput } from 'ui-component/switch/Switch' @@ -18,6 +19,9 @@ import { flowContext } from 'store/context/ReactFlowContext' import { isValidConnection, getAvailableNodesForVariable } from 'utils/genericHelper' import { JsonEditorInput } from 'ui-component/json/JsonEditor' import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' +import ToolDialog from 'views/tools/ToolDialog' + +const EDITABLE_TOOLS = ['selectedTool'] const CustomWidthTooltip = styled(({ className, ...props }) => )({ [`& .${tooltipClasses.tooltip}`]: { @@ -36,6 +40,9 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA const [position, setPosition] = useState(0) const [showExpandDialog, setShowExpandDialog] = useState(false) const [expandDialogProps, setExpandDialogProps] = useState({}) + const [showAsyncOptionDialog, setAsyncOptionEditDialog] = useState('') + const [asyncOptionEditDialogProps, setAsyncOptionEditDialogProps] = useState({}) + const [reloadTimestamp, setReloadTimestamp] = useState(Date.now().toString()) const onExpandDialogClicked = (value, inputParam) => { const dialogProp = { @@ -61,6 +68,42 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA data.inputs[inputParamName] = newValue } + const editAsyncOption = (inputParamName, inputValue) => { + if (inputParamName === 'selectedTool') { + setAsyncOptionEditDialogProps({ + title: 'Edit Tool', + type: 'EDIT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Save', + toolId: inputValue + }) + } + setAsyncOptionEditDialog(inputParamName) + } + + const addAsyncOption = (inputParamName) => { + if (inputParamName === 'selectedTool') { + setAsyncOptionEditDialogProps({ + title: 'Add New Tool', + type: 'ADD', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add' + }) + } + setAsyncOptionEditDialog(inputParamName) + } + + const onConfirmAsyncOption = (selectedOptionId = '') => { + if (!selectedOptionId) { + data.inputs[showAsyncOptionDialog] = '' + } else { + data.inputs[showAsyncOptionDialog] = selectedOptionId + setReloadTimestamp(Date.now().toString()) + } + setAsyncOptionEditDialogProps({}) + setAsyncOptionEditDialog('') + } + useEffect(() => { if (ref.current && ref.current.offsetTop && ref.current.clientHeight) { setPosition(ref.current.offsetTop + ref.current.clientHeight / 2) @@ -186,12 +229,44 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA name={inputParam.name} options={inputParam.options} onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)} - value={data.inputs[inputParam.name] ?? inputParam.default ?? 'chose an option'} + value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'} /> )} + {inputParam.type === 'asyncOptions' && ( + <> + {data.inputParams.length === 1 &&
} +
+ (data.inputs[inputParam.name] = newValue)} + onCreateNew={() => addAsyncOption(inputParam.name)} + /> + {EDITABLE_TOOLS.includes(inputParam.name) && data.inputs[inputParam.name] && ( + editAsyncOption(inputParam.name, data.inputs[inputParam.name])} + > + + + )} +
+ + )} )} + setAsyncOptionEditDialog('')} + onConfirm={onConfirmAsyncOption} + >
) } diff --git a/packages/ui/src/views/tools/ToolDialog.js b/packages/ui/src/views/tools/ToolDialog.js new file mode 100644 index 00000000..bd5af355 --- /dev/null +++ b/packages/ui/src/views/tools/ToolDialog.js @@ -0,0 +1,448 @@ +import { createPortal } from 'react-dom' +import PropTypes from 'prop-types' +import { useState, useEffect, useCallback, useMemo } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import { cloneDeep } from 'lodash' + +import { Box, Typography, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, OutlinedInput } from '@mui/material' +import { StyledButton } from 'ui-component/button/StyledButton' +import { Grid } from 'ui-component/grid/Grid' +import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' +import { GridActionsCellItem } from '@mui/x-data-grid' +import DeleteIcon from '@mui/icons-material/Delete' +import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' +import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' +import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' +import { useTheme } from '@mui/material/styles' + +// Icons +import { IconX } from '@tabler/icons' + +// API +import toolsApi from 'api/tools' + +// Hooks +import useConfirm from 'hooks/useConfirm' +import useApi from 'hooks/useApi' + +// utils +import useNotifier from 'utils/useNotifier' +import { generateRandomGradient } from 'utils/genericHelper' + +const exampleAPIFunc = `/* +* You can use any libraries imported in Flowise +* You can use properties specified in Output Schema as variables. Ex: Property = userid, Variable = $userid +* Must return a string value at the end of function +*/ + +const fetch = require('node-fetch'); +const url = 'https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t_weather=true'; +const options = { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } +}; +try { + const response = await fetch(url, options); + const text = await response.text(); + return text; +} catch (error) { + console.error(error); + return ''; +}` + +const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const portalElement = document.getElementById('portal') + const theme = useTheme() + + const customization = useSelector((state) => state.customization) + const dispatch = useDispatch() + + // ==============================|| Snackbar ||============================== // + + useNotifier() + const { confirm } = useConfirm() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const getSpecificToolApi = useApi(toolsApi.getSpecificTool) + + const [toolId, setToolId] = useState('') + const [toolName, setToolName] = useState('') + const [toolDesc, setToolDesc] = useState('') + const [toolSchema, setToolSchema] = useState([]) + const [toolFunc, setToolFunc] = useState('') + + const deleteItem = useCallback( + (id) => () => { + setTimeout(() => { + setToolSchema((prevRows) => prevRows.filter((row) => row.id !== id)) + }) + }, + [] + ) + + const addNewRow = () => { + setTimeout(() => { + setToolSchema((prevRows) => { + let allRows = [...cloneDeep(prevRows)] + const lastRowId = allRows.length ? allRows[allRows.length - 1].id + 1 : 1 + allRows.push({ + id: lastRowId, + property: '', + description: '', + type: '', + required: false + }) + return allRows + }) + }) + } + + const onRowUpdate = (newRow) => { + setTimeout(() => { + setToolSchema((prevRows) => { + let allRows = [...cloneDeep(prevRows)] + const indexToUpdate = allRows.findIndex((row) => row.id === newRow.id) + if (indexToUpdate >= 0) { + allRows[indexToUpdate] = { ...newRow } + } + return allRows + }) + }) + } + + const columns = useMemo( + () => [ + { field: 'property', headerName: 'Property', editable: true, flex: 1 }, + { + field: 'type', + headerName: 'Type', + type: 'singleSelect', + valueOptions: ['string', 'number', 'boolean', 'date'], + editable: true, + width: 120 + }, + { field: 'description', headerName: 'Description', editable: true, flex: 1 }, + { field: 'required', headerName: 'Required', type: 'boolean', editable: true, width: 80 }, + { + field: 'actions', + type: 'actions', + width: 80, + getActions: (params) => [ + } label='Delete' onClick={deleteItem(params.id)} /> + ] + } + ], + [deleteItem] + ) + + const formatSchema = (schema) => { + try { + const parsedSchema = JSON.parse(schema) + return parsedSchema.map((sch, index) => { + return { + ...sch, + id: index + } + }) + } catch (e) { + return [] + } + } + + useEffect(() => { + if (getSpecificToolApi.data) { + setToolId(getSpecificToolApi.data.id) + setToolName(getSpecificToolApi.data.name) + setToolDesc(getSpecificToolApi.data.description) + setToolSchema(formatSchema(getSpecificToolApi.data.schema)) + if (getSpecificToolApi.data.func) setToolFunc(getSpecificToolApi.data.func) + else setToolFunc('') + } + }, [getSpecificToolApi.data]) + + useEffect(() => { + if (dialogProps.type === 'EDIT' && dialogProps.data) { + setToolId(dialogProps.data.id) + setToolName(dialogProps.data.name) + setToolDesc(dialogProps.data.description) + setToolSchema(formatSchema(dialogProps.data.schema)) + if (dialogProps.data.func) setToolFunc(dialogProps.data.func) + else setToolFunc('') + } else if (dialogProps.type === 'EDIT' && dialogProps.toolId) { + getSpecificToolApi.request(dialogProps.toolId) + } else if (dialogProps.type === 'ADD') { + setToolId('') + setToolName('') + setToolDesc('') + setToolSchema([]) + setToolFunc('') + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dialogProps]) + + const addNewTool = async () => { + try { + const obj = { + name: toolName, + description: toolDesc, + color: generateRandomGradient(), + schema: JSON.stringify(toolSchema), + func: toolFunc + } + const createResp = await toolsApi.createNewTool(obj) + if (createResp.data) { + enqueueSnackbar({ + message: 'New Tool added', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm(createResp.data.id) + } + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to add new Tool: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + + const saveTool = async () => { + try { + const saveResp = await toolsApi.updateTool(toolId, { + name: toolName, + description: toolDesc, + schema: JSON.stringify(toolSchema), + func: toolFunc + }) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Tool saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm(saveResp.data.id) + } + } catch (error) { + console.error(error) + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to save Tool: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + + const deleteTool = async () => { + const confirmPayload = { + title: `Delete Tool`, + description: `Delete tool ${toolName}?`, + confirmButtonName: 'Delete', + cancelButtonName: 'Cancel' + } + const isConfirmed = await confirm(confirmPayload) + + if (isConfirmed) { + try { + const delResp = await toolsApi.deleteTool(toolId) + if (delResp.data) { + enqueueSnackbar({ + message: 'Tool deleted', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm() + } + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to delete Tool: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + } + + const component = show ? ( + + + {dialogProps.title} + + + + + + Tool Name +  * + + + setToolName(e.target.value)} + /> + + + + + Tool description +  * + + + setToolDesc(e.target.value)} + /> + + + + + Output Schema + + + + + + + + + Javascript Function + + + + + {customization.isDarkMode ? ( + setToolFunc(code)} + style={{ + fontSize: '0.875rem', + minHeight: 'calc(100vh - 220px)', + width: '100%', + borderRadius: 5 + }} + /> + ) : ( + setToolFunc(code)} + style={{ + fontSize: '0.875rem', + minHeight: 'calc(100vh - 220px)', + width: '100%', + border: `1px solid ${theme.palette.grey[300]}`, + borderRadius: 5 + }} + /> + )} + + + + {dialogProps.type === 'EDIT' && ( + deleteTool()}> + Delete + + )} + (dialogProps.type === 'ADD' ? addNewTool() : saveTool())} + > + {dialogProps.confirmButtonName} + + + + + ) : null + + return createPortal(component, portalElement) +} + +ToolDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func, + onConfirm: PropTypes.func +} + +export default ToolDialog diff --git a/packages/ui/src/views/tools/index.js b/packages/ui/src/views/tools/index.js new file mode 100644 index 00000000..efe9e69d --- /dev/null +++ b/packages/ui/src/views/tools/index.js @@ -0,0 +1,112 @@ +import { useEffect, useState } from 'react' +import { useSelector } from 'react-redux' + +// material-ui +import { Grid, Box, Stack } from '@mui/material' +import { useTheme } from '@mui/material/styles' + +// project imports +import MainCard from 'ui-component/cards/MainCard' +import ItemCard from 'ui-component/cards/ItemCard' +import { gridSpacing } from 'store/constant' +import ToolEmptySVG from 'assets/images/tools_empty.svg' +import { StyledButton } from 'ui-component/button/StyledButton' +import ToolDialog from './ToolDialog' + +// API +import toolsApi from 'api/tools' + +// Hooks +import useApi from 'hooks/useApi' + +// icons +import { IconPlus } from '@tabler/icons' + +// ==============================|| CHATFLOWS ||============================== // + +const Tools = () => { + const theme = useTheme() + const customization = useSelector((state) => state.customization) + + const getAllToolsApi = useApi(toolsApi.getAllTools) + + const [showDialog, setShowDialog] = useState(false) + const [dialogProps, setDialogProps] = useState({}) + + const addNew = () => { + const dialogProp = { + title: 'Add New Tool', + type: 'ADD', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add' + } + setDialogProps(dialogProp) + setShowDialog(true) + } + + const edit = (selectedTool) => { + const dialogProp = { + title: 'Edit Tool', + type: 'EDIT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Save', + data: selectedTool + } + setDialogProps(dialogProp) + setShowDialog(true) + } + + const onConfirm = () => { + setShowDialog(false) + getAllToolsApi.request() + } + + useEffect(() => { + getAllToolsApi.request() + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return ( + <> + + +

Tools

+ + + + }> + Create New + + + +
+ + {!getAllToolsApi.loading && + getAllToolsApi.data && + getAllToolsApi.data.map((data, index) => ( + + edit(data)} /> + + ))} + + {!getAllToolsApi.loading && (!getAllToolsApi.data || getAllToolsApi.data.length === 0) && ( + + + ToolEmptySVG + +
No Tools Created Yet
+
+ )} +
+ setShowDialog(false)} + onConfirm={onConfirm} + > + + ) +} + +export default Tools From bee16d4982819741fcf5a78308bc79bf32b55a17 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Wed, 21 Jun 2023 19:29:32 +0100 Subject: [PATCH 122/398] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index cd443a19..fa9d527b 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +github: [FlowiseAI] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username From 412539a9db9f865d0ed9142368d65c440cf94427 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 21 Jun 2023 20:56:25 +0100 Subject: [PATCH 123/398] update openai agent --- .../OpenAIFunctionAgent.ts | 14 +- .../server/marketplaces/OpenAI Agent.json | 322 +++++++++++++----- 2 files changed, 244 insertions(+), 92 deletions(-) diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index 4c740874..1cbcb547 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -42,6 +42,14 @@ class OpenAIFunctionAgent_Agents implements INode { description: 'Only works with gpt-3.5-turbo-0613 and gpt-4-0613. Refer docs for more info', type: 'BaseChatModel' + }, + { + label: 'System Message', + name: 'systemMessage', + type: 'string', + rows: 4, + optional: true, + additionalParams: true } ] } @@ -49,13 +57,17 @@ class OpenAIFunctionAgent_Agents implements INode { async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as BaseLanguageModel const memory = nodeData.inputs?.memory as BaseChatMemory + const systemMessage = nodeData.inputs?.systemMessage as string let tools = nodeData.inputs?.tools tools = flatten(tools) const executor = await initializeAgentExecutorWithOptions(tools, model, { agentType: 'openai-functions', - verbose: process.env.DEBUG === 'true' ? true : false + verbose: process.env.DEBUG === 'true' ? true : false, + agentArgs: { + prefix: systemMessage ?? `You are a helpful AI assistant.` + } }) if (memory) executor.memory = memory diff --git a/packages/server/marketplaces/OpenAI Agent.json b/packages/server/marketplaces/OpenAI Agent.json index 7e685546..75dc1527 100644 --- a/packages/server/marketplaces/OpenAI Agent.json +++ b/packages/server/marketplaces/OpenAI Agent.json @@ -6,8 +6,8 @@ "height": 524, "id": "chatOpenAI_0", "position": { - "x": 373.8366297840716, - "y": 448.58765780622326 + "x": 648.7470970481406, + "y": 462.3331811694268 }, "type": "customNode", "data": { @@ -34,33 +34,29 @@ "label": "gpt-4", "name": "gpt-4" }, + { + "label": "gpt-4-0314", + "name": "gpt-4-0314" + }, + { + "label": "gpt-4-32k-0314", + "name": "gpt-4-32k-0314" + }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, - { - "label": "gpt-4-32k", - "name": "gpt-4-32k" - }, - { - "label": "gpt-4-32k-0613", - "name": "gpt-4-32k-0613" - }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, + { + "label": "gpt-3.5-turbo-0301", + "name": "gpt-3.5-turbo-0301" + }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" - }, - { - "label": "gpt-3.5-turbo-16k", - "name": "gpt-3.5-turbo-16k" - }, - { - "label": "gpt-3.5-turbo-16k-0613", - "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", @@ -148,64 +144,8 @@ }, "selected": false, "positionAbsolute": { - "x": 373.8366297840716, - "y": 448.58765780622326 - }, - "dragging": false - }, - { - "width": 300, - "height": 280, - "id": "openAIFunctionAgent_0", - "position": { - "x": 1084.5405852317417, - "y": 384.4653768834282 - }, - "type": "customNode", - "data": { - "id": "openAIFunctionAgent_0", - "label": "OpenAI Function Agent", - "name": "openAIFunctionAgent", - "type": "AgentExecutor", - "baseClasses": ["AgentExecutor", "BaseChain", "BaseLangChain", "Serializable"], - "category": "Agents", - "description": "An agent that uses OpenAI's Function Calling functionality to pick the tool and args to call", - "inputParams": [], - "inputAnchors": [ - { - "label": "Allowed Tools", - "name": "tools", - "type": "Tool", - "list": true, - "id": "openAIFunctionAgent_0-input-tools-Tool" - }, - { - "label": "OpenAI Chat Model", - "name": "model", - "description": "Only works with gpt-3.5-turbo-0613 and gpt-4-0613. Refer docs for more info", - "type": "BaseChatModel", - "id": "openAIFunctionAgent_0-input-model-BaseChatModel" - } - ], - "inputs": { - "tools": ["{{calculator_0.data.instance}}", "{{serper_0.data.instance}}"], - "model": "{{chatOpenAI_0.data.instance}}" - }, - "outputAnchors": [ - { - "id": "openAIFunctionAgent_0-output-openAIFunctionAgent-AgentExecutor|BaseChain|BaseLangChain|Serializable", - "name": "openAIFunctionAgent", - "label": "AgentExecutor", - "type": "AgentExecutor | BaseChain | BaseLangChain | Serializable" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 1084.5405852317417, - "y": 384.4653768834282 + "x": 648.7470970481406, + "y": 462.3331811694268 }, "dragging": false }, @@ -214,8 +154,8 @@ "height": 278, "id": "serper_0", "position": { - "x": 691.7580226065319, - "y": 34.00444633899792 + "x": 486.27248799490576, + "y": 4.465900738576664 }, "type": "customNode", "data": { @@ -249,8 +189,8 @@ }, "selected": false, "positionAbsolute": { - "x": 691.7580226065319, - "y": 34.00444633899792 + "x": 486.27248799490576, + "y": 4.465900738576664 }, "dragging": false }, @@ -259,8 +199,8 @@ "height": 143, "id": "calculator_0", "position": { - "x": 341.63347110886497, - "y": 261.6753474034481 + "x": 286.4092336819905, + "y": 304.05673891709597 }, "type": "customNode", "data": { @@ -287,20 +227,198 @@ }, "selected": false, "positionAbsolute": { - "x": 341.63347110886497, - "y": 261.6753474034481 + "x": 286.4092336819905, + "y": 304.05673891709597 + }, + "dragging": false + }, + { + "width": 300, + "height": 383, + "id": "openAIFunctionAgent_0", + "position": { + "x": 1341.2259105169032, + "y": 318.35651549722945 + }, + "type": "customNode", + "data": { + "id": "openAIFunctionAgent_0", + "label": "OpenAI Function Agent", + "name": "openAIFunctionAgent", + "type": "AgentExecutor", + "baseClasses": ["AgentExecutor", "BaseChain"], + "category": "Agents", + "description": "An agent that uses OpenAI's Function Calling functionality to pick the tool and args to call", + "inputParams": [ + { + "label": "System Message", + "name": "systemMessage", + "type": "string", + "rows": 4, + "optional": true, + "additionalParams": true, + "id": "openAIFunctionAgent_0-input-systemMessage-string" + } + ], + "inputAnchors": [ + { + "label": "Allowed Tools", + "name": "tools", + "type": "Tool", + "list": true, + "id": "openAIFunctionAgent_0-input-tools-Tool" + }, + { + "label": "Memory", + "name": "memory", + "type": "BaseChatMemory", + "id": "openAIFunctionAgent_0-input-memory-BaseChatMemory" + }, + { + "label": "OpenAI Chat Model", + "name": "model", + "description": "Only works with gpt-3.5-turbo-0613 and gpt-4-0613. Refer docs for more info", + "type": "BaseChatModel", + "id": "openAIFunctionAgent_0-input-model-BaseChatModel" + } + ], + "inputs": { + "tools": ["{{serper_0.data.instance}}", "{{calculator_0.data.instance}}", "{{customTool_0.data.instance}}"], + "memory": "{{bufferMemory_0.data.instance}}", + "model": "{{chatOpenAI_0.data.instance}}", + "systemMessage": "" + }, + "outputAnchors": [ + { + "id": "openAIFunctionAgent_0-output-openAIFunctionAgent-AgentExecutor|BaseChain", + "name": "openAIFunctionAgent", + "label": "AgentExecutor", + "type": "AgentExecutor | BaseChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1341.2259105169032, + "y": 318.35651549722945 + }, + "dragging": false + }, + { + "width": 300, + "height": 376, + "id": "bufferMemory_0", + "position": { + "x": 285.7750469157585, + "y": 465.1140427303788 + }, + "type": "customNode", + "data": { + "id": "bufferMemory_0", + "label": "Buffer Memory", + "name": "bufferMemory", + "type": "BufferMemory", + "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], + "category": "Memory", + "description": "Remembers previous conversational back and forths directly", + "inputParams": [ + { + "label": "Memory Key", + "name": "memoryKey", + "type": "string", + "default": "chat_history", + "id": "bufferMemory_0-input-memoryKey-string" + }, + { + "label": "Input Key", + "name": "inputKey", + "type": "string", + "default": "input", + "id": "bufferMemory_0-input-inputKey-string" + } + ], + "inputAnchors": [], + "inputs": { + "memoryKey": "chat_history", + "inputKey": "input" + }, + "outputAnchors": [ + { + "id": "bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory", + "name": "bufferMemory", + "label": "BufferMemory", + "type": "BufferMemory | BaseChatMemory | BaseMemory" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 285.7750469157585, + "y": 465.1140427303788 + }, + "dragging": false + }, + { + "width": 300, + "height": 277, + "id": "customTool_0", + "position": { + "x": 883.9529939431576, + "y": -32.32503903826486 + }, + "type": "customNode", + "data": { + "id": "customTool_0", + "label": "Custom Tool", + "name": "customTool", + "type": "CustomTool", + "baseClasses": ["CustomTool", "Tool", "StructuredTool"], + "category": "Tools", + "description": "Use custom tool you've created in Flowise within chatflow", + "inputParams": [ + { + "label": "Select Tool", + "name": "selectedTool", + "type": "asyncOptions", + "loadMethod": "listTools", + "id": "customTool_0-input-selectedTool-asyncOptions" + } + ], + "inputAnchors": [], + "inputs": { + "selectedTool": "" + }, + "outputAnchors": [ + { + "id": "customTool_0-output-customTool-CustomTool|Tool|StructuredTool", + "name": "customTool", + "label": "CustomTool", + "type": "CustomTool | Tool | StructuredTool" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 883.9529939431576, + "y": -32.32503903826486 }, "dragging": false } ], "edges": [ { - "source": "chatOpenAI_0", - "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain|Serializable", + "source": "serper_0", + "sourceHandle": "serper_0-output-serper-Serper|Tool|StructuredTool|BaseLangChain|Serializable", "target": "openAIFunctionAgent_0", - "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel", + "targetHandle": "openAIFunctionAgent_0-input-tools-Tool", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain|Serializable-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-BaseChatModel", + "id": "serper_0-serper_0-output-serper-Serper|Tool|StructuredTool|BaseLangChain|Serializable-openAIFunctionAgent_0-openAIFunctionAgent_0-input-tools-Tool", "data": { "label": "" } @@ -317,12 +435,34 @@ } }, { - "source": "serper_0", - "sourceHandle": "serper_0-output-serper-Serper|Tool|StructuredTool|BaseLangChain|Serializable", + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain|Serializable", + "target": "openAIFunctionAgent_0", + "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain|Serializable-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-BaseChatModel", + "data": { + "label": "" + } + }, + { + "source": "bufferMemory_0", + "sourceHandle": "bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory", + "target": "openAIFunctionAgent_0", + "targetHandle": "openAIFunctionAgent_0-input-memory-BaseChatMemory", + "type": "buttonedge", + "id": "bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-openAIFunctionAgent_0-openAIFunctionAgent_0-input-memory-BaseChatMemory", + "data": { + "label": "" + } + }, + { + "source": "customTool_0", + "sourceHandle": "customTool_0-output-customTool-CustomTool|Tool|StructuredTool", "target": "openAIFunctionAgent_0", "targetHandle": "openAIFunctionAgent_0-input-tools-Tool", "type": "buttonedge", - "id": "serper_0-serper_0-output-serper-Serper|Tool|StructuredTool|BaseLangChain|Serializable-openAIFunctionAgent_0-openAIFunctionAgent_0-input-tools-Tool", + "id": "customTool_0-customTool_0-output-customTool-CustomTool|Tool|StructuredTool-openAIFunctionAgent_0-openAIFunctionAgent_0-input-tools-Tool", "data": { "label": "" } From 7ce0f71e2fc53cf9180c5af8659611b1b39d4c0f Mon Sep 17 00:00:00 2001 From: ivalkshfoeif Date: Wed, 21 Jun 2023 18:56:37 -0700 Subject: [PATCH 124/398] add JSONLines Loader --- .../documentloaders/Jsonlines/Jsonlines.ts | 105 ++++++++++++++++++ .../documentloaders/Jsonlines/jsonlines.svg | 16 +++ 2 files changed, 121 insertions(+) create mode 100644 packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts create mode 100644 packages/components/nodes/documentloaders/Jsonlines/jsonlines.svg diff --git a/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts b/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts new file mode 100644 index 00000000..e92a97aa --- /dev/null +++ b/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts @@ -0,0 +1,105 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { TextSplitter } from 'langchain/text_splitter' +import { JSONLinesLoader } from 'langchain/document_loaders/fs/json' + +class Jsonlines_DocumentLoaders implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Json Lines File' + this.name = 'jsonlinesFile' + this.type = 'Document' + this.icon = 'jsonlines.svg' + this.category = 'Document Loaders' + this.description = `Load data from JSON Lines files` + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Jsonlines File', + name: 'jsonlinesFile', + type: 'file', + fileType: '.jsonl' + }, + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + label: 'Pointer Extraction', + name: 'pointerName', + type: 'string', + description: 'Extracting the pointer', + placeholder: 'Enter pointer name', + optional: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const textSplitter = nodeData.inputs?.textSplitter as TextSplitter + const jsonLinesFileBase64 = nodeData.inputs?.jsonlinesFile as string + const pointerName = nodeData.inputs?.pointerName as string + const metadata = nodeData.inputs?.metadata + + let alldocs = [] + let files: string[] = [] + + if (jsonLinesFileBase64.startsWith('[') && jsonLinesFileBase64.endsWith(']')) { + files = JSON.parse(jsonLinesFileBase64) + } else { + files = [jsonLinesFileBase64] + } + + for (const file of files) { + const splitDataURI = file.split(',') + splitDataURI.pop() + const bf = Buffer.from(splitDataURI.pop() || '', 'base64') + const blob = new Blob([bf]) + const loader = new JSONLinesLoader(blob, pointerName) + + if (textSplitter) { + const docs = await loader.loadAndSplit(textSplitter) + alldocs.push(...docs) + } else { + const docs = await loader.load() + alldocs.push(...docs) + } + } + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + let finaldocs = [] + for (const doc of alldocs) { + const newdoc = { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + finaldocs.push(newdoc) + } + return finaldocs + } + + return alldocs + } +} + +module.exports = { nodeClass: Jsonlines_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/Jsonlines/jsonlines.svg b/packages/components/nodes/documentloaders/Jsonlines/jsonlines.svg new file mode 100644 index 00000000..f3686f0c --- /dev/null +++ b/packages/components/nodes/documentloaders/Jsonlines/jsonlines.svg @@ -0,0 +1,16 @@ + + + + + background + + + + + + + Layer 1 + JSON + Lines + + \ No newline at end of file From 7646e973e3a77d8f4500f82d3e70ba51bd203048 Mon Sep 17 00:00:00 2001 From: ivalkshfoeif Date: Wed, 21 Jun 2023 19:34:30 -0700 Subject: [PATCH 125/398] update pointer attribute and logic --- .../nodes/documentloaders/Jsonlines/Jsonlines.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts b/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts index e92a97aa..4af8c2ce 100644 --- a/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts +++ b/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts @@ -37,9 +37,8 @@ class Jsonlines_DocumentLoaders implements INode { label: 'Pointer Extraction', name: 'pointerName', type: 'string', - description: 'Extracting the pointer', placeholder: 'Enter pointer name', - optional: true + optional: false }, { label: 'Metadata', @@ -60,6 +59,8 @@ class Jsonlines_DocumentLoaders implements INode { let alldocs = [] let files: string[] = [] + let pointer = '/' + pointerName.trim() + if (jsonLinesFileBase64.startsWith('[') && jsonLinesFileBase64.endsWith(']')) { files = JSON.parse(jsonLinesFileBase64) } else { @@ -71,7 +72,7 @@ class Jsonlines_DocumentLoaders implements INode { splitDataURI.pop() const bf = Buffer.from(splitDataURI.pop() || '', 'base64') const blob = new Blob([bf]) - const loader = new JSONLinesLoader(blob, pointerName) + const loader = new JSONLinesLoader(blob, pointer) if (textSplitter) { const docs = await loader.loadAndSplit(textSplitter) From dd328dcd51f73cb2666e35805f684f8a15d1b843 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 22 Jun 2023 15:47:03 +0100 Subject: [PATCH 126/398] add db_path --- docker/.env.example | 2 ++ docker/docker-compose.yml | 2 ++ packages/server/.env.example | 1 + packages/server/README.md | 24 +++++++++++++++++++----- packages/server/src/DataSource.ts | 2 +- packages/server/src/commands/start.ts | 2 ++ 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index f3211196..e313316d 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -1,3 +1,5 @@ PORT=3000 # FLOWISE_USERNAME=user # FLOWISE_PASSWORD=1234 +# DATABASE_PATH=/your_database_path/.flowise +# EXECUTION_MODE=child or main \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index c776f96e..0bb68097 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -8,6 +8,8 @@ services: - PORT=${PORT} - FLOWISE_USERNAME=${FLOWISE_USERNAME} - FLOWISE_PASSWORD=${FLOWISE_PASSWORD} + - DATABASE_PATH=${DATABASE_PATH} + - EXECUTION_MODE=${EXECUTION_MODE} ports: - '${PORT}:${PORT}' volumes: diff --git a/packages/server/.env.example b/packages/server/.env.example index fd82c096..e313316d 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -1,4 +1,5 @@ PORT=3000 # FLOWISE_USERNAME=user # FLOWISE_PASSWORD=1234 +# DATABASE_PATH=/your_database_path/.flowise # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/packages/server/README.md b/packages/server/README.md index 2cdf41d1..e4d1e439 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -31,14 +31,28 @@ FLOWISE_PASSWORD=1234 ## 📖 Documentation -Coming Soon - -## 💻 Cloud Hosted - -Coming Soon +[Flowise Docs](https://docs.flowiseai.com/) ## 🌐 Self Host +### [Railway](https://docs.flowiseai.com/deployment/railway) + +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/YK7J0v) + +### [Render](https://docs.flowiseai.com/deployment/render) + +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) + +### [AWS](https://docs.flowiseai.com/deployment/aws) + +### [Azure](https://docs.flowiseai.com/deployment/azure) + +### [DigitalOcean](https://docs.flowiseai.com/deployment/digital-ocean) + +### [GCP](https://docs.flowiseai.com/deployment/gcp) + +## 💻 Cloud Hosted + Coming Soon ## 🙋 Support diff --git a/packages/server/src/DataSource.ts b/packages/server/src/DataSource.ts index 76c8e144..19396315 100644 --- a/packages/server/src/DataSource.ts +++ b/packages/server/src/DataSource.ts @@ -8,7 +8,7 @@ import { getUserHome } from './utils' let appDataSource: DataSource export const init = async (): Promise => { - const homePath = path.join(getUserHome(), '.flowise') + const homePath = process.env.DATABASE_PATH ?? path.join(getUserHome(), '.flowise') appDataSource = new DataSource({ type: 'sqlite', diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index 94b8d995..9066f1cf 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -18,6 +18,7 @@ export default class Start extends Command { FLOWISE_USERNAME: Flags.string(), FLOWISE_PASSWORD: Flags.string(), PORT: Flags.string(), + DATABASE_PATH: Flags.string(), EXECUTION_MODE: Flags.string() } @@ -53,6 +54,7 @@ export default class Start extends Command { if (flags.FLOWISE_USERNAME) process.env.FLOWISE_USERNAME = flags.FLOWISE_USERNAME if (flags.FLOWISE_PASSWORD) process.env.FLOWISE_PASSWORD = flags.FLOWISE_PASSWORD if (flags.PORT) process.env.PORT = flags.PORT + if (flags.DATABASE_PATH) process.env.DATABASE_PATH = flags.DATABASE_PATH if (flags.EXECUTION_MODE) process.env.EXECUTION_MODE = flags.EXECUTION_MODE await (async () => { From 4a63b68bbe4d7c63d47d45c8168bc3e95ddef709 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 22 Jun 2023 16:22:12 +0100 Subject: [PATCH 127/398] add DATABSE_PATH --- packages/server/src/ChildProcess.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index 95f7368a..e8aeaff2 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -128,7 +128,7 @@ export class ChildProcess { * @returns {DataSource} */ async function initDB() { - const homePath = path.join(getUserHome(), '.flowise') + const homePath = process.env.DATABASE_PATH ?? path.join(getUserHome(), '.flowise') const childAppDataSource = new DataSource({ type: 'sqlite', database: path.resolve(homePath, 'database.sqlite'), From 60eb5254189992d9246016b943de8d1c29e514d0 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 22 Jun 2023 17:28:54 +0100 Subject: [PATCH 128/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.2.1?= =?UTF-8?q?4=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index ec97d4d7..e5e0ba00 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.13", + "version": "1.2.14", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From de922e1a71afca98e5e5c7a1ec7ed34795d73093 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 22 Jun 2023 17:30:28 +0100 Subject: [PATCH 129/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.2.12=20rele?= =?UTF-8?q?ase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 1d7bc490..258b5471 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.11", + "version": "1.2.12", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From 160aa87aba27a573e38d141c6bbf076199e3c09a Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 22 Jun 2023 17:31:01 +0100 Subject: [PATCH 130/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.2.13=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fc08e450..0ff76284 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.12", + "version": "1.2.13", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index e7a8bc61..eda69322 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.12", + "version": "1.2.13", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 2c0a8723f941fba6d956f5d5c4a5e89fd11db80b Mon Sep 17 00:00:00 2001 From: drobnikj Date: Fri, 23 Jun 2023 13:41:59 +0200 Subject: [PATCH 131/398] feat: add document loader for Apify Website Content Crawler --- .../ApifyWebsiteContentCrawler.ts | 68 +++++++++++++++++++ .../apify-symbol-transparent.svg | 1 + packages/components/package.json | 1 + 3 files changed, 70 insertions(+) create mode 100644 packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts create mode 100644 packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/apify-symbol-transparent.svg diff --git a/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts b/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts new file mode 100644 index 00000000..f292ada3 --- /dev/null +++ b/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts @@ -0,0 +1,68 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { TextSplitter } from 'langchain/text_splitter' +import { ApifyDatasetLoader } from 'langchain/document_loaders/web/apify_dataset' +import { Document } from 'langchain/document' + +class ApifyWebsiteContentCrawler_DocumentLoaders implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Apify Website Content Crawler' + this.name = 'apifyWebsiteContentCrawler' + this.type = 'Document' + this.icon = 'apify-symbol-transparent.svg' + this.category = 'Document Loaders' + this.description = 'Load data from Apify Website Content Crawler' + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Apify API Token', + name: 'apifyApiToken', + type: 'password' + }, + { + label: 'Input', + name: 'input', + type: 'json', + default: JSON.stringify({ + startUrls: [{ url: 'https://js.langchain.com/docs/' }], + maxCrawlPages: 1 + }) + }, + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const textSplitter = nodeData.inputs?.textSplitter as TextSplitter + const apifyApiToken = nodeData.inputs?.apifyApiToken as string + const input = typeof nodeData.inputs?.input === 'object' ? nodeData.inputs?.input : JSON.parse(nodeData.inputs?.input as string) + + const loader = await ApifyDatasetLoader.fromActorCall('apify/website-content-crawler', input, { + datasetMappingFunction: (item) => + new Document({ + pageContent: (item.text || '') as string, + metadata: { source: item.url } + }), + clientOptions: { + token: apifyApiToken + } + }) + + return textSplitter ? loader.loadAndSplit(textSplitter) : loader.load() + } +} + +module.exports = { nodeClass: ApifyWebsiteContentCrawler_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/apify-symbol-transparent.svg b/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/apify-symbol-transparent.svg new file mode 100644 index 00000000..423a3328 --- /dev/null +++ b/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/apify-symbol-transparent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index e5e0ba00..bc55fb70 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -22,6 +22,7 @@ "@pinecone-database/pinecone": "^0.0.12", "@supabase/supabase-js": "^2.21.0", "@types/js-yaml": "^4.0.5", + "apify-client": "^2.7.1", "axios": "^0.27.2", "cheerio": "^1.0.0-rc.12", "chromadb": "^1.4.2", From 8745776342e5d5379846f2126a68b037b4e72f64 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 23 Jun 2023 23:03:53 +0100 Subject: [PATCH 132/398] add env variables --- docker/.env.example | 1 + docker/docker-compose.yml | 1 + packages/components/.env.example | 1 - packages/components/README.md | 8 -------- packages/server/.env.example | 1 + packages/server/README.md | 4 ++++ packages/server/src/commands/start.ts | 2 ++ 7 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 packages/components/.env.example diff --git a/docker/.env.example b/docker/.env.example index e313316d..3d524e5c 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -1,5 +1,6 @@ PORT=3000 # FLOWISE_USERNAME=user # FLOWISE_PASSWORD=1234 +# DEBUG=true # DATABASE_PATH=/your_database_path/.flowise # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 0bb68097..7ab43142 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -10,6 +10,7 @@ services: - FLOWISE_PASSWORD=${FLOWISE_PASSWORD} - DATABASE_PATH=${DATABASE_PATH} - EXECUTION_MODE=${EXECUTION_MODE} + - DEBUG=${DEBUG} ports: - '${PORT}:${PORT}' volumes: diff --git a/packages/components/.env.example b/packages/components/.env.example deleted file mode 100644 index 352bc6cb..00000000 --- a/packages/components/.env.example +++ /dev/null @@ -1 +0,0 @@ -DEBUG=true \ No newline at end of file diff --git a/packages/components/README.md b/packages/components/README.md index 8014661e..5b564bec 100644 --- a/packages/components/README.md +++ b/packages/components/README.md @@ -12,14 +12,6 @@ Install: npm i flowise-components ``` -## Debug - -To view all the logs, create an `.env` file and add: - -``` -DEBUG=true -``` - ## License Source code in this repository is made available under the [MIT License](https://github.com/FlowiseAI/Flowise/blob/master/LICENSE.md). diff --git a/packages/server/.env.example b/packages/server/.env.example index e313316d..3d524e5c 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -1,5 +1,6 @@ PORT=3000 # FLOWISE_USERNAME=user # FLOWISE_PASSWORD=1234 +# DEBUG=true # DATABASE_PATH=/your_database_path/.flowise # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/packages/server/README.md b/packages/server/README.md index e4d1e439..74ba9a25 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -29,6 +29,10 @@ FLOWISE_USERNAME=user FLOWISE_PASSWORD=1234 ``` +## 🔎 Debugging + +You can set `DEBUG=true` to the `.env` file. Refer [here](https://docs.flowiseai.com/environment-variables) for full list of env variables + ## 📖 Documentation [Flowise Docs](https://docs.flowiseai.com/) diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index 9066f1cf..0f64322b 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -18,6 +18,7 @@ export default class Start extends Command { FLOWISE_USERNAME: Flags.string(), FLOWISE_PASSWORD: Flags.string(), PORT: Flags.string(), + DEBUG: Flags.string(), DATABASE_PATH: Flags.string(), EXECUTION_MODE: Flags.string() } @@ -56,6 +57,7 @@ export default class Start extends Command { if (flags.PORT) process.env.PORT = flags.PORT if (flags.DATABASE_PATH) process.env.DATABASE_PATH = flags.DATABASE_PATH if (flags.EXECUTION_MODE) process.env.EXECUTION_MODE = flags.EXECUTION_MODE + if (flags.DEBUG) process.env.DEBUG = flags.DEBUG await (async () => { try { From 40662b087a56e66154c87a9c8279b67ef5d82ddc Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 24 Jun 2023 01:22:39 +0100 Subject: [PATCH 133/398] add fix --- packages/ui/src/ui-component/input/Input.js | 1 + packages/ui/src/views/canvas/NodeInputHandler.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index 1861bf65..7f0e0610 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -37,6 +37,7 @@ export const Input = ({ inputParam, value, onChange, disabled = false, showDialo onChange(e.target.value) }} inputProps={{ + step: 0.1, style: { height: inputParam.rows ? '90px' : 'inherit' } diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 31a8a37d..4ad21904 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -205,6 +205,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA )} {(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && ( (data.inputs[inputParam.name] = newValue)} From 0fb1d8a1ff056e1462954d396b40c6a70dbf8b07 Mon Sep 17 00:00:00 2001 From: ivalkshfoeif Date: Fri, 23 Jun 2023 17:57:36 -0700 Subject: [PATCH 134/398] add redis-backed chat memory --- .../RedisBackedChatMemory.ts | 85 +++++++++++++++++++ .../memory/RedisBackedChatMemory/memory.svg | 8 ++ packages/components/package.json | 1 + 3 files changed, 94 insertions(+) create mode 100644 packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts create mode 100644 packages/components/nodes/memory/RedisBackedChatMemory/memory.svg diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts new file mode 100644 index 00000000..155ea5f5 --- /dev/null +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -0,0 +1,85 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { ICommonObject } from '../../../src' +import { BufferMemory } from 'langchain/memory' +import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/redis' +import { createClient } from 'redis' + +class RedisBackedChatMemory_Memory implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Redis-Backed Chat Memory' + this.name = 'RedisBackedChatMemory' + this.type = 'RedisBackedChatMemory' + this.icon = 'memory.svg' + this.category = 'Memory' + this.description = 'Summarizes the conversation and stores the memory in Redis server' + this.baseClasses = [this.type, ...getBaseClasses(BufferMemory)] + this.inputs = [ + { + label: 'Base URL', + name: 'baseURL', + type: 'string', + default: 'redis://localhost:6379' + }, + { + label: 'Session Id', + name: 'sessionId', + type: 'string', + description: 'if empty, chatId will be used automatically', + default: '', + additionalParams: true + }, + { + label: 'Session Timeouts', + name: 'sessionTTL', + type: 'number', + description: 'Omit this parameter to make sessions never expire', + optional: true + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history' + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const baseURL = nodeData.inputs?.baseURL as string + const sessionId = nodeData.inputs?.sessionId as string + const sessionTTL = nodeData.inputs?.sessionTTL as number + const memoryKey = nodeData.inputs?.memoryKey as string + + const chatId = options?.chatId as string + + const redisClient = createClient({ url: baseURL }) + let obj: RedisChatMessageHistoryInput = { + sessionId: sessionId ? sessionId : chatId, + client: redisClient + } + + if (sessionTTL) { + obj = { + ...obj, + sessionTTL + } + } + + let redisChatMessageHistory = new RedisChatMessageHistory(obj) + let redis = new BufferMemory({ memoryKey, chatHistory: redisChatMessageHistory, returnMessages: true }) + + return redis + } +} + +module.exports = { nodeClass: RedisBackedChatMemory_Memory } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/memory.svg b/packages/components/nodes/memory/RedisBackedChatMemory/memory.svg new file mode 100644 index 00000000..ca8e17da --- /dev/null +++ b/packages/components/nodes/memory/RedisBackedChatMemory/memory.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index e5e0ba00..12210951 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -42,6 +42,7 @@ "pdfjs-dist": "^3.7.107", "playwright": "^1.35.0", "puppeteer": "^20.7.1", + "redis": "^4.6.7", "srt-parser-2": "^1.2.3", "vm2": "^3.9.19", "weaviate-ts-client": "^1.1.0", From 3481d7decab88b08f224fa26ab5376fe8fa539e3 Mon Sep 17 00:00:00 2001 From: Govind Kumar Date: Sat, 24 Jun 2023 11:56:27 +0530 Subject: [PATCH 135/398] added qdrant vector store integration --- .../Qdrant_Existing/Qdrant_Existing.ts | 119 ++++++++++++++++++ .../Qdrant_Existing/qdrant_logo.svg | 27 ++++ .../Qdrant_Upsert/Qdrant_Upsert.ts | 119 ++++++++++++++++++ .../Qdrant_Upsert/qdrant_logo.svg | 27 ++++ packages/components/package.json | 1 + 5 files changed, 293 insertions(+) create mode 100644 packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts create mode 100644 packages/components/nodes/vectorstores/Qdrant_Existing/qdrant_logo.svg create mode 100644 packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts create mode 100644 packages/components/nodes/vectorstores/Qdrant_Upsert/qdrant_logo.svg diff --git a/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts new file mode 100644 index 00000000..031917f5 --- /dev/null +++ b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts @@ -0,0 +1,119 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { QdrantClient } from '@qdrant/js-client-rest' +import { QdrantVectorStore, QdrantLibArgs } from 'langchain/vectorstores/qdrant' +import { Embeddings } from 'langchain/embeddings/base' +import { getBaseClasses } from '../../../src/utils' + +class Qdrant_Existing_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Qdrant Load Existing Index' + this.name = 'qdrantExistingIndex' + this.type = 'Qdrant' + this.icon = 'qdrant_logo.svg' + this.category = 'Vector Stores' + this.description = 'Load existing index from Qdrant (i.e., documents have been upserted)' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Qdrant Server URL', + name: 'qdrantServerUrl', + type: 'string' + }, + { + label: 'Qdrant Collection Name', + name: 'qdrantCollection', + type: 'string' + }, + { + label: 'Qdrant API Key', + name: 'qdrantApiKey', + type: 'password' + }, + { + label: 'Qdrant Collection Cofiguration', + name: 'qdrantCollectionCofiguration', + type: 'json', + optional: true, + + additionalParams: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'Qdrant Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Qdrant Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(QdrantVectorStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const qdrantServerUrl = nodeData.inputs?.qdrantServerUrl as string + const collectionName = nodeData.inputs?.qdrantCollection as string + const qdrantApiKey = nodeData.inputs?.qdrantApiKey as string + let qdrantCollectionCofiguration = nodeData.inputs?.qdrantCollectionCofiguration + const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 + + // connect to Qdrant Cloud + const client = new QdrantClient({ + url: qdrantServerUrl, + apiKey: qdrantApiKey + }) + + const dbConfig: QdrantLibArgs = { + client, + collectionName + } + + if (qdrantCollectionCofiguration) { + qdrantCollectionCofiguration = + typeof qdrantCollectionCofiguration === 'object' ? qdrantCollectionCofiguration : JSON.parse(qdrantCollectionCofiguration) + dbConfig.collectionConfig = qdrantCollectionCofiguration + } + + const vectorStore = await QdrantVectorStore.fromExistingCollection(embeddings, dbConfig) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: Qdrant_Existing_VectorStores } diff --git a/packages/components/nodes/vectorstores/Qdrant_Existing/qdrant_logo.svg b/packages/components/nodes/vectorstores/Qdrant_Existing/qdrant_logo.svg new file mode 100644 index 00000000..82fb8b39 --- /dev/null +++ b/packages/components/nodes/vectorstores/Qdrant_Existing/qdrant_logo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts b/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts new file mode 100644 index 00000000..111fc5c3 --- /dev/null +++ b/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts @@ -0,0 +1,119 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { QdrantClient } from '@qdrant/js-client-rest' +import { QdrantVectorStore, QdrantLibArgs } from 'langchain/vectorstores/qdrant' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { getBaseClasses } from '../../../src/utils' +import { flatten } from 'lodash' + +class QdrantUpsert_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Qdrant Upsert Document' + this.name = 'qdrantUpsert' + this.type = 'Qdrant' + this.icon = 'qdrant_logo.svg' + this.category = 'Vector Stores' + this.description = 'Upsert documents to Qdrant' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Document', + name: 'document', + type: 'Document', + list: true + }, + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Qdrant Server URL', + name: 'qdrantServerUrl', + type: 'string' + }, + { + label: 'Qdrant Collection Name', + name: 'qdrantCollection', + type: 'string' + }, + { + label: 'Qdrant API Key', + name: 'qdrantApiKey', + type: 'password' + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'Qdrant Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Qdrant Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(QdrantVectorStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const qdrantServerUrl = nodeData.inputs?.qdrantServerUrl as string + const collectionName = nodeData.inputs?.qdrantCollection as string + const qdrantApiKey = nodeData.inputs?.qdrantApiKey as string + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 + + // connect to Qdrant Cloud + const client = new QdrantClient({ + url: qdrantServerUrl, + apiKey: qdrantApiKey + }) + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + finalDocs.push(new Document(flattenDocs[i])) + } + + const dbConfig: QdrantLibArgs = { + client, + url: qdrantServerUrl, + collectionName + } + const vectorStore = await QdrantVectorStore.fromDocuments(finalDocs, embeddings, dbConfig) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: QdrantUpsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/Qdrant_Upsert/qdrant_logo.svg b/packages/components/nodes/vectorstores/Qdrant_Upsert/qdrant_logo.svg new file mode 100644 index 00000000..82fb8b39 --- /dev/null +++ b/packages/components/nodes/vectorstores/Qdrant_Upsert/qdrant_logo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/components/package.json b/packages/components/package.json index 738c7752..fd7e1b14 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -20,6 +20,7 @@ "@getzep/zep-js": "^0.3.1", "@huggingface/inference": "1", "@pinecone-database/pinecone": "^0.0.12", + "@qdrant/js-client-rest": "^1.2.2", "@supabase/supabase-js": "^2.21.0", "@types/js-yaml": "^4.0.5", "axios": "^0.27.2", From e554ac54dddf54b742eac1234e66957d534e726e Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 26 Jun 2023 02:37:40 +0100 Subject: [PATCH 136/398] add full page chatbot and react chatbot --- packages/server/src/Interface.ts | 1 + packages/server/src/entity/ChatFlow.ts | 3 + packages/ui/craco.config.js | 16 + packages/ui/package.json | 14 +- packages/ui/src/assets/images/sharing.png | Bin 0 -> 17473 bytes packages/ui/src/routes/ChatbotRoutes.js | 23 + packages/ui/src/routes/index.js | 3 +- packages/ui/src/views/canvas/CanvasHeader.js | 5 +- packages/ui/src/views/chatbot/index.js | 59 +++ .../chatflows}/APICodeDialog.js | 204 +++------ packages/ui/src/views/chatflows/EmbedChat.js | 324 ++++++++++++++ .../ui/src/views/chatflows/ShareChatbot.js | 420 ++++++++++++++++++ 12 files changed, 924 insertions(+), 148 deletions(-) create mode 100644 packages/ui/craco.config.js create mode 100644 packages/ui/src/assets/images/sharing.png create mode 100644 packages/ui/src/routes/ChatbotRoutes.js create mode 100644 packages/ui/src/views/chatbot/index.js rename packages/ui/src/{ui-component/dialog => views/chatflows}/APICodeDialog.js (73%) create mode 100644 packages/ui/src/views/chatflows/EmbedChat.js create mode 100644 packages/ui/src/views/chatflows/ShareChatbot.js diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 1eafcae6..9473638f 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -13,6 +13,7 @@ export interface IChatFlow { deployed: boolean updatedDate: Date createdDate: Date + chatbotConfig?: string } export interface IChatMessage { diff --git a/packages/server/src/entity/ChatFlow.ts b/packages/server/src/entity/ChatFlow.ts index d9b12929..910272ad 100644 --- a/packages/server/src/entity/ChatFlow.ts +++ b/packages/server/src/entity/ChatFlow.ts @@ -19,6 +19,9 @@ export class ChatFlow implements IChatFlow { @Column() deployed: boolean + @Column({ nullable: true }) + chatbotConfig?: string + @CreateDateColumn() createdDate: Date diff --git a/packages/ui/craco.config.js b/packages/ui/craco.config.js new file mode 100644 index 00000000..142305e0 --- /dev/null +++ b/packages/ui/craco.config.js @@ -0,0 +1,16 @@ +module.exports = { + webpack: { + configure: { + module: { + rules: [ + { + test: /\.m?js$/, + resolve: { + fullySpecified: false + } + } + ] + } + } + } +} diff --git a/packages/ui/package.json b/packages/ui/package.json index 258b5471..1e55f1c8 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -16,6 +16,8 @@ "@mui/x-data-grid": "^6.8.0", "@tabler/icons": "^1.39.1", "clsx": "^1.1.1", + "flowise-embed": "*", + "flowise-embed-react": "*", "formik": "^2.2.6", "framer-motion": "^4.1.13", "history": "^5.0.0", @@ -27,6 +29,7 @@ "prop-types": "^15.7.2", "react": "^18.2.0", "react-code-blocks": "^0.0.9-0", + "react-color": "^2.19.3", "react-datepicker": "^4.8.0", "react-device-detect": "^1.17.0", "react-dom": "^18.2.0", @@ -47,11 +50,11 @@ "yup": "^0.32.9" }, "scripts": { - "start": "react-scripts start", - "dev": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" + "start": "craco start", + "dev": "craco start", + "build": "craco build", + "test": "craco test", + "eject": "craco eject" }, "babel": { "presets": [ @@ -72,6 +75,7 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.15.8", + "@craco/craco": "^7.1.0", "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^12.8.3", diff --git a/packages/ui/src/assets/images/sharing.png b/packages/ui/src/assets/images/sharing.png new file mode 100644 index 0000000000000000000000000000000000000000..1e538f2efd29a6d0d6d5dd2c88ca04aa74fffa36 GIT binary patch literal 17473 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 Jx@Z3G{{f}$cL4wZ literal 0 HcmV?d00001 diff --git a/packages/ui/src/routes/ChatbotRoutes.js b/packages/ui/src/routes/ChatbotRoutes.js new file mode 100644 index 00000000..25d298d6 --- /dev/null +++ b/packages/ui/src/routes/ChatbotRoutes.js @@ -0,0 +1,23 @@ +import { lazy } from 'react' + +// project imports +import Loadable from 'ui-component/loading/Loadable' +import MinimalLayout from 'layout/MinimalLayout' + +// canvas routing +const ChatbotFull = Loadable(lazy(() => import('views/chatbot'))) + +// ==============================|| CANVAS ROUTING ||============================== // + +const ChatbotRoutes = { + path: '/', + element: , + children: [ + { + path: '/chatbot/:id', + element: + } + ] +} + +export default ChatbotRoutes diff --git a/packages/ui/src/routes/index.js b/packages/ui/src/routes/index.js index 15fe4dca..ff8c1920 100644 --- a/packages/ui/src/routes/index.js +++ b/packages/ui/src/routes/index.js @@ -3,10 +3,11 @@ import { useRoutes } from 'react-router-dom' // routes import MainRoutes from './MainRoutes' import CanvasRoutes from './CanvasRoutes' +import ChatbotRoutes from './ChatbotRoutes' import config from 'config' // ==============================|| ROUTING RENDER ||============================== // export default function ThemeRoutes() { - return useRoutes([MainRoutes, CanvasRoutes], config.basename) + return useRoutes([MainRoutes, CanvasRoutes, ChatbotRoutes], config.basename) } diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index 1f4a1f93..521aa9d3 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -13,7 +13,7 @@ import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck, // project imports import Settings from 'views/settings' import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog' -import APICodeDialog from 'ui-component/dialog/APICodeDialog' +import APICodeDialog from 'views/chatflows/APICodeDialog' // API import chatflowsApi from 'api/chatflows' @@ -107,7 +107,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl title: 'Embed in website or use as API', chatflowid: chatflow.id, chatflowApiKeyId: chatflow.apikeyid, - isFormDataRequired + isFormDataRequired, + chatbotConfig: chatflow.chatbotConfig }) setAPIDialogOpen(true) } diff --git a/packages/ui/src/views/chatbot/index.js b/packages/ui/src/views/chatbot/index.js new file mode 100644 index 00000000..b33bec2c --- /dev/null +++ b/packages/ui/src/views/chatbot/index.js @@ -0,0 +1,59 @@ +import { useEffect, useState } from 'react' +import { baseURL } from 'store/constant' +import axios from 'axios' +import { FullPageChat } from 'flowise-embed-react' + +// ==============================|| Chatbot ||============================== // + +const fetchChatflow = async ({ chatflowId }) => { + const username = localStorage.getItem('username') + const password = localStorage.getItem('password') + + let chatflow = await axios + .get(`${baseURL}/api/v1/chatflows/${chatflowId}`, { auth: username && password ? { username, password } : undefined }) + .then(async function (response) { + return response.data + }) + .catch(function (error) { + console.error(error) + }) + return chatflow +} + +const ChatbotFull = () => { + const URLpath = document.location.pathname.toString().split('/') + const chatflowId = URLpath[URLpath.length - 1] === 'chatbot' ? '' : URLpath[URLpath.length - 1] + + const [chatflow, setChatflow] = useState(null) + const [chatbotTheme, setChatbotTheme] = useState({}) + + useEffect(() => { + ;(async () => { + const fetchData = async () => { + let response = await fetchChatflow({ chatflowId }) + setChatflow(response) + if (response.chatbotConfig) { + try { + setChatbotTheme(JSON.parse(response.chatbotConfig)) + } catch (e) { + console.error(e) + setChatbotTheme({}) + } + } + } + fetchData() + })() + }, [chatflowId]) + + return ( + <> + {!chatflow || chatflow.apikeyid ? ( +

Invalid Chatbot

+ ) : ( + + )} + + ) +} + +export default ChatbotFull diff --git a/packages/ui/src/ui-component/dialog/APICodeDialog.js b/packages/ui/src/views/chatflows/APICodeDialog.js similarity index 73% rename from packages/ui/src/ui-component/dialog/APICodeDialog.js rename to packages/ui/src/views/chatflows/APICodeDialog.js index e64f4bf8..fea49909 100644 --- a/packages/ui/src/ui-component/dialog/APICodeDialog.js +++ b/packages/ui/src/views/chatflows/APICodeDialog.js @@ -9,6 +9,8 @@ import { CopyBlock, atomOneDark } from 'react-code-blocks' // Project import import { Dropdown } from 'ui-component/dropdown/Dropdown' +import ShareChatbot from './ShareChatbot' +import EmbedChat from './EmbedChat' // Const import { baseURL } from 'store/constant' @@ -19,6 +21,7 @@ import pythonSVG from 'assets/images/python.svg' import javascriptSVG from 'assets/images/javascript.svg' import cURLSVG from 'assets/images/cURL.svg' import EmbedSVG from 'assets/images/embed.svg' +import ShareChatbotSVG from 'assets/images/sharing.png' // API import apiKeyApi from 'api/apikey' @@ -119,77 +122,19 @@ const getConfigExamplesForCurl = (configData, bodyType) => { return finalStr } -const embedCode = (chatflowid) => { - return `` -} - -const embedCodeCustomization = (chatflowid) => { - return `` -} - const APICodeDialog = ({ show, dialogProps, onCancel }) => { const portalElement = document.getElementById('portal') const navigate = useNavigate() const dispatch = useDispatch() - const codes = ['Embed', 'Python', 'JavaScript', 'cURL'] + + const codes = ['Embed', 'Python', 'JavaScript', 'cURL', 'Share Chatbot'] const [value, setValue] = useState(0) const [keyOptions, setKeyOptions] = useState([]) const [apiKeys, setAPIKeys] = useState([]) const [chatflowApiKeyId, setChatflowApiKeyId] = useState('') const [selectedApiKey, setSelectedApiKey] = useState({}) const [checkboxVal, setCheckbox] = useState(false) - const [embedChatCheckboxVal, setEmbedChatCheckbox] = useState(false) + const [chatbotConfig, setChatbotConfig] = useState(null) const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys) const updateChatflowApi = useApi(chatflowsApi.updateChatflow) @@ -203,10 +148,6 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { } } - const onCheckBoxEmbedChatChanged = (newVal) => { - setEmbedChatCheckbox(newVal) - } - const onApiKeySelected = (keyValue) => { if (keyValue === 'addnewkey') { navigate('/apikey') @@ -265,8 +206,6 @@ query({"question": "Hey, how are you?"}).then((response) => { return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\ -X POST \\ -d '{"question": "Hey, how are you?"}'` - } else if (codeLang === 'Embed') { - return embedCode(dialogProps.chatflowid) } return '' } @@ -309,8 +248,6 @@ query({"question": "Hey, how are you?"}).then((response) => { -X POST \\ -d '{"question": "Hey, how are you?"}' \\ -H "Authorization: Bearer ${selectedApiKey?.apiKey}"` - } else if (codeLang === 'Embed') { - return embedCode(dialogProps.chatflowid) } return '' } @@ -318,7 +255,7 @@ query({"question": "Hey, how are you?"}).then((response) => { const getLang = (codeLang) => { if (codeLang === 'Python') { return 'python' - } else if (codeLang === 'JavaScript' || codeLang === 'Embed') { + } else if (codeLang === 'JavaScript') { return 'javascript' } else if (codeLang === 'cURL') { return 'bash' @@ -335,6 +272,8 @@ query({"question": "Hey, how are you?"}).then((response) => { return EmbedSVG } else if (codeLang === 'cURL') { return cURLSVG + } else if (codeLang === 'Share Chatbot') { + return ShareChatbotSVG } return pythonSVG } @@ -552,6 +491,12 @@ query({ setChatflowApiKeyId(dialogProps.chatflowApiKeyId) setSelectedApiKey(getAllAPIKeysApi.data.find((key) => key.id === dialogProps.chatflowApiKeyId)) } + + if (dialogProps.chatbotConfig) { + setChatbotConfig(JSON.parse(dialogProps.chatbotConfig)) + } else { + setChatbotConfig(null) + } } }, [dialogProps, getAllAPIKeysApi.data]) @@ -593,92 +538,71 @@ query({ ))}
- {value !== 0 && ( -
- onApiKeySelected(newValue)} - value={dialogProps.chatflowApiKeyId ?? chatflowApiKeyId ?? 'Choose an API key'} - /> -
- )} +
+ onApiKeySelected(newValue)} + value={dialogProps.chatflowApiKeyId ?? chatflowApiKeyId ?? 'Choose an API key'} + /> +
{codes.map((codeLang, index) => ( - {value === 0 && ( + {(codeLang === 'Embed' || codeLang === 'Share Chatbot') && chatflowApiKeyId && ( <> - - Paste this anywhere in the {``} tag of your html file. -

- You can also specify a  - - version - - : {`https://cdn.jsdelivr.net/npm/flowise-embed@/dist/web.js`} -

-
-
+

You cannot use API key while embedding/sharing chatbot.

+

+ Please select "No Authorization" from the dropdown at the top right corner. +

)} - - {value !== 0 && } - {value !== 0 && checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && ( + {codeLang === 'Embed' && !chatflowApiKeyId && } + {codeLang !== 'Embed' && codeLang !== 'Share Chatbot' && ( <> - + + {checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && ( + <> + + + + )} + {getIsChatflowStreamingApi.data?.isStreaming && ( +

+ Read  + + here + +  on how to stream response back to application +

+ )} )} - {value === 0 && ( - - )} - {value === 0 && embedChatCheckboxVal && ( - - )} - {value !== 0 && getIsChatflowStreamingApi.data?.isStreaming && ( -

- Read  - - here - -  on how to stream response back to application -

+ {codeLang === 'Share Chatbot' && !chatflowApiKeyId && ( + )}
))} diff --git a/packages/ui/src/views/chatflows/EmbedChat.js b/packages/ui/src/views/chatflows/EmbedChat.js new file mode 100644 index 00000000..c6385efb --- /dev/null +++ b/packages/ui/src/views/chatflows/EmbedChat.js @@ -0,0 +1,324 @@ +import { useState } from 'react' +import PropTypes from 'prop-types' + +import { Tabs, Tab, Box } from '@mui/material' +import { CopyBlock, atomOneDark } from 'react-code-blocks' + +// Project import +import { CheckboxInput } from 'ui-component/checkbox/Checkbox' + +// Const +import { baseURL } from 'store/constant' + +function TabPanel(props) { + const { children, value, index, ...other } = props + return ( + + ) +} + +TabPanel.propTypes = { + children: PropTypes.node, + index: PropTypes.number.isRequired, + value: PropTypes.number.isRequired +} + +function a11yProps(index) { + return { + id: `attachment-tab-${index}`, + 'aria-controls': `attachment-tabpanel-${index}` + } +} + +const embedPopupHtmlCode = (chatflowid) => { + return `` +} + +const embedPopupReactCode = (chatflowid) => { + return `import { BubbleChat } from 'flowise-embed-react' + +const App = () => { + return ( + + ); +};` +} + +const embedFullpageHtmlCode = (chatflowid) => { + return ` +` +} + +const embedFullpageReactCode = (chatflowid) => { + return `import { FullPageChat } from "flowise-embed-react" + +const App = () => { + return ( + + ); +};` +} + +const buttonConfig = (isReact = false) => { + return isReact + ? `button: { + backgroundColor: "#3B81F6", + right: 20, + bottom: 20, + size: "medium", + iconColor: "white", + customIconSrc: "https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/svg/google-messages.svg", + }` + : `button: { + backgroundColor: "#3B81F6", + right: 20, + bottom: 20, + size: "medium", + iconColor: "white", + customIconSrc: "https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/svg/google-messages.svg", + }` +} + +const chatwindowConfig = (isReact = false) => { + return isReact + ? `chatWindow: { + welcomeMessage: "Hello! This is custom welcome message", + backgroundColor: "#ffffff", + height: 700, + width: 400, + fontSize: 16, + poweredByTextColor: "#303235", + botMessage: { + backgroundColor: "#f7f8ff", + textColor: "#303235", + showAvatar: true, + avatarSrc: "https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/parroticon.png", + }, + userMessage: { + backgroundColor: "#3B81F6", + textColor: "#ffffff", + showAvatar: true, + avatarSrc: "https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/usericon.png", + }, + textInput: { + placeholder: "Type your question", + backgroundColor: "#ffffff", + textColor: "#303235", + sendButtonColor: "#3B81F6", + } + }` + : `chatWindow: { + welcomeMessage: "Hello! This is custom welcome message", + backgroundColor: "#ffffff", + height: 700, + width: 400, + fontSize: 16, + poweredByTextColor: "#303235", + botMessage: { + backgroundColor: "#f7f8ff", + textColor: "#303235", + showAvatar: true, + avatarSrc: "https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/parroticon.png", + }, + userMessage: { + backgroundColor: "#3B81F6", + textColor: "#ffffff", + showAvatar: true, + avatarSrc: "https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/usericon.png", + }, + textInput: { + placeholder: "Type your question", + backgroundColor: "#ffffff", + textColor: "#303235", + sendButtonColor: "#3B81F6", + } + }` +} + +const embedPopupHtmlCodeCustomization = (chatflowid) => { + return `` +} + +const embedPopupReactCodeCustomization = (chatflowid) => { + return `import { BubbleChat } from 'flowise-embed-react' + +const App = () => { + return ( + + ); +};` +} + +const embedFullpageHtmlCodeCustomization = (chatflowid) => { + return ` +` +} + +const embedFullpageReactCodeCustomization = (chatflowid) => { + return `import { FullPageChat } from "flowise-embed-react" + +const App = () => { + return ( + + ); +};` +} + +const EmbedChat = ({ chatflowid }) => { + const codes = ['Popup Html', 'Fullpage Html', 'Popup React', 'Fullpage React'] + const [value, setValue] = useState(0) + const [embedChatCheckboxVal, setEmbedChatCheckbox] = useState(false) + + const onCheckBoxEmbedChatChanged = (newVal) => { + setEmbedChatCheckbox(newVal) + } + + const handleChange = (event, newValue) => { + setValue(newValue) + } + + const getCode = (codeLang) => { + switch (codeLang) { + case 'Popup Html': + return embedPopupHtmlCode(chatflowid) + case 'Fullpage Html': + return embedFullpageHtmlCode(chatflowid) + case 'Popup React': + return embedPopupReactCode(chatflowid) + case 'Fullpage React': + return embedFullpageReactCode(chatflowid) + default: + return '' + } + } + + const getCodeCustomization = (codeLang) => { + switch (codeLang) { + case 'Popup Html': + return embedPopupHtmlCodeCustomization(chatflowid) + case 'Fullpage Html': + return embedFullpageHtmlCodeCustomization(chatflowid) + case 'Popup React': + return embedPopupReactCodeCustomization(chatflowid) + case 'Fullpage React': + return embedFullpageReactCodeCustomization(chatflowid) + default: + return '' + } + } + + return ( + <> +
+
+ + {codes.map((codeLang, index) => ( + + ))} + +
+
+
+ {codes.map((codeLang, index) => ( + + {(value === 0 || value === 1) && ( + <> + + Paste this anywhere in the {``} tag of your html file. +

+ You can also specify a  + + version + + : {`https://cdn.jsdelivr.net/npm/flowise-embed@/dist/web.js`} +

+
+
+ + )} + + + + + {embedChatCheckboxVal && ( + + )} +
+ ))} + + ) +} + +EmbedChat.propTypes = { + chatflowid: PropTypes.string +} + +export default EmbedChat diff --git a/packages/ui/src/views/chatflows/ShareChatbot.js b/packages/ui/src/views/chatflows/ShareChatbot.js new file mode 100644 index 00000000..dffecf5b --- /dev/null +++ b/packages/ui/src/views/chatflows/ShareChatbot.js @@ -0,0 +1,420 @@ +import PropTypes from 'prop-types' +import { useState } from 'react' +import { useDispatch } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import { SketchPicker } from 'react-color' + +import { Box, Typography, Button, Switch, OutlinedInput, Popover, Stack, IconButton } from '@mui/material' +import { useTheme } from '@mui/material/styles' + +// Project import +import { StyledButton } from 'ui-component/button/StyledButton' + +// Icons +import { IconX, IconCopy, IconArrowUpRightCircle } from '@tabler/icons' + +// API +import chatflowsApi from 'api/chatflows' + +// utils +import useNotifier from 'utils/useNotifier' + +// Const +import { baseURL } from 'store/constant' + +const defaultConfig = { + backgroundColor: '#ffffff', + fontSize: 16, + poweredByTextColor: '#303235', + botMessage: { + backgroundColor: '#f7f8ff', + textColor: '#303235' + }, + userMessage: { + backgroundColor: '#3B81F6', + textColor: '#ffffff' + }, + textInput: { + backgroundColor: '#ffffff', + textColor: '#303235', + sendButtonColor: '#3B81F6' + } +} + +const ShareChatbot = ({ chatflowid, chatbotConfig }) => { + const dispatch = useDispatch() + const theme = useTheme() + + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [welcomeMessage, setWelcomeMessage] = useState(chatbotConfig?.welcomeMessage ?? '') + const [backgroundColor, setBackgroundColor] = useState(chatbotConfig?.backgroundColor ?? defaultConfig.backgroundColor) + const [fontSize, setFontSize] = useState(chatbotConfig?.fontSize ?? defaultConfig.fontSize) + const [poweredByTextColor, setPoweredByTextColor] = useState(chatbotConfig?.poweredByTextColor ?? defaultConfig.poweredByTextColor) + + const [botMessageBackgroundColor, setBotMessageBackgroundColor] = useState( + chatbotConfig?.botMessage?.backgroundColor ?? defaultConfig.botMessage.backgroundColor + ) + const [botMessageTextColor, setBotMessageTextColor] = useState( + chatbotConfig?.botMessage?.textColor ?? defaultConfig.botMessage.textColor + ) + const [botMessageAvatarSrc, setBotMessageAvatarSrc] = useState(chatbotConfig?.botMessage?.avatarSrc ?? '') + const [botMessageShowAvatar, setBotMessageShowAvatar] = useState(chatbotConfig?.botMessage?.showAvatar ?? false) + + const [userMessageBackgroundColor, setUserMessageBackgroundColor] = useState( + chatbotConfig?.userMessage?.backgroundColor ?? defaultConfig.userMessage.backgroundColor + ) + const [userMessageTextColor, setUserMessageTextColor] = useState( + chatbotConfig?.userMessage?.textColor ?? defaultConfig.userMessage.textColor + ) + const [userMessageAvatarSrc, setUserMessageAvatarSrc] = useState(chatbotConfig?.userMessage?.avatarSrc ?? '') + const [userMessageShowAvatar, setUserMessageShowAvatar] = useState(chatbotConfig?.userMessage?.showAvatar ?? false) + + const [textInputBackgroundColor, setTextInputBackgroundColor] = useState( + chatbotConfig?.textInput?.backgroundColor ?? defaultConfig.textInput.backgroundColor + ) + const [textInputTextColor, setTextInputTextColor] = useState(chatbotConfig?.textInput?.textColor ?? defaultConfig.textInput.textColor) + const [textInputPlaceholder, setTextInputPlaceholder] = useState(chatbotConfig?.textInput?.placeholder ?? '') + const [textInputSendButtonColor, setTextInputSendButtonColor] = useState( + chatbotConfig?.textInput?.sendButtonColor ?? defaultConfig.textInput.sendButtonColor + ) + + const [colorAnchorEl, setColorAnchorEl] = useState(null) + const [selectedColorConfig, setSelectedColorConfig] = useState('') + const [sketchPickerColor, setSketchPickerColor] = useState('') + const openColorPopOver = Boolean(colorAnchorEl) + + const [copyAnchorEl, setCopyAnchorEl] = useState(null) + const openCopyPopOver = Boolean(copyAnchorEl) + + const formatObj = () => { + const obj = { + botMessage: { + showAvatar: false + }, + userMessage: { + showAvatar: false + }, + textInput: {} + } + if (welcomeMessage) obj.welcomeMessage = welcomeMessage + if (backgroundColor) obj.backgroundColor = backgroundColor + if (fontSize) obj.fontSize = fontSize + if (poweredByTextColor) obj.poweredByTextColor = poweredByTextColor + + if (botMessageBackgroundColor) obj.botMessage.backgroundColor = botMessageBackgroundColor + if (botMessageTextColor) obj.botMessage.textColor = botMessageTextColor + if (botMessageAvatarSrc) obj.botMessage.avatarSrc = botMessageAvatarSrc + if (botMessageShowAvatar) obj.botMessage.showAvatar = botMessageShowAvatar + + if (userMessageBackgroundColor) obj.userMessage.backgroundColor = userMessageBackgroundColor + if (userMessageTextColor) obj.userMessage.textColor = userMessageTextColor + if (userMessageAvatarSrc) obj.userMessage.avatarSrc = userMessageAvatarSrc + if (userMessageShowAvatar) obj.userMessage.showAvatar = userMessageShowAvatar + + if (textInputBackgroundColor) obj.textInput.backgroundColor = textInputBackgroundColor + if (textInputTextColor) obj.textInput.textColor = textInputTextColor + if (textInputPlaceholder) obj.textInput.placeholder = textInputPlaceholder + if (textInputSendButtonColor) obj.textInput.sendButtonColor = textInputSendButtonColor + + return obj + } + + const onSave = async () => { + try { + const saveResp = await chatflowsApi.updateChatflow(chatflowid, { + chatbotConfig: JSON.stringify(formatObj()) + }) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Chatbot Configuration Saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + } + } catch (error) { + console.error(error) + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to save Chatbot Configuration: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + } + } + + const handleClosePopOver = () => { + setColorAnchorEl(null) + } + + const handleCloseCopyPopOver = () => { + setCopyAnchorEl(null) + } + + const onColorSelected = (hexColor) => { + switch (selectedColorConfig) { + case 'backgroundColor': + setBackgroundColor(hexColor) + break + case 'poweredByTextColor': + setPoweredByTextColor(hexColor) + break + case 'botMessageBackgroundColor': + setBotMessageBackgroundColor(hexColor) + break + case 'botMessageTextColor': + setBotMessageTextColor(hexColor) + break + case 'userMessageBackgroundColor': + setUserMessageBackgroundColor(hexColor) + break + case 'userMessageTextColor': + setUserMessageTextColor(hexColor) + break + case 'textInputBackgroundColor': + setTextInputBackgroundColor(hexColor) + break + case 'textInputTextColor': + setTextInputTextColor(hexColor) + break + case 'textInputSendButtonColor': + setTextInputSendButtonColor(hexColor) + break + } + setSketchPickerColor(hexColor) + } + + const onTextChanged = (value, fieldName) => { + switch (fieldName) { + case 'welcomeMessage': + setWelcomeMessage(value) + break + case 'fontSize': + setFontSize(value) + break + case 'botMessageAvatarSrc': + setBotMessageAvatarSrc(value) + break + case 'userMessageAvatarSrc': + setUserMessageAvatarSrc(value) + break + case 'textInputPlaceholder': + setTextInputPlaceholder(value) + break + } + } + + const onBooleanChanged = (value, fieldName) => { + switch (fieldName) { + case 'botMessageShowAvatar': + setBotMessageShowAvatar(value) + break + case 'userMessageShowAvatar': + setUserMessageShowAvatar(value) + break + } + } + + const colorField = (color, fieldName, fieldLabel) => { + return ( + +
+ {fieldLabel} + { + setSelectedColorConfig(fieldName) + setSketchPickerColor(color ?? '#ffffff') + setColorAnchorEl(event.currentTarget) + }} + > +
+
+ ) + } + + const booleanField = (value, fieldName, fieldLabel) => { + return ( + +
+ {fieldLabel} + { + onBooleanChanged(event.target.checked, fieldName) + }} + /> +
+
+ ) + } + + const textField = (message, fieldName, fieldLabel, fieldType = 'string', placeholder = '') => { + return ( + +
+ {fieldLabel} + { + onTextChanged(e.target.value, fieldName) + }} + /> +
+
+ ) + } + + return ( + <> + + + {`${baseURL}/chatbot/${chatflowid}`} + + { + navigator.clipboard.writeText(`${baseURL}/chatbot/${chatflowid}`) + setCopyAnchorEl(event.currentTarget) + setTimeout(() => { + handleCloseCopyPopOver() + }, 1500) + }} + > + + + window.open(`${baseURL}/chatbot/${chatflowid}`, '_blank')}> + + + + {textField(welcomeMessage, 'welcomeMessage', 'Welcome Message', 'string', 'Hello! This is custom welcome message')} + {colorField(backgroundColor, 'backgroundColor', 'Background Color')} + {textField(fontSize, 'fontSize', 'Font Size', 'number')} + {colorField(poweredByTextColor, 'poweredByTextColor', 'PoweredBy TextColor')} + + {/*BOT Message*/} + + Bot Message + + {colorField(botMessageBackgroundColor, 'botMessageBackgroundColor', 'Background Color')} + {colorField(botMessageTextColor, 'botMessageTextColor', 'Text Color')} + {textField( + botMessageAvatarSrc, + 'botMessageAvatarSrc', + 'Avatar Link', + 'string', + `https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/parroticon.png` + )} + {booleanField(botMessageShowAvatar, 'botMessageShowAvatar', 'Show Avatar')} + + {/*USER Message*/} + + User Message + + {colorField(userMessageBackgroundColor, 'userMessageBackgroundColor', 'Background Color')} + {colorField(userMessageTextColor, 'userMessageTextColor', 'Text Color')} + {textField( + userMessageAvatarSrc, + 'userMessageAvatarSrc', + 'Avatar Link', + 'string', + `https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/usericon.png` + )} + {booleanField(userMessageShowAvatar, 'userMessageShowAvatar', 'Show Avatar')} + + {/*TEXT Input*/} + + Text Input + + {colorField(textInputBackgroundColor, 'textInputBackgroundColor', 'Background Color')} + {colorField(textInputTextColor, 'textInputTextColor', 'Text Color')} + {textField(textInputPlaceholder, 'textInputPlaceholder', 'TextInput Placeholder', 'string', `Type question..`)} + {colorField(textInputSendButtonColor, 'textInputSendButtonColor', 'TextIntput Send Button Color')} + + onSave()}> + Save Changes + + + onColorSelected(color.hex)} /> + + + + Copied! + + + + ) +} + +ShareChatbot.propTypes = { + chatflowid: PropTypes.string, + chatbotConfig: PropTypes.object +} + +export default ShareChatbot From ab029a845e3a5a0ac4a20a6173194334eeed8f9c Mon Sep 17 00:00:00 2001 From: Govind Kumar Date: Mon, 26 Jun 2023 20:46:37 +0530 Subject: [PATCH 137/398] added revised changes --- .../nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts | 7 ++++--- .../nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts index 031917f5..f1eef8f9 100644 --- a/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts +++ b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts @@ -32,7 +32,8 @@ class Qdrant_Existing_VectorStores implements INode { { label: 'Qdrant Server URL', name: 'qdrantServerUrl', - type: 'string' + type: 'string', + placeholder: 'http://localhost:6333' }, { label: 'Qdrant Collection Name', @@ -42,14 +43,14 @@ class Qdrant_Existing_VectorStores implements INode { { label: 'Qdrant API Key', name: 'qdrantApiKey', - type: 'password' + type: 'password', + optional: true }, { label: 'Qdrant Collection Cofiguration', name: 'qdrantCollectionCofiguration', type: 'json', optional: true, - additionalParams: true }, { diff --git a/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts b/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts index 111fc5c3..dae1d31d 100644 --- a/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts +++ b/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts @@ -40,7 +40,8 @@ class QdrantUpsert_VectorStores implements INode { { label: 'Qdrant Server URL', name: 'qdrantServerUrl', - type: 'string' + type: 'string', + placeholder: 'http://localhost:6333' }, { label: 'Qdrant Collection Name', @@ -50,7 +51,8 @@ class QdrantUpsert_VectorStores implements INode { { label: 'Qdrant API Key', name: 'qdrantApiKey', - type: 'password' + type: 'password', + optional: true }, { label: 'Top K', From 3784a700b6ddf9c8c9c22d69f3d72ff5dafb02b9 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 26 Jun 2023 18:05:48 +0100 Subject: [PATCH 138/398] update faiss version --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index f5b67cc7..72feeed5 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -30,7 +30,7 @@ "d3-dsv": "2", "dotenv": "^16.0.0", "express": "^4.17.3", - "faiss-node": "^0.2.1", + "faiss-node": "^0.2.2", "form-data": "^4.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", From a0ddad8e52352ff9bcfc4c9880b1f916bb7b4c55 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 26 Jun 2023 19:26:13 +0100 Subject: [PATCH 139/398] update README --- README.md | 4 ++-- packages/server/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dbce8f3b..90f2711f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# Flowise - LangchainJS UI +# Flowise - Low-Code LLM apps builder -Drag & drop UI to build your customized LLM flow using [LangchainJS](https://github.com/hwchase17/langchainjs) +Drag & drop UI to build your customized LLM flow ## ⚡Quick Start diff --git a/packages/server/README.md b/packages/server/README.md index 74ba9a25..7895bd90 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -1,10 +1,10 @@ -# Flowise - LangchainJS UI +# Flowise - Low-Code LLM apps builder ![Flowise](https://github.com/FlowiseAI/Flowise/blob/main/images/flowise.gif?raw=true) -Drag & drop UI to build your customized LLM flow using [LangchainJS](https://github.com/hwchase17/langchainjs) +Drag & drop UI to build your customized LLM flow ## ⚡Quick Start From dd8b59abb8a3850eef2e9b5f78db70a59eb55ec7 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Tue, 27 Jun 2023 23:01:09 +0800 Subject: [PATCH 140/398] add zapier integration --- packages/server/src/index.ts | 32 +++++++++++++++++++++++++++++- packages/server/src/utils/index.ts | 12 +++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 65bfef23..3e729464 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -36,7 +36,8 @@ import { replaceAllAPIKeys, isFlowValidForStream, isVectorStoreFaiss, - databaseEntities + databaseEntities, + getApiKey } from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' @@ -177,6 +178,24 @@ export class App { return res.json(chatflows) }) + // Get specific chatflow via api key + this.app.get('/api/v1/chatflows/apikey/:apiKey', async (req: Request, res: Response) => { + try { + const apiKey = await getApiKey(req.params.apiKey) + if (!apiKey) return res.status(401).send('Unauthorized') + const chatflows = await this.AppDataSource.getRepository(ChatFlow) + .createQueryBuilder('cf') + .where('cf.apikeyid = :apikeyid', { apikeyid: apiKey.id }) + .orWhere('cf.apikeyid IS NULL') + .orderBy('cf.name', 'ASC') + .getMany() + if (chatflows.length >= 1) return res.status(200).send(chatflows) + return res.status(404).send('Chatflow not found') + } catch (err: any) { + return res.status(500).send(err?.message) + } + }) + // Get specific chatflow via id this.app.get('/api/v1/chatflows/:id', async (req: Request, res: Response) => { const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ @@ -472,6 +491,17 @@ export class App { return res.json(keys) }) + // Verify api key + this.app.get('/api/v1/apikey/:apiKey', async (req: Request, res: Response) => { + try { + const apiKey = await getApiKey(req.params.apiKey) + if (!apiKey) return res.status(401).send('Unauthorized') + return res.status(200).send('OK') + } catch (err: any) { + return res.status(500).send(err?.message) + } + }) + // ---------------------------------------- // Serve UI static // ---------------------------------------- diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index e3005c7b..e861e6fa 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -547,6 +547,18 @@ export const addAPIKey = async (keyName: string): Promise => { return content } +/** + * Get API Key details + * @param {string} apiKey + * @returns {Promise} + */ +export const getApiKey = async (apiKey: string) => { + const existingAPIKeys = await getAPIKeys() + const keyIndex = existingAPIKeys.findIndex((key) => key.apiKey === apiKey) + if (keyIndex < 0) return undefined + return existingAPIKeys[keyIndex] +} + /** * Update existing API key * @param {string} keyIdToUpdate From b6a5cd0cb327e9b380f62b61b790632cbdaf8a2b Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Tue, 27 Jun 2023 23:07:31 +0800 Subject: [PATCH 141/398] remove returnJSONStr function --- .../nodes/prompts/PromptTemplate/PromptTemplate.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts index cfa2c488..f976d64c 100644 --- a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts +++ b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts @@ -1,5 +1,5 @@ import { ICommonObject, INode, INodeData, INodeParams, PromptTemplate } from '../../../src/Interface' -import { getBaseClasses, getInputVariables, returnJSONStr } from '../../../src/utils' +import { getBaseClasses, getInputVariables } from '../../../src/utils' import { PromptTemplateInput } from 'langchain/prompts' class PromptTemplate_Prompts implements INode { @@ -46,12 +46,11 @@ class PromptTemplate_Prompts implements INode { async init(nodeData: INodeData): Promise { const template = nodeData.inputs?.template as string - let promptValuesStr = nodeData.inputs?.promptValues as string + const promptValuesStr = nodeData.inputs?.promptValues as string let promptValues: ICommonObject = {} if (promptValuesStr) { - promptValuesStr = promptValuesStr.replace(/\s/g, '') - promptValues = JSON.parse(returnJSONStr(promptValuesStr)) + promptValues = JSON.parse(promptValuesStr.replace(/\s/g, '')) } const inputVariables = getInputVariables(template) From c36489f9470aa81b92ec89d51b18f1e85f1da80c Mon Sep 17 00:00:00 2001 From: ivalkshfoeif Date: Wed, 28 Jun 2023 10:10:12 -0700 Subject: [PATCH 142/398] Update redis icon --- .../memory/RedisBackedChatMemory/RedisBackedChatMemory.ts | 2 +- .../nodes/memory/RedisBackedChatMemory/memory.svg | 8 -------- .../nodes/memory/RedisBackedChatMemory/redis.svg | 1 + 3 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 packages/components/nodes/memory/RedisBackedChatMemory/memory.svg create mode 100644 packages/components/nodes/memory/RedisBackedChatMemory/redis.svg diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 155ea5f5..e332181d 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -19,7 +19,7 @@ class RedisBackedChatMemory_Memory implements INode { this.label = 'Redis-Backed Chat Memory' this.name = 'RedisBackedChatMemory' this.type = 'RedisBackedChatMemory' - this.icon = 'memory.svg' + this.icon = 'redis.svg' this.category = 'Memory' this.description = 'Summarizes the conversation and stores the memory in Redis server' this.baseClasses = [this.type, ...getBaseClasses(BufferMemory)] diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/memory.svg b/packages/components/nodes/memory/RedisBackedChatMemory/memory.svg deleted file mode 100644 index ca8e17da..00000000 --- a/packages/components/nodes/memory/RedisBackedChatMemory/memory.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/redis.svg b/packages/components/nodes/memory/RedisBackedChatMemory/redis.svg new file mode 100644 index 00000000..90359069 --- /dev/null +++ b/packages/components/nodes/memory/RedisBackedChatMemory/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file From 511c4995e9cc9ffc5fd1b85ecc40e4591cfd97c5 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 28 Jun 2023 22:59:56 +0100 Subject: [PATCH 143/398] format prompt values revamp --- .../nodes/chains/LLMChain/LLMChain.ts | 2 +- .../ChatPromptTemplate/ChatPromptTemplate.ts | 7 +- .../prompts/PromptTemplate/PromptTemplate.ts | 7 +- packages/components/src/utils.ts | 41 -- packages/server/marketplaces/Antonym.json | 154 +++--- .../marketplaces/HuggingFace LLM Chain.json | 150 +++--- .../server/marketplaces/Prompt Chaining.json | 502 +++++++++--------- .../server/marketplaces/Simple LLM Chain.json | 136 +++-- packages/server/marketplaces/Translator.json | 154 +++--- packages/ui/package.json | 2 +- .../dialog/EditPromptValuesDialog.js | 256 --------- ...tValuesDialog.css => ExpandTextDialog.css} | 0 .../ui-component/dialog/ExpandTextDialog.js | 105 ++++ .../dialog/FormatPromptValuesDialog.js | 56 ++ .../ui-component/dialog/SourceDocDialog.js | 2 +- packages/ui/src/ui-component/input/Input.js | 6 +- .../ui/src/ui-component/json/JsonEditor.js | 115 +++- .../src/ui-component/json/SelectVariable.js | 126 +++++ packages/ui/src/utils/genericHelper.js | 30 +- .../ui/src/views/canvas/NodeInputHandler.js | 73 ++- 20 files changed, 1014 insertions(+), 910 deletions(-) delete mode 100644 packages/ui/src/ui-component/dialog/EditPromptValuesDialog.js rename packages/ui/src/ui-component/dialog/{EditPromptValuesDialog.css => ExpandTextDialog.css} (100%) create mode 100644 packages/ui/src/ui-component/dialog/ExpandTextDialog.js create mode 100644 packages/ui/src/ui-component/dialog/FormatPromptValuesDialog.js create mode 100644 packages/ui/src/ui-component/json/SelectVariable.js diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index 9cd08d35..67c21ce4 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -50,7 +50,7 @@ class LLMChain_Chains implements INode { { label: 'Output Prediction', name: 'outputPrediction', - baseClasses: ['string'] + baseClasses: ['string', 'json'] } ] } diff --git a/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts b/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts index c3c4d77f..4eeb1dd2 100644 --- a/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts +++ b/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts @@ -38,12 +38,7 @@ class ChatPromptTemplate_Prompts implements INode { { label: 'Format Prompt Values', name: 'promptValues', - type: 'string', - rows: 4, - placeholder: `{ - "input_language": "English", - "output_language": "French" -}`, + type: 'json', optional: true, acceptVariable: true, list: true diff --git a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts index f976d64c..f9c6c53e 100644 --- a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts +++ b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts @@ -31,12 +31,7 @@ class PromptTemplate_Prompts implements INode { { label: 'Format Prompt Values', name: 'promptValues', - type: 'string', - rows: 4, - placeholder: `{ - "input_language": "English", - "output_language": "French" -}`, + type: 'json', optional: true, acceptVariable: true, list: true diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index ad8d28dc..c247ebc2 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -245,47 +245,6 @@ export class CustomChainHandler extends BaseCallbackHandler { } } -export const returnJSONStr = (jsonStr: string): string => { - let jsonStrArray = jsonStr.split(':') - - let wholeString = '' - for (let i = 0; i < jsonStrArray.length; i++) { - if (jsonStrArray[i].includes(',') && jsonStrArray[i + 1] !== undefined) { - const splitValueAndTitle = jsonStrArray[i].split(',') - const value = splitValueAndTitle[0] - const newTitle = splitValueAndTitle[1] - wholeString += handleEscapeDoubleQuote(value) + ',' + newTitle + ':' - } else { - wholeString += wholeString === '' ? jsonStrArray[i] + ':' : handleEscapeDoubleQuote(jsonStrArray[i]) - } - } - return wholeString -} - -const handleEscapeDoubleQuote = (value: string): string => { - let newValue = '' - if (value.includes('"')) { - const valueArray = value.split('"') - for (let i = 0; i < valueArray.length; i++) { - if ((i + 1) % 2 !== 0) { - switch (valueArray[i]) { - case '': - newValue += '"' - break - case '}': - newValue += '"}' - break - default: - newValue += '\\"' + valueArray[i] + '\\"' - } - } else { - newValue += valueArray[i] - } - } - } - return newValue === '' ? value : newValue -} - export const availableDependencies = [ '@dqbd/tiktoken', '@getzep/zep-js', diff --git a/packages/server/marketplaces/Antonym.json b/packages/server/marketplaces/Antonym.json index 2e21fd22..817e3ee9 100644 --- a/packages/server/marketplaces/Antonym.json +++ b/packages/server/marketplaces/Antonym.json @@ -3,68 +3,7 @@ "nodes": [ { "width": 300, - "height": 534, - "id": "promptTemplate_1", - "position": { - "x": 532.2791692529131, - "y": -31.128527027841372 - }, - "type": "customNode", - "data": { - "id": "promptTemplate_1", - "label": "Prompt Template", - "name": "promptTemplate", - "type": "PromptTemplate", - "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a basic prompt for an LLM", - "inputParams": [ - { - "label": "Template", - "name": "template", - "type": "string", - "rows": 4, - "placeholder": "What is a good name for a company that makes {product}?", - "id": "promptTemplate_1-input-template-string" - }, - { - "label": "Format Prompt Values", - "name": "promptValues", - "type": "string", - "rows": 4, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true, - "acceptVariable": true, - "list": true, - "id": "promptTemplate_1-input-promptValues-string" - } - ], - "inputAnchors": [], - "inputs": { - "template": "Word: {word}\\nAntonym: {antonym}\\n", - "promptValues": "" - }, - "outputAnchors": [ - { - "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "name": "promptTemplate", - "label": "PromptTemplate", - "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 532.2791692529131, - "y": -31.128527027841372 - }, - "dragging": false - }, - { - "width": 300, - "height": 956, + "height": 955, "id": "fewShotPromptTemplate_1", "position": { "x": 886.3229032369354, @@ -139,7 +78,7 @@ ], "inputs": { "examples": "[\n { \"word\": \"happy\", \"antonym\": \"sad\" },\n { \"word\": \"tall\", \"antonym\": \"short\" }\n]", - "examplePrompt": "{{promptTemplate_1.data.instance}}", + "examplePrompt": "{{promptTemplate_0.data.instance}}", "prefix": "Give the antonym of every input", "suffix": "Word: {input}\\nAntonym:", "exampleSeparator": "\\n\\n", @@ -165,7 +104,7 @@ }, { "width": 300, - "height": 526, + "height": 524, "id": "openAI_1", "position": { "x": 1224.5139327142097, @@ -318,7 +257,7 @@ }, { "width": 300, - "height": 407, + "height": 405, "id": "llmChain_1", "position": { "x": 1635.363191180743, @@ -375,10 +314,10 @@ "type": "LLMChain | BaseChain | BaseLangChain" }, { - "id": "llmChain_1-output-outputPrediction-string", + "id": "llmChain_1-output-outputPrediction-string|json", "name": "outputPrediction", "label": "Output Prediction", - "type": "string" + "type": "string | json" } ], "default": "llmChain" @@ -395,20 +334,68 @@ }, "selected": false, "dragging": false + }, + { + "width": 300, + "height": 475, + "id": "promptTemplate_0", + "position": { + "x": 540.0140796251119, + "y": -33.31673494170347 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_0", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "Word: {word}\\nAntonym: {antonym}\\n", + "promptValues": "" + }, + "outputAnchors": [ + { + "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 540.0140796251119, + "y": -33.31673494170347 + }, + "dragging": false } ], "edges": [ - { - "source": "promptTemplate_1", - "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "target": "fewShotPromptTemplate_1", - "targetHandle": "fewShotPromptTemplate_1-input-examplePrompt-PromptTemplate", - "type": "buttonedge", - "id": "promptTemplate_1-promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-fewShotPromptTemplate_1-fewShotPromptTemplate_1-input-examplePrompt-PromptTemplate", - "data": { - "label": "" - } - }, { "source": "openAI_1", "sourceHandle": "openAI_1-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain", @@ -430,6 +417,17 @@ "data": { "label": "" } + }, + { + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "fewShotPromptTemplate_1", + "targetHandle": "fewShotPromptTemplate_1-input-examplePrompt-PromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-fewShotPromptTemplate_1-fewShotPromptTemplate_1-input-examplePrompt-PromptTemplate", + "data": { + "label": "" + } } ] } diff --git a/packages/server/marketplaces/HuggingFace LLM Chain.json b/packages/server/marketplaces/HuggingFace LLM Chain.json index 9d3492c6..d46f9d64 100644 --- a/packages/server/marketplaces/HuggingFace LLM Chain.json +++ b/packages/server/marketplaces/HuggingFace LLM Chain.json @@ -1,67 +1,6 @@ { "description": "Simple LLM Chain using HuggingFace Inference API on falcon-7b-instruct model", "nodes": [ - { - "width": 300, - "height": 532, - "id": "promptTemplate_1", - "position": { - "x": 514.5434056794296, - "y": 507.47798128037107 - }, - "type": "customNode", - "data": { - "id": "promptTemplate_1", - "label": "Prompt Template", - "name": "promptTemplate", - "type": "PromptTemplate", - "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a basic prompt for an LLM", - "inputParams": [ - { - "label": "Template", - "name": "template", - "type": "string", - "rows": 4, - "placeholder": "What is a good name for a company that makes {product}?", - "id": "promptTemplate_1-input-template-string" - }, - { - "label": "Format Prompt Values", - "name": "promptValues", - "type": "string", - "rows": 4, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true, - "acceptVariable": true, - "list": true, - "id": "promptTemplate_1-input-promptValues-string" - } - ], - "inputAnchors": [], - "inputs": { - "template": "Question: {question}\n\nAnswer: Let's think step by step.", - "promptValues": "" - }, - "outputAnchors": [ - { - "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "name": "promptTemplate", - "label": "PromptTemplate", - "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 514.5434056794296, - "y": 507.47798128037107 - }, - "dragging": false - }, { "width": 300, "height": 405, @@ -105,7 +44,7 @@ ], "inputs": { "model": "{{huggingFaceInference_LLMs_0.data.instance}}", - "prompt": "{{promptTemplate_1.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", "chainName": "" }, "outputAnchors": [ @@ -121,10 +60,10 @@ "type": "LLMChain | BaseChain | BaseLangChain" }, { - "id": "llmChain_1-output-outputPrediction-string", + "id": "llmChain_1-output-outputPrediction-string|json", "name": "outputPrediction", "label": "Output Prediction", - "type": "string" + "type": "string | json" } ], "default": "llmChain" @@ -144,7 +83,7 @@ }, { "width": 300, - "height": 427, + "height": 429, "id": "huggingFaceInference_LLMs_0", "position": { "x": 503.5630827259226, @@ -245,20 +184,68 @@ "y": 50.79125094823999 }, "dragging": false + }, + { + "width": 300, + "height": 475, + "id": "promptTemplate_0", + "position": { + "x": 506.50436294210306, + "y": 504.50766458127396 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_0", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "Question: {question}\n\nAnswer: Let's think step by step.", + "promptValues": "" + }, + "outputAnchors": [ + { + "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 506.50436294210306, + "y": 504.50766458127396 + }, + "dragging": false } ], "edges": [ - { - "source": "promptTemplate_1", - "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "target": "llmChain_1", - "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", - "type": "buttonedge", - "id": "promptTemplate_1-promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", - "data": { - "label": "" - } - }, { "source": "huggingFaceInference_LLMs_0", "sourceHandle": "huggingFaceInference_LLMs_0-output-huggingFaceInference_LLMs-HuggingFaceInference|LLM|BaseLLM|BaseLanguageModel|BaseLangChain", @@ -269,6 +256,17 @@ "data": { "label": "" } + }, + { + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } } ] } diff --git a/packages/server/marketplaces/Prompt Chaining.json b/packages/server/marketplaces/Prompt Chaining.json index 33a64081..96987660 100644 --- a/packages/server/marketplaces/Prompt Chaining.json +++ b/packages/server/marketplaces/Prompt Chaining.json @@ -3,7 +3,7 @@ "nodes": [ { "width": 300, - "height": 526, + "height": 524, "id": "openAI_2", "position": { "x": 793.6674026500068, @@ -156,213 +156,11 @@ }, { "width": 300, - "height": 534, - "id": "promptTemplate_2", - "position": { - "x": 796.3399644963663, - "y": 512.349657546027 - }, - "type": "customNode", - "data": { - "id": "promptTemplate_2", - "label": "Prompt Template", - "name": "promptTemplate", - "type": "PromptTemplate", - "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a basic prompt for an LLM", - "inputParams": [ - { - "label": "Template", - "name": "template", - "type": "string", - "rows": 4, - "placeholder": "What is a good name for a company that makes {product}?", - "id": "promptTemplate_2-input-template-string" - }, - { - "label": "Format Prompt Values", - "name": "promptValues", - "type": "string", - "rows": 4, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true, - "acceptVariable": true, - "list": true, - "id": "promptTemplate_2-input-promptValues-string" - } - ], - "inputAnchors": [], - "inputs": { - "template": "You are an AI who performs one task based on the following objective: {objective}.\nRespond with how you would complete this task:", - "promptValues": "{\n \"objective\": \"{{question}}\"\n}" - }, - "outputAnchors": [ - { - "id": "promptTemplate_2-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "name": "promptTemplate", - "label": "PromptTemplate", - "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 796.3399644963663, - "y": 512.349657546027 - }, - "dragging": false - }, - { - "width": 300, - "height": 407, - "id": "llmChain_2", - "position": { - "x": 1225.2861408370582, - "y": 485.62403908243243 - }, - "type": "customNode", - "data": { - "id": "llmChain_2", - "label": "LLM Chain", - "name": "llmChain", - "type": "LLMChain", - "baseClasses": ["LLMChain", "BaseChain", "BaseLangChain"], - "category": "Chains", - "description": "Chain to run queries against LLMs", - "inputParams": [ - { - "label": "Chain Name", - "name": "chainName", - "type": "string", - "placeholder": "Name Your Chain", - "optional": true, - "id": "llmChain_2-input-chainName-string" - } - ], - "inputAnchors": [ - { - "label": "Language Model", - "name": "model", - "type": "BaseLanguageModel", - "id": "llmChain_2-input-model-BaseLanguageModel" - }, - { - "label": "Prompt", - "name": "prompt", - "type": "BasePromptTemplate", - "id": "llmChain_2-input-prompt-BasePromptTemplate" - } - ], - "inputs": { - "model": "{{openAI_2.data.instance}}", - "prompt": "{{promptTemplate_2.data.instance}}", - "chainName": "First Chain" - }, - "outputAnchors": [ - { - "name": "output", - "label": "Output", - "type": "options", - "options": [ - { - "id": "llmChain_2-output-llmChain-LLMChain|BaseChain|BaseLangChain", - "name": "llmChain", - "label": "LLM Chain", - "type": "LLMChain | BaseChain | BaseLangChain" - }, - { - "id": "llmChain_2-output-outputPrediction-string", - "name": "outputPrediction", - "label": "Output Prediction", - "type": "string" - } - ], - "default": "llmChain" - } - ], - "outputs": { - "output": "outputPrediction" - }, - "selected": false - }, - "selected": false, - "dragging": false, - "positionAbsolute": { - "x": 1225.2861408370582, - "y": 485.62403908243243 - } - }, - { - "width": 300, - "height": 534, - "id": "promptTemplate_3", - "position": { - "x": 1589.206555911206, - "y": 460.23470154201766 - }, - "type": "customNode", - "data": { - "id": "promptTemplate_3", - "label": "Prompt Template", - "name": "promptTemplate", - "type": "PromptTemplate", - "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a basic prompt for an LLM", - "inputParams": [ - { - "label": "Template", - "name": "template", - "type": "string", - "rows": 4, - "placeholder": "What is a good name for a company that makes {product}?", - "id": "promptTemplate_3-input-template-string" - }, - { - "label": "Format Prompt Values", - "name": "promptValues", - "type": "string", - "rows": 4, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true, - "acceptVariable": true, - "list": true, - "id": "promptTemplate_3-input-promptValues-string" - } - ], - "inputAnchors": [], - "inputs": { - "template": "You are a task creation AI that uses the result of an execution agent to create new tasks with the following objective: {objective}.\nThe last completed task has the result: {result}.\nBased on the result, create new tasks to be completed by the AI system that do not overlap with result.\nReturn the tasks as an array.", - "promptValues": "{\n \"objective\": \"{{question}}\",\n \"result\": \"\"\n}" - }, - "outputAnchors": [ - { - "id": "promptTemplate_3-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "name": "promptTemplate", - "label": "PromptTemplate", - "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 1589.206555911206, - "y": 460.23470154201766 - }, - "dragging": false - }, - { - "width": 300, - "height": 526, + "height": 524, "id": "openAI_3", "position": { - "x": 1225.2861408370586, - "y": -62.7856517905272 + "x": 1216.061423775753, + "y": -20.35195330852082 }, "type": "customNode", "data": { @@ -503,27 +301,145 @@ "selected": false }, "positionAbsolute": { - "x": 1225.2861408370586, - "y": -62.7856517905272 + "x": 1216.061423775753, + "y": -20.35195330852082 }, "selected": false, "dragging": false }, { "width": 300, - "height": 407, - "id": "llmChain_3", + "height": 475, + "id": "promptTemplate_0", "position": { - "x": 1972.2671768945252, - "y": 142.73435419451476 + "x": 792.9464838535649, + "y": 527.1718536712464 }, "type": "customNode", "data": { - "id": "llmChain_3", + "id": "promptTemplate_0", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "You are an AI who performs one task based on the following objective: {objective}.\nRespond with how you would complete this task:", + "promptValues": "{\"objective\":\"{{question}}\"}" + }, + "outputAnchors": [ + { + "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 792.9464838535649, + "y": 527.1718536712464 + }, + "dragging": false + }, + { + "width": 300, + "height": 475, + "id": "promptTemplate_1", + "position": { + "x": 1577.7482561604884, + "y": 516.186942924815 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_1", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_1-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_1-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "You are a task creation AI that uses the result of an execution agent to create new tasks with the following objective: {objective}.\nThe last completed task has the result: {result}.\nBased on the result, create new tasks to be completed by the AI system that do not overlap with result.\nReturn the tasks as an array.", + "promptValues": "{\"objective\":\"{{question}}\",\"result\":\"{{llmChain_0.data.instance}}\"}" + }, + "outputAnchors": [ + { + "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "positionAbsolute": { + "x": 1577.7482561604884, + "y": 516.186942924815 + }, + "selected": false, + "dragging": false + }, + { + "width": 300, + "height": 405, + "id": "llmChain_0", + "position": { + "x": 1221.1346231272787, + "y": 538.9546839784628 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", "label": "LLM Chain", "name": "llmChain", "type": "LLMChain", - "baseClasses": ["LLMChain", "BaseChain", "BaseLangChain"], + "baseClasses": ["LLMChain", "BaseChain"], "category": "Chains", "description": "Chain to run queries against LLMs", "inputParams": [ @@ -533,7 +449,7 @@ "type": "string", "placeholder": "Name Your Chain", "optional": true, - "id": "llmChain_3-input-chainName-string" + "id": "llmChain_0-input-chainName-string" } ], "inputAnchors": [ @@ -541,18 +457,98 @@ "label": "Language Model", "name": "model", "type": "BaseLanguageModel", - "id": "llmChain_3-input-model-BaseLanguageModel" + "id": "llmChain_0-input-model-BaseLanguageModel" }, { "label": "Prompt", "name": "prompt", "type": "BasePromptTemplate", - "id": "llmChain_3-input-prompt-BasePromptTemplate" + "id": "llmChain_0-input-prompt-BasePromptTemplate" + } + ], + "inputs": { + "model": "{{openAI_2.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", + "chainName": "FirstChain" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_0-output-llmChain-LLMChain|BaseChain", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain" + }, + { + "id": "llmChain_0-output-outputPrediction-string|json", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string | json" + } + ], + "default": "llmChain" + } + ], + "outputs": { + "output": "outputPrediction" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1221.1346231272787, + "y": 538.9546839784628 + }, + "dragging": false + }, + { + "width": 300, + "height": 405, + "id": "llmChain_1", + "position": { + "x": 1971.8054567964418, + "y": 207.624530381245 + }, + "type": "customNode", + "data": { + "id": "llmChain_1", + "label": "LLM Chain", + "name": "llmChain", + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain"], + "category": "Chains", + "description": "Chain to run queries against LLMs", + "inputParams": [ + { + "label": "Chain Name", + "name": "chainName", + "type": "string", + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_1-input-chainName-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "llmChain_1-input-model-BaseLanguageModel" + }, + { + "label": "Prompt", + "name": "prompt", + "type": "BasePromptTemplate", + "id": "llmChain_1-input-prompt-BasePromptTemplate" } ], "inputs": { "model": "{{openAI_3.data.instance}}", - "prompt": "{{promptTemplate_3.data.instance}}", + "prompt": "{{promptTemplate_1.data.instance}}", "chainName": "LastChain" }, "outputAnchors": [ @@ -562,16 +558,16 @@ "type": "options", "options": [ { - "id": "llmChain_3-output-llmChain-LLMChain|BaseChain|BaseLangChain", + "id": "llmChain_1-output-llmChain-LLMChain|BaseChain", "name": "llmChain", "label": "LLM Chain", - "type": "LLMChain | BaseChain | BaseLangChain" + "type": "LLMChain | BaseChain" }, { - "id": "llmChain_3-output-outputPrediction-string", + "id": "llmChain_1-output-outputPrediction-string|json", "name": "outputPrediction", "label": "Output Prediction", - "type": "string" + "type": "string | json" } ], "default": "llmChain" @@ -583,43 +579,43 @@ "selected": false }, "selected": false, - "dragging": false, "positionAbsolute": { - "x": 1972.2671768945252, - "y": 142.73435419451476 - } + "x": 1971.8054567964418, + "y": 207.624530381245 + }, + "dragging": false } ], "edges": [ - { - "source": "llmChain_2", - "sourceHandle": "llmChain_2-output-outputPrediction-string", - "target": "promptTemplate_3", - "targetHandle": "promptTemplate_3-input-promptValues-string", - "type": "buttonedge", - "id": "llmChain_2-llmChain_2-output-outputPrediction-string-promptTemplate_3-promptTemplate_3-input-promptValues-string", - "data": { - "label": "" - } - }, { "source": "openAI_2", "sourceHandle": "openAI_2-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain", - "target": "llmChain_2", - "targetHandle": "llmChain_2-input-model-BaseLanguageModel", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-model-BaseLanguageModel", "type": "buttonedge", - "id": "openAI_2-openAI_2-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain-llmChain_2-llmChain_2-input-model-BaseLanguageModel", + "id": "openAI_2-openAI_2-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain-llmChain_0-llmChain_0-input-model-BaseLanguageModel", "data": { "label": "" } }, { - "source": "promptTemplate_2", - "sourceHandle": "promptTemplate_2-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "target": "llmChain_2", - "targetHandle": "llmChain_2-input-prompt-BasePromptTemplate", + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", "type": "buttonedge", - "id": "promptTemplate_2-promptTemplate_2-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_2-llmChain_2-input-prompt-BasePromptTemplate", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } + }, + { + "source": "llmChain_0", + "sourceHandle": "llmChain_0-output-outputPrediction-string|json", + "target": "promptTemplate_1", + "targetHandle": "promptTemplate_1-input-promptValues-json", + "type": "buttonedge", + "id": "llmChain_0-llmChain_0-output-outputPrediction-string|json-promptTemplate_1-promptTemplate_1-input-promptValues-json", "data": { "label": "" } @@ -627,21 +623,21 @@ { "source": "openAI_3", "sourceHandle": "openAI_3-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain", - "target": "llmChain_3", - "targetHandle": "llmChain_3-input-model-BaseLanguageModel", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-model-BaseLanguageModel", "type": "buttonedge", - "id": "openAI_3-openAI_3-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain-llmChain_3-llmChain_3-input-model-BaseLanguageModel", + "id": "openAI_3-openAI_3-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain-llmChain_1-llmChain_1-input-model-BaseLanguageModel", "data": { "label": "" } }, { - "source": "promptTemplate_3", - "sourceHandle": "promptTemplate_3-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "target": "llmChain_3", - "targetHandle": "llmChain_3-input-prompt-BasePromptTemplate", + "source": "promptTemplate_1", + "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", "type": "buttonedge", - "id": "promptTemplate_3-promptTemplate_3-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_3-llmChain_3-input-prompt-BasePromptTemplate", + "id": "promptTemplate_1-promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", "data": { "label": "" } diff --git a/packages/server/marketplaces/Simple LLM Chain.json b/packages/server/marketplaces/Simple LLM Chain.json index c9d354bc..cc193d5c 100644 --- a/packages/server/marketplaces/Simple LLM Chain.json +++ b/packages/server/marketplaces/Simple LLM Chain.json @@ -3,7 +3,7 @@ "nodes": [ { "width": 300, - "height": 526, + "height": 524, "id": "openAI_1", "position": { "x": 510.75932526856377, @@ -156,68 +156,7 @@ }, { "width": 300, - "height": 534, - "id": "promptTemplate_1", - "position": { - "x": 514.5434056794296, - "y": 507.47798128037107 - }, - "type": "customNode", - "data": { - "id": "promptTemplate_1", - "label": "Prompt Template", - "name": "promptTemplate", - "type": "PromptTemplate", - "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a basic prompt for an LLM", - "inputParams": [ - { - "label": "Template", - "name": "template", - "type": "string", - "rows": 4, - "placeholder": "What is a good name for a company that makes {product}?", - "id": "promptTemplate_1-input-template-string" - }, - { - "label": "Format Prompt Values", - "name": "promptValues", - "type": "string", - "rows": 4, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true, - "acceptVariable": true, - "list": true, - "id": "promptTemplate_1-input-promptValues-string" - } - ], - "inputAnchors": [], - "inputs": { - "template": "", - "promptValues": "" - }, - "outputAnchors": [ - { - "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "name": "promptTemplate", - "label": "PromptTemplate", - "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 514.5434056794296, - "y": 507.47798128037107 - }, - "dragging": false - }, - { - "width": 300, - "height": 407, + "height": 405, "id": "llmChain_1", "position": { "x": 970.9254258940236, @@ -258,7 +197,7 @@ ], "inputs": { "model": "{{openAI_1.data.instance}}", - "prompt": "{{promptTemplate_1.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", "chainName": "" }, "outputAnchors": [ @@ -274,10 +213,10 @@ "type": "LLMChain | BaseChain | BaseLangChain" }, { - "id": "llmChain_1-output-outputPrediction-string", + "id": "llmChain_1-output-outputPrediction-string|json", "name": "outputPrediction", "label": "Output Prediction", - "type": "string" + "type": "string | json" } ], "default": "llmChain" @@ -294,6 +233,65 @@ }, "selected": false, "dragging": false + }, + { + "width": 300, + "height": 475, + "id": "promptTemplate_0", + "position": { + "x": 517.7412884791509, + "y": 506.7411400888471 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_0", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "What is a good name for a company that makes {product}?", + "promptValues": "" + }, + "outputAnchors": [ + { + "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 517.7412884791509, + "y": 506.7411400888471 + }, + "dragging": false } ], "edges": [ @@ -309,12 +307,12 @@ } }, { - "source": "promptTemplate_1", - "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", "target": "llmChain_1", "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", "type": "buttonedge", - "id": "promptTemplate_1-promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", "data": { "label": "" } diff --git a/packages/server/marketplaces/Translator.json b/packages/server/marketplaces/Translator.json index fda400e2..942bbecd 100644 --- a/packages/server/marketplaces/Translator.json +++ b/packages/server/marketplaces/Translator.json @@ -3,77 +3,7 @@ "nodes": [ { "width": 300, - "height": 711, - "id": "chatPromptTemplate_1", - "position": { - "x": 441.8516979620723, - "y": 636.1108860994266 - }, - "type": "customNode", - "data": { - "id": "chatPromptTemplate_1", - "label": "Chat Prompt Template", - "name": "chatPromptTemplate", - "type": "ChatPromptTemplate", - "baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a chat prompt", - "inputParams": [ - { - "label": "System Message", - "name": "systemMessagePrompt", - "type": "string", - "rows": 4, - "placeholder": "You are a helpful assistant that translates {input_language} to {output_language}.", - "id": "chatPromptTemplate_1-input-systemMessagePrompt-string" - }, - { - "label": "Human Message", - "name": "humanMessagePrompt", - "type": "string", - "rows": 4, - "placeholder": "{text}", - "id": "chatPromptTemplate_1-input-humanMessagePrompt-string" - }, - { - "label": "Format Prompt Values", - "name": "promptValues", - "type": "string", - "rows": 4, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true, - "acceptVariable": true, - "list": true, - "id": "chatPromptTemplate_1-input-promptValues-string" - } - ], - "inputAnchors": [], - "inputs": { - "systemMessagePrompt": "You are a helpful assistant that translates {input_language} to {output_language}.", - "humanMessagePrompt": "{input}", - "promptValues": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}" - }, - "outputAnchors": [ - { - "id": "chatPromptTemplate_1-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate", - "name": "chatPromptTemplate", - "label": "ChatPromptTemplate", - "type": "ChatPromptTemplate | BaseChatPromptTemplate | BasePromptTemplate" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 441.8516979620723, - "y": 636.1108860994266 - }, - "dragging": false - }, - { - "width": 300, - "height": 526, + "height": 524, "id": "chatOpenAI_1", "position": { "x": 439.5219561593599, @@ -224,7 +154,7 @@ }, { "width": 300, - "height": 407, + "height": 405, "id": "llmChain_1", "position": { "x": 865.7775572410412, @@ -265,7 +195,7 @@ ], "inputs": { "model": "{{chatOpenAI_1.data.instance}}", - "prompt": "{{chatPromptTemplate_1.data.instance}}", + "prompt": "{{chatPromptTemplate_0.data.instance}}", "chainName": "Language Translation" }, "outputAnchors": [ @@ -281,10 +211,10 @@ "type": "LLMChain | BaseChain | BaseLangChain" }, { - "id": "llmChain_1-output-outputPrediction-string", + "id": "llmChain_1-output-outputPrediction-string|json", "name": "outputPrediction", "label": "Output Prediction", - "type": "string" + "type": "string | json" } ], "default": "llmChain" @@ -301,6 +231,74 @@ "y": 543.9211372857111 }, "dragging": false + }, + { + "width": 300, + "height": 652, + "id": "chatPromptTemplate_0", + "position": { + "x": 437.51367850489396, + "y": 649.7619214034173 + }, + "type": "customNode", + "data": { + "id": "chatPromptTemplate_0", + "label": "Chat Prompt Template", + "name": "chatPromptTemplate", + "type": "ChatPromptTemplate", + "baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a chat prompt", + "inputParams": [ + { + "label": "System Message", + "name": "systemMessagePrompt", + "type": "string", + "rows": 4, + "placeholder": "You are a helpful assistant that translates {input_language} to {output_language}.", + "id": "chatPromptTemplate_0-input-systemMessagePrompt-string" + }, + { + "label": "Human Message", + "name": "humanMessagePrompt", + "type": "string", + "rows": 4, + "placeholder": "{text}", + "id": "chatPromptTemplate_0-input-humanMessagePrompt-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "chatPromptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "systemMessagePrompt": "You are a helpful assistant that translates {input_language} to {output_language}.", + "humanMessagePrompt": "{text}", + "promptValues": "{\"input_language\":\"English\",\"output_language\":\"French\",\"text\":\"{{question}}\"}" + }, + "outputAnchors": [ + { + "id": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate", + "name": "chatPromptTemplate", + "label": "ChatPromptTemplate", + "type": "ChatPromptTemplate | BaseChatPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 437.51367850489396, + "y": 649.7619214034173 + }, + "dragging": false } ], "edges": [ @@ -316,12 +314,12 @@ } }, { - "source": "chatPromptTemplate_1", - "sourceHandle": "chatPromptTemplate_1-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate", + "source": "chatPromptTemplate_0", + "sourceHandle": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate", "target": "llmChain_1", "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", "type": "buttonedge", - "id": "chatPromptTemplate_1-chatPromptTemplate_1-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "id": "chatPromptTemplate_0-chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", "data": { "label": "" } diff --git a/packages/ui/package.json b/packages/ui/package.json index 1e55f1c8..ba1483b4 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -18,6 +18,7 @@ "clsx": "^1.1.1", "flowise-embed": "*", "flowise-embed-react": "*", + "flowise-react-json-view": "*", "formik": "^2.2.6", "framer-motion": "^4.1.13", "history": "^5.0.0", @@ -33,7 +34,6 @@ "react-datepicker": "^4.8.0", "react-device-detect": "^1.17.0", "react-dom": "^18.2.0", - "react-json-view": "^1.21.3", "react-markdown": "^8.0.6", "react-perfect-scrollbar": "^1.5.8", "react-redux": "^8.0.5", diff --git a/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.js b/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.js deleted file mode 100644 index 199b1306..00000000 --- a/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.js +++ /dev/null @@ -1,256 +0,0 @@ -import { createPortal } from 'react-dom' -import { useState, useEffect } from 'react' -import { useSelector } from 'react-redux' -import PropTypes from 'prop-types' -import { - Button, - Dialog, - DialogActions, - DialogContent, - Box, - List, - ListItemButton, - ListItem, - ListItemAvatar, - ListItemText, - Typography, - Stack -} from '@mui/material' -import { useTheme } from '@mui/material/styles' -import PerfectScrollbar from 'react-perfect-scrollbar' -import { StyledButton } from 'ui-component/button/StyledButton' -import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' -import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' - -import './EditPromptValuesDialog.css' -import { baseURL } from 'store/constant' - -const EditPromptValuesDialog = ({ show, dialogProps, onCancel, onConfirm }) => { - const portalElement = document.getElementById('portal') - - const theme = useTheme() - const customization = useSelector((state) => state.customization) - const languageType = 'json' - - const [inputValue, setInputValue] = useState('') - const [inputParam, setInputParam] = useState(null) - const [textCursorPosition, setTextCursorPosition] = useState({}) - - useEffect(() => { - if (dialogProps.value) setInputValue(dialogProps.value) - if (dialogProps.inputParam) setInputParam(dialogProps.inputParam) - - return () => { - setInputValue('') - setInputParam(null) - setTextCursorPosition({}) - } - }, [dialogProps]) - - const onMouseUp = (e) => { - if (e.target && e.target.selectionEnd && e.target.value) { - const cursorPosition = e.target.selectionEnd - const textBeforeCursorPosition = e.target.value.substring(0, cursorPosition) - const textAfterCursorPosition = e.target.value.substring(cursorPosition, e.target.value.length) - const body = { - textBeforeCursorPosition, - textAfterCursorPosition - } - setTextCursorPosition(body) - } else { - setTextCursorPosition({}) - } - } - - const onSelectOutputResponseClick = (node, isUserQuestion = false) => { - let variablePath = isUserQuestion ? `question` : `${node.id}.data.instance` - if (textCursorPosition) { - let newInput = '' - if (textCursorPosition.textBeforeCursorPosition === undefined && textCursorPosition.textAfterCursorPosition === undefined) - newInput = `${inputValue}${`{{${variablePath}}}`}` - else newInput = `${textCursorPosition.textBeforeCursorPosition}{{${variablePath}}}${textCursorPosition.textAfterCursorPosition}` - setInputValue(newInput) - } - } - - const component = show ? ( - - -
- {inputParam && inputParam.type === 'string' && ( -
- - {inputParam.label} - - - {customization.isDarkMode ? ( - setInputValue(code)} - placeholder={inputParam.placeholder} - type={languageType} - onMouseUp={(e) => onMouseUp(e)} - onBlur={(e) => onMouseUp(e)} - style={{ - fontSize: '0.875rem', - minHeight: 'calc(100vh - 220px)', - width: '100%' - }} - /> - ) : ( - setInputValue(code)} - placeholder={inputParam.placeholder} - type={languageType} - onMouseUp={(e) => onMouseUp(e)} - onBlur={(e) => onMouseUp(e)} - style={{ - fontSize: '0.875rem', - minHeight: 'calc(100vh - 220px)', - width: '100%' - }} - /> - )} - -
- )} - {!dialogProps.disabled && inputParam && inputParam.acceptVariable && ( -
- - Select Variable - - - - - onSelectOutputResponseClick(null, true)} - > - - -
- AI -
-
- -
-
- {dialogProps.availableNodesForVariable && - dialogProps.availableNodesForVariable.length > 0 && - dialogProps.availableNodesForVariable.map((node, index) => { - const selectedOutputAnchor = node.data.outputAnchors[0].options.find( - (ancr) => ancr.name === node.data.outputs['output'] - ) - return ( - onSelectOutputResponseClick(node)} - > - - -
- {node.data.name} -
-
- -
-
- ) - })} -
-
-
-
- )} -
-
- - - onConfirm(inputValue, inputParam.name)}> - {dialogProps.confirmButtonName} - - -
- ) : null - - return createPortal(component, portalElement) -} - -EditPromptValuesDialog.propTypes = { - show: PropTypes.bool, - dialogProps: PropTypes.object, - onCancel: PropTypes.func, - onConfirm: PropTypes.func -} - -export default EditPromptValuesDialog diff --git a/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.css b/packages/ui/src/ui-component/dialog/ExpandTextDialog.css similarity index 100% rename from packages/ui/src/ui-component/dialog/EditPromptValuesDialog.css rename to packages/ui/src/ui-component/dialog/ExpandTextDialog.css diff --git a/packages/ui/src/ui-component/dialog/ExpandTextDialog.js b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js new file mode 100644 index 00000000..b955ccdb --- /dev/null +++ b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js @@ -0,0 +1,105 @@ +import { createPortal } from 'react-dom' +import { useState, useEffect } from 'react' +import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' +import { Button, Dialog, DialogActions, DialogContent, Typography } from '@mui/material' +import { useTheme } from '@mui/material/styles' +import PerfectScrollbar from 'react-perfect-scrollbar' +import { StyledButton } from 'ui-component/button/StyledButton' +import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' +import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' + +import './ExpandTextDialog.css' + +const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const portalElement = document.getElementById('portal') + + const theme = useTheme() + const customization = useSelector((state) => state.customization) + const languageType = 'json' + + const [inputValue, setInputValue] = useState('') + const [inputParam, setInputParam] = useState(null) + + useEffect(() => { + if (dialogProps.value) setInputValue(dialogProps.value) + if (dialogProps.inputParam) setInputParam(dialogProps.inputParam) + + return () => { + setInputValue('') + setInputParam(null) + } + }, [dialogProps]) + + const component = show ? ( + + +
+ {inputParam && inputParam.type === 'string' && ( +
+ + {inputParam.label} + + + {customization.isDarkMode ? ( + setInputValue(code)} + placeholder={inputParam.placeholder} + type={languageType} + style={{ + fontSize: '0.875rem', + minHeight: 'calc(100vh - 220px)', + width: '100%' + }} + /> + ) : ( + setInputValue(code)} + placeholder={inputParam.placeholder} + type={languageType} + style={{ + fontSize: '0.875rem', + minHeight: 'calc(100vh - 220px)', + width: '100%' + }} + /> + )} + +
+ )} +
+
+ + + onConfirm(inputValue, inputParam.name)}> + {dialogProps.confirmButtonName} + + +
+ ) : null + + return createPortal(component, portalElement) +} + +ExpandTextDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func, + onConfirm: PropTypes.func +} + +export default ExpandTextDialog diff --git a/packages/ui/src/ui-component/dialog/FormatPromptValuesDialog.js b/packages/ui/src/ui-component/dialog/FormatPromptValuesDialog.js new file mode 100644 index 00000000..df1d357e --- /dev/null +++ b/packages/ui/src/ui-component/dialog/FormatPromptValuesDialog.js @@ -0,0 +1,56 @@ +import { createPortal } from 'react-dom' +import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' +import { Dialog, DialogContent, DialogTitle } from '@mui/material' +import PerfectScrollbar from 'react-perfect-scrollbar' +import { JsonEditorInput } from 'ui-component/json/JsonEditor' + +const FormatPromptValuesDialog = ({ show, dialogProps, onChange, onCancel }) => { + const portalElement = document.getElementById('portal') + const customization = useSelector((state) => state.customization) + + const component = show ? ( + + + Format Prompt Values + + + + onChange(newValue)} + value={dialogProps.value} + isDarkMode={customization.isDarkMode} + inputParam={dialogProps.inputParam} + nodes={dialogProps.nodes} + edges={dialogProps.edges} + nodeId={dialogProps.nodeId} + /> + + + + ) : null + + return createPortal(component, portalElement) +} + +FormatPromptValuesDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onChange: PropTypes.func, + onCancel: PropTypes.func +} + +export default FormatPromptValuesDialog diff --git a/packages/ui/src/ui-component/dialog/SourceDocDialog.js b/packages/ui/src/ui-component/dialog/SourceDocDialog.js index a088a6c4..6bf8692f 100644 --- a/packages/ui/src/ui-component/dialog/SourceDocDialog.js +++ b/packages/ui/src/ui-component/dialog/SourceDocDialog.js @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' import { Dialog, DialogContent, DialogTitle } from '@mui/material' -import ReactJson from 'react-json-view' +import ReactJson from 'flowise-react-json-view' const SourceDocDialog = ({ show, dialogProps, onCancel }) => { const portalElement = document.getElementById('portal') diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index 7f0e0610..e7744764 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -1,7 +1,7 @@ import { useState } from 'react' import PropTypes from 'prop-types' import { FormControl, OutlinedInput } from '@mui/material' -import EditPromptValuesDialog from 'ui-component/dialog/EditPromptValuesDialog' +import ExpandTextDialog from 'ui-component/dialog/ExpandTextDialog' export const Input = ({ inputParam, value, onChange, disabled = false, showDialog, dialogProps, onDialogCancel, onDialogConfirm }) => { const [myValue, setMyValue] = useState(value ?? '') @@ -45,7 +45,7 @@ export const Input = ({ inputParam, value, onChange, disabled = false, showDialo /> {showDialog && ( - + > )} ) diff --git a/packages/ui/src/ui-component/json/JsonEditor.js b/packages/ui/src/ui-component/json/JsonEditor.js index 06442df2..4bf8f306 100644 --- a/packages/ui/src/ui-component/json/JsonEditor.js +++ b/packages/ui/src/ui-component/json/JsonEditor.js @@ -1,10 +1,32 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import PropTypes from 'prop-types' -import { FormControl } from '@mui/material' -import ReactJson from 'react-json-view' +import { FormControl, Popover } from '@mui/material' +import ReactJson from 'flowise-react-json-view' +import SelectVariable from './SelectVariable' +import { cloneDeep } from 'lodash' +import { getAvailableNodesForVariable } from 'utils/genericHelper' -export const JsonEditorInput = ({ value, onChange, disabled = false, isDarkMode = false }) => { +export const JsonEditorInput = ({ value, onChange, inputParam, nodes, edges, nodeId, disabled = false, isDarkMode = false }) => { const [myValue, setMyValue] = useState(value ? JSON.parse(value) : {}) + const [availableNodesForVariable, setAvailableNodesForVariable] = useState([]) + const [mouseUpKey, setMouseUpKey] = useState('') + + const [anchorEl, setAnchorEl] = useState(null) + const openPopOver = Boolean(anchorEl) + + const handleClosePopOver = () => { + setAnchorEl(null) + } + + const setNewVal = (val) => { + const newVal = cloneDeep(myValue) + newVal[mouseUpKey] = val + onChange(JSON.stringify(newVal)) + setMyValue((params) => ({ + ...params, + [mouseUpKey]: val + })) + } const onClipboardCopy = (e) => { const src = e.src @@ -15,6 +37,13 @@ export const JsonEditorInput = ({ value, onChange, disabled = false, isDarkMode } } + useEffect(() => { + if (!disabled && nodes && edges && nodeId && inputParam) { + const nodesForVariable = inputParam?.acceptVariable ? getAvailableNodesForVariable(nodes, edges, nodeId, inputParam.id) : [] + setAvailableNodesForVariable(nodesForVariable) + } + }, [disabled, inputParam, nodes, edges, nodeId]) + return ( <> @@ -30,28 +59,60 @@ export const JsonEditorInput = ({ value, onChange, disabled = false, isDarkMode /> )} {!disabled && ( - onClipboardCopy(e)} - onEdit={(edit) => { - setMyValue(edit.updated_src) - onChange(JSON.stringify(edit.updated_src)) - }} - onAdd={() => { - //console.log(add) - }} - onDelete={(deleteobj) => { - setMyValue(deleteobj.updated_src) - onChange(JSON.stringify(deleteobj.updated_src)) - }} - /> +
+ onClipboardCopy(e)} + onMouseUp={(event) => { + if (inputParam?.acceptVariable) { + setMouseUpKey(event.name) + setAnchorEl(event.currentTarget) + } + }} + onEdit={(edit) => { + setMyValue(edit.updated_src) + onChange(JSON.stringify(edit.updated_src)) + }} + onAdd={() => { + //console.log(add) + }} + onDelete={(deleteobj) => { + setMyValue(deleteobj.updated_src) + onChange(JSON.stringify(deleteobj.updated_src)) + }} + /> +
)}
+ {inputParam?.acceptVariable && ( + + { + setNewVal(val) + handleClosePopOver() + }} + /> + + )} ) } @@ -60,5 +121,9 @@ JsonEditorInput.propTypes = { value: PropTypes.string, onChange: PropTypes.func, disabled: PropTypes.bool, - isDarkMode: PropTypes.bool + isDarkMode: PropTypes.bool, + inputParam: PropTypes.object, + nodes: PropTypes.array, + edges: PropTypes.array, + nodeId: PropTypes.string } diff --git a/packages/ui/src/ui-component/json/SelectVariable.js b/packages/ui/src/ui-component/json/SelectVariable.js new file mode 100644 index 00000000..1b891ed1 --- /dev/null +++ b/packages/ui/src/ui-component/json/SelectVariable.js @@ -0,0 +1,126 @@ +import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' +import { Box, List, ListItemButton, ListItem, ListItemAvatar, ListItemText, Typography, Stack } from '@mui/material' +import PerfectScrollbar from 'react-perfect-scrollbar' + +import { baseURL } from 'store/constant' + +const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectAndReturnVal }) => { + const customization = useSelector((state) => state.customization) + + const onSelectOutputResponseClick = (node, isUserQuestion = false) => { + let variablePath = isUserQuestion ? `question` : `${node.id}.data.instance` + const newInput = `{{${variablePath}}}` + onSelectAndReturnVal(newInput) + } + + return ( + <> + {!disabled && ( +
+ + Select Variable + + + + + onSelectOutputResponseClick(null, true)} + > + + +
+ AI +
+
+ +
+
+ {availableNodesForVariable && + availableNodesForVariable.length > 0 && + availableNodesForVariable.map((node, index) => { + const selectedOutputAnchor = node.data.outputAnchors[0].options.find( + (ancr) => ancr.name === node.data.outputs['output'] + ) + return ( + onSelectOutputResponseClick(node)} + > + + +
+ {node.data.name} +
+
+ +
+
+ ) + })} +
+
+
+
+ )} + + ) +} + +SelectVariable.propTypes = { + availableNodesForVariable: PropTypes.array, + disabled: PropTypes.bool, + onSelectAndReturnVal: PropTypes.func +} + +export default SelectVariable diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index 03f891ec..42a63057 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -285,7 +285,7 @@ export const generateExportFlowData = (flowData) => { } export const getAvailableNodesForVariable = (nodes, edges, target, targetHandle) => { - // example edge id = "llmChain_0-llmChain_0-output-outputPrediction-string-llmChain_1-llmChain_1-input-promptValues-string" + // example edge id = "llmChain_0-llmChain_0-output-outputPrediction-string|json-llmChain_1-llmChain_1-input-promptValues-string" // {source} -{sourceHandle} -{target} -{targetHandle} const parentNodes = [] const inputEdges = edges.filter((edg) => edg.target === target && edg.targetHandle === targetHandle) @@ -353,3 +353,31 @@ export const generateRandomGradient = () => { return gradient } + +export const getInputVariables = (paramValue) => { + let returnVal = paramValue + const variableStack = [] + const inputVariables = [] + let startIdx = 0 + const endIdx = returnVal.length + + while (startIdx < endIdx) { + const substr = returnVal.substring(startIdx, startIdx + 1) + + // Store the opening double curly bracket + if (substr === '{') { + variableStack.push({ substr, startIdx: startIdx + 1 }) + } + + // Found the complete variable + if (substr === '}' && variableStack.length > 0 && variableStack[variableStack.length - 1].substr === '{') { + const variableStartIdx = variableStack[variableStack.length - 1].startIdx + const variableEndIdx = startIdx + const variableFullPath = returnVal.substring(variableStartIdx, variableEndIdx) + inputVariables.push(variableFullPath) + variableStack.pop() + } + startIdx += 1 + } + return inputVariables +} diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 4ad21904..2d96bcb5 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -5,7 +5,7 @@ import { useSelector } from 'react-redux' // material-ui import { useTheme, styled } from '@mui/material/styles' -import { Box, Typography, Tooltip, IconButton } from '@mui/material' +import { Box, Typography, Tooltip, IconButton, Button } from '@mui/material' import { tooltipClasses } from '@mui/material/Tooltip' import { IconArrowsMaximize, IconEdit } from '@tabler/icons' @@ -16,10 +16,13 @@ import { Input } from 'ui-component/input/Input' import { File } from 'ui-component/file/File' import { SwitchInput } from 'ui-component/switch/Switch' import { flowContext } from 'store/context/ReactFlowContext' -import { isValidConnection, getAvailableNodesForVariable } from 'utils/genericHelper' +import { isValidConnection } from 'utils/genericHelper' import { JsonEditorInput } from 'ui-component/json/JsonEditor' import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' import ToolDialog from 'views/tools/ToolDialog' +import FormatPromptValuesDialog from 'ui-component/dialog/FormatPromptValuesDialog' + +import { getInputVariables } from 'utils/genericHelper' const EDITABLE_TOOLS = ['selectedTool'] @@ -43,6 +46,8 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA const [showAsyncOptionDialog, setAsyncOptionEditDialog] = useState('') const [asyncOptionEditDialogProps, setAsyncOptionEditDialogProps] = useState({}) const [reloadTimestamp, setReloadTimestamp] = useState(Date.now().toString()) + const [showFormatPromptValuesDialog, setShowFormatPromptValuesDialog] = useState(false) + const [formatPromptValuesDialogProps, setFormatPromptValuesDialogProps] = useState({}) const onExpandDialogClicked = (value, inputParam) => { const dialogProp = { @@ -52,17 +57,34 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA confirmButtonName: 'Save', cancelButtonName: 'Cancel' } - - if (!disabled) { - const nodes = reactFlowInstance.getNodes() - const edges = reactFlowInstance.getEdges() - const nodesForVariable = inputParam.acceptVariable ? getAvailableNodesForVariable(nodes, edges, data.id, inputParam.id) : [] - dialogProp.availableNodesForVariable = nodesForVariable - } setExpandDialogProps(dialogProp) setShowExpandDialog(true) } + const onFormatPromptValuesClicked = (value, inputParam) => { + // Preset values if the field is format prompt values + let inputValue = value + if (inputParam.name === 'promptValues' && !value) { + const obj = {} + const templateValue = + (data.inputs['template'] ?? '') + (data.inputs['systemMessagePrompt'] ?? '') + (data.inputs['humanMessagePrompt'] ?? '') + const inputVariables = getInputVariables(templateValue) + for (const inputVariable of inputVariables) { + obj[inputVariable] = '' + } + if (Object.keys(obj).length) inputValue = JSON.stringify(obj) + } + const dialogProp = { + value: inputValue, + inputParam, + nodes: reactFlowInstance.getNodes(), + edges: reactFlowInstance.getEdges(), + nodeId: data.id + } + setFormatPromptValuesDialogProps(dialogProp) + setShowFormatPromptValuesDialog(true) + } + const onExpandDialogSave = (newValue, inputParamName) => { setShowExpandDialog(false) data.inputs[inputParamName] = newValue @@ -217,12 +239,33 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA /> )} {inputParam.type === 'json' && ( - (data.inputs[inputParam.name] = newValue)} - value={data.inputs[inputParam.name] ?? inputParam.default ?? ''} - isDarkMode={customization.isDarkMode} - /> + <> + {!inputParam?.acceptVariable && ( + (data.inputs[inputParam.name] = newValue)} + value={data.inputs[inputParam.name] ?? inputParam.default ?? ''} + isDarkMode={customization.isDarkMode} + /> + )} + {inputParam?.acceptVariable && ( + <> + + setShowFormatPromptValuesDialog(false)} + onChange={(newValue) => (data.inputs[inputParam.name] = newValue)} + > + + )} + )} {inputParam.type === 'options' && ( Date: Thu, 29 Jun 2023 14:21:44 +0800 Subject: [PATCH 144/398] fix: temperature should convert to float --- packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts index 26f54db8..955563ff 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts @@ -132,7 +132,7 @@ class ChatOpenAI_ChatModels implements INode { const basePath = nodeData.inputs?.basepath as string const obj: Partial & { openAIApiKey?: string } = { - temperature: parseInt(temperature, 10), + temperature: parseFloat(temperature), modelName, openAIApiKey, streaming: streaming ?? true From 184a783847c0b7388c48f06c863a94ccca8a37e5 Mon Sep 17 00:00:00 2001 From: Jeffrey-Wang Date: Thu, 29 Jun 2023 21:23:21 +0800 Subject: [PATCH 145/398] fix: temperature convert to float --- .../nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts | 2 +- .../components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts | 2 +- .../nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts | 2 +- packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts | 2 +- packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts | 2 +- packages/components/nodes/llms/Cohere/Cohere.ts | 2 +- .../nodes/llms/HuggingFaceInference/HuggingFaceInference.ts | 2 +- packages/components/nodes/llms/OpenAI/OpenAI.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts index 7857bfdf..60295890 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts @@ -124,7 +124,7 @@ class AzureChatOpenAI_ChatModels implements INode { const streaming = nodeData.inputs?.streaming as boolean const obj: Partial & Partial = { - temperature: parseInt(temperature, 10), + temperature: parseFloat(temperature), modelName, azureOpenAIApiKey, azureOpenAIApiInstanceName, diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts index 708849e5..3d861d24 100644 --- a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts +++ b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts @@ -120,7 +120,7 @@ class ChatAnthropic_ChatModels implements INode { const streaming = nodeData.inputs?.streaming as boolean const obj: Partial & { anthropicApiKey?: string } = { - temperature: parseInt(temperature, 10), + temperature: parseFloat(temperature), modelName, anthropicApiKey, streaming: streaming ?? true diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts index 3252a61a..1dae41e4 100644 --- a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts @@ -89,7 +89,7 @@ class ChatHuggingFace_ChatModels implements INode { apiKey } - if (temperature) obj.temperature = parseInt(temperature, 10) + if (temperature) obj.temperature = parseFloat(temperature) if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) if (topP) obj.topP = parseInt(topP, 10) if (hfTopK) obj.topK = parseInt(hfTopK, 10) diff --git a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts index bd25a9fa..c5860b24 100644 --- a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts +++ b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts @@ -74,7 +74,7 @@ class ChatLocalAI_ChatModels implements INode { const basePath = nodeData.inputs?.basePath as string const obj: Partial & { openAIApiKey?: string } = { - temperature: parseInt(temperature, 10), + temperature: parseFloat(temperature), modelName, openAIApiKey: 'sk-' } diff --git a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts index c19aa83a..f81c9349 100644 --- a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts +++ b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts @@ -179,7 +179,7 @@ class AzureOpenAI_LLMs implements INode { const streaming = nodeData.inputs?.streaming as boolean const obj: Partial & Partial = { - temperature: parseInt(temperature, 10), + temperature: parseFloat(temperature), modelName, azureOpenAIApiKey, azureOpenAIApiInstanceName, diff --git a/packages/components/nodes/llms/Cohere/Cohere.ts b/packages/components/nodes/llms/Cohere/Cohere.ts index a7e9c696..75151571 100644 --- a/packages/components/nodes/llms/Cohere/Cohere.ts +++ b/packages/components/nodes/llms/Cohere/Cohere.ts @@ -87,7 +87,7 @@ class Cohere_LLMs implements INode { if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) if (modelName) obj.model = modelName - if (temperature) obj.temperature = parseInt(temperature, 10) + if (temperature) obj.temperature = parseFloat(temperature) const model = new Cohere(obj) return model diff --git a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts index 88a7db07..291f67c9 100644 --- a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts +++ b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts @@ -89,7 +89,7 @@ class HuggingFaceInference_LLMs implements INode { apiKey } - if (temperature) obj.temperature = parseInt(temperature, 10) + if (temperature) obj.temperature = parseFloat(temperature) if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) if (topP) obj.topP = parseInt(topP, 10) if (hfTopK) obj.topK = parseInt(hfTopK, 10) diff --git a/packages/components/nodes/llms/OpenAI/OpenAI.ts b/packages/components/nodes/llms/OpenAI/OpenAI.ts index fb7e5b6b..b0af867d 100644 --- a/packages/components/nodes/llms/OpenAI/OpenAI.ts +++ b/packages/components/nodes/llms/OpenAI/OpenAI.ts @@ -132,7 +132,7 @@ class OpenAI_LLMs implements INode { const basePath = nodeData.inputs?.basepath as string const obj: Partial & { openAIApiKey?: string } = { - temperature: parseInt(temperature, 10), + temperature: parseFloat(temperature), modelName, openAIApiKey, streaming: streaming ?? true From 7141401e265a7dfa8261970c430470683697eeeb Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 29 Jun 2023 23:47:20 +0100 Subject: [PATCH 146/398] fix bug where share chatflow is not able to open on any other browser --- packages/server/.env.example | 1 + packages/server/src/Interface.ts | 10 +- packages/server/src/entity/ChatFlow.ts | 8 +- packages/server/src/entity/ChatMessage.ts | 2 +- packages/server/src/entity/Tool.ts | 4 +- packages/server/src/index.ts | 12 +- packages/server/src/utils/index.ts | 2 +- packages/ui/src/api/chatflows.js | 3 + packages/ui/src/views/canvas/CanvasHeader.js | 8 +- packages/ui/src/views/canvas/index.js | 2 +- packages/ui/src/views/chatbot/index.js | 122 ++++++++++++------ .../ui/src/views/chatflows/APICodeDialog.js | 11 +- .../ui/src/views/chatflows/ShareChatbot.js | 71 ++++++++-- 13 files changed, 183 insertions(+), 73 deletions(-) diff --git a/packages/server/.env.example b/packages/server/.env.example index 3d524e5c..80fbc3be 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -3,4 +3,5 @@ PORT=3000 # FLOWISE_PASSWORD=1234 # DEBUG=true # DATABASE_PATH=/your_database_path/.flowise +# APIKEY_PATH=/your_api_key_path/.flowise # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 9473638f..9c47405c 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -9,10 +9,10 @@ export interface IChatFlow { id: string name: string flowData: string - apikeyid: string - deployed: boolean + isPublic: boolean updatedDate: Date createdDate: Date + apikeyid?: string chatbotConfig?: string } @@ -22,7 +22,7 @@ export interface IChatMessage { content: string chatflowid: string createdDate: Date - sourceDocuments: string + sourceDocuments?: string } export interface ITool { @@ -30,8 +30,8 @@ export interface ITool { name: string description: string color: string - schema: string - func: string + schema?: string + func?: string updatedDate: Date createdDate: Date } diff --git a/packages/server/src/entity/ChatFlow.ts b/packages/server/src/entity/ChatFlow.ts index 910272ad..400e0517 100644 --- a/packages/server/src/entity/ChatFlow.ts +++ b/packages/server/src/entity/ChatFlow.ts @@ -13,11 +13,11 @@ export class ChatFlow implements IChatFlow { @Column() flowData: string - @Column({ nullable: true }) - apikeyid: string - @Column() - deployed: boolean + isPublic: boolean + + @Column({ nullable: true }) + apikeyid?: string @Column({ nullable: true }) chatbotConfig?: string diff --git a/packages/server/src/entity/ChatMessage.ts b/packages/server/src/entity/ChatMessage.ts index 236dc5f9..3e4e41d2 100644 --- a/packages/server/src/entity/ChatMessage.ts +++ b/packages/server/src/entity/ChatMessage.ts @@ -18,7 +18,7 @@ export class ChatMessage implements IChatMessage { content: string @Column({ nullable: true }) - sourceDocuments: string + sourceDocuments?: string @CreateDateColumn() createdDate: Date diff --git a/packages/server/src/entity/Tool.ts b/packages/server/src/entity/Tool.ts index d547374c..307e8d23 100644 --- a/packages/server/src/entity/Tool.ts +++ b/packages/server/src/entity/Tool.ts @@ -17,10 +17,10 @@ export class Tool implements ITool { color: string @Column({ nullable: true }) - schema: string + schema?: string @Column({ nullable: true }) - func: string + func?: string @CreateDateColumn() createdDate: Date diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 65bfef23..e13934b2 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -92,7 +92,7 @@ export class App { const basicAuthMiddleware = basicAuth({ users: { [username]: password } }) - const whitelistURLs = ['/api/v1/prediction/', '/api/v1/node-icon/', '/api/v1/chatflows-streaming'] + const whitelistURLs = ['/api/v1/public-chatflows', '/api/v1/prediction/', '/api/v1/node-icon/', '/api/v1/chatflows-streaming'] this.app.use((req, res, next) => { if (req.url.includes('/api/v1/')) { whitelistURLs.some((url) => req.url.includes(url)) ? next() : basicAuthMiddleware(req, res, next) @@ -186,6 +186,16 @@ export class App { return res.status(404).send(`Chatflow ${req.params.id} not found`) }) + // Get specific chatflow via id (PUBLIC endpoint, used when sharing chatbot link) + this.app.get('/api/v1/public-chatflows/:id', async (req: Request, res: Response) => { + const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ + id: req.params.id + }) + if (chatflow && chatflow.isPublic) return res.json(chatflow) + else if (chatflow && !chatflow.isPublic) return res.status(401).send(`Unauthorized`) + return res.status(404).send(`Chatflow ${req.params.id} not found`) + }) + // Save chatflow this.app.post('/api/v1/chatflows', async (req: Request, res: Response) => { const body = req.body diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index e3005c7b..3601c77d 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -463,7 +463,7 @@ export const isSameOverrideConfig = ( * @returns {string} */ export const getAPIKeyPath = (): string => { - return path.join(__dirname, '..', '..', 'api.json') + return process.env.APIKEY_PATH ? path.join(process.env.APIKEY_PATH, 'api.json') : path.join(__dirname, '..', '..', 'api.json') } /** diff --git a/packages/ui/src/api/chatflows.js b/packages/ui/src/api/chatflows.js index 1cd1ebb0..8810b5a5 100644 --- a/packages/ui/src/api/chatflows.js +++ b/packages/ui/src/api/chatflows.js @@ -4,6 +4,8 @@ const getAllChatflows = () => client.get('/chatflows') const getSpecificChatflow = (id) => client.get(`/chatflows/${id}`) +const getSpecificChatflowFromPublicEndpoint = (id) => client.get(`/public-chatflows/${id}`) + const createNewChatflow = (body) => client.post(`/chatflows`, body) const updateChatflow = (id, body) => client.put(`/chatflows/${id}`, body) @@ -15,6 +17,7 @@ const getIsChatflowStreaming = (id) => client.get(`/chatflows-streaming/${id}`) export default { getAllChatflows, getSpecificChatflow, + getSpecificChatflowFromPublicEndpoint, createNewChatflow, updateChatflow, deleteChatflow, diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index 521aa9d3..1c1e5212 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types' import { useNavigate } from 'react-router-dom' -import { useSelector } from 'react-redux' +import { useSelector, useDispatch } from 'react-redux' import { useEffect, useRef, useState } from 'react' // material-ui @@ -24,11 +24,13 @@ import useApi from 'hooks/useApi' // utils import { generateExportFlowData } from 'utils/genericHelper' import { uiBaseURL } from 'store/constant' +import { SET_CHATFLOW } from 'store/actions' // ==============================|| CANVAS HEADER ||============================== // const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFlow }) => { const theme = useTheme() + const dispatch = useDispatch() const navigate = useNavigate() const flowNameRef = useRef() const settingsRef = useRef() @@ -107,8 +109,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl title: 'Embed in website or use as API', chatflowid: chatflow.id, chatflowApiKeyId: chatflow.apikeyid, - isFormDataRequired, - chatbotConfig: chatflow.chatbotConfig + isFormDataRequired }) setAPIDialogOpen(true) } @@ -126,6 +127,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl useEffect(() => { if (updateChatflowApi.data) { setFlowName(updateChatflowApi.data.name) + dispatch({ type: SET_CHATFLOW, chatflow: updateChatflowApi.data }) } setEditingFlowName(false) diff --git a/packages/ui/src/views/canvas/index.js b/packages/ui/src/views/canvas/index.js index 2d71f03a..03098963 100644 --- a/packages/ui/src/views/canvas/index.js +++ b/packages/ui/src/views/canvas/index.js @@ -201,7 +201,7 @@ const Canvas = () => { if (!chatflow.id) { const newChatflowBody = { name: chatflowName, - deployed: false, + isPublic: false, flowData } createNewChatflowApi.request(newChatflowBody) diff --git a/packages/ui/src/views/chatbot/index.js b/packages/ui/src/views/chatbot/index.js index b33bec2c..f29c35ee 100644 --- a/packages/ui/src/views/chatbot/index.js +++ b/packages/ui/src/views/chatbot/index.js @@ -1,57 +1,107 @@ import { useEffect, useState } from 'react' -import { baseURL } from 'store/constant' -import axios from 'axios' import { FullPageChat } from 'flowise-embed-react' +import { useNavigate } from 'react-router-dom' + +// Project import +import LoginDialog from 'ui-component/dialog/LoginDialog' + +// API +import chatflowsApi from 'api/chatflows' + +// Hooks +import useApi from 'hooks/useApi' + +//Const +import { baseURL } from 'store/constant' // ==============================|| Chatbot ||============================== // -const fetchChatflow = async ({ chatflowId }) => { - const username = localStorage.getItem('username') - const password = localStorage.getItem('password') - - let chatflow = await axios - .get(`${baseURL}/api/v1/chatflows/${chatflowId}`, { auth: username && password ? { username, password } : undefined }) - .then(async function (response) { - return response.data - }) - .catch(function (error) { - console.error(error) - }) - return chatflow -} - const ChatbotFull = () => { const URLpath = document.location.pathname.toString().split('/') const chatflowId = URLpath[URLpath.length - 1] === 'chatbot' ? '' : URLpath[URLpath.length - 1] + const navigate = useNavigate() const [chatflow, setChatflow] = useState(null) const [chatbotTheme, setChatbotTheme] = useState({}) + const [loginDialogOpen, setLoginDialogOpen] = useState(false) + const [loginDialogProps, setLoginDialogProps] = useState({}) + const [isLoading, setLoading] = useState(true) + + const getSpecificChatflowFromPublicApi = useApi(chatflowsApi.getSpecificChatflowFromPublicEndpoint) + const getSpecificChatflowApi = useApi(chatflowsApi.getSpecificChatflow) + + const onLoginClick = (username, password) => { + localStorage.setItem('username', username) + localStorage.setItem('password', password) + navigate(0) + } useEffect(() => { - ;(async () => { - const fetchData = async () => { - let response = await fetchChatflow({ chatflowId }) - setChatflow(response) - if (response.chatbotConfig) { - try { - setChatbotTheme(JSON.parse(response.chatbotConfig)) - } catch (e) { - console.error(e) - setChatbotTheme({}) - } + getSpecificChatflowFromPublicApi.request(chatflowId) + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (getSpecificChatflowFromPublicApi.error) { + if (getSpecificChatflowFromPublicApi.error?.response?.status === 401) { + if (localStorage.getItem('username') && localStorage.getItem('password')) { + getSpecificChatflowApi.request(chatflowId) + } else { + setLoginDialogProps({ + title: 'Login', + confirmButtonName: 'Login' + }) + setLoginDialogOpen(true) } } - fetchData() - })() - }, [chatflowId]) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getSpecificChatflowFromPublicApi.error]) + + useEffect(() => { + if (getSpecificChatflowApi.error) { + if (getSpecificChatflowApi.error?.response?.status === 401) { + setLoginDialogProps({ + title: 'Login', + confirmButtonName: 'Login' + }) + setLoginDialogOpen(true) + } + } + }, [getSpecificChatflowApi.error]) + + useEffect(() => { + if (getSpecificChatflowFromPublicApi.data || getSpecificChatflowApi.data) { + const chatflowData = getSpecificChatflowFromPublicApi.data || getSpecificChatflowApi.data + setChatflow(chatflowData) + if (chatflowData.chatbotConfig) { + try { + setChatbotTheme(JSON.parse(chatflowData.chatbotConfig)) + } catch (e) { + console.error(e) + setChatbotTheme({}) + } + } + } + }, [getSpecificChatflowFromPublicApi.data, getSpecificChatflowApi.data]) + + useEffect(() => { + setLoading(getSpecificChatflowFromPublicApi.loading || getSpecificChatflowApi.loading) + }, [getSpecificChatflowFromPublicApi.loading, getSpecificChatflowApi.loading]) return ( <> - {!chatflow || chatflow.apikeyid ? ( -

Invalid Chatbot

- ) : ( - - )} + {!isLoading ? ( + <> + {!chatflow || chatflow.apikeyid ? ( +

Invalid Chatbot

+ ) : ( + + )} + + + ) : null} ) } diff --git a/packages/ui/src/views/chatflows/APICodeDialog.js b/packages/ui/src/views/chatflows/APICodeDialog.js index fea49909..5e32c1d4 100644 --- a/packages/ui/src/views/chatflows/APICodeDialog.js +++ b/packages/ui/src/views/chatflows/APICodeDialog.js @@ -134,7 +134,6 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { const [chatflowApiKeyId, setChatflowApiKeyId] = useState('') const [selectedApiKey, setSelectedApiKey] = useState({}) const [checkboxVal, setCheckbox] = useState(false) - const [chatbotConfig, setChatbotConfig] = useState(null) const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys) const updateChatflowApi = useApi(chatflowsApi.updateChatflow) @@ -491,12 +490,6 @@ query({ setChatflowApiKeyId(dialogProps.chatflowApiKeyId) setSelectedApiKey(getAllAPIKeysApi.data.find((key) => key.id === dialogProps.chatflowApiKeyId)) } - - if (dialogProps.chatbotConfig) { - setChatbotConfig(JSON.parse(dialogProps.chatbotConfig)) - } else { - setChatbotConfig(null) - } } }, [dialogProps, getAllAPIKeysApi.data]) @@ -601,9 +594,7 @@ query({ )} )} - {codeLang === 'Share Chatbot' && !chatflowApiKeyId && ( - - )} + {codeLang === 'Share Chatbot' && !chatflowApiKeyId && } ))} diff --git a/packages/ui/src/views/chatflows/ShareChatbot.js b/packages/ui/src/views/chatflows/ShareChatbot.js index dffecf5b..51e12e54 100644 --- a/packages/ui/src/views/chatflows/ShareChatbot.js +++ b/packages/ui/src/views/chatflows/ShareChatbot.js @@ -1,7 +1,6 @@ -import PropTypes from 'prop-types' import { useState } from 'react' -import { useDispatch } from 'react-redux' -import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import { useDispatch, useSelector } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions' import { SketchPicker } from 'react-color' import { Box, Typography, Button, Switch, OutlinedInput, Popover, Stack, IconButton } from '@mui/material' @@ -9,6 +8,7 @@ import { useTheme } from '@mui/material/styles' // Project import import { StyledButton } from 'ui-component/button/StyledButton' +import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' // Icons import { IconX, IconCopy, IconArrowUpRightCircle } from '@tabler/icons' @@ -41,15 +41,20 @@ const defaultConfig = { } } -const ShareChatbot = ({ chatflowid, chatbotConfig }) => { +const ShareChatbot = () => { const dispatch = useDispatch() const theme = useTheme() + const chatflow = useSelector((state) => state.canvas.chatflow) + const chatflowid = chatflow.id + const chatbotConfig = chatflow.chatbotConfig ? JSON.parse(chatflow.chatbotConfig) : {} useNotifier() const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + const [isPublicChatflow, setChatflowIsPublic] = useState(chatflow.isPublic ?? false) + const [welcomeMessage, setWelcomeMessage] = useState(chatbotConfig?.welcomeMessage ?? '') const [backgroundColor, setBackgroundColor] = useState(chatbotConfig?.backgroundColor ?? defaultConfig.backgroundColor) const [fontSize, setFontSize] = useState(chatbotConfig?.fontSize ?? defaultConfig.fontSize) @@ -141,6 +146,44 @@ const ShareChatbot = ({ chatflowid, chatbotConfig }) => { ) } }) + dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) + } + } catch (error) { + console.error(error) + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to save Chatbot Configuration: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + } + } + + const onSwitchChange = async (checked) => { + try { + const saveResp = await chatflowsApi.updateChatflow(chatflowid, { isPublic: checked }) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Chatbot Configuration Saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) } } catch (error) { console.error(error) @@ -328,6 +371,21 @@ const ShareChatbot = ({ chatflowid, chatbotConfig }) => { window.open(`${baseURL}/chatbot/${chatflowid}`, '_blank')}> +
+
+ { + setChatflowIsPublic(event.target.checked) + onSwitchChange(event.target.checked) + }} + /> + Make Public + +
{textField(welcomeMessage, 'welcomeMessage', 'Welcome Message', 'string', 'Hello! This is custom welcome message')} {colorField(backgroundColor, 'backgroundColor', 'Background Color')} @@ -412,9 +470,4 @@ const ShareChatbot = ({ chatflowid, chatbotConfig }) => { ) } -ShareChatbot.propTypes = { - chatflowid: PropTypes.string, - chatbotConfig: PropTypes.object -} - export default ShareChatbot From 0729d0dea37114641c71ad554f28d2e81e85943d Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Fri, 30 Jun 2023 09:18:32 +0800 Subject: [PATCH 147/398] modify whitelistURLs --- packages/server/src/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 3e729464..0b39f73b 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -93,7 +93,13 @@ export class App { const basicAuthMiddleware = basicAuth({ users: { [username]: password } }) - const whitelistURLs = ['/api/v1/prediction/', '/api/v1/node-icon/', '/api/v1/chatflows-streaming'] + const whitelistURLs = [ + '/api/v1/verify/apikey/', + '/api/v1/chatflows/apikey/', + '/api/v1/prediction/', + '/api/v1/node-icon/', + '/api/v1/chatflows-streaming' + ] this.app.use((req, res, next) => { if (req.url.includes('/api/v1/')) { whitelistURLs.some((url) => req.url.includes(url)) ? next() : basicAuthMiddleware(req, res, next) @@ -492,7 +498,7 @@ export class App { }) // Verify api key - this.app.get('/api/v1/apikey/:apiKey', async (req: Request, res: Response) => { + this.app.get('/api/v1/verify/apikey/:apiKey', async (req: Request, res: Response) => { try { const apiKey = await getApiKey(req.params.apiKey) if (!apiKey) return res.status(401).send('Unauthorized') From 4796d84b50f1dfce423d6572cbfcffd4adeaf75e Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Fri, 30 Jun 2023 17:37:33 +0800 Subject: [PATCH 148/398] fix missing public-chatflows in whitelist --- packages/server/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index fba5be5e..3c97a2e7 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -96,6 +96,7 @@ export class App { const whitelistURLs = [ '/api/v1/verify/apikey/', '/api/v1/chatflows/apikey/', + '/api/v1/public-chatflows', '/api/v1/prediction/', '/api/v1/node-icon/', '/api/v1/chatflows-streaming' From 55489c1c774294f6f0fd23de487e5419d0a1fc94 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 30 Jun 2023 14:00:57 +0100 Subject: [PATCH 149/398] add blank space --- packages/server/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 3c97a2e7..15762a23 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -194,6 +194,7 @@ export class App { .createQueryBuilder('cf') .where('cf.apikeyid = :apikeyid', { apikeyid: apiKey.id }) .orWhere('cf.apikeyid IS NULL') + .orWhere('cf.apikeyid = ""') .orderBy('cf.name', 'ASC') .getMany() if (chatflows.length >= 1) return res.status(200).send(chatflows) From bc71e57834c2419e4e96f3017dbb5911bea15e10 Mon Sep 17 00:00:00 2001 From: Govind Kumar Date: Fri, 30 Jun 2023 18:58:15 +0530 Subject: [PATCH 150/398] added initial code --- .../nodes/memory/DynamoDb/DynamoDb.ts | 90 +++++++++++++++++++ .../nodes/memory/DynamoDb/dynamodb.svg | 18 ++++ packages/components/package.json | 1 + 3 files changed, 109 insertions(+) create mode 100644 packages/components/nodes/memory/DynamoDb/DynamoDb.ts create mode 100644 packages/components/nodes/memory/DynamoDb/dynamodb.svg diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts new file mode 100644 index 00000000..d0845d96 --- /dev/null +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -0,0 +1,90 @@ +import { ICommonObject, INode, INodeData, INodeParams, getBaseClasses } from '../../../src' +import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' +import { BufferMemory } from 'langchain/memory' + +class DynamoDb_Memory implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'DynamoDB Memory' + this.name = 'DynamoDbMemory' + this.icon = 'dynamodb.svg' + this.category = 'Memory' + this.description = 'Stores the conversation in dynamo db table' + this.baseClasses = [this.type, ...getBaseClasses(DynamoDBChatMessageHistory)] + this.inputs = [ + { + label: 'Table Name', + name: 'tableName', + type: 'string' + }, + { + label: 'Partition Key', + name: 'partitionKey', + type: 'string' + }, + { + label: 'Session ID', + name: 'sessionId', + type: 'string', + description: 'if empty, chatId will be used automatically', + default: '', + additionalParams: true + }, + { + label: 'Region', + name: 'region', + type: 'string', + description: 'The aws region in which table is located', + placeholder: 'us-east-1' + }, + { + label: 'Access Key', + name: 'accessKey', + type: 'password' + }, + { + label: 'Secret Access Key', + name: 'secretAccessKey', + type: 'password' + } + ] + } + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const tableName = nodeData.inputs?.tableName as string + const partitionKey = nodeData.inputs?.partitionKey as string + const sessionId = nodeData.inputs?.sessionId as string + const region = nodeData.inputs?.region as string + const accessKey = nodeData.inputs?.accessKey as string + const secretAccessKey = nodeData.inputs?.secretAccessKey as string + + const chatId = options.chatId + + const dynamoDb = new DynamoDBChatMessageHistory({ + tableName, + partitionKey, + sessionId: sessionId ? sessionId : chatId, + config: { + region, + credentials: { + accessKeyId: accessKey, + secretAccessKey + } + } + }) + + const memory = new BufferMemory({ + chatHistory: dynamoDb + }) + return memory + } +} + +module.exports = { nodeClass: DynamoDb_Memory } diff --git a/packages/components/nodes/memory/DynamoDb/dynamodb.svg b/packages/components/nodes/memory/DynamoDb/dynamodb.svg new file mode 100644 index 00000000..f2798350 --- /dev/null +++ b/packages/components/nodes/memory/DynamoDb/dynamodb.svg @@ -0,0 +1,18 @@ + + + + Icon-Architecture/16/Arch_Amazon-DynamoDB_16 + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index f5b67cc7..5ecd12e4 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -16,6 +16,7 @@ }, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { + "@aws-sdk/client-dynamodb": "^3.360.0", "@dqbd/tiktoken": "^1.0.7", "@getzep/zep-js": "^0.3.1", "@huggingface/inference": "1", From 894902ea831d1e2b3313af9c245b6f3fec496602 Mon Sep 17 00:00:00 2001 From: Govind Kumar Date: Fri, 30 Jun 2023 22:33:11 +0530 Subject: [PATCH 151/398] added gitBook integration --- .../nodes/documentloaders/Gitbook/Gitbook.ts | 82 +++++++++++++++++++ .../documentloaders/Gitbook/gitbook_logo.svg | 11 +++ 2 files changed, 93 insertions(+) create mode 100644 packages/components/nodes/documentloaders/Gitbook/Gitbook.ts create mode 100644 packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg diff --git a/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts b/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts new file mode 100644 index 00000000..836cd2cb --- /dev/null +++ b/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts @@ -0,0 +1,82 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { TextSplitter } from 'langchain/text_splitter' +import { GitbookLoader } from 'langchain/document_loaders/web/gitbook' + +class Gitbook_DocumentLoaders implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs?: INodeParams[] + + constructor() { + this.label = 'GitBook' + this.name = 'gitbook' + this.type = 'Document' + this.icon = 'gitbook_logo.svg' + this.category = 'Document Loaders' + this.description = `Load data from GitBook` + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Web Path', + name: 'webPath', + type: 'string', + placeholder: 'https://docs.gitbook.com/product-tour/navigation', + description: 'If want to load all paths from the GitBook provide only root path e.g.https://docs.gitbook.com/ ' + }, + { + label: 'Should Load All Paths', + name: 'shouldLoadAllPaths', + type: 'boolean', + description: 'Load from all paths in a given GitBook', + optional: true + }, + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + async init(nodeData: INodeData): Promise { + const webPath = nodeData.inputs?.webPath as string + const shouldLoadAllPaths = nodeData.inputs?.shouldLoadAllPaths as boolean + const textSplitter = nodeData.inputs?.textSplitter as TextSplitter + const metadata = nodeData.inputs?.metadata + + const loader = shouldLoadAllPaths ? new GitbookLoader(webPath, { shouldLoadAllPaths }) : new GitbookLoader(webPath) + + const docs = textSplitter ? await loader.loadAndSplit() : await loader.load() + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + return docs.map((doc) => { + return { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + }) + } + + return docs + } +} + +module.exports = { + nodeClass: Gitbook_DocumentLoaders +} diff --git a/packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg b/packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg new file mode 100644 index 00000000..9839f9bf --- /dev/null +++ b/packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + From c66c7eadc7e660049bd57f61bbbfe9f3b9a3a872 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 30 Jun 2023 18:31:41 +0100 Subject: [PATCH 152/398] add APIKEY_PATH --- docker/.env.example | 1 + docker/docker-compose.yml | 1 + packages/server/src/commands/start.ts | 2 ++ 3 files changed, 4 insertions(+) diff --git a/docker/.env.example b/docker/.env.example index 3d524e5c..80fbc3be 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -3,4 +3,5 @@ PORT=3000 # FLOWISE_PASSWORD=1234 # DEBUG=true # DATABASE_PATH=/your_database_path/.flowise +# APIKEY_PATH=/your_api_key_path/.flowise # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 7ab43142..97aea017 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -9,6 +9,7 @@ services: - FLOWISE_USERNAME=${FLOWISE_USERNAME} - FLOWISE_PASSWORD=${FLOWISE_PASSWORD} - DATABASE_PATH=${DATABASE_PATH} + - APIKEY_PATH=${APIKEY_PATH} - EXECUTION_MODE=${EXECUTION_MODE} - DEBUG=${DEBUG} ports: diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index 0f64322b..d3efc1eb 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -20,6 +20,7 @@ export default class Start extends Command { PORT: Flags.string(), DEBUG: Flags.string(), DATABASE_PATH: Flags.string(), + APIKEY_PATH: Flags.string(), EXECUTION_MODE: Flags.string() } @@ -56,6 +57,7 @@ export default class Start extends Command { if (flags.FLOWISE_PASSWORD) process.env.FLOWISE_PASSWORD = flags.FLOWISE_PASSWORD if (flags.PORT) process.env.PORT = flags.PORT if (flags.DATABASE_PATH) process.env.DATABASE_PATH = flags.DATABASE_PATH + if (flags.APIKEY_PATH) process.env.APIKEY_PATH = flags.APIKEY_PATH if (flags.EXECUTION_MODE) process.env.EXECUTION_MODE = flags.EXECUTION_MODE if (flags.DEBUG) process.env.DEBUG = flags.DEBUG From 40075b12e732405a844f9c982b5591cd99de132b Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sat, 1 Jul 2023 02:29:11 +0100 Subject: [PATCH 153/398] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 90f2711f..4613e19f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Flowise - Low-Code LLM apps builder +# Flowise From 7dda0d19c01f37f6b1c6db768092a667aa73e46e Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 1 Jul 2023 13:21:40 +0100 Subject: [PATCH 154/398] update Gitbook icon --- .../nodes/documentloaders/Gitbook/Gitbook.ts | 2 +- .../nodes/documentloaders/Gitbook/gitbook.svg | 1 + .../nodes/documentloaders/Gitbook/gitbook_logo.svg | 11 ----------- 3 files changed, 2 insertions(+), 12 deletions(-) create mode 100644 packages/components/nodes/documentloaders/Gitbook/gitbook.svg delete mode 100644 packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg diff --git a/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts b/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts index 836cd2cb..933fa9d4 100644 --- a/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts +++ b/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts @@ -16,7 +16,7 @@ class Gitbook_DocumentLoaders implements INode { this.label = 'GitBook' this.name = 'gitbook' this.type = 'Document' - this.icon = 'gitbook_logo.svg' + this.icon = 'gitbook.svg' this.category = 'Document Loaders' this.description = `Load data from GitBook` this.baseClasses = [this.type] diff --git a/packages/components/nodes/documentloaders/Gitbook/gitbook.svg b/packages/components/nodes/documentloaders/Gitbook/gitbook.svg new file mode 100644 index 00000000..df16237a --- /dev/null +++ b/packages/components/nodes/documentloaders/Gitbook/gitbook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg b/packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg deleted file mode 100644 index 9839f9bf..00000000 --- a/packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - From c8ba8e2aee4328da69795cc6a0e18c038cdb344a Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 1 Jul 2023 23:59:51 +0100 Subject: [PATCH 155/398] add tools marketplace --- .../{ => chatflows}/API Agent.json | 0 .../marketplaces/{ => chatflows}/Antonym.json | 0 .../marketplaces/{ => chatflows}/AutoGPT.json | 0 .../marketplaces/{ => chatflows}/BabyAGI.json | 0 .../{ => chatflows}/ChatGPTPlugin.json | 0 .../{ => chatflows}/Conversational Agent.json | 0 .../Conversational Retrieval QA Chain.json | 0 .../{ => chatflows}/Github Repo QnA.json | 0 .../HuggingFace LLM Chain.json | 0 .../{ => chatflows}/Local QnA.json | 0 .../{ => chatflows}/MRKLAgent.json | 0 .../{ => chatflows}/Metadata Filter Load.json | 0 .../Metadata Filter Upsert.json | 0 .../{ => chatflows}/Multi Prompt Chain.json | 0 .../Multi Retrieval QA Chain.json | 0 .../{ => chatflows}/Multiple VectorDB.json | 0 .../{ => chatflows}/OpenAI Agent.json | 0 .../{ => chatflows}/Prompt Chaining.json | 0 .../{ => chatflows}/SQL DB Chain.json | 0 .../Simple Conversation Chain.json | 0 .../{ => chatflows}/Simple LLM Chain.json | 0 .../{ => chatflows}/Translator.json | 0 .../{ => chatflows}/WebBrowser.json | 0 .../{ => chatflows}/Zapier NLA.json | 0 .../tools/Add Hubspot Contact.json | 8 + .../tools/Create Airtable Record.json | 8 + .../marketplaces/tools/Get Stock Mover.json | 8 + .../tools/Send Discord Message.json | 8 + .../tools/Send Slack Message.json | 8 + .../tools/Send Teams Message.json | 8 + .../marketplaces/tools/SendGrid Email.json | 8 + packages/server/src/Interface.ts | 1 + packages/server/src/entity/Tool.ts | 3 + packages/server/src/index.ts | 25 ++- packages/ui/public/index.html | 8 +- packages/ui/src/api/marketplaces.js | 6 +- packages/ui/src/themes/compStyleOverride.js | 3 + .../ui/src/ui-component/cards/ItemCard.js | 23 ++- packages/ui/src/ui-component/grid/Grid.js | 14 +- packages/ui/src/views/marketplaces/index.js | 173 +++++++++++++++--- packages/ui/src/views/tools/ToolDialog.js | 148 +++++++++++++-- packages/ui/src/views/tools/index.js | 53 +++++- 42 files changed, 444 insertions(+), 69 deletions(-) rename packages/server/marketplaces/{ => chatflows}/API Agent.json (100%) rename packages/server/marketplaces/{ => chatflows}/Antonym.json (100%) rename packages/server/marketplaces/{ => chatflows}/AutoGPT.json (100%) rename packages/server/marketplaces/{ => chatflows}/BabyAGI.json (100%) rename packages/server/marketplaces/{ => chatflows}/ChatGPTPlugin.json (100%) rename packages/server/marketplaces/{ => chatflows}/Conversational Agent.json (100%) rename packages/server/marketplaces/{ => chatflows}/Conversational Retrieval QA Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Github Repo QnA.json (100%) rename packages/server/marketplaces/{ => chatflows}/HuggingFace LLM Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Local QnA.json (100%) rename packages/server/marketplaces/{ => chatflows}/MRKLAgent.json (100%) rename packages/server/marketplaces/{ => chatflows}/Metadata Filter Load.json (100%) rename packages/server/marketplaces/{ => chatflows}/Metadata Filter Upsert.json (100%) rename packages/server/marketplaces/{ => chatflows}/Multi Prompt Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Multi Retrieval QA Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Multiple VectorDB.json (100%) rename packages/server/marketplaces/{ => chatflows}/OpenAI Agent.json (100%) rename packages/server/marketplaces/{ => chatflows}/Prompt Chaining.json (100%) rename packages/server/marketplaces/{ => chatflows}/SQL DB Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Simple Conversation Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Simple LLM Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Translator.json (100%) rename packages/server/marketplaces/{ => chatflows}/WebBrowser.json (100%) rename packages/server/marketplaces/{ => chatflows}/Zapier NLA.json (100%) create mode 100644 packages/server/marketplaces/tools/Add Hubspot Contact.json create mode 100644 packages/server/marketplaces/tools/Create Airtable Record.json create mode 100644 packages/server/marketplaces/tools/Get Stock Mover.json create mode 100644 packages/server/marketplaces/tools/Send Discord Message.json create mode 100644 packages/server/marketplaces/tools/Send Slack Message.json create mode 100644 packages/server/marketplaces/tools/Send Teams Message.json create mode 100644 packages/server/marketplaces/tools/SendGrid Email.json diff --git a/packages/server/marketplaces/API Agent.json b/packages/server/marketplaces/chatflows/API Agent.json similarity index 100% rename from packages/server/marketplaces/API Agent.json rename to packages/server/marketplaces/chatflows/API Agent.json diff --git a/packages/server/marketplaces/Antonym.json b/packages/server/marketplaces/chatflows/Antonym.json similarity index 100% rename from packages/server/marketplaces/Antonym.json rename to packages/server/marketplaces/chatflows/Antonym.json diff --git a/packages/server/marketplaces/AutoGPT.json b/packages/server/marketplaces/chatflows/AutoGPT.json similarity index 100% rename from packages/server/marketplaces/AutoGPT.json rename to packages/server/marketplaces/chatflows/AutoGPT.json diff --git a/packages/server/marketplaces/BabyAGI.json b/packages/server/marketplaces/chatflows/BabyAGI.json similarity index 100% rename from packages/server/marketplaces/BabyAGI.json rename to packages/server/marketplaces/chatflows/BabyAGI.json diff --git a/packages/server/marketplaces/ChatGPTPlugin.json b/packages/server/marketplaces/chatflows/ChatGPTPlugin.json similarity index 100% rename from packages/server/marketplaces/ChatGPTPlugin.json rename to packages/server/marketplaces/chatflows/ChatGPTPlugin.json diff --git a/packages/server/marketplaces/Conversational Agent.json b/packages/server/marketplaces/chatflows/Conversational Agent.json similarity index 100% rename from packages/server/marketplaces/Conversational Agent.json rename to packages/server/marketplaces/chatflows/Conversational Agent.json diff --git a/packages/server/marketplaces/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json similarity index 100% rename from packages/server/marketplaces/Conversational Retrieval QA Chain.json rename to packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json diff --git a/packages/server/marketplaces/Github Repo QnA.json b/packages/server/marketplaces/chatflows/Github Repo QnA.json similarity index 100% rename from packages/server/marketplaces/Github Repo QnA.json rename to packages/server/marketplaces/chatflows/Github Repo QnA.json diff --git a/packages/server/marketplaces/HuggingFace LLM Chain.json b/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json similarity index 100% rename from packages/server/marketplaces/HuggingFace LLM Chain.json rename to packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json diff --git a/packages/server/marketplaces/Local QnA.json b/packages/server/marketplaces/chatflows/Local QnA.json similarity index 100% rename from packages/server/marketplaces/Local QnA.json rename to packages/server/marketplaces/chatflows/Local QnA.json diff --git a/packages/server/marketplaces/MRKLAgent.json b/packages/server/marketplaces/chatflows/MRKLAgent.json similarity index 100% rename from packages/server/marketplaces/MRKLAgent.json rename to packages/server/marketplaces/chatflows/MRKLAgent.json diff --git a/packages/server/marketplaces/Metadata Filter Load.json b/packages/server/marketplaces/chatflows/Metadata Filter Load.json similarity index 100% rename from packages/server/marketplaces/Metadata Filter Load.json rename to packages/server/marketplaces/chatflows/Metadata Filter Load.json diff --git a/packages/server/marketplaces/Metadata Filter Upsert.json b/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json similarity index 100% rename from packages/server/marketplaces/Metadata Filter Upsert.json rename to packages/server/marketplaces/chatflows/Metadata Filter Upsert.json diff --git a/packages/server/marketplaces/Multi Prompt Chain.json b/packages/server/marketplaces/chatflows/Multi Prompt Chain.json similarity index 100% rename from packages/server/marketplaces/Multi Prompt Chain.json rename to packages/server/marketplaces/chatflows/Multi Prompt Chain.json diff --git a/packages/server/marketplaces/Multi Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json similarity index 100% rename from packages/server/marketplaces/Multi Retrieval QA Chain.json rename to packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json diff --git a/packages/server/marketplaces/Multiple VectorDB.json b/packages/server/marketplaces/chatflows/Multiple VectorDB.json similarity index 100% rename from packages/server/marketplaces/Multiple VectorDB.json rename to packages/server/marketplaces/chatflows/Multiple VectorDB.json diff --git a/packages/server/marketplaces/OpenAI Agent.json b/packages/server/marketplaces/chatflows/OpenAI Agent.json similarity index 100% rename from packages/server/marketplaces/OpenAI Agent.json rename to packages/server/marketplaces/chatflows/OpenAI Agent.json diff --git a/packages/server/marketplaces/Prompt Chaining.json b/packages/server/marketplaces/chatflows/Prompt Chaining.json similarity index 100% rename from packages/server/marketplaces/Prompt Chaining.json rename to packages/server/marketplaces/chatflows/Prompt Chaining.json diff --git a/packages/server/marketplaces/SQL DB Chain.json b/packages/server/marketplaces/chatflows/SQL DB Chain.json similarity index 100% rename from packages/server/marketplaces/SQL DB Chain.json rename to packages/server/marketplaces/chatflows/SQL DB Chain.json diff --git a/packages/server/marketplaces/Simple Conversation Chain.json b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json similarity index 100% rename from packages/server/marketplaces/Simple Conversation Chain.json rename to packages/server/marketplaces/chatflows/Simple Conversation Chain.json diff --git a/packages/server/marketplaces/Simple LLM Chain.json b/packages/server/marketplaces/chatflows/Simple LLM Chain.json similarity index 100% rename from packages/server/marketplaces/Simple LLM Chain.json rename to packages/server/marketplaces/chatflows/Simple LLM Chain.json diff --git a/packages/server/marketplaces/Translator.json b/packages/server/marketplaces/chatflows/Translator.json similarity index 100% rename from packages/server/marketplaces/Translator.json rename to packages/server/marketplaces/chatflows/Translator.json diff --git a/packages/server/marketplaces/WebBrowser.json b/packages/server/marketplaces/chatflows/WebBrowser.json similarity index 100% rename from packages/server/marketplaces/WebBrowser.json rename to packages/server/marketplaces/chatflows/WebBrowser.json diff --git a/packages/server/marketplaces/Zapier NLA.json b/packages/server/marketplaces/chatflows/Zapier NLA.json similarity index 100% rename from packages/server/marketplaces/Zapier NLA.json rename to packages/server/marketplaces/chatflows/Zapier NLA.json diff --git a/packages/server/marketplaces/tools/Add Hubspot Contact.json b/packages/server/marketplaces/tools/Add Hubspot Contact.json new file mode 100644 index 00000000..584df4c3 --- /dev/null +++ b/packages/server/marketplaces/tools/Add Hubspot Contact.json @@ -0,0 +1,8 @@ +{ + "name": "add_contact_hubspot", + "description": "Add new contact to Hubspot", + "color": "linear-gradient(rgb(85,198,123), rgb(0,230,99))", + "iconSrc": "https://cdn.worldvectorlogo.com/logos/hubspot-1.svg", + "schema": "[{\"id\":1,\"property\":\"email\",\"description\":\"email address of contact\",\"type\":\"string\",\"required\":true},{\"id\":2,\"property\":\"firstname\",\"description\":\"first name of contact\",\"type\":\"string\",\"required\":false},{\"id\":3,\"property\":\"lastname\",\"description\":\"last name of contact\",\"type\":\"string\",\"required\":false}]", + "func": "const fetch = require('node-fetch');\nconst url = 'https://api.hubapi.com/crm/v3/objects/contacts'\nconst token = 'YOUR-TOKEN';\n\nconst body = {\n\t\"properties\": {\n\t \"email\": $email\n\t}\n};\n\nif ($firstname) body.properties.firstname = $firstname;\nif ($lastname) body.properties.lastname = $lastname;\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t 'Authorization': `Bearer ${token}`,\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/marketplaces/tools/Create Airtable Record.json b/packages/server/marketplaces/tools/Create Airtable Record.json new file mode 100644 index 00000000..c52c9199 --- /dev/null +++ b/packages/server/marketplaces/tools/Create Airtable Record.json @@ -0,0 +1,8 @@ +{ + "name": "add_airtable", + "description": "Add column1, column2 to Airtable", + "color": "linear-gradient(rgb(125,71,222), rgb(128,102,23))", + "iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/airtable.svg", + "schema": "[{\"id\":0,\"property\":\"column1\",\"description\":\"this is column1\",\"type\":\"string\",\"required\":true},{\"id\":1,\"property\":\"column2\",\"description\":\"this is column2\",\"type\":\"string\",\"required\":true}]", + "func": "const fetch = require('node-fetch');\nconst baseId = 'YOUR-BASE-ID';\nconst tableId = 'YOUR-TABLE-ID';\nconst token = 'YOUR-TOKEN';\n\nconst body = {\n\t\"records\": [\n\t\t{\n\t\t\t\"fields\": {\n\t\t\t\t\"column1\": $column1,\n\t\t\t\t\"column2\": $column2,\n\t\t\t}\n\t\t}\n\t]\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t\t'Authorization': `Bearer ${token}`,\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\nconst url = `https://api.airtable.com/v0/${baseId}/${tableId}`\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/marketplaces/tools/Get Stock Mover.json b/packages/server/marketplaces/tools/Get Stock Mover.json new file mode 100644 index 00000000..9108cc50 --- /dev/null +++ b/packages/server/marketplaces/tools/Get Stock Mover.json @@ -0,0 +1,8 @@ +{ + "name": "get_stock_movers", + "description": "Get the stocks that has biggest price/volume moves, e.g. actives, gainers, losers, etc.", + "iconSrc": "https://rapidapi.com/cdn/images?url=https://rapidapi-prod-apis.s3.amazonaws.com/9c/e743343bdd41edad39a3fdffd5b974/016c33699f51603ae6fe4420c439124b.png", + "color": "linear-gradient(rgb(191,202,167), rgb(143,202,246))", + "schema": "[]", + "func": "const fetch = require('node-fetch');\nconst url = 'https://morning-star.p.rapidapi.com/market/v2/get-movers';\nconst options = {\n\tmethod: 'GET',\n\theaders: {\n\t\t'X-RapidAPI-Key': 'YOUR-API-KEY',\n\t\t'X-RapidAPI-Host': 'morning-star.p.rapidapi.com'\n\t}\n};\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst result = await response.text();\n\tconsole.log(result);\n\treturn result;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/marketplaces/tools/Send Discord Message.json b/packages/server/marketplaces/tools/Send Discord Message.json new file mode 100644 index 00000000..bbfaaa90 --- /dev/null +++ b/packages/server/marketplaces/tools/Send Discord Message.json @@ -0,0 +1,8 @@ +{ + "name": "send_message_to_discord_channel", + "description": "Send message to Discord channel", + "color": "linear-gradient(rgb(155,190,84), rgb(176,69,245))", + "iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/discord-icon.svg", + "schema": "[{\"id\":1,\"property\":\"content\",\"description\":\"message to send\",\"type\":\"string\",\"required\":true}]", + "func": "const fetch = require('node-fetch');\nconst webhookUrl = 'YOUR-WEBHOOK-URL'\n\nconst body = {\n\t\"content\": $content\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\nconst url = `${webhookUrl}?wait=true`\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/marketplaces/tools/Send Slack Message.json b/packages/server/marketplaces/tools/Send Slack Message.json new file mode 100644 index 00000000..f15d4050 --- /dev/null +++ b/packages/server/marketplaces/tools/Send Slack Message.json @@ -0,0 +1,8 @@ +{ + "name": "send_message_to_slack_channel", + "description": "Send message to Slack channel", + "color": "linear-gradient(rgb(155,190,84), rgb(176,69,245))", + "iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/slack-icon.svg", + "schema": "[{\"id\":1,\"property\":\"text\",\"description\":\"message to send\",\"type\":\"string\",\"required\":true}]", + "func": "const fetch = require('node-fetch');\nconst webhookUrl = 'YOUR-WEBHOOK-URL'\n\nconst body = {\n\t\"text\": $text\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\nconst url = `${webhookUrl}`\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/marketplaces/tools/Send Teams Message.json b/packages/server/marketplaces/tools/Send Teams Message.json new file mode 100644 index 00000000..1af8111b --- /dev/null +++ b/packages/server/marketplaces/tools/Send Teams Message.json @@ -0,0 +1,8 @@ +{ + "name": "send_message_to_teams_channel", + "description": "Send message to Teams channel", + "color": "linear-gradient(rgb(155,190,84), rgb(176,69,245))", + "iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/microsoft-teams.svg", + "schema": "[{\"id\":1,\"property\":\"content\",\"description\":\"message to send\",\"type\":\"string\",\"required\":true}]", + "func": "const fetch = require('node-fetch');\nconst webhookUrl = 'YOUR-WEBHOOK-URL'\n\nconst body = {\n\t\"content\": $content\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\nconst url = `${webhookUrl}?wait=true`\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/marketplaces/tools/SendGrid Email.json b/packages/server/marketplaces/tools/SendGrid Email.json new file mode 100644 index 00000000..18f6dad8 --- /dev/null +++ b/packages/server/marketplaces/tools/SendGrid Email.json @@ -0,0 +1,8 @@ +{ + "name": "sendgrid_email", + "description": "Send email using SendGrid", + "color": "linear-gradient(rgb(230,108,70), rgb(222,4,98))", + "iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/sendgrid-icon.svg", + "schema": "[{\"id\":0,\"property\":\"fromEmail\",\"description\":\"Email address used to send the message\",\"type\":\"string\",\"required\":true},{\"id\":1,\"property\":\"toEmail \",\"description\":\"The intended recipient's email address\",\"type\":\"string\",\"required\":true},{\"id\":2,\"property\":\"subject\",\"description\":\"The subject of email\",\"type\":\"string\",\"required\":true},{\"id\":3,\"property\":\"content\",\"description\":\"Content of email\",\"type\":\"string\",\"required\":true}]", + "func": "const fetch = require('node-fetch');\nconst url = 'https://api.sendgrid.com/v3/mail/send';\nconst api_key = 'YOUR-API-KEY';\n\nconst body = {\n \"personalizations\": [\n {\n \"to\": [{ \"email\": $toEmail }]\n }\n ],\n\t\"from\": {\n\t \"email\": $fromEmail\n\t},\n\t\"subject\": $subject,\n\t\"content\": [\n\t {\n\t \"type\": 'text/plain',\n\t \"value\": $content\n\t }\n\t]\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t 'Authorization': `Bearer ${api_key}`,\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 9c47405c..b4783f76 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -30,6 +30,7 @@ export interface ITool { name: string description: string color: string + iconSrc?: string schema?: string func?: string updatedDate: Date diff --git a/packages/server/src/entity/Tool.ts b/packages/server/src/entity/Tool.ts index 307e8d23..222fd766 100644 --- a/packages/server/src/entity/Tool.ts +++ b/packages/server/src/entity/Tool.ts @@ -16,6 +16,9 @@ export class Tool implements ITool { @Column() color: string + @Column({ nullable: true }) + iconSrc?: string + @Column({ nullable: true }) schema?: string diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 15762a23..cd4978a0 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -462,12 +462,12 @@ export class App { // ---------------------------------------- // Get all chatflows for marketplaces - this.app.get('/api/v1/marketplaces', async (req: Request, res: Response) => { - const marketplaceDir = path.join(__dirname, '..', 'marketplaces') + this.app.get('/api/v1/marketplaces/chatflows', async (req: Request, res: Response) => { + const marketplaceDir = path.join(__dirname, '..', 'marketplaces', 'chatflows') const jsonsInDir = fs.readdirSync(marketplaceDir).filter((file) => path.extname(file) === '.json') const templates: any[] = [] jsonsInDir.forEach((file, index) => { - const filePath = path.join(__dirname, '..', 'marketplaces', file) + const filePath = path.join(__dirname, '..', 'marketplaces', 'chatflows', file) const fileData = fs.readFileSync(filePath) const fileDataObj = JSON.parse(fileData.toString()) const template = { @@ -481,6 +481,25 @@ export class App { return res.json(templates) }) + // Get all tools for marketplaces + this.app.get('/api/v1/marketplaces/tools', async (req: Request, res: Response) => { + const marketplaceDir = path.join(__dirname, '..', 'marketplaces', 'tools') + const jsonsInDir = fs.readdirSync(marketplaceDir).filter((file) => path.extname(file) === '.json') + const templates: any[] = [] + jsonsInDir.forEach((file, index) => { + const filePath = path.join(__dirname, '..', 'marketplaces', 'tools', file) + const fileData = fs.readFileSync(filePath) + const fileDataObj = JSON.parse(fileData.toString()) + const template = { + ...fileDataObj, + id: index, + templateName: file.split('.json')[0] + } + templates.push(template) + }) + return res.json(templates) + }) + // ---------------------------------------- // API Keys // ---------------------------------------- diff --git a/packages/ui/public/index.html b/packages/ui/public/index.html index 270cc805..b4ec9ea1 100644 --- a/packages/ui/public/index.html +++ b/packages/ui/public/index.html @@ -1,13 +1,13 @@ - Flowise - LangchainJS UI + Flowise - Low-code LLM apps builder - + @@ -17,13 +17,13 @@ - + - + diff --git a/packages/ui/src/api/marketplaces.js b/packages/ui/src/api/marketplaces.js index 6906fb4e..3fd4ae87 100644 --- a/packages/ui/src/api/marketplaces.js +++ b/packages/ui/src/api/marketplaces.js @@ -1,7 +1,9 @@ import client from './client' -const getAllMarketplaces = () => client.get('/marketplaces') +const getAllChatflowsMarketplaces = () => client.get('/marketplaces/chatflows') +const getAllToolsMarketplaces = () => client.get('/marketplaces/tools') export default { - getAllMarketplaces + getAllChatflowsMarketplaces, + getAllToolsMarketplaces } diff --git a/packages/ui/src/themes/compStyleOverride.js b/packages/ui/src/themes/compStyleOverride.js index b7ebc8b2..c04cc3f1 100644 --- a/packages/ui/src/themes/compStyleOverride.js +++ b/packages/ui/src/themes/compStyleOverride.js @@ -136,6 +136,9 @@ export default function componentStyleOverrides(theme) { '&::placeholder': { color: theme.darkTextSecondary, fontSize: '0.875rem' + }, + '&.Mui-disabled': { + WebkitTextFillColor: theme?.customization?.isDarkMode ? theme.colors?.grey500 : theme.darkTextSecondary } } } diff --git a/packages/ui/src/ui-component/cards/ItemCard.js b/packages/ui/src/ui-component/cards/ItemCard.js index 345a88d5..1e8789d7 100644 --- a/packages/ui/src/ui-component/cards/ItemCard.js +++ b/packages/ui/src/ui-component/cards/ItemCard.js @@ -27,7 +27,7 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({ // ===========================|| CONTRACT CARD ||=========================== // -const ItemCard = ({ isLoading, data, images, color, onClick }) => { +const ItemCard = ({ isLoading, data, images, onClick }) => { return ( <> {isLoading ? ( @@ -43,21 +43,35 @@ const ItemCard = ({ isLoading, data, images, color, onClick }) => { alignItems: 'center' }} > - {color && ( + {data.iconSrc && (
+ )} + {!data.iconSrc && data.color && ( +
)} - {data.name} + {data.templateName || data.name}
{data.description && ( @@ -107,7 +121,6 @@ ItemCard.propTypes = { isLoading: PropTypes.bool, data: PropTypes.object, images: PropTypes.array, - color: PropTypes.string, onClick: PropTypes.func } diff --git a/packages/ui/src/ui-component/grid/Grid.js b/packages/ui/src/ui-component/grid/Grid.js index 2049f56c..0670d69b 100644 --- a/packages/ui/src/ui-component/grid/Grid.js +++ b/packages/ui/src/ui-component/grid/Grid.js @@ -3,7 +3,7 @@ import { DataGrid } from '@mui/x-data-grid' import { IconPlus } from '@tabler/icons' import { Button } from '@mui/material' -export const Grid = ({ columns, rows, style, onRowUpdate, addNewRow }) => { +export const Grid = ({ columns, rows, style, disabled = false, onRowUpdate, addNewRow }) => { const handleProcessRowUpdate = (newRow) => { onRowUpdate(newRow) return newRow @@ -11,13 +11,18 @@ export const Grid = ({ columns, rows, style, onRowUpdate, addNewRow }) => { return ( <> - + {!disabled && ( + + )} {rows && columns && (
{ + return !disabled + }} onProcessRowUpdateError={(error) => console.error(error)} rows={rows} columns={columns} @@ -32,6 +37,7 @@ Grid.propTypes = { rows: PropTypes.array, columns: PropTypes.array, style: PropTypes.any, + disabled: PropTypes.bool, addNewRow: PropTypes.func, onRowUpdate: PropTypes.func } diff --git a/packages/ui/src/views/marketplaces/index.js b/packages/ui/src/views/marketplaces/index.js index ba9eb3d6..a7836161 100644 --- a/packages/ui/src/views/marketplaces/index.js +++ b/packages/ui/src/views/marketplaces/index.js @@ -1,16 +1,19 @@ import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' // material-ui -import { Grid, Box, Stack } from '@mui/material' +import { Grid, Box, Stack, Tabs, Tab } from '@mui/material' import { useTheme } from '@mui/material/styles' +import { IconHierarchy, IconTool } from '@tabler/icons' // project imports import MainCard from 'ui-component/cards/MainCard' import ItemCard from 'ui-component/cards/ItemCard' import { gridSpacing } from 'store/constant' import WorkflowEmptySVG from 'assets/images/workflow_empty.svg' +import ToolDialog from 'views/tools/ToolDialog' // API import marketplacesApi from 'api/marketplaces' @@ -21,6 +24,27 @@ import useApi from 'hooks/useApi' // const import { baseURL } from 'store/constant' +function TabPanel(props) { + const { children, value, index, ...other } = props + return ( + + ) +} + +TabPanel.propTypes = { + children: PropTypes.node, + index: PropTypes.number.isRequired, + value: PropTypes.number.isRequired +} + // ==============================|| Marketplace ||============================== // const Marketplace = () => { @@ -29,29 +53,66 @@ const Marketplace = () => { const theme = useTheme() const customization = useSelector((state) => state.customization) - const [isLoading, setLoading] = useState(true) + const [isChatflowsLoading, setChatflowsLoading] = useState(true) + const [isToolsLoading, setToolsLoading] = useState(true) const [images, setImages] = useState({}) + const tabItems = ['Chatflows', 'Tools'] + const [value, setValue] = useState(0) + const [showToolDialog, setShowToolDialog] = useState(false) + const [toolDialogProps, setToolDialogProps] = useState({}) - const getAllMarketplacesApi = useApi(marketplacesApi.getAllMarketplaces) + const getAllChatflowsMarketplacesApi = useApi(marketplacesApi.getAllChatflowsMarketplaces) + const getAllToolsMarketplacesApi = useApi(marketplacesApi.getAllToolsMarketplaces) + + const onUseTemplate = (selectedTool) => { + const dialogProp = { + title: 'Add New Tool', + type: 'IMPORT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add', + data: selectedTool + } + setToolDialogProps(dialogProp) + setShowToolDialog(true) + } + + const goToTool = (selectedTool) => { + const dialogProp = { + title: selectedTool.templateName, + type: 'TEMPLATE', + data: selectedTool + } + setToolDialogProps(dialogProp) + setShowToolDialog(true) + } const goToCanvas = (selectedChatflow) => { navigate(`/marketplace/${selectedChatflow.id}`, { state: selectedChatflow }) } + const handleChange = (event, newValue) => { + setValue(newValue) + } + useEffect(() => { - getAllMarketplacesApi.request() + getAllChatflowsMarketplacesApi.request() + getAllToolsMarketplacesApi.request() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { - setLoading(getAllMarketplacesApi.loading) - }, [getAllMarketplacesApi.loading]) + setChatflowsLoading(getAllChatflowsMarketplacesApi.loading) + }, [getAllChatflowsMarketplacesApi.loading]) useEffect(() => { - if (getAllMarketplacesApi.data) { + setToolsLoading(getAllToolsMarketplacesApi.loading) + }, [getAllToolsMarketplacesApi.loading]) + + useEffect(() => { + if (getAllChatflowsMarketplacesApi.data) { try { - const chatflows = getAllMarketplacesApi.data + const chatflows = getAllChatflowsMarketplacesApi.data const images = {} for (let i = 0; i < chatflows.length; i += 1) { const flowDataStr = chatflows[i].flowData @@ -70,31 +131,83 @@ const Marketplace = () => { console.error(e) } } - }, [getAllMarketplacesApi.data]) + }, [getAllChatflowsMarketplacesApi.data]) return ( - - -

Marketplace

-
- - {!isLoading && - getAllMarketplacesApi.data && - getAllMarketplacesApi.data.map((data, index) => ( - - goToCanvas(data)} data={data} images={images[data.id]} /> - - ))} - - {!isLoading && (!getAllMarketplacesApi.data || getAllMarketplacesApi.data.length === 0) && ( - - - WorkflowEmptySVG - -
No Marketplace Yet
+ <> + + +

Marketplace

- )} -
+ + {tabItems.map((item, index) => ( + : } + iconPosition='start' + label={{item}} + /> + ))} + + {tabItems.map((item, index) => ( + + {item === 'Chatflows' && ( + + {!isChatflowsLoading && + getAllChatflowsMarketplacesApi.data && + getAllChatflowsMarketplacesApi.data.map((data, index) => ( + + goToCanvas(data)} data={data} images={images[data.id]} /> + + ))} + + )} + {item === 'Tools' && ( + + {!isToolsLoading && + getAllToolsMarketplacesApi.data && + getAllToolsMarketplacesApi.data.map((data, index) => ( + + goToTool(data)} /> + + ))} + + )} + + ))} + {!isChatflowsLoading && (!getAllChatflowsMarketplacesApi.data || getAllChatflowsMarketplacesApi.data.length === 0) && ( + + + WorkflowEmptySVG + +
No Marketplace Yet
+
+ )} + {!isToolsLoading && (!getAllToolsMarketplacesApi.data || getAllToolsMarketplacesApi.data.length === 0) && ( + + + WorkflowEmptySVG + +
No Marketplace Yet
+
+ )} +
+ setShowToolDialog(false)} + onConfirm={() => setShowToolDialog(false)} + onUseTemplate={(tool) => onUseTemplate(tool)} + > + ) } diff --git a/packages/ui/src/views/tools/ToolDialog.js b/packages/ui/src/views/tools/ToolDialog.js index bd5af355..77ef770d 100644 --- a/packages/ui/src/views/tools/ToolDialog.js +++ b/packages/ui/src/views/tools/ToolDialog.js @@ -17,7 +17,7 @@ import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' import { useTheme } from '@mui/material/styles' // Icons -import { IconX } from '@tabler/icons' +import { IconX, IconFileExport } from '@tabler/icons' // API import toolsApi from 'api/tools' @@ -53,7 +53,7 @@ try { return ''; }` -const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { +const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) => { const portalElement = document.getElementById('portal') const theme = useTheme() @@ -73,6 +73,7 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const [toolId, setToolId] = useState('') const [toolName, setToolName] = useState('') const [toolDesc, setToolDesc] = useState('') + const [toolIcon, setToolIcon] = useState('') const [toolSchema, setToolSchema] = useState([]) const [toolFunc, setToolFunc] = useState('') @@ -167,18 +168,39 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { useEffect(() => { if (dialogProps.type === 'EDIT' && dialogProps.data) { + // When tool dialog is opened from Tools dashboard setToolId(dialogProps.data.id) setToolName(dialogProps.data.name) setToolDesc(dialogProps.data.description) + setToolIcon(dialogProps.data.iconSrc) setToolSchema(formatSchema(dialogProps.data.schema)) if (dialogProps.data.func) setToolFunc(dialogProps.data.func) else setToolFunc('') } else if (dialogProps.type === 'EDIT' && dialogProps.toolId) { + // When tool dialog is opened from CustomTool node in canvas getSpecificToolApi.request(dialogProps.toolId) + } else if (dialogProps.type === 'IMPORT' && dialogProps.data) { + // When tool dialog is to import existing tool + setToolName(dialogProps.data.name) + setToolDesc(dialogProps.data.description) + setToolIcon(dialogProps.data.iconSrc) + setToolSchema(formatSchema(dialogProps.data.schema)) + if (dialogProps.data.func) setToolFunc(dialogProps.data.func) + else setToolFunc('') + } else if (dialogProps.type === 'TEMPLATE' && dialogProps.data) { + // When tool dialog is a template + setToolName(dialogProps.data.name) + setToolDesc(dialogProps.data.description) + setToolIcon(dialogProps.data.iconSrc) + setToolSchema(formatSchema(dialogProps.data.schema)) + if (dialogProps.data.func) setToolFunc(dialogProps.data.func) + else setToolFunc('') } else if (dialogProps.type === 'ADD') { + // When tool dialog is to add a new tool setToolId('') setToolName('') setToolDesc('') + setToolIcon('') setToolSchema([]) setToolFunc('') } @@ -186,6 +208,47 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [dialogProps]) + const useToolTemplate = () => { + onUseTemplate(dialogProps.data) + } + + const exportTool = async () => { + try { + const toolResp = await toolsApi.getSpecificTool(toolId) + if (toolResp.data) { + const toolData = toolResp.data + delete toolData.id + delete toolData.createdDate + delete toolData.updatedDate + let dataStr = JSON.stringify(toolData) + let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr) + + let exportFileDefaultName = `${toolName}-CustomTool.json` + + let linkElement = document.createElement('a') + linkElement.setAttribute('href', dataUri) + linkElement.setAttribute('download', exportFileDefaultName) + linkElement.click() + } + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to export Tool: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + const addNewTool = async () => { try { const obj = { @@ -193,7 +256,8 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { description: toolDesc, color: generateRandomGradient(), schema: JSON.stringify(toolSchema), - func: toolFunc + func: toolFunc, + iconSrc: toolIcon } const createResp = await toolsApi.createNewTool(obj) if (createResp.data) { @@ -236,7 +300,8 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { name: toolName, description: toolDesc, schema: JSON.stringify(toolSchema), - func: toolFunc + func: toolFunc, + iconSrc: toolIcon }) if (saveResp.data) { enqueueSnackbar({ @@ -330,7 +395,15 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { aria-describedby='alert-dialog-description' > - {dialogProps.title} +
+ {dialogProps.title} +
+ {dialogProps.type === 'EDIT' && ( + + )} +
@@ -338,12 +411,17 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { Tool Name  * + { Tool description  * + { onChange={(e) => setToolDesc(e.target.value)} /> + + + Tool Icon Src + + setToolIcon(e.target.value)} + /> + @@ -376,7 +474,13 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { - + @@ -388,12 +492,15 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { /> - + {dialogProps.type !== 'TEMPLATE' && ( + + )} {customization.isDarkMode ? ( setToolFunc(code)} style={{ fontSize: '0.875rem', @@ -405,6 +512,7 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { ) : ( setToolFunc(code)} style={{ fontSize: '0.875rem', @@ -423,13 +531,20 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { Delete )} - (dialogProps.type === 'ADD' ? addNewTool() : saveTool())} - > - {dialogProps.confirmButtonName} - + {dialogProps.type === 'TEMPLATE' && ( + + Use Template + + )} + {dialogProps.type !== 'TEMPLATE' && ( + (dialogProps.type === 'ADD' || dialogProps.type === 'IMPORT' ? addNewTool() : saveTool())} + > + {dialogProps.confirmButtonName} + + )} @@ -441,6 +556,7 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { ToolDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, + onUseTemplate: PropTypes.func, onCancel: PropTypes.func, onConfirm: PropTypes.func } diff --git a/packages/ui/src/views/tools/index.js b/packages/ui/src/views/tools/index.js index efe9e69d..c97ec660 100644 --- a/packages/ui/src/views/tools/index.js +++ b/packages/ui/src/views/tools/index.js @@ -1,8 +1,8 @@ -import { useEffect, useState } from 'react' +import { useEffect, useState, useRef } from 'react' import { useSelector } from 'react-redux' // material-ui -import { Grid, Box, Stack } from '@mui/material' +import { Grid, Box, Stack, Button } from '@mui/material' import { useTheme } from '@mui/material/styles' // project imports @@ -20,7 +20,7 @@ import toolsApi from 'api/tools' import useApi from 'hooks/useApi' // icons -import { IconPlus } from '@tabler/icons' +import { IconPlus, IconFileImport } from '@tabler/icons' // ==============================|| CHATFLOWS ||============================== // @@ -33,6 +33,40 @@ const Tools = () => { const [showDialog, setShowDialog] = useState(false) const [dialogProps, setDialogProps] = useState({}) + const inputRef = useRef(null) + + const onUploadFile = (file) => { + try { + const dialogProp = { + title: 'Add New Tool', + type: 'IMPORT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Save', + data: JSON.parse(file) + } + setDialogProps(dialogProp) + setShowDialog(true) + } catch (e) { + console.error(e) + } + } + + const handleFileUpload = (e) => { + if (!e.target.files) return + + const file = e.target.files[0] + + const reader = new FileReader() + reader.onload = (evt) => { + if (!evt?.target?.result) { + return + } + const { result } = evt.target + onUploadFile(result) + } + reader.readAsText(file) + } + const addNew = () => { const dialogProp = { title: 'Add New Tool', @@ -75,8 +109,17 @@ const Tools = () => { + + handleFileUpload(e)} /> }> - Create New + Create @@ -86,7 +129,7 @@ const Tools = () => { getAllToolsApi.data && getAllToolsApi.data.map((data, index) => ( - edit(data)} /> + edit(data)} /> ))} From 2ac229a242b4664cae47cef0e54daf617bc0326c Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 2 Jul 2023 00:55:25 +0100 Subject: [PATCH 156/398] add 16k model to Azure --- .../AzureChatOpenAI/AzureChatOpenAI.ts | 16 ++++++++-------- .../AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts | 2 +- .../nodes/llms/Azure OpenAI/AzureOpenAI.ts | 16 ++++------------ 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts index 60295890..2cdb505d 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts @@ -43,6 +43,10 @@ class AzureChatOpenAI_ChatModels implements INode { { label: 'gpt-35-turbo', name: 'gpt-35-turbo' + }, + { + label: 'gpt-35-turbo-16k', + name: 'gpt-35-turbo-16k' } ], default: 'gpt-35-turbo', @@ -70,14 +74,10 @@ class AzureChatOpenAI_ChatModels implements INode { { label: 'Azure OpenAI Api Version', name: 'azureOpenAIApiVersion', - type: 'options', - options: [ - { - label: '2023-03-15-preview', - name: '2023-03-15-preview' - } - ], - default: '2023-03-15-preview' + type: 'string', + placeholder: '2023-06-01-preview', + description: + 'Description of Supported API Versions. Please refer examples' }, { label: 'Max Tokens', diff --git a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts index 2a211622..4133539e 100644 --- a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts +++ b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts @@ -43,7 +43,7 @@ class AzureOpenAIEmbedding_Embeddings implements INode { label: 'Azure OpenAI Api Version', name: 'azureOpenAIApiVersion', type: 'string', - placeholder: 'YOUR-API-VERSION', + placeholder: '2023-03-15-preview', description: 'Description of Supported API Versions. Please refer examples' }, diff --git a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts index f81c9349..130eed33 100644 --- a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts +++ b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts @@ -105,18 +105,10 @@ class AzureOpenAI_LLMs implements INode { { label: 'Azure OpenAI Api Version', name: 'azureOpenAIApiVersion', - type: 'options', - options: [ - { - label: '2023-03-15-preview', - name: '2023-03-15-preview' - }, - { - label: '2022-12-01', - name: '2022-12-01' - } - ], - default: '2023-03-15-preview' + type: 'string', + placeholder: '2023-06-01-preview', + description: + 'Description of Supported API Versions. Please refer examples' }, { label: 'Max Tokens', From 0efed9d7d43bf66a8e10155bfeb865300984925d Mon Sep 17 00:00:00 2001 From: Govind Kumar Date: Sun, 2 Jul 2023 23:45:42 +0530 Subject: [PATCH 157/398] added dynamo db backed memory --- .../components/nodes/memory/DynamoDb/DynamoDb.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index d0845d96..8b4cd69d 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -18,7 +18,7 @@ class DynamoDb_Memory implements INode { this.icon = 'dynamodb.svg' this.category = 'Memory' this.description = 'Stores the conversation in dynamo db table' - this.baseClasses = [this.type, ...getBaseClasses(DynamoDBChatMessageHistory)] + this.baseClasses = [this.type, ...getBaseClasses(BufferMemory)] this.inputs = [ { label: 'Table Name', @@ -54,6 +54,12 @@ class DynamoDb_Memory implements INode { label: 'Secret Access Key', name: 'secretAccessKey', type: 'password' + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history' } ] } @@ -64,6 +70,7 @@ class DynamoDb_Memory implements INode { const region = nodeData.inputs?.region as string const accessKey = nodeData.inputs?.accessKey as string const secretAccessKey = nodeData.inputs?.secretAccessKey as string + const memoryKey = nodeData.inputs?.memoryKey as string const chatId = options.chatId @@ -81,7 +88,9 @@ class DynamoDb_Memory implements INode { }) const memory = new BufferMemory({ - chatHistory: dynamoDb + memoryKey, + chatHistory: dynamoDb, + returnMessages: true }) return memory } From 0ca7e8db01b1bdeee32e82dee84c1cae346f53ab Mon Sep 17 00:00:00 2001 From: toshilow Date: Mon, 3 Jul 2023 21:28:09 +0900 Subject: [PATCH 158/398] do not submit during IME composition --- packages/ui/src/views/chatmessage/ChatMessage.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 5021cd9b..52ff4bb9 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -163,7 +163,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { // Prevent blank submissions and allow for multiline input const handleEnter = (e) => { - if (e.key === 'Enter' && userInput) { + // Check if IME composition is in progress + const isIMEComposition = e.isComposing || e.keyCode === 229 + if (e.key === 'Enter' && userInput && !isIMEComposition) { if (!e.shiftKey && userInput) { handleSubmit(e) } From a9e269b52c8578301a1f098f1d175e0148551cc4 Mon Sep 17 00:00:00 2001 From: Matthias Platzer Date: Mon, 3 Jul 2023 17:58:41 +0200 Subject: [PATCH 159/398] Added winston logging - use logger.xxx instead of console.xxx - added express middleware logging (using jsonl) - added LOG_PATH as environment variable - more configs postponed for later iteration --- .gitignore | 1 + docker/.env.example | 1 + packages/server/.env.example | 1 + packages/server/package.json | 3 +- packages/server/src/commands/start.ts | 13 ++-- packages/server/src/index.ts | 15 ++-- packages/server/src/utils/config.ts | 25 +++++++ packages/server/src/utils/index.ts | 5 +- packages/server/src/utils/logger.ts | 100 ++++++++++++++++++++++++++ 9 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 packages/server/src/utils/config.ts create mode 100644 packages/server/src/utils/logger.ts diff --git a/.gitignore b/.gitignore index 9f5ef2e5..3ae87776 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ **/yarn.lock ## logs +logs/**/* **/*.log ## build diff --git a/docker/.env.example b/docker/.env.example index 80fbc3be..8e66d25e 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -4,4 +4,5 @@ PORT=3000 # DEBUG=true # DATABASE_PATH=/your_database_path/.flowise # APIKEY_PATH=/your_api_key_path/.flowise +# LOG_PATH=/your_api_key_path/logs # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/packages/server/.env.example b/packages/server/.env.example index 80fbc3be..f1fbf990 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -4,4 +4,5 @@ PORT=3000 # DEBUG=true # DATABASE_PATH=/your_database_path/.flowise # APIKEY_PATH=/your_api_key_path/.flowise +# LOG_PATH=./logs # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/packages/server/package.json b/packages/server/package.json index eda69322..05abd6c9 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -58,7 +58,8 @@ "reflect-metadata": "^0.1.13", "socket.io": "^4.6.1", "sqlite3": "^5.1.6", - "typeorm": "^0.3.6" + "typeorm": "^0.3.6", + "winston": "^3.9.0" }, "devDependencies": { "@types/cors": "^2.8.12", diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index d3efc1eb..9bd1d64b 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -3,6 +3,7 @@ import path from 'path' import * as Server from '../index' import * as DataSource from '../DataSource' import dotenv from 'dotenv' +import logger from '../utils/logger' dotenv.config({ path: path.join(__dirname, '..', '..', '.env'), override: true }) @@ -25,11 +26,11 @@ export default class Start extends Command { } async stopProcess() { - console.info('Shutting down Flowise...') + logger.info('Shutting down Flowise...') try { // Shut down the app after timeout if it ever stuck removing pools setTimeout(() => { - console.info('Flowise was forced to shut down after 30 secs') + logger.info('Flowise was forced to shut down after 30 secs') process.exit(processExitCode) }, 30000) @@ -37,7 +38,7 @@ export default class Start extends Command { const serverApp = Server.getInstance() if (serverApp) await serverApp.stopApp() } catch (error) { - console.error('There was an error shutting down Flowise...', error) + logger.error('There was an error shutting down Flowise...', error) } process.exit(processExitCode) } @@ -49,7 +50,7 @@ export default class Start extends Command { // Prevent throw new Error from crashing the app // TODO: Get rid of this and send proper error message to ui process.on('uncaughtException', (err) => { - console.error('uncaughtException: ', err) + logger.error('uncaughtException: ', err) }) const { flags } = await this.parse(Start) @@ -63,11 +64,11 @@ export default class Start extends Command { await (async () => { try { - this.log('Starting Flowise...') + logger.info('Starting Flowise...') await DataSource.init() await Server.start() } catch (error) { - console.error('There was an error starting Flowise...', error) + logger.error('There was an error starting Flowise...', error) processExitCode = EXIT_CODE.FAILED // @ts-ignore process.emit('SIGINT') diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 15762a23..73dbada4 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -6,6 +6,8 @@ import http from 'http' import * as fs from 'fs' import basicAuth from 'express-basic-auth' import { Server } from 'socket.io' +import logger from './utils/logger' +import { expressRequestLogger } from './utils/logger' import { IChatFlow, @@ -57,13 +59,16 @@ export class App { constructor() { this.app = express() + + // Add the expressRequestLogger middleware to log all requests + this.app.use(expressRequestLogger) } async initDatabase() { // Initialize database this.AppDataSource.initialize() .then(async () => { - console.info('📦[server]: Data Source has been initialized!') + logger.info('📦 [server]: Data Source has been initialized!') // Initialize pools this.nodesPool = new NodesPool() @@ -75,7 +80,7 @@ export class App { await getAPIKeys() }) .catch((err) => { - console.error('❌[server]: Error during Data Source initialization:', err) + logger.error('❌ [server]: Error during Data Source initialization:', err) }) } @@ -614,7 +619,7 @@ export class App { }) }) } catch (err) { - console.error(err) + logger.error(err) } } @@ -792,7 +797,7 @@ export class App { const removePromises: any[] = [] await Promise.all(removePromises) } catch (e) { - console.error(`❌[server]: Flowise Server shut down error: ${e}`) + logger.error(`❌[server]: Flowise Server shut down error: ${e}`) } } } @@ -832,7 +837,7 @@ export async function start(): Promise { await serverApp.config(io) server.listen(port, () => { - console.info(`⚡️[server]: Flowise Server is listening at ${port}`) + logger.info(`⚡️ [server]: Flowise Server is listening at ${port}`) }) } diff --git a/packages/server/src/utils/config.ts b/packages/server/src/utils/config.ts new file mode 100644 index 00000000..a4a33937 --- /dev/null +++ b/packages/server/src/utils/config.ts @@ -0,0 +1,25 @@ +// BEWARE: This file is an intereem solution until we have a proper config strategy + +import path from 'path' +import dotenv from 'dotenv' + +dotenv.config({ path: path.join(__dirname, '..', '..', '.env'), override: true }) + +// default config +const loggingConfig = { + dir: process.env.LOG_PATH ?? './logs', + server: { + level: 'info', + filename: 'server.log', + errorFilename: 'server-error.log' + }, + express: { + level: 'info', + format: 'jsonl', // can't be changed currently + filename: 'server-requests.log.jsonl' // should end with .jsonl + } +} + +export default { + logging: loggingConfig +} diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 005f4a4b..55529afb 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -1,6 +1,7 @@ import path from 'path' import fs from 'fs' import moment from 'moment' +import logger from './logger' import { IComponentNodes, IDepthQueue, @@ -227,7 +228,7 @@ export const buildLangchain = async ( databaseEntities }) } catch (e: any) { - console.error(e) + logger.error(e) throw new Error(e) } @@ -595,7 +596,7 @@ export const replaceAllAPIKeys = async (content: ICommonObject[]): Promise try { await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') } catch (error) { - console.error(error) + logger.error(error) } } diff --git a/packages/server/src/utils/logger.ts b/packages/server/src/utils/logger.ts new file mode 100644 index 00000000..277cf3ad --- /dev/null +++ b/packages/server/src/utils/logger.ts @@ -0,0 +1,100 @@ +import * as path from 'path' +import * as fs from 'fs' +import config from './config' // should be replaced by node-config or similar +import { createLogger, transports, format } from 'winston' +import { NextFunction, Request, Response } from 'express' + +const { combine, timestamp, printf } = format + +// expect the log dir be relative to the projects root +const logDir = path.join(__dirname, '../../../..', config.logging.dir ?? './logs') + +// Create the log directory if it doesn't exist +if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir) +} + +const logger = createLogger({ + format: combine( + timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + format.json(), + printf(({ level, message, timestamp }) => { + return `${timestamp} [${level.toUpperCase()}]: ${message}` + }) + ), + defaultMeta: { + package: 'server' + }, + transports: [ + new transports.Console(), + new transports.File({ + filename: path.join(logDir, config.logging.server.filename ?? 'server.log'), + level: config.logging.server.level ?? 'info' + }), + new transports.File({ + filename: path.join(logDir, config.logging.server.errorFilename ?? 'server-error.log'), + level: 'error' // Log only errors to this file + }) + ], + exceptionHandlers: [ + new transports.File({ + filename: path.join(logDir, config.logging.server.errorFilename ?? 'server-error.log') + }) + ], + rejectionHandlers: [ + new transports.File({ + filename: path.join(logDir, config.logging.server.errorFilename ?? 'server-error.log') + }) + ] +}) + +/** + * This function is used by express as a middleware. + * @example + * this.app = express() + * this.app.use(expressRequestLogger) + */ +export function expressRequestLogger(req: Request, res: Response, next: NextFunction): void { + const fileLogger = createLogger({ + format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.json()), + defaultMeta: { + package: 'server', + request: { + method: req.method, + url: req.url, + body: req.body, + query: req.query, + params: req.params, + headers: req.headers + } + }, + transports: [ + new transports.File({ + filename: path.join(logDir, config.logging.express.filename ?? 'server-requests.log.jsonl'), + level: 'debug' + }) + ] + }) + + const getRequestEmoji = (method: string) => { + const requetsEmojis: Record = { + GET: '⬇️', + POST: '⬆️', + PUT: '🖊', + DELETE: '❌' + } + + return requetsEmojis[method] || '?' + } + + if (req.method !== 'GET') { + fileLogger.info(`${getRequestEmoji(req.method)} ${req.method} ${req.url}`) + logger.info(`${getRequestEmoji(req.method)} ${req.method} ${req.url}`) + } else { + fileLogger.http(`${getRequestEmoji(req.method)} ${req.method} ${req.url}`) + } + + next() +} + +export default logger From 479a6bc7eb2020abc9d12e1d547ec3d3441f8331 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 3 Jul 2023 17:59:52 +0100 Subject: [PATCH 160/398] update sessionId to optional --- packages/components/nodes/memory/DynamoDb/DynamoDb.ts | 3 ++- .../memory/RedisBackedChatMemory/RedisBackedChatMemory.ts | 3 ++- packages/components/nodes/memory/ZepMemory/ZepMemory.ts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index 8b4cd69d..b1368044 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -36,7 +36,8 @@ class DynamoDb_Memory implements INode { type: 'string', description: 'if empty, chatId will be used automatically', default: '', - additionalParams: true + additionalParams: true, + optional: true }, { label: 'Region', diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index e332181d..2b4e51c2 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -36,7 +36,8 @@ class RedisBackedChatMemory_Memory implements INode { type: 'string', description: 'if empty, chatId will be used automatically', default: '', - additionalParams: true + additionalParams: true, + optional: true }, { label: 'Session Timeouts', diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 1fb6d9ff..2e7ba001 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -41,7 +41,8 @@ class ZepMemory_Memory implements INode { type: 'string', description: 'if empty, chatId will be used automatically', default: '', - additionalParams: true + additionalParams: true, + optional: true }, { label: 'Auto Summary Template', From 89511c83950b826bed24964381eb00c1d21bfd7b Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 22:23:34 +0100 Subject: [PATCH 161/398] Update config.ts --- packages/server/src/utils/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/config.ts b/packages/server/src/utils/config.ts index a4a33937..d81fe7c3 100644 --- a/packages/server/src/utils/config.ts +++ b/packages/server/src/utils/config.ts @@ -7,7 +7,7 @@ dotenv.config({ path: path.join(__dirname, '..', '..', '.env'), override: true } // default config const loggingConfig = { - dir: process.env.LOG_PATH ?? './logs', + dir: process.env.LOG_PATH ?? path.join(__dirname, '..', '..', '..', 'logs'), server: { level: 'info', filename: 'server.log', From ec777c65eac979172af911fc7498be311fb52af6 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 22:23:54 +0100 Subject: [PATCH 162/398] Update logger.ts --- packages/server/src/utils/logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/logger.ts b/packages/server/src/utils/logger.ts index 277cf3ad..1c28b173 100644 --- a/packages/server/src/utils/logger.ts +++ b/packages/server/src/utils/logger.ts @@ -7,7 +7,7 @@ import { NextFunction, Request, Response } from 'express' const { combine, timestamp, printf } = format // expect the log dir be relative to the projects root -const logDir = path.join(__dirname, '../../../..', config.logging.dir ?? './logs') +const logDir = config.logging.dir // Create the log directory if it doesn't exist if (!fs.existsSync(logDir)) { From 19758105584ee09bd8e1f431b3dceea249425b4d Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 22:26:01 +0100 Subject: [PATCH 163/398] Update .env.example --- packages/server/.env.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/.env.example b/packages/server/.env.example index f1fbf990..262e08a6 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -4,5 +4,5 @@ PORT=3000 # DEBUG=true # DATABASE_PATH=/your_database_path/.flowise # APIKEY_PATH=/your_api_key_path/.flowise -# LOG_PATH=./logs -# EXECUTION_MODE=child or main \ No newline at end of file +# LOG_PATH=/your_log_path/logs +# EXECUTION_MODE=child or main From 13c4a732eb194267aeba0a7df01f71959bf552bd Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 22:26:22 +0100 Subject: [PATCH 164/398] Update .env.example --- docker/.env.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index 8e66d25e..262e08a6 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -4,5 +4,5 @@ PORT=3000 # DEBUG=true # DATABASE_PATH=/your_database_path/.flowise # APIKEY_PATH=/your_api_key_path/.flowise -# LOG_PATH=/your_api_key_path/logs -# EXECUTION_MODE=child or main \ No newline at end of file +# LOG_PATH=/your_log_path/logs +# EXECUTION_MODE=child or main From 1fc9e9e4362282a3ab8f2a9d667943cb9c82335b Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 22:27:23 +0100 Subject: [PATCH 165/398] Update docker-compose.yml --- docker/docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 97aea017..3077c43d 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -10,6 +10,7 @@ services: - FLOWISE_PASSWORD=${FLOWISE_PASSWORD} - DATABASE_PATH=${DATABASE_PATH} - APIKEY_PATH=${APIKEY_PATH} + - LOG_PATH=${LOG_PATH} - EXECUTION_MODE=${EXECUTION_MODE} - DEBUG=${DEBUG} ports: From c0f36387a7ca62c44d3dc1a0064cdff76cf1ac5c Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 22:28:17 +0100 Subject: [PATCH 166/398] Update start.ts --- packages/server/src/commands/start.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index 9bd1d64b..c05c042a 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -22,6 +22,7 @@ export default class Start extends Command { DEBUG: Flags.string(), DATABASE_PATH: Flags.string(), APIKEY_PATH: Flags.string(), + LOG_PATH: Flags.string(), EXECUTION_MODE: Flags.string() } @@ -59,6 +60,7 @@ export default class Start extends Command { if (flags.PORT) process.env.PORT = flags.PORT if (flags.DATABASE_PATH) process.env.DATABASE_PATH = flags.DATABASE_PATH if (flags.APIKEY_PATH) process.env.APIKEY_PATH = flags.APIKEY_PATH + if (flags.LOG_PATH) process.env.LOG_PATH = flags.LOG_PATH if (flags.EXECUTION_MODE) process.env.EXECUTION_MODE = flags.EXECUTION_MODE if (flags.DEBUG) process.env.DEBUG = flags.DEBUG From 7ee2b1194d0290d8904d17ba2e884e796465e94a Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 23:05:51 +0100 Subject: [PATCH 167/398] Only enable logger when DEBUG=true --- packages/server/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 73dbada4..c7206154 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -61,7 +61,7 @@ export class App { this.app = express() // Add the expressRequestLogger middleware to log all requests - this.app.use(expressRequestLogger) + if (process.env.DEBUG === 'true') this.app.use(expressRequestLogger) } async initDatabase() { From da1cfc79c424b1ee82ff515cdb84e5eaccd05de1 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 23:07:31 +0100 Subject: [PATCH 168/398] ignore all files inside logs folder --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3ae87776..533f68a5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ **/yarn.lock ## logs -logs/**/* +**/logs **/*.log ## build @@ -43,4 +43,4 @@ logs/**/* **/uploads ## compressed -**/*.tgz \ No newline at end of file +**/*.tgz From 92e50a676c27ba29f3168f1720b11b4ba1258b0d Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Wed, 5 Jul 2023 16:47:01 +0800 Subject: [PATCH 169/398] add web crawl --- .../nodes/documentloaders/Cheerio/Cheerio.ts | 48 ++++++----- packages/components/package.json | 1 + packages/components/src/utils.ts | 84 +++++++++++++++++++ 3 files changed, 113 insertions(+), 20 deletions(-) diff --git a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts index 9e113505..10eff77e 100644 --- a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts +++ b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts @@ -2,7 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { CheerioWebBaseLoader } from 'langchain/document_loaders/web/cheerio' import { test } from 'linkifyjs' -import { getAvailableURLs } from '../../../src' +import { webCrawl } from '../../../src' class Cheerio_DocumentLoaders implements INode { label: string @@ -35,19 +35,20 @@ class Cheerio_DocumentLoaders implements INode { optional: true }, { - label: 'Web Scrap for Relative Links', - name: 'webScrap', + label: 'Web Crawl for Relative Links', + name: 'boolWebCrawl', type: 'boolean', optional: true, additionalParams: true }, { - label: 'Web Scrap Links Limit', + label: 'Web Crawl Links Limit', name: 'limit', type: 'number', default: 10, optional: true, - additionalParams: true + additionalParams: true, + description: 'Set 0 to crawl all relative links' }, { label: 'Metadata', @@ -62,7 +63,7 @@ class Cheerio_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const webScrap = nodeData.inputs?.webScrap as boolean + const boolWebCrawl = nodeData.inputs?.boolWebCrawl as boolean let limit = nodeData.inputs?.limit as string let url = nodeData.inputs?.url as string @@ -71,25 +72,32 @@ class Cheerio_DocumentLoaders implements INode { throw new Error('Invalid URL') } - const cheerioLoader = async (url: string): Promise => { - let docs = [] - const loader = new CheerioWebBaseLoader(url) - if (textSplitter) { - docs = await loader.loadAndSplit(textSplitter) - } else { - docs = await loader.load() + async function cheerioLoader(url: string): Promise { + try { + let docs = [] + const loader = new CheerioWebBaseLoader(url) + if (textSplitter) { + docs = await loader.loadAndSplit(textSplitter) + } else { + docs = await loader.load() + } + return docs + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error in CheerioWebBaseLoader: ${err.message}, on page: ${url}`) } - return docs } - let availableUrls: string[] let docs = [] - if (webScrap) { - if (!limit) limit = '10' - availableUrls = await getAvailableURLs(url, parseInt(limit)) - for (let i = 0; i < availableUrls.length; i++) { - docs.push(...(await cheerioLoader(availableUrls[i]))) + if (boolWebCrawl) { + if (process.env.DEBUG === 'true') console.info('Start Web Crawl') + if (!limit) throw new Error('Please set a limit to crawl') + else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') + const pages: string[] = await webCrawl(url, parseInt(limit)) + if (process.env.DEBUG === 'true') console.info(`pages: ${JSON.stringify(pages)}, length: ${pages.length}`) + for (const page of pages) { + docs.push(...(await cheerioLoader(page))) } + if (process.env.DEBUG === 'true') console.info('Finish Web Crawl') } else { docs = await cheerioLoader(url) } diff --git a/packages/components/package.json b/packages/components/package.json index 4555a883..81f963ed 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -24,6 +24,7 @@ "@qdrant/js-client-rest": "^1.2.2", "@supabase/supabase-js": "^2.21.0", "@types/js-yaml": "^4.0.5", + "@types/jsdom": "^21.1.1", "axios": "^0.27.2", "cheerio": "^1.0.0-rc.12", "chromadb": "^1.4.2", diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index c247ebc2..63bb3969 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -2,6 +2,7 @@ import axios from 'axios' import { load } from 'cheerio' import * as fs from 'fs' import * as path from 'path' +import { JSDOM } from 'jsdom' import { BaseCallbackHandler } from 'langchain/callbacks' import { Server } from 'socket.io' import { ChainValues } from 'langchain/dist/schema' @@ -201,6 +202,89 @@ export const getAvailableURLs = async (url: string, limit: number) => { } } +function getURLsFromHTML(htmlBody: string, baseURL: string): string[] { + const dom = new JSDOM(htmlBody) + const linkElements = dom.window.document.querySelectorAll('a') + const urls: string[] = [] + for (const linkElement of linkElements) { + if (linkElement.href.slice(0, 1) === '/') { + try { + const urlObj = new URL(baseURL + linkElement.href) + urls.push(urlObj.href) //relative + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error with relative url: ${err.message}`) + continue + } + } else { + try { + const urlObj = new URL(linkElement.href) + urls.push(urlObj.href) //absolute + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error with absolute url: ${err.message}`) + continue + } + } + } + return urls +} + +function normalizeURL(urlString: string): string { + const urlObj = new URL(urlString) + const hostPath = urlObj.hostname + urlObj.pathname + if (hostPath.length > 0 && hostPath.slice(-1) == '/') { + // handling trailing slash + return hostPath.slice(0, -1) + } + return hostPath +} + +export async function crawl(baseURL: string, currentURL: string, pages: string[], limit: number): Promise { + const baseURLObj = new URL(baseURL) + const currentURLObj = new URL(currentURL) + + if (limit !== 0) if (pages.length === limit) return pages + + if (baseURLObj.hostname !== currentURLObj.hostname) return pages + + const normalizeCurrentURL = baseURLObj.protocol + '//' + normalizeURL(currentURL) + if (pages.includes(normalizeCurrentURL)) { + return pages + } + + pages.push(normalizeCurrentURL) + + if (process.env.DEBUG === 'true') console.info(`actively crawling ${currentURL}`) + try { + const resp = await fetch(currentURL) + + if (resp.status > 399) { + if (process.env.DEBUG === 'true') console.error(`error in fetch with status code: ${resp.status}, on page: ${currentURL}`) + return pages + } + + const contentType: string | null = resp.headers.get('content-type') + if ((contentType && !contentType.includes('text/html')) || !contentType) { + if (process.env.DEBUG === 'true') console.error(`non html response, content type: ${contentType}, on page: ${currentURL}`) + return pages + } + + const htmlBody = await resp.text() + const nextURLs = getURLsFromHTML(htmlBody, baseURL) + for (const nextURL of nextURLs) { + pages = await crawl(baseURL, nextURL, pages, limit) + } + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error in fetch url: ${err.message}, on page: ${currentURL}`) + } + return pages +} + +export async function webCrawl(stringURL: string, limit: number): Promise { + const URLObj = new URL(stringURL) + const modifyURL = stringURL.slice(-1) === '/' ? stringURL.slice(0, -1) : stringURL + return await crawl(URLObj.protocol + '//' + URLObj.hostname, modifyURL, [], limit) +} + /** * Custom chain handler class */ From c18e98761af3dd908df5ae6969289c3dc331bf28 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Wed, 5 Jul 2023 17:07:45 +0800 Subject: [PATCH 170/398] modify puppeteer web crawl --- .../documentloaders/Puppeteer/Puppeteer.ts | 53 ++++++++++--------- packages/components/src/utils.ts | 2 +- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index 1331c736..3f27dc03 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -2,7 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { PuppeteerWebBaseLoader } from 'langchain/document_loaders/web/puppeteer' import { test } from 'linkifyjs' -import { getAvailableURLs } from '../../../src' +import { webCrawl } from '../../../src' class Puppeteer_DocumentLoaders implements INode { label: string @@ -35,19 +35,20 @@ class Puppeteer_DocumentLoaders implements INode { optional: true }, { - label: 'Web Scrape for Relative Links', - name: 'webScrape', + label: 'Web Crawl for Relative Links', + name: 'boolWebCrawl', type: 'boolean', optional: true, additionalParams: true }, { - label: 'Web Scrape Links Limit', + label: 'Web Crawl Links Limit', name: 'limit', type: 'number', default: 10, optional: true, - additionalParams: true + additionalParams: true, + description: 'Set 0 to crawl all relative links' }, { label: 'Metadata', @@ -62,7 +63,7 @@ class Puppeteer_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const webScrape = nodeData.inputs?.webScrape as boolean + const boolWebCrawl = nodeData.inputs?.boolWebCrawl as boolean let limit = nodeData.inputs?.limit as string let url = nodeData.inputs?.url as string @@ -71,30 +72,32 @@ class Puppeteer_DocumentLoaders implements INode { throw new Error('Invalid URL') } - const puppeteerLoader = async (url: string): Promise => { - let docs = [] - const loader = new PuppeteerWebBaseLoader(url) - if (textSplitter) { - docs = await loader.loadAndSplit(textSplitter) - } else { - docs = await loader.load() + async function puppeteerLoader(url: string): Promise { + try { + let docs = [] + const loader = new PuppeteerWebBaseLoader(url) + if (textSplitter) { + docs = await loader.loadAndSplit(textSplitter) + } else { + docs = await loader.load() + } + return docs + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error in CheerioWebBaseLoader: ${err.message}, on page: ${url}`) } - return docs } - let availableUrls: string[] let docs = [] - if (webScrape) { - if (!limit) limit = '10' - availableUrls = await getAvailableURLs(url, parseInt(limit)) - for (let i = 0; i < availableUrls.length; i++) { - try { - docs.push(...(await puppeteerLoader(availableUrls[i]))) - } catch (error) { - console.error('Error loading url with puppeteer. URL: ', availableUrls[i], 'Error: ', error) - continue - } + if (boolWebCrawl) { + if (process.env.DEBUG === 'true') console.info('Start Web Crawl') + if (!limit) throw new Error('Please set a limit to crawl') + else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') + const pages: string[] = await webCrawl(url, parseInt(limit)) + if (process.env.DEBUG === 'true') console.info(`pages: ${JSON.stringify(pages)}, length: ${pages.length}`) + for (const page of pages) { + docs.push(...(await puppeteerLoader(page))) } + if (process.env.DEBUG === 'true') console.info('Finish Web Crawl') } else { docs = await puppeteerLoader(url) } diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 63bb3969..d99517f0 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -242,7 +242,7 @@ export async function crawl(baseURL: string, currentURL: string, pages: string[] const baseURLObj = new URL(baseURL) const currentURLObj = new URL(currentURL) - if (limit !== 0) if (pages.length === limit) return pages + if (limit !== 0 && pages.length === limit) return pages if (baseURLObj.hostname !== currentURLObj.hostname) return pages From 607d4a3394b0a3de05aedef72b4400a6d82ed907 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Wed, 5 Jul 2023 17:17:13 +0800 Subject: [PATCH 171/398] modify playwright web crawl --- .../documentloaders/Playwright/Playwright.ts | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts index 6b7790af..6e22d55d 100644 --- a/packages/components/nodes/documentloaders/Playwright/Playwright.ts +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -2,7 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { PlaywrightWebBaseLoader } from 'langchain/document_loaders/web/playwright' import { test } from 'linkifyjs' -import { getAvailableURLs } from '../../../src' +import { webCrawl } from '../../../src' class Playwright_DocumentLoaders implements INode { label: string @@ -35,19 +35,20 @@ class Playwright_DocumentLoaders implements INode { optional: true }, { - label: 'Web Scrap for Relative Links', - name: 'webScrap', + label: 'Web Crawl for Relative Links', + name: 'boolWebCrawl', type: 'boolean', optional: true, additionalParams: true }, { - label: 'Web Scrap Links Limit', + label: 'Web Crawl Links Limit', name: 'limit', type: 'number', default: 10, optional: true, - additionalParams: true + additionalParams: true, + description: 'Set 0 to crawl all relative links' }, { label: 'Metadata', @@ -62,7 +63,7 @@ class Playwright_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const webScrap = nodeData.inputs?.webScrap as boolean + const boolWebCrawl = nodeData.inputs?.boolWebCrawl as boolean let limit = nodeData.inputs?.limit as string let url = nodeData.inputs?.url as string @@ -71,25 +72,32 @@ class Playwright_DocumentLoaders implements INode { throw new Error('Invalid URL') } - const playwrightLoader = async (url: string): Promise => { - let docs = [] - const loader = new PlaywrightWebBaseLoader(url) - if (textSplitter) { - docs = await loader.loadAndSplit(textSplitter) - } else { - docs = await loader.load() + async function playwrightLoader(url: string): Promise { + try { + let docs = [] + const loader = new PlaywrightWebBaseLoader(url) + if (textSplitter) { + docs = await loader.loadAndSplit(textSplitter) + } else { + docs = await loader.load() + } + return docs + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error in CheerioWebBaseLoader: ${err.message}, on page: ${url}`) } - return docs } - let availableUrls: string[] let docs = [] - if (webScrap) { - if (!limit) limit = '10' - availableUrls = await getAvailableURLs(url, parseInt(limit)) - for (let i = 0; i < availableUrls.length; i++) { - docs.push(...(await playwrightLoader(availableUrls[i]))) + if (boolWebCrawl) { + if (process.env.DEBUG === 'true') console.info('Start Web Crawl') + if (!limit) throw new Error('Please set a limit to crawl') + else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') + const pages: string[] = await webCrawl(url, parseInt(limit)) + if (process.env.DEBUG === 'true') console.info(`pages: ${JSON.stringify(pages)}, length: ${pages.length}`) + for (const page of pages) { + docs.push(...(await playwrightLoader(page))) } + if (process.env.DEBUG === 'true') console.info('Finish Web Crawl') } else { docs = await playwrightLoader(url) } From af67d70cfcd0514e3462070a531d539630827d12 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Wed, 5 Jul 2023 17:28:59 +0800 Subject: [PATCH 172/398] add function desc --- packages/components/src/utils.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index d99517f0..39d7e333 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -202,6 +202,9 @@ export const getAvailableURLs = async (url: string, limit: number) => { } } +/** + * Search for href through htmlBody string + */ function getURLsFromHTML(htmlBody: string, baseURL: string): string[] { const dom = new JSDOM(htmlBody) const linkElements = dom.window.document.querySelectorAll('a') @@ -228,6 +231,9 @@ function getURLsFromHTML(htmlBody: string, baseURL: string): string[] { return urls } +/** + * Normalize URL to prevent crawling the same page + */ function normalizeURL(urlString: string): string { const urlObj = new URL(urlString) const hostPath = urlObj.hostname + urlObj.pathname @@ -238,7 +244,10 @@ function normalizeURL(urlString: string): string { return hostPath } -export async function crawl(baseURL: string, currentURL: string, pages: string[], limit: number): Promise { +/** + * Recursive crawl using normalizeURL and getURLsFromHTML + */ +async function crawl(baseURL: string, currentURL: string, pages: string[], limit: number): Promise { const baseURLObj = new URL(baseURL) const currentURLObj = new URL(currentURL) @@ -279,6 +288,9 @@ export async function crawl(baseURL: string, currentURL: string, pages: string[] return pages } +/** + * Prep URL before passing into recursive carwl function + */ export async function webCrawl(stringURL: string, limit: number): Promise { const URLObj = new URL(stringURL) const modifyURL = stringURL.slice(-1) === '/' ? stringURL.slice(0, -1) : stringURL From 636ad5dc0c8209961a5cf22e0cb39b11f1eb9712 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 5 Jul 2023 18:14:54 +0100 Subject: [PATCH 173/398] add deployed boolean back --- packages/server/src/Interface.ts | 1 + packages/server/src/entity/ChatFlow.ts | 3 +++ packages/ui/src/views/canvas/index.js | 1 + 3 files changed, 5 insertions(+) diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index b4783f76..f8f2f4ac 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -9,6 +9,7 @@ export interface IChatFlow { id: string name: string flowData: string + deployed: boolean isPublic: boolean updatedDate: Date createdDate: Date diff --git a/packages/server/src/entity/ChatFlow.ts b/packages/server/src/entity/ChatFlow.ts index 400e0517..c454160d 100644 --- a/packages/server/src/entity/ChatFlow.ts +++ b/packages/server/src/entity/ChatFlow.ts @@ -13,6 +13,9 @@ export class ChatFlow implements IChatFlow { @Column() flowData: string + @Column() + deployed: boolean + @Column() isPublic: boolean diff --git a/packages/ui/src/views/canvas/index.js b/packages/ui/src/views/canvas/index.js index 03098963..1c6d610f 100644 --- a/packages/ui/src/views/canvas/index.js +++ b/packages/ui/src/views/canvas/index.js @@ -201,6 +201,7 @@ const Canvas = () => { if (!chatflow.id) { const newChatflowBody = { name: chatflowName, + deployed: false, isPublic: false, flowData } From 9bca78b66a2eecad44c567f6212c194a402986fd Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 5 Jul 2023 18:27:02 +0100 Subject: [PATCH 174/398] add nullable to isPublic --- packages/server/src/Interface.ts | 2 +- packages/server/src/entity/ChatFlow.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index f8f2f4ac..ab55e87a 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -10,9 +10,9 @@ export interface IChatFlow { name: string flowData: string deployed: boolean - isPublic: boolean updatedDate: Date createdDate: Date + isPublic?: boolean apikeyid?: string chatbotConfig?: string } diff --git a/packages/server/src/entity/ChatFlow.ts b/packages/server/src/entity/ChatFlow.ts index c454160d..0e1e8698 100644 --- a/packages/server/src/entity/ChatFlow.ts +++ b/packages/server/src/entity/ChatFlow.ts @@ -16,8 +16,8 @@ export class ChatFlow implements IChatFlow { @Column() deployed: boolean - @Column() - isPublic: boolean + @Column({ nullable: true }) + isPublic?: boolean @Column({ nullable: true }) apikeyid?: string From 1d74473ef38dc948225bb3eb951b3e81da5611c0 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 5 Jul 2023 18:50:09 +0100 Subject: [PATCH 175/398] update deployed as nullable --- packages/server/src/Interface.ts | 2 +- packages/server/src/entity/ChatFlow.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index ab55e87a..0c630490 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -9,9 +9,9 @@ export interface IChatFlow { id: string name: string flowData: string - deployed: boolean updatedDate: Date createdDate: Date + deployed?: boolean isPublic?: boolean apikeyid?: string chatbotConfig?: string diff --git a/packages/server/src/entity/ChatFlow.ts b/packages/server/src/entity/ChatFlow.ts index 0e1e8698..e1d212cf 100644 --- a/packages/server/src/entity/ChatFlow.ts +++ b/packages/server/src/entity/ChatFlow.ts @@ -13,8 +13,8 @@ export class ChatFlow implements IChatFlow { @Column() flowData: string - @Column() - deployed: boolean + @Column({ nullable: true }) + deployed?: boolean @Column({ nullable: true }) isPublic?: boolean From 5707c414bd03cc5f4d0928c36fad8b38b9ddab9c Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 5 Jul 2023 19:28:47 +0100 Subject: [PATCH 176/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.2.1?= =?UTF-8?q?5=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 4555a883..a5f03e10 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.14", + "version": "1.2.15", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From d4b299ba324b4c22e11309330f8e92e47df0f07f Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 5 Jul 2023 19:29:25 +0100 Subject: [PATCH 177/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.2.13=20rele?= =?UTF-8?q?ase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index ba1483b4..2cadc89d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.12", + "version": "1.2.13", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From c78c74f73d1dda648e0fa50e1171380aebe34ab4 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 5 Jul 2023 19:34:35 +0100 Subject: [PATCH 178/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.2.14=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0ff76284..61ea436b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.13", + "version": "1.2.14", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index eda69322..3e18c3d7 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.13", + "version": "1.2.14", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From c2dde8bf8f2e936572876984e96f4fc618be9048 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 6 Jul 2023 12:02:43 +0100 Subject: [PATCH 179/398] add fix to streaming --- .../nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts index 27a245e1..c6c40600 100644 --- a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts +++ b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts @@ -66,7 +66,7 @@ class SqlDatabaseChain_Chains implements INode { const chain = await getSQLDBChain(databaseType, dbFilePath, model) if (options.socketIO && options.socketIOClientId) { - const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) const res = await chain.run(input, [handler]) return res } else { From e977fe7d70db31ecec37abc2265e6545135d82b6 Mon Sep 17 00:00:00 2001 From: Matthias Platzer Date: Thu, 6 Jul 2023 14:29:37 +0200 Subject: [PATCH 180/398] Update config.ts /logs was in /packages -> moved it to project root (one level up) --- packages/server/src/utils/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/config.ts b/packages/server/src/utils/config.ts index d81fe7c3..c38d5a0c 100644 --- a/packages/server/src/utils/config.ts +++ b/packages/server/src/utils/config.ts @@ -7,7 +7,7 @@ dotenv.config({ path: path.join(__dirname, '..', '..', '.env'), override: true } // default config const loggingConfig = { - dir: process.env.LOG_PATH ?? path.join(__dirname, '..', '..', '..', 'logs'), + dir: process.env.LOG_PATH ?? path.join(__dirname, '..', '..', '..', '..', 'logs'), server: { level: 'info', filename: 'server.log', From c78ca494f57d1c6c8193d306b7b9b92942b66bfd Mon Sep 17 00:00:00 2001 From: vjsai Date: Thu, 6 Jul 2023 18:28:33 +0530 Subject: [PATCH 181/398] Added OpenAPI chain --- .../nodes/chains/ApiChain/OpenAPIChain.ts | 84 +++++++++++++++++++ .../SqlDatabaseChain/SqlDatabaseChain.ts | 2 +- packages/components/package.json | 2 +- 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 packages/components/nodes/chains/ApiChain/OpenAPIChain.ts diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts new file mode 100644 index 00000000..f0656f6d --- /dev/null +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -0,0 +1,84 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { APIChain, createOpenAPIChain } from 'langchain/chains' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { ChatOpenAI } from 'langchain/chat_models/openai' + +class OpenApiChain_Chains implements INode { + label: string + name: string + type: string + icon: string + category: string + baseClasses: string[] + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'OpenAPI Chain' + this.name = 'openApiChain' + this.type = 'openApiChain' + this.icon = 'apichain.svg' + this.category = 'Chains' + this.description = 'Chain to run queries against OpenAPI' + this.baseClasses = [this.type, ...getBaseClasses(APIChain)] + this.inputs = [ + { + label: 'ChatOpenAI Model', + name: 'model', + type: 'ChatOpenAI' + }, + { + label: 'YAML File', + name: 'yamlFile', + type: 'file', + fileType: '.yaml' + }, + { + label: 'Headers', + name: 'headers', + type: 'json', + additionalParams: true, + optional: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const model = nodeData.inputs?.model as ChatOpenAI + const headers = nodeData.inputs?.headers as Record + const yamlFileBase64 = nodeData.inputs?.yamlFile as string + const splitDataURI = yamlFileBase64.split(',') + splitDataURI.pop() + const bf = Buffer.from(splitDataURI.pop() || '', 'base64') + const utf8String = bf.toString('utf-8') + const chain = await createOpenAPIChain(utf8String, { + llm: model, + headers + }) + return chain + } + + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { + const model = nodeData.inputs?.model as ChatOpenAI + const headers = nodeData.inputs?.headers as Record + const yamlFileBase64 = nodeData.inputs?.yamlFile as string + const splitDataURI = yamlFileBase64.split(',') + splitDataURI.pop() + const bf = Buffer.from(splitDataURI.pop() || '', 'base64') + const utf8String = bf.toString('utf-8') + const chain = await createOpenAPIChain(utf8String, { + llm: model, + headers + }) + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) + const res = await chain.run(input, [handler]) + return res + } else { + const res = await chain.run(input) + return res + } + } +} + +module.exports = { nodeClass: OpenApiChain_Chains } diff --git a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts index 27a245e1..441ec4cd 100644 --- a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts +++ b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts @@ -1,5 +1,5 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { SqlDatabaseChain, SqlDatabaseChainInput } from 'langchain/chains' +import { SqlDatabaseChain, SqlDatabaseChainInput } from 'langchain/chains/sql_db' import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { DataSource } from 'typeorm' import { SqlDatabase } from 'langchain/sql_db' diff --git a/packages/components/package.json b/packages/components/package.json index a5f03e10..3e5f540b 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -35,7 +35,7 @@ "form-data": "^4.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "^0.0.96", + "langchain": "^0.0.103", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", From 6bd44e2d47581849024d4f829bd4e2a361199d69 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Fri, 7 Jul 2023 00:10:12 +0800 Subject: [PATCH 182/398] cheerio add xml scraper --- .../nodes/documentloaders/Cheerio/Cheerio.ts | 36 +++++++++++------ packages/components/src/utils.ts | 39 +++++++++++++++++++ 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts index 10eff77e..2106b86f 100644 --- a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts +++ b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts @@ -2,7 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { CheerioWebBaseLoader } from 'langchain/document_loaders/web/cheerio' import { test } from 'linkifyjs' -import { webCrawl } from '../../../src' +import { webCrawl, xmlScrape } from '../../../src' class Cheerio_DocumentLoaders implements INode { label: string @@ -35,20 +35,30 @@ class Cheerio_DocumentLoaders implements INode { optional: true }, { - label: 'Web Crawl for Relative Links', - name: 'boolWebCrawl', - type: 'boolean', + label: 'Get Relative Links Method', + name: 'relativeLinksMethod', + type: 'options', + options: [ + { + label: 'Web Crawl', + name: 'webCrawl' + }, + { + label: 'Scrape XML Sitemap', + name: 'scrapeXMLSitemap' + } + ], optional: true, additionalParams: true }, { - label: 'Web Crawl Links Limit', + label: 'Crawl/Scrape Links Limit', name: 'limit', type: 'number', default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl all relative links' + description: 'Set 0 to crawl/scrape all relative links' }, { label: 'Metadata', @@ -63,7 +73,7 @@ class Cheerio_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const boolWebCrawl = nodeData.inputs?.boolWebCrawl as boolean + const relativeLinksMethod = nodeData.inputs?.relativeLinksMethod as string let limit = nodeData.inputs?.limit as string let url = nodeData.inputs?.url as string @@ -88,16 +98,18 @@ class Cheerio_DocumentLoaders implements INode { } let docs = [] - if (boolWebCrawl) { - if (process.env.DEBUG === 'true') console.info('Start Web Crawl') - if (!limit) throw new Error('Please set a limit to crawl') + if (relativeLinksMethod) { + if (process.env.DEBUG === 'true') console.info(`Start ${relativeLinksMethod}`) + if (!limit) throw new Error('Please set a limit to crawl/scrape') else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') - const pages: string[] = await webCrawl(url, parseInt(limit)) + const pages: string[] = + relativeLinksMethod === 'webCrawl' ? await webCrawl(url, parseInt(limit)) : await xmlScrape(url, parseInt(limit)) if (process.env.DEBUG === 'true') console.info(`pages: ${JSON.stringify(pages)}, length: ${pages.length}`) + if (!pages || pages.length === 0) throw new Error('No relative links found') for (const page of pages) { docs.push(...(await cheerioLoader(page))) } - if (process.env.DEBUG === 'true') console.info('Finish Web Crawl') + if (process.env.DEBUG === 'true') console.info(`Finish ${relativeLinksMethod}`) } else { docs = await cheerioLoader(url) } diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 39d7e333..a5f69ad9 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -297,6 +297,45 @@ export async function webCrawl(stringURL: string, limit: number): Promise { + let urls: string[] = [] + if (process.env.DEBUG === 'true') console.info(`actively scarping ${currentURL}`) + try { + const resp = await fetch(currentURL) + + if (resp.status > 399) { + if (process.env.DEBUG === 'true') console.error(`error in fetch with status code: ${resp.status}, on page: ${currentURL}`) + return urls + } + + const contentType: string | null = resp.headers.get('content-type') + if ((contentType && !contentType.includes('application/xml')) || !contentType) { + if (process.env.DEBUG === 'true') console.error(`non xml response, content type: ${contentType}, on page: ${currentURL}`) + return urls + } + + const xmlBody = await resp.text() + urls = getURLsFromXML(xmlBody, limit) + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error in fetch url: ${err.message}, on page: ${currentURL}`) + } + return urls +} + /** * Custom chain handler class */ From c2523d8eecc75e434d51d07abb1317bbcae4740a Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Fri, 7 Jul 2023 00:21:11 +0800 Subject: [PATCH 183/398] puppeteer add xml scraper --- .../documentloaders/Puppeteer/Puppeteer.ts | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index 3f27dc03..101a41ea 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -2,7 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { PuppeteerWebBaseLoader } from 'langchain/document_loaders/web/puppeteer' import { test } from 'linkifyjs' -import { webCrawl } from '../../../src' +import { webCrawl, xmlScrape } from '../../../src' class Puppeteer_DocumentLoaders implements INode { label: string @@ -35,20 +35,30 @@ class Puppeteer_DocumentLoaders implements INode { optional: true }, { - label: 'Web Crawl for Relative Links', - name: 'boolWebCrawl', - type: 'boolean', + label: 'Get Relative Links Method', + name: 'relativeLinksMethod', + type: 'options', + options: [ + { + label: 'Web Crawl', + name: 'webCrawl' + }, + { + label: 'Scrape XML Sitemap', + name: 'scrapeXMLSitemap' + } + ], optional: true, additionalParams: true }, { - label: 'Web Crawl Links Limit', + label: 'Crawl/Scrape Links Limit', name: 'limit', type: 'number', default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl all relative links' + description: 'Set 0 to crawl/scrape all relative links' }, { label: 'Metadata', @@ -63,7 +73,7 @@ class Puppeteer_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const boolWebCrawl = nodeData.inputs?.boolWebCrawl as boolean + const relativeLinksMethod = nodeData.inputs?.relativeLinksMethod as string let limit = nodeData.inputs?.limit as string let url = nodeData.inputs?.url as string @@ -88,16 +98,18 @@ class Puppeteer_DocumentLoaders implements INode { } let docs = [] - if (boolWebCrawl) { - if (process.env.DEBUG === 'true') console.info('Start Web Crawl') - if (!limit) throw new Error('Please set a limit to crawl') + if (relativeLinksMethod) { + if (process.env.DEBUG === 'true') console.info(`Start ${relativeLinksMethod}`) + if (!limit) throw new Error('Please set a limit to crawl/scrape') else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') - const pages: string[] = await webCrawl(url, parseInt(limit)) + const pages: string[] = + relativeLinksMethod === 'webCrawl' ? await webCrawl(url, parseInt(limit)) : await xmlScrape(url, parseInt(limit)) if (process.env.DEBUG === 'true') console.info(`pages: ${JSON.stringify(pages)}, length: ${pages.length}`) + if (!pages || pages.length === 0) throw new Error('No relative links found') for (const page of pages) { docs.push(...(await puppeteerLoader(page))) } - if (process.env.DEBUG === 'true') console.info('Finish Web Crawl') + if (process.env.DEBUG === 'true') console.info(`Finish ${relativeLinksMethod}`) } else { docs = await puppeteerLoader(url) } From e4a488c211b05c5e4f6d126fb4cabb176f53dafd Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Fri, 7 Jul 2023 00:37:10 +0800 Subject: [PATCH 184/398] playwright add xml scraper --- .../documentloaders/Playwright/Playwright.ts | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts index 6e22d55d..c02ab442 100644 --- a/packages/components/nodes/documentloaders/Playwright/Playwright.ts +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -2,7 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { PlaywrightWebBaseLoader } from 'langchain/document_loaders/web/playwright' import { test } from 'linkifyjs' -import { webCrawl } from '../../../src' +import { webCrawl, xmlScrape } from '../../../src' class Playwright_DocumentLoaders implements INode { label: string @@ -35,20 +35,30 @@ class Playwright_DocumentLoaders implements INode { optional: true }, { - label: 'Web Crawl for Relative Links', - name: 'boolWebCrawl', - type: 'boolean', + label: 'Get Relative Links Method', + name: 'relativeLinksMethod', + type: 'options', + options: [ + { + label: 'Web Crawl', + name: 'webCrawl' + }, + { + label: 'Scrape XML Sitemap', + name: 'scrapeXMLSitemap' + } + ], optional: true, additionalParams: true }, { - label: 'Web Crawl Links Limit', + label: 'Crawl/Scrape Links Limit', name: 'limit', type: 'number', default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl all relative links' + description: 'Set 0 to crawl/scrape all relative links' }, { label: 'Metadata', @@ -63,7 +73,7 @@ class Playwright_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const boolWebCrawl = nodeData.inputs?.boolWebCrawl as boolean + const relativeLinksMethod = nodeData.inputs?.relativeLinksMethod as string let limit = nodeData.inputs?.limit as string let url = nodeData.inputs?.url as string @@ -88,16 +98,18 @@ class Playwright_DocumentLoaders implements INode { } let docs = [] - if (boolWebCrawl) { - if (process.env.DEBUG === 'true') console.info('Start Web Crawl') - if (!limit) throw new Error('Please set a limit to crawl') + if (relativeLinksMethod) { + if (process.env.DEBUG === 'true') console.info(`Start ${relativeLinksMethod}`) + if (!limit) throw new Error('Please set a limit to crawl/scrape') else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') - const pages: string[] = await webCrawl(url, parseInt(limit)) + const pages: string[] = + relativeLinksMethod === 'webCrawl' ? await webCrawl(url, parseInt(limit)) : await xmlScrape(url, parseInt(limit)) if (process.env.DEBUG === 'true') console.info(`pages: ${JSON.stringify(pages)}, length: ${pages.length}`) + if (!pages || pages.length === 0) throw new Error('No relative links found') for (const page of pages) { docs.push(...(await playwrightLoader(page))) } - if (process.env.DEBUG === 'true') console.info('Finish Web Crawl') + if (process.env.DEBUG === 'true') console.info(`Finish ${relativeLinksMethod}`) } else { docs = await playwrightLoader(url) } From a570893a161b8972286ee3c2fa55a6f0a958f466 Mon Sep 17 00:00:00 2001 From: ivalkshfoeif Date: Thu, 6 Jul 2023 16:20:21 -0700 Subject: [PATCH 185/398] add motorhead memory --- .../memory/MotorheadMemory/MotorheadMemory.ts | 96 +++++++++++++++++++ .../nodes/memory/MotorheadMemory/memory.svg | 8 ++ 2 files changed, 104 insertions(+) create mode 100644 packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts create mode 100644 packages/components/nodes/memory/MotorheadMemory/memory.svg diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts new file mode 100644 index 00000000..383ad613 --- /dev/null +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -0,0 +1,96 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { ICommonObject } from '../../../src' +import { MotorheadMemory, MotorheadMemoryInput } from 'langchain/memory' + +class MotorMemory_Memory implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Motorhead Memory' + this.name = 'motorheadMemory' + this.type = 'MotorheadMemory' + this.icon = 'memory.svg' + this.category = 'Memory' + this.description = 'Remembers previous conversational back and forths directly' + this.baseClasses = [this.type, ...getBaseClasses(MotorheadMemory)] + this.inputs = [ + { + label: 'Base URL', + name: 'baseURL', + type: 'string', + optional: true, + description: 'To use the online version, leave the URL blank. More details at https://getmetal.io.' + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history' + }, + { + label: 'Session Id', + name: 'sessionId', + type: 'string', + description: 'if empty, chatId will be used automatically', + default: '', + additionalParams: true, + optional: true + }, + { + label: 'API Key', + name: 'apiKey', + type: 'string', + optional: true + }, + { + label: 'Client ID', + name: 'clientId', + type: 'string', + optional: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const baseURL = nodeData.inputs?.baseURL as string + const sessionId = nodeData.inputs?.sessionId as string + const apiKey = nodeData.inputs?.apiKey as string + const clientId = nodeData.inputs?.clientId as string + + const chatId = options?.chatId as string + + console.log(chatId) + + let obj: MotorheadMemoryInput = { + returnMessages: true, + sessionId: sessionId ? sessionId : chatId, + memoryKey + } + + if (baseURL) { + obj = { + ...obj, + url: baseURL + } + } else { + obj = { + ...obj, + apiKey, + clientId + } + } + + return new MotorheadMemory(obj) + } +} + +module.exports = { nodeClass: MotorMemory_Memory } diff --git a/packages/components/nodes/memory/MotorheadMemory/memory.svg b/packages/components/nodes/memory/MotorheadMemory/memory.svg new file mode 100644 index 00000000..ca8e17da --- /dev/null +++ b/packages/components/nodes/memory/MotorheadMemory/memory.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From ab535e011ff030263c45fc5e6562865601be2865 Mon Sep 17 00:00:00 2001 From: atilgner Date: Thu, 6 Jul 2023 16:41:54 -0700 Subject: [PATCH 186/398] feat: added category search and clear search button --- packages/ui/src/views/canvas/AddNodes.js | 37 ++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/views/canvas/AddNodes.js b/packages/ui/src/views/canvas/AddNodes.js index 50978700..810fc53f 100644 --- a/packages/ui/src/views/canvas/AddNodes.js +++ b/packages/ui/src/views/canvas/AddNodes.js @@ -34,7 +34,7 @@ import Transitions from 'ui-component/extended/Transitions' import { StyledFab } from 'ui-component/button/StyledFab' // icons -import { IconPlus, IconSearch, IconMinus } from '@tabler/icons' +import { IconPlus, IconSearch, IconMinus, IconX } from '@tabler/icons' // const import { baseURL } from 'store/constant' @@ -61,11 +61,20 @@ const AddNodes = ({ nodesData, node }) => { } } + const getSearchedNodes = (value) => { + const passed = nodesData.filter((nd) => { + const passesQuery = nd.name.toLowerCase().includes(value.toLowerCase()) + const passesCategory = nd.category.toLowerCase().includes(value.toLowerCase()) + return passesQuery || passesCategory + }) + return passed + } + const filterSearch = (value) => { setSearchValue(value) setTimeout(() => { if (value) { - const returnData = nodesData.filter((nd) => nd.name.toLowerCase().includes(value.toLowerCase())) + const returnData = getSearchedNodes(value) groupByCategory(returnData, true) scrollTop() } else if (value === '') { @@ -167,7 +176,7 @@ const AddNodes = ({ nodesData, node }) => { Add Nodes filterSearch(e.target.value)} @@ -177,6 +186,28 @@ const AddNodes = ({ nodesData, node }) => { } + endAdornment={ + + filterSearch('')} + style={{ + cursor: 'pointer' + }} + /> + + } aria-describedby='search-helper-text' inputProps={{ 'aria-label': 'weight' From 717468a018b724bd83a4de2f0953023c11698b88 Mon Sep 17 00:00:00 2001 From: vjsai Date: Fri, 7 Jul 2023 18:38:22 +0530 Subject: [PATCH 187/398] Updated appropriate icon for OpenAPIChain --- .../nodes/chains/ApiChain/OpenAPIChain.ts | 2 +- .../nodes/chains/ApiChain/openapi.png | Bin 0 -> 25114 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 packages/components/nodes/chains/ApiChain/openapi.png diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts index f0656f6d..b5970bb8 100644 --- a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -17,7 +17,7 @@ class OpenApiChain_Chains implements INode { this.label = 'OpenAPI Chain' this.name = 'openApiChain' this.type = 'openApiChain' - this.icon = 'apichain.svg' + this.icon = 'openapi.png' this.category = 'Chains' this.description = 'Chain to run queries against OpenAPI' this.baseClasses = [this.type, ...getBaseClasses(APIChain)] diff --git a/packages/components/nodes/chains/ApiChain/openapi.png b/packages/components/nodes/chains/ApiChain/openapi.png new file mode 100644 index 0000000000000000000000000000000000000000..457c2e4050c8eef06588f25c0acc67a6059ebe01 GIT binary patch literal 25114 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 Date: Fri, 7 Jul 2023 16:54:19 +0100 Subject: [PATCH 188/398] add fix to allow chaintool with llmchain --- .../nodes/tools/ChainTool/ChainTool.ts | 2 +- .../components/nodes/tools/ChainTool/core.ts | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 packages/components/nodes/tools/ChainTool/core.ts diff --git a/packages/components/nodes/tools/ChainTool/ChainTool.ts b/packages/components/nodes/tools/ChainTool/ChainTool.ts index 32e414af..669b5947 100644 --- a/packages/components/nodes/tools/ChainTool/ChainTool.ts +++ b/packages/components/nodes/tools/ChainTool/ChainTool.ts @@ -1,7 +1,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { ChainTool } from 'langchain/tools' import { BaseChain } from 'langchain/chains' +import { ChainTool } from './core' class ChainTool_Tools implements INode { label: string diff --git a/packages/components/nodes/tools/ChainTool/core.ts b/packages/components/nodes/tools/ChainTool/core.ts new file mode 100644 index 00000000..6c3dba55 --- /dev/null +++ b/packages/components/nodes/tools/ChainTool/core.ts @@ -0,0 +1,25 @@ +import { DynamicTool, DynamicToolInput } from 'langchain/tools' +import { BaseChain } from 'langchain/chains' + +export interface ChainToolInput extends Omit { + chain: BaseChain +} + +export class ChainTool extends DynamicTool { + chain: BaseChain + + constructor({ chain, ...rest }: ChainToolInput) { + super({ + ...rest, + func: async (input, runManager) => { + // To enable LLM Chain which has promptValues + if ((chain as any).prompt && (chain as any).prompt.promptValues) { + const values = await chain.call((chain as any).prompt.promptValues, runManager?.getChild()) + return values?.text + } + return chain.run(input, runManager?.getChild()) + } + }) + this.chain = chain + } +} From 0923a356834607a728be3ebccc9a534bb2e73c39 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 7 Jul 2023 17:36:23 +0100 Subject: [PATCH 189/398] add endpoint to HF --- .../ChatHuggingFace/ChatHuggingFace.ts | 13 ++- .../nodes/chatmodels/ChatHuggingFace/core.ts | 109 ++++++++++++++++++ .../HuggingFaceInferenceEmbedding.ts | 12 +- .../HuggingFaceInferenceEmbedding/core.ts | 48 ++++++++ .../HuggingFaceInference.ts | 13 ++- .../nodes/llms/HuggingFaceInference/core.ts | 109 ++++++++++++++++++ packages/components/package.json | 2 +- packages/components/src/utils.ts | 14 +++ 8 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 packages/components/nodes/chatmodels/ChatHuggingFace/core.ts create mode 100644 packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts create mode 100644 packages/components/nodes/llms/HuggingFaceInference/core.ts diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts index 1dae41e4..d92dd1e0 100644 --- a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts @@ -1,6 +1,6 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { HFInput, HuggingFaceInference } from 'langchain/llms/hf' +import { HFInput, HuggingFaceInference } from './core' class ChatHuggingFace_ChatModels implements INode { label: string @@ -71,6 +71,15 @@ class ChatHuggingFace_ChatModels implements INode { description: 'Frequency Penalty parameter may not apply to certain model. Please check available model parameters', optional: true, additionalParams: true + }, + { + label: 'Endpoint', + name: 'endpoint', + type: 'string', + placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2', + description: 'Using your own inference endpoint', + optional: true, + additionalParams: true } ] } @@ -83,6 +92,7 @@ class ChatHuggingFace_ChatModels implements INode { const topP = nodeData.inputs?.topP as string const hfTopK = nodeData.inputs?.hfTopK as string const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string + const endpoint = nodeData.inputs?.endpoint as string const obj: Partial = { model, @@ -94,6 +104,7 @@ class ChatHuggingFace_ChatModels implements INode { if (topP) obj.topP = parseInt(topP, 10) if (hfTopK) obj.topK = parseInt(hfTopK, 10) if (frequencyPenalty) obj.frequencyPenalty = parseInt(frequencyPenalty, 10) + if (endpoint) obj.endpoint = endpoint const huggingFace = new HuggingFaceInference(obj) return huggingFace diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts b/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts new file mode 100644 index 00000000..958e9072 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts @@ -0,0 +1,109 @@ +import { getEnvironmentVariable } from '../../../src/utils' +import { LLM, BaseLLMParams } from 'langchain/llms/base' + +export interface HFInput { + /** Model to use */ + model: string + + /** Sampling temperature to use */ + temperature?: number + + /** + * Maximum number of tokens to generate in the completion. + */ + maxTokens?: number + + /** Total probability mass of tokens to consider at each step */ + topP?: number + + /** Integer to define the top tokens considered within the sample operation to create new text. */ + topK?: number + + /** Penalizes repeated tokens according to frequency */ + frequencyPenalty?: number + + /** API key to use. */ + apiKey?: string + + /** Private endpoint to use. */ + endpoint?: string +} + +export class HuggingFaceInference extends LLM implements HFInput { + get lc_secrets(): { [key: string]: string } | undefined { + return { + apiKey: 'HUGGINGFACEHUB_API_KEY' + } + } + + model = 'gpt2' + + temperature: number | undefined = undefined + + maxTokens: number | undefined = undefined + + topP: number | undefined = undefined + + topK: number | undefined = undefined + + frequencyPenalty: number | undefined = undefined + + apiKey: string | undefined = undefined + + endpoint: string | undefined = undefined + + constructor(fields?: Partial & BaseLLMParams) { + super(fields ?? {}) + + this.model = fields?.model ?? this.model + this.temperature = fields?.temperature ?? this.temperature + this.maxTokens = fields?.maxTokens ?? this.maxTokens + this.topP = fields?.topP ?? this.topP + this.topK = fields?.topK ?? this.topK + this.frequencyPenalty = fields?.frequencyPenalty ?? this.frequencyPenalty + this.endpoint = fields?.endpoint ?? '' + this.apiKey = fields?.apiKey ?? getEnvironmentVariable('HUGGINGFACEHUB_API_KEY') + if (!this.apiKey) { + throw new Error( + 'Please set an API key for HuggingFace Hub in the environment variable HUGGINGFACEHUB_API_KEY or in the apiKey field of the HuggingFaceInference constructor.' + ) + } + } + + _llmType() { + return 'hf' + } + + /** @ignore */ + async _call(prompt: string, options: this['ParsedCallOptions']): Promise { + const { HfInference } = await HuggingFaceInference.imports() + const hf = new HfInference(this.apiKey) + if (this.endpoint) hf.endpoint(this.endpoint) + const res = await this.caller.callWithOptions({ signal: options.signal }, hf.textGeneration.bind(hf), { + model: this.model, + parameters: { + // make it behave similar to openai, returning only the generated text + return_full_text: false, + temperature: this.temperature, + max_new_tokens: this.maxTokens, + top_p: this.topP, + top_k: this.topK, + repetition_penalty: this.frequencyPenalty + }, + inputs: prompt + }) + return res.generated_text + } + + /** @ignore */ + static async imports(): Promise<{ + HfInference: typeof import('@huggingface/inference').HfInference + }> { + try { + const { HfInference } = await import('@huggingface/inference') + return { HfInference } + } catch (e) { + throw new Error('Please install huggingface as a dependency with, e.g. `yarn add @huggingface/inference`') + } + } +} diff --git a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts index 6f14325a..d77d623f 100644 --- a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts +++ b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts @@ -1,6 +1,6 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { HuggingFaceInferenceEmbeddings, HuggingFaceInferenceEmbeddingsParams } from 'langchain/embeddings/hf' +import { HuggingFaceInferenceEmbeddings, HuggingFaceInferenceEmbeddingsParams } from './core' class HuggingFaceInferenceEmbedding_Embeddings implements INode { label: string @@ -31,6 +31,14 @@ class HuggingFaceInferenceEmbedding_Embeddings implements INode { name: 'modelName', type: 'string', optional: true + }, + { + label: 'Endpoint', + name: 'endpoint', + type: 'string', + placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/sentence-transformers/all-MiniLM-L6-v2', + description: 'Using your own inference endpoint', + optional: true } ] } @@ -38,12 +46,14 @@ class HuggingFaceInferenceEmbedding_Embeddings implements INode { async init(nodeData: INodeData): Promise { const apiKey = nodeData.inputs?.apiKey as string const modelName = nodeData.inputs?.modelName as string + const endpoint = nodeData.inputs?.endpoint as string const obj: Partial = { apiKey } if (modelName) obj.model = modelName + if (endpoint) obj.endpoint = endpoint const model = new HuggingFaceInferenceEmbeddings(obj) return model diff --git a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts new file mode 100644 index 00000000..b8d89ebe --- /dev/null +++ b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts @@ -0,0 +1,48 @@ +import { HfInference } from '@huggingface/inference' +import { Embeddings, EmbeddingsParams } from 'langchain/embeddings/base' +import { getEnvironmentVariable } from '../../../src/utils' + +export interface HuggingFaceInferenceEmbeddingsParams extends EmbeddingsParams { + apiKey?: string + model?: string + endpoint?: string +} + +export class HuggingFaceInferenceEmbeddings extends Embeddings implements HuggingFaceInferenceEmbeddingsParams { + apiKey?: string + + endpoint?: string + + model: string + + client: HfInference + + constructor(fields?: HuggingFaceInferenceEmbeddingsParams) { + super(fields ?? {}) + + this.model = fields?.model ?? 'sentence-transformers/distilbert-base-nli-mean-tokens' + this.apiKey = fields?.apiKey ?? getEnvironmentVariable('HUGGINGFACEHUB_API_KEY') + this.endpoint = fields?.endpoint ?? '' + this.client = new HfInference(this.apiKey) + if (this.endpoint) this.client.endpoint(this.endpoint) + } + + async _embed(texts: string[]): Promise { + // replace newlines, which can negatively affect performance. + const clean = texts.map((text) => text.replace(/\n/g, ' ')) + return this.caller.call(() => + this.client.featureExtraction({ + model: this.model, + inputs: clean + }) + ) as Promise + } + + embedQuery(document: string): Promise { + return this._embed([document]).then((embeddings) => embeddings[0]) + } + + embedDocuments(documents: string[]): Promise { + return this._embed(documents) + } +} diff --git a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts index 291f67c9..92eb46d5 100644 --- a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts +++ b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts @@ -1,6 +1,6 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { HFInput, HuggingFaceInference } from 'langchain/llms/hf' +import { HFInput, HuggingFaceInference } from './core' class HuggingFaceInference_LLMs implements INode { label: string @@ -71,6 +71,15 @@ class HuggingFaceInference_LLMs implements INode { description: 'Frequency Penalty parameter may not apply to certain model. Please check available model parameters', optional: true, additionalParams: true + }, + { + label: 'Endpoint', + name: 'endpoint', + type: 'string', + placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2', + description: 'Using your own inference endpoint', + optional: true, + additionalParams: true } ] } @@ -83,6 +92,7 @@ class HuggingFaceInference_LLMs implements INode { const topP = nodeData.inputs?.topP as string const hfTopK = nodeData.inputs?.hfTopK as string const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string + const endpoint = nodeData.inputs?.endpoint as string const obj: Partial = { model, @@ -94,6 +104,7 @@ class HuggingFaceInference_LLMs implements INode { if (topP) obj.topP = parseInt(topP, 10) if (hfTopK) obj.topK = parseInt(hfTopK, 10) if (frequencyPenalty) obj.frequencyPenalty = parseInt(frequencyPenalty, 10) + if (endpoint) obj.endpoint = endpoint const huggingFace = new HuggingFaceInference(obj) return huggingFace diff --git a/packages/components/nodes/llms/HuggingFaceInference/core.ts b/packages/components/nodes/llms/HuggingFaceInference/core.ts new file mode 100644 index 00000000..958e9072 --- /dev/null +++ b/packages/components/nodes/llms/HuggingFaceInference/core.ts @@ -0,0 +1,109 @@ +import { getEnvironmentVariable } from '../../../src/utils' +import { LLM, BaseLLMParams } from 'langchain/llms/base' + +export interface HFInput { + /** Model to use */ + model: string + + /** Sampling temperature to use */ + temperature?: number + + /** + * Maximum number of tokens to generate in the completion. + */ + maxTokens?: number + + /** Total probability mass of tokens to consider at each step */ + topP?: number + + /** Integer to define the top tokens considered within the sample operation to create new text. */ + topK?: number + + /** Penalizes repeated tokens according to frequency */ + frequencyPenalty?: number + + /** API key to use. */ + apiKey?: string + + /** Private endpoint to use. */ + endpoint?: string +} + +export class HuggingFaceInference extends LLM implements HFInput { + get lc_secrets(): { [key: string]: string } | undefined { + return { + apiKey: 'HUGGINGFACEHUB_API_KEY' + } + } + + model = 'gpt2' + + temperature: number | undefined = undefined + + maxTokens: number | undefined = undefined + + topP: number | undefined = undefined + + topK: number | undefined = undefined + + frequencyPenalty: number | undefined = undefined + + apiKey: string | undefined = undefined + + endpoint: string | undefined = undefined + + constructor(fields?: Partial & BaseLLMParams) { + super(fields ?? {}) + + this.model = fields?.model ?? this.model + this.temperature = fields?.temperature ?? this.temperature + this.maxTokens = fields?.maxTokens ?? this.maxTokens + this.topP = fields?.topP ?? this.topP + this.topK = fields?.topK ?? this.topK + this.frequencyPenalty = fields?.frequencyPenalty ?? this.frequencyPenalty + this.endpoint = fields?.endpoint ?? '' + this.apiKey = fields?.apiKey ?? getEnvironmentVariable('HUGGINGFACEHUB_API_KEY') + if (!this.apiKey) { + throw new Error( + 'Please set an API key for HuggingFace Hub in the environment variable HUGGINGFACEHUB_API_KEY or in the apiKey field of the HuggingFaceInference constructor.' + ) + } + } + + _llmType() { + return 'hf' + } + + /** @ignore */ + async _call(prompt: string, options: this['ParsedCallOptions']): Promise { + const { HfInference } = await HuggingFaceInference.imports() + const hf = new HfInference(this.apiKey) + if (this.endpoint) hf.endpoint(this.endpoint) + const res = await this.caller.callWithOptions({ signal: options.signal }, hf.textGeneration.bind(hf), { + model: this.model, + parameters: { + // make it behave similar to openai, returning only the generated text + return_full_text: false, + temperature: this.temperature, + max_new_tokens: this.maxTokens, + top_p: this.topP, + top_k: this.topK, + repetition_penalty: this.frequencyPenalty + }, + inputs: prompt + }) + return res.generated_text + } + + /** @ignore */ + static async imports(): Promise<{ + HfInference: typeof import('@huggingface/inference').HfInference + }> { + try { + const { HfInference } = await import('@huggingface/inference') + return { HfInference } + } catch (e) { + throw new Error('Please install huggingface as a dependency with, e.g. `yarn add @huggingface/inference`') + } + } +} diff --git a/packages/components/package.json b/packages/components/package.json index a5f03e10..df0589e7 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -19,7 +19,7 @@ "@aws-sdk/client-dynamodb": "^3.360.0", "@dqbd/tiktoken": "^1.0.7", "@getzep/zep-js": "^0.3.1", - "@huggingface/inference": "1", + "@huggingface/inference": "^2.6.1", "@pinecone-database/pinecone": "^0.0.12", "@qdrant/js-client-rest": "^1.2.2", "@supabase/supabase-js": "^2.21.0", diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index c247ebc2..c87b0831 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -201,6 +201,20 @@ export const getAvailableURLs = async (url: string, limit: number) => { } } +/** + * Get env variables + * @param {string} url + * @param {number} limit + * @returns {string[]} + */ +export const getEnvironmentVariable = (name: string): string | undefined => { + try { + return typeof process !== 'undefined' ? process.env?.[name] : undefined + } catch (e) { + return undefined + } +} + /** * Custom chain handler class */ From 90d7f4472dae028e5eb9dab3cf05f3add4d6142b Mon Sep 17 00:00:00 2001 From: atilgner Date: Fri, 7 Jul 2023 12:39:30 -0700 Subject: [PATCH 190/398] fix: docker should install chromium and puppeteer should be no sandbox --- docker/Dockerfile | 5 +++++ .../nodes/documentloaders/Puppeteer/Puppeteer.ts | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 2203af11..1ad1bf5e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,7 +6,12 @@ RUN apk add --no-cache git RUN apk add --no-cache python3 py3-pip make g++ # needed for pdfjs-dist RUN apk add --no-cache build-base cairo-dev pango-dev + +# Install Chromium +RUN apk add --no-cache chromium + ENV PUPPETEER_SKIP_DOWNLOAD=true +ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser # You can install a specific version like: flowise@1.0.0 RUN npm install -g flowise diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index 1331c736..bc1bc9ed 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -73,7 +73,12 @@ class Puppeteer_DocumentLoaders implements INode { const puppeteerLoader = async (url: string): Promise => { let docs = [] - const loader = new PuppeteerWebBaseLoader(url) + const loader = new PuppeteerWebBaseLoader(url, { + launchOptions: { + args: ['--no-sandbox'], + headless: 'new' + } + }) if (textSplitter) { docs = await loader.loadAndSplit(textSplitter) } else { From 7e076028dfdb3fe728648fbadab95dd6b3df59f7 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 8 Jul 2023 01:55:44 +0100 Subject: [PATCH 191/398] add warning description --- .../nodes/documentloaders/Cheerio/Cheerio.ts | 3 ++- .../documentloaders/Playwright/Playwright.ts | 3 ++- .../documentloaders/Puppeteer/Puppeteer.ts | 3 ++- packages/components/src/Interface.ts | 1 + .../ui/src/views/canvas/NodeInputHandler.js | 18 +++++++++++++++++- 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts index 2106b86f..2521b039 100644 --- a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts +++ b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts @@ -58,7 +58,8 @@ class Cheerio_DocumentLoaders implements INode { default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl/scrape all relative links' + description: 'Set 0 to crawl/scrape all relative links', + warning: `Scraping all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc) ` }, { label: 'Metadata', diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts index c02ab442..2301b4e9 100644 --- a/packages/components/nodes/documentloaders/Playwright/Playwright.ts +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -58,7 +58,8 @@ class Playwright_DocumentLoaders implements INode { default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl/scrape all relative links' + description: 'Set 0 to crawl/scrape all relative links', + warning: `Scraping all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc) ` }, { label: 'Metadata', diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index 101a41ea..bf0920bb 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -58,7 +58,8 @@ class Puppeteer_DocumentLoaders implements INode { default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl/scrape all relative links' + description: 'Set 0 to crawl/scrape all relative links', + warning: `Scraping all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc) ` }, { label: 'Metadata', diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index f8a6fd58..d9233e49 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -57,6 +57,7 @@ export interface INodeParams { type: NodeParamsType | string default?: CommonType | ICommonObject | ICommonObject[] description?: string + warning?: string options?: Array optional?: boolean | INodeDisplay rows?: number diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 2d96bcb5..ba72a4ce 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -7,7 +7,7 @@ import { useSelector } from 'react-redux' import { useTheme, styled } from '@mui/material/styles' import { Box, Typography, Tooltip, IconButton, Button } from '@mui/material' import { tooltipClasses } from '@mui/material/Tooltip' -import { IconArrowsMaximize, IconEdit } from '@tabler/icons' +import { IconArrowsMaximize, IconEdit, IconAlertTriangle } from '@tabler/icons' // project import import { Dropdown } from 'ui-component/dropdown/Dropdown' @@ -210,6 +210,22 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA )}

+ {inputParam.warning && ( +
+ + {inputParam.warning} +
+ )} {inputParam.type === 'file' && ( Date: Sat, 8 Jul 2023 15:00:47 +0530 Subject: [PATCH 192/398] bug/chat input issue --- packages/ui/src/views/chatmessage/ChatMessage.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.css b/packages/ui/src/views/chatmessage/ChatMessage.css index ef7f1e93..3b006c1d 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.css +++ b/packages/ui/src/views/chatmessage/ChatMessage.css @@ -127,7 +127,8 @@ .cloud-dialog { width: 100%; - height: calc(100vh - 230px); + height: 100vh; + overflow-y: scroll; border-radius: 0.5rem; display: flex; justify-content: center; From 6f77461921605bf011e30dbc5460714fbbc1ac93 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 8 Jul 2023 11:36:31 +0100 Subject: [PATCH 193/398] update error message --- .../components/nodes/documentloaders/Playwright/Playwright.ts | 2 +- .../components/nodes/documentloaders/Puppeteer/Puppeteer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts index 2301b4e9..273536ae 100644 --- a/packages/components/nodes/documentloaders/Playwright/Playwright.ts +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -94,7 +94,7 @@ class Playwright_DocumentLoaders implements INode { } return docs } catch (err) { - if (process.env.DEBUG === 'true') console.error(`error in CheerioWebBaseLoader: ${err.message}, on page: ${url}`) + if (process.env.DEBUG === 'true') console.error(`error in PlaywrightWebBaseLoader: ${err.message}, on page: ${url}`) } } diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index bf0920bb..86a9477d 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -94,7 +94,7 @@ class Puppeteer_DocumentLoaders implements INode { } return docs } catch (err) { - if (process.env.DEBUG === 'true') console.error(`error in CheerioWebBaseLoader: ${err.message}, on page: ${url}`) + if (process.env.DEBUG === 'true') console.error(`error in PuppeteerWebBaseLoader: ${err.message}, on page: ${url}`) } } From 41346594c60185711e2eb3048e2538b5c44f69e3 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Sat, 8 Jul 2023 19:01:11 +0800 Subject: [PATCH 194/398] copy paste chrome fix to root docker file --- Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Dockerfile b/Dockerfile index fe01ed8d..e485cd3e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,12 @@ FROM node:18-alpine RUN apk add --update libc6-compat python3 make g++ # needed for pdfjs-dist RUN apk add --no-cache build-base cairo-dev pango-dev + +# Install Chromium +RUN apk add --no-cache chromium + ENV PUPPETEER_SKIP_DOWNLOAD=true +ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser WORKDIR /usr/src/packages From 0d46c0226e6987d34b407a2d695b19e455e01190 Mon Sep 17 00:00:00 2001 From: vjsai Date: Sat, 8 Jul 2023 18:01:51 +0530 Subject: [PATCH 195/398] fixed header parsing issue --- packages/components/nodes/chains/ApiChain/OpenAPIChain.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts index b5970bb8..2e54d237 100644 --- a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -60,7 +60,7 @@ class OpenApiChain_Chains implements INode { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as ChatOpenAI - const headers = nodeData.inputs?.headers as Record + const headers = nodeData.inputs?.headers as string const yamlFileBase64 = nodeData.inputs?.yamlFile as string const splitDataURI = yamlFileBase64.split(',') splitDataURI.pop() @@ -68,7 +68,7 @@ class OpenApiChain_Chains implements INode { const utf8String = bf.toString('utf-8') const chain = await createOpenAPIChain(utf8String, { llm: model, - headers + headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {} }) if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) From 9cda188c73124901467b58108f335e3562e09b07 Mon Sep 17 00:00:00 2001 From: vjsai Date: Sat, 8 Jul 2023 18:45:18 +0530 Subject: [PATCH 196/398] fixed the issue in init as well --- packages/components/nodes/chains/ApiChain/OpenAPIChain.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts index 2e54d237..a7e8441a 100644 --- a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -45,7 +45,7 @@ class OpenApiChain_Chains implements INode { async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as ChatOpenAI - const headers = nodeData.inputs?.headers as Record + const headers = nodeData.inputs?.headers as string const yamlFileBase64 = nodeData.inputs?.yamlFile as string const splitDataURI = yamlFileBase64.split(',') splitDataURI.pop() @@ -53,7 +53,7 @@ class OpenApiChain_Chains implements INode { const utf8String = bf.toString('utf-8') const chain = await createOpenAPIChain(utf8String, { llm: model, - headers + headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {} }) return chain } From 3cda348e95762a09dbc0abc89b92eae86c57a9dc Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 8 Jul 2023 21:31:43 +0100 Subject: [PATCH 197/398] add fix to HF endpoint --- .../nodes/chatmodels/ChatHuggingFace/core.ts | 12 ++++++++---- .../embeddings/HuggingFaceInferenceEmbedding/core.ts | 11 +++++------ .../nodes/llms/HuggingFaceInference/core.ts | 12 ++++++++---- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts b/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts index 958e9072..416567f0 100644 --- a/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts @@ -78,9 +78,7 @@ export class HuggingFaceInference extends LLM implements HFInput { async _call(prompt: string, options: this['ParsedCallOptions']): Promise { const { HfInference } = await HuggingFaceInference.imports() const hf = new HfInference(this.apiKey) - if (this.endpoint) hf.endpoint(this.endpoint) - const res = await this.caller.callWithOptions({ signal: options.signal }, hf.textGeneration.bind(hf), { - model: this.model, + const obj: any = { parameters: { // make it behave similar to openai, returning only the generated text return_full_text: false, @@ -91,7 +89,13 @@ export class HuggingFaceInference extends LLM implements HFInput { repetition_penalty: this.frequencyPenalty }, inputs: prompt - }) + } + if (this.endpoint) { + hf.endpoint(this.endpoint) + } else { + obj.model = this.model + } + const res = await this.caller.callWithOptions({ signal: options.signal }, hf.textGeneration.bind(hf), obj) return res.generated_text } diff --git a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts index b8d89ebe..41e63aa4 100644 --- a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts +++ b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts @@ -30,12 +30,11 @@ export class HuggingFaceInferenceEmbeddings extends Embeddings implements Huggin async _embed(texts: string[]): Promise { // replace newlines, which can negatively affect performance. const clean = texts.map((text) => text.replace(/\n/g, ' ')) - return this.caller.call(() => - this.client.featureExtraction({ - model: this.model, - inputs: clean - }) - ) as Promise + const obj: any = { + inputs: clean + } + if (!this.endpoint) obj.model = this.model + return this.caller.call(() => this.client.featureExtraction(obj)) as Promise } embedQuery(document: string): Promise { diff --git a/packages/components/nodes/llms/HuggingFaceInference/core.ts b/packages/components/nodes/llms/HuggingFaceInference/core.ts index 958e9072..416567f0 100644 --- a/packages/components/nodes/llms/HuggingFaceInference/core.ts +++ b/packages/components/nodes/llms/HuggingFaceInference/core.ts @@ -78,9 +78,7 @@ export class HuggingFaceInference extends LLM implements HFInput { async _call(prompt: string, options: this['ParsedCallOptions']): Promise { const { HfInference } = await HuggingFaceInference.imports() const hf = new HfInference(this.apiKey) - if (this.endpoint) hf.endpoint(this.endpoint) - const res = await this.caller.callWithOptions({ signal: options.signal }, hf.textGeneration.bind(hf), { - model: this.model, + const obj: any = { parameters: { // make it behave similar to openai, returning only the generated text return_full_text: false, @@ -91,7 +89,13 @@ export class HuggingFaceInference extends LLM implements HFInput { repetition_penalty: this.frequencyPenalty }, inputs: prompt - }) + } + if (this.endpoint) { + hf.endpoint(this.endpoint) + } else { + obj.model = this.model + } + const res = await this.caller.callWithOptions({ signal: options.signal }, hf.textGeneration.bind(hf), obj) return res.generated_text } From 45792665df1e8971bbfa89f2fb1558c350af9bbb Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sat, 8 Jul 2023 22:34:46 +0100 Subject: [PATCH 198/398] Update OpenAPIChain.ts --- .../nodes/chains/ApiChain/OpenAPIChain.ts | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts index a7e8441a..ae1ae3c0 100644 --- a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -27,11 +27,19 @@ class OpenApiChain_Chains implements INode { name: 'model', type: 'ChatOpenAI' }, + { + label: 'YAML Link', + name: 'yamlLink', + type: 'string', + placeholder: 'https://api.speak.com/openapi.yaml', + description: 'If YAML link is provided, uploaded YAML File will be ignored and YAML link will be used instead' + }, { label: 'YAML File', name: 'yamlFile', type: 'file', - fileType: '.yaml' + fileType: '.yaml', + description: 'If YAML link is provided, uploaded YAML File will be ignored and YAML link will be used instead' }, { label: 'Headers', @@ -44,34 +52,13 @@ class OpenApiChain_Chains implements INode { } async init(nodeData: INodeData): Promise { - const model = nodeData.inputs?.model as ChatOpenAI - const headers = nodeData.inputs?.headers as string - const yamlFileBase64 = nodeData.inputs?.yamlFile as string - const splitDataURI = yamlFileBase64.split(',') - splitDataURI.pop() - const bf = Buffer.from(splitDataURI.pop() || '', 'base64') - const utf8String = bf.toString('utf-8') - const chain = await createOpenAPIChain(utf8String, { - llm: model, - headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {} - }) - return chain + return await initChain(nodeData) } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const model = nodeData.inputs?.model as ChatOpenAI - const headers = nodeData.inputs?.headers as string - const yamlFileBase64 = nodeData.inputs?.yamlFile as string - const splitDataURI = yamlFileBase64.split(',') - splitDataURI.pop() - const bf = Buffer.from(splitDataURI.pop() || '', 'base64') - const utf8String = bf.toString('utf-8') - const chain = await createOpenAPIChain(utf8String, { - llm: model, - headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {} - }) + const chain = await initChain(nodeData) if (options.socketIO && options.socketIOClientId) { - const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) const res = await chain.run(input, [handler]) return res } else { @@ -81,4 +68,28 @@ class OpenApiChain_Chains implements INode { } } +const initChain = async (nodeData: INodeData) => { + const model = nodeData.inputs?.model as ChatOpenAI + const headers = nodeData.inputs?.headers as string + const yamlLink = nodeData.inputs?.yamlLink as string + const yamlFileBase64 = nodeData.inputs?.yamlFile as string + + let yamlString = '' + + if (yamlLink) { + yamlString = yamlLink + } else { + const splitDataURI = yamlFileBase64.split(',') + splitDataURI.pop() + const bf = Buffer.from(splitDataURI.pop() || '', 'base64') + yamlString = bf.toString('utf-8') + } + + return await createOpenAPIChain(yamlString, { + llm: model, + headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {}, + verbose: process.env.DEBUG === 'true' ? true : false + }) +} + module.exports = { nodeClass: OpenApiChain_Chains } From 83a62011cbe2f2e552c5dd3725764d55957ef20f Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sat, 8 Jul 2023 22:35:25 +0100 Subject: [PATCH 199/398] Update package.json --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index cca3e47a..775d6e94 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -35,7 +35,7 @@ "form-data": "^4.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "^0.0.103", + "langchain": "^0.0.104", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", From e66c07d336dcaac9695862e8eb946b40e72ad044 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sat, 8 Jul 2023 22:38:56 +0100 Subject: [PATCH 200/398] Update utils isFlowValidForStream Some chains/agents streaming are not working atm, disabling streaming for those chains/agents --- packages/server/src/utils/index.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 005f4a4b..d32faa8b 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -680,10 +680,16 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod } } - return ( - isChatOrLLMsExist && - (endingNodeData.category === 'Chains' || endingNodeData.name === 'openAIFunctionAgent') && - !isVectorStoreFaiss(endingNodeData) && - process.env.EXECUTION_MODE !== 'child' - ) + let isValidChainOrAgent = false + if (endingNodeData.category === 'Chains') { + // Chains that are not available to stream + const blacklistChains = ['openApiChain'] + isValidChainOrAgent = !blacklistChains.includes(endingNodeData.name) + } else if (endingNodeData.category === 'Agents') { + // Agent that are available to stream + const whitelistAgents = ['openAIFunctionAgent'] + isValidChainOrAgent = whitelistAgents.includes(endingNodeData.name) + } + + return isChatOrLLMsExist && isValidChainOrAgent && !isVectorStoreFaiss(endingNodeData) && process.env.EXECUTION_MODE !== 'child' } From 21552776d6f939593f4d8956fe723f5bf2c26d85 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 8 Jul 2023 23:17:01 +0100 Subject: [PATCH 201/398] update HF marketplace template --- .../marketplaces/chatflows/HuggingFace LLM Chain.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json b/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json index d46f9d64..63a04b03 100644 --- a/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json +++ b/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json @@ -156,6 +156,16 @@ "optional": true, "additionalParams": true, "id": "huggingFaceInference_LLMs_0-input-frequencyPenalty-number" + }, + { + "label": "Endpoint", + "name": "endpoint", + "type": "string", + "placeholder": "https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2", + "description": "Using your own inference endpoint", + "optional": true, + "additionalParams": true, + "id": "huggingFaceInference_LLMs_0-input-endpoint-string" } ], "inputAnchors": [], From f83f1c64d3009f0b22cadd63592ac32447eacc2d Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sat, 8 Jul 2023 23:26:28 +0100 Subject: [PATCH 202/398] allow logger by default --- packages/server/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 280d870b..38917023 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -61,7 +61,7 @@ export class App { this.app = express() // Add the expressRequestLogger middleware to log all requests - if (process.env.DEBUG === 'true') this.app.use(expressRequestLogger) + this.app.use(expressRequestLogger) } async initDatabase() { From d53522a0a800c11f209379210b1a27b9569dcd59 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Sun, 9 Jul 2023 10:32:05 +0800 Subject: [PATCH 203/398] add description and modify default limit to 10 if empty --- .../nodes/documentloaders/Cheerio/Cheerio.ts | 17 ++++++++++------- .../documentloaders/Playwright/Playwright.ts | 17 ++++++++++------- .../documentloaders/Puppeteer/Puppeteer.ts | 17 ++++++++++------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts index 2521b039..b93a8685 100644 --- a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts +++ b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts @@ -38,28 +38,31 @@ class Cheerio_DocumentLoaders implements INode { label: 'Get Relative Links Method', name: 'relativeLinksMethod', type: 'options', + description: 'Select a method to retrieve relative links', options: [ { label: 'Web Crawl', - name: 'webCrawl' + name: 'webCrawl', + description: 'Crawl relative links from HTML URL' }, { label: 'Scrape XML Sitemap', - name: 'scrapeXMLSitemap' + name: 'scrapeXMLSitemap', + description: 'Scrape relative links from XML sitemap URL' } ], optional: true, additionalParams: true }, { - label: 'Crawl/Scrape Links Limit', + label: 'Get Relative Links Limit', name: 'limit', type: 'number', - default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl/scrape all relative links', - warning: `Scraping all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc) ` + description: + 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', + warning: `Retreiving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` }, { label: 'Metadata', @@ -101,7 +104,7 @@ class Cheerio_DocumentLoaders implements INode { let docs = [] if (relativeLinksMethod) { if (process.env.DEBUG === 'true') console.info(`Start ${relativeLinksMethod}`) - if (!limit) throw new Error('Please set a limit to crawl/scrape') + if (!limit) limit = '10' else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') const pages: string[] = relativeLinksMethod === 'webCrawl' ? await webCrawl(url, parseInt(limit)) : await xmlScrape(url, parseInt(limit)) diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts index 273536ae..73a3e290 100644 --- a/packages/components/nodes/documentloaders/Playwright/Playwright.ts +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -38,28 +38,31 @@ class Playwright_DocumentLoaders implements INode { label: 'Get Relative Links Method', name: 'relativeLinksMethod', type: 'options', + description: 'Select a method to retrieve relative links', options: [ { label: 'Web Crawl', - name: 'webCrawl' + name: 'webCrawl', + description: 'Crawl relative links from HTML URL' }, { label: 'Scrape XML Sitemap', - name: 'scrapeXMLSitemap' + name: 'scrapeXMLSitemap', + description: 'Scrape relative links from XML sitemap URL' } ], optional: true, additionalParams: true }, { - label: 'Crawl/Scrape Links Limit', + label: 'Get Relative Links Limit', name: 'limit', type: 'number', - default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl/scrape all relative links', - warning: `Scraping all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc) ` + description: + 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', + warning: `Retreiving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` }, { label: 'Metadata', @@ -101,7 +104,7 @@ class Playwright_DocumentLoaders implements INode { let docs = [] if (relativeLinksMethod) { if (process.env.DEBUG === 'true') console.info(`Start ${relativeLinksMethod}`) - if (!limit) throw new Error('Please set a limit to crawl/scrape') + if (!limit) limit = '10' else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') const pages: string[] = relativeLinksMethod === 'webCrawl' ? await webCrawl(url, parseInt(limit)) : await xmlScrape(url, parseInt(limit)) diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index 618d110b..014845d2 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -38,28 +38,31 @@ class Puppeteer_DocumentLoaders implements INode { label: 'Get Relative Links Method', name: 'relativeLinksMethod', type: 'options', + description: 'Select a method to retrieve relative links', options: [ { label: 'Web Crawl', - name: 'webCrawl' + name: 'webCrawl', + description: 'Crawl relative links from HTML URL' }, { label: 'Scrape XML Sitemap', - name: 'scrapeXMLSitemap' + name: 'scrapeXMLSitemap', + description: 'Scrape relative links from XML sitemap URL' } ], optional: true, additionalParams: true }, { - label: 'Crawl/Scrape Links Limit', + label: 'Get Relative Links Limit', name: 'limit', type: 'number', - default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl/scrape all relative links', - warning: `Scraping all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc) ` + description: + 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', + warning: `Retreiving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` }, { label: 'Metadata', @@ -106,7 +109,7 @@ class Puppeteer_DocumentLoaders implements INode { let docs = [] if (relativeLinksMethod) { if (process.env.DEBUG === 'true') console.info(`Start ${relativeLinksMethod}`) - if (!limit) throw new Error('Please set a limit to crawl/scrape') + if (!limit) limit = '10' else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') const pages: string[] = relativeLinksMethod === 'webCrawl' ? await webCrawl(url, parseInt(limit)) : await xmlScrape(url, parseInt(limit)) From f13c16569ffc35fa2a48df1cd23a8972114f152b Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Sun, 9 Jul 2023 18:18:04 +0800 Subject: [PATCH 204/398] modify options api config --- packages/server/src/utils/index.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index d32faa8b..9e8c5c32 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -632,7 +632,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => { for (const flowNode of reactFlowNodes) { for (const inputParam of flowNode.data.inputParams) { let obj: IOverrideConfig - if (inputParam.type === 'password' || inputParam.type === 'options') { + if (inputParam.type === 'password') { continue } else if (inputParam.type === 'file') { obj = { @@ -641,6 +641,19 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => { name: 'files', type: inputParam.fileType ?? inputParam.type } + } else if (inputParam.type === 'options') { + obj = { + node: flowNode.data.label, + label: inputParam.label, + name: inputParam.name, + type: inputParam.options + ? inputParam.options + ?.map((option) => { + return option.name + }) + .join(', ') + : 'string' + } } else { obj = { node: flowNode.data.label, From fb28f61f8b966163c6d8d653a0e270a894e04723 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 9 Jul 2023 16:11:42 +0100 Subject: [PATCH 205/398] add memory option --- .../ConversationalRetrievalQAChain.ts | 49 ++++++++++++++++--- .../nodes/memory/DynamoDb/DynamoDb.ts | 5 +- packages/ui/src/utils/genericHelper.js | 6 ++- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 3b7e1413..4872717f 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -2,7 +2,7 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { ConversationalRetrievalQAChain } from 'langchain/chains' -import { AIChatMessage, BaseRetriever, HumanChatMessage } from 'langchain/schema' +import { AIMessage, BaseRetriever, HumanMessage } from 'langchain/schema' import { BaseChatMemory, BufferMemory, ChatMessageHistory } from 'langchain/memory' import { PromptTemplate } from 'langchain/prompts' @@ -20,6 +20,20 @@ const qa_template = `Use the following pieces of context to answer the question Question: {question} Helpful Answer:` +const CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT = `Given the following conversation and a follow up question, return the conversation history excerpt that includes any relevant context to the question if it exists and rephrase the follow up question to be a standalone question. +Chat History: +{chat_history} +Follow Up Input: {question} +Your answer should follow the following format: +\`\`\` +Use the following pieces of context to answer the users question. +If you don't know the answer, just say that you don't know, don't try to make up an answer. +---------------- + +Standalone question: +\`\`\` +Your answer:` + class ConversationalRetrievalQAChain_Chains implements INode { label: string name: string @@ -49,6 +63,13 @@ class ConversationalRetrievalQAChain_Chains implements INode { name: 'vectorStoreRetriever', type: 'BaseRetriever' }, + { + label: 'Memory', + name: 'memory', + type: 'DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory', + optional: true, + description: 'If no memory connected, default BufferMemory will be used' + }, { label: 'Return Source Documents', name: 'returnSourceDocuments', @@ -99,6 +120,7 @@ class ConversationalRetrievalQAChain_Chains implements INode { const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean const chainOption = nodeData.inputs?.chainOption as string + const memory = nodeData.inputs?.memory const obj: any = { verbose: process.env.DEBUG === 'true' ? true : false, @@ -106,15 +128,25 @@ class ConversationalRetrievalQAChain_Chains implements INode { type: 'stuff', prompt: PromptTemplate.fromTemplate(systemMessagePrompt ? `${systemMessagePrompt}\n${qa_template}` : default_qa_template) }, - memory: new BufferMemory({ + questionGeneratorChainOptions: { + template: CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT + } + } + if (returnSourceDocuments) obj.returnSourceDocuments = returnSourceDocuments + if (chainOption) obj.qaChainOptions = { ...obj.qaChainOptions, type: chainOption } + if (memory) { + memory.inputKey = 'question' + memory.outputKey = 'text' + memory.memoryKey = 'chat_history' + obj.memory = memory + } else { + obj.memory = new BufferMemory({ memoryKey: 'chat_history', inputKey: 'question', outputKey: 'text', returnMessages: true }) } - if (returnSourceDocuments) obj.returnSourceDocuments = returnSourceDocuments - if (chainOption) obj.qaChainOptions = { ...obj.qaChainOptions, type: chainOption } const chain = ConversationalRetrievalQAChain.fromLLM(model, vectorStoreRetriever, obj) return chain @@ -123,6 +155,8 @@ class ConversationalRetrievalQAChain_Chains implements INode { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = nodeData.instance as ConversationalRetrievalQAChain const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean + const memory = nodeData.inputs?.memory + let model = nodeData.inputs?.model // Temporary fix: https://github.com/hwchase17/langchainjs/issues/754 @@ -131,16 +165,17 @@ class ConversationalRetrievalQAChain_Chains implements INode { const obj = { question: input } - if (chain.memory && options && options.chatHistory) { + // If external memory like Zep, Redis is being used, ignore below + if (!memory && chain.memory && options && options.chatHistory) { const chatHistory = [] const histories: IMessage[] = options.chatHistory const memory = chain.memory as BaseChatMemory for (const message of histories) { if (message.type === 'apiMessage') { - chatHistory.push(new AIChatMessage(message.message)) + chatHistory.push(new AIMessage(message.message)) } else if (message.type === 'userMessage') { - chatHistory.push(new HumanChatMessage(message.message)) + chatHistory.push(new HumanMessage(message.message)) } } memory.chatHistory = new ChatMessageHistory(chatHistory) diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index b1368044..49d15cb6 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -13,8 +13,9 @@ class DynamoDb_Memory implements INode { inputs: INodeParams[] constructor() { - this.label = 'DynamoDB Memory' - this.name = 'DynamoDbMemory' + this.label = 'DynamoDB Chat Memory' + this.name = 'DynamoDBChatMemory' + this.type = 'DynamoDBChatMemory' this.icon = 'dynamodb.svg' this.category = 'Memory' this.description = 'Stores the conversation in dynamo db table' diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index 42a63057..305326f7 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -168,8 +168,10 @@ export const isValidConnection = (connection, reactFlowInstance) => { //sourceHandle: "llmChain_0-output-llmChain-BaseChain" //targetHandle: "mrlkAgentLLM_0-input-model-BaseLanguageModel" - const sourceTypes = sourceHandle.split('-')[sourceHandle.split('-').length - 1].split('|') - const targetTypes = targetHandle.split('-')[targetHandle.split('-').length - 1].split('|') + let sourceTypes = sourceHandle.split('-')[sourceHandle.split('-').length - 1].split('|') + sourceTypes = sourceTypes.map((s) => s.trim()) + let targetTypes = targetHandle.split('-')[targetHandle.split('-').length - 1].split('|') + targetTypes = targetTypes.map((t) => t.trim()) if (targetTypes.some((t) => sourceTypes.includes(t))) { let targetNode = reactFlowInstance.getNode(target) From 050a972da818bfcbf3b380ce45c9742fbbdf0968 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 9 Jul 2023 16:16:13 +0100 Subject: [PATCH 206/398] update marketplace --- .../chatflows/Conversational Retrieval QA Chain.json | 8 ++++++++ .../server/marketplaces/chatflows/Github Repo QnA.json | 8 ++++++++ packages/server/marketplaces/chatflows/Local QnA.json | 8 ++++++++ .../marketplaces/chatflows/Metadata Filter Load.json | 8 ++++++++ .../marketplaces/chatflows/Metadata Filter Upsert.json | 8 ++++++++ 5 files changed, 40 insertions(+) diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json index ed190cdc..e420fefe 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json @@ -539,6 +539,14 @@ "name": "vectorStoreRetriever", "type": "BaseRetriever", "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + }, + { + "label": "Memory", + "name": "memory", + "type": "DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory", + "optional": true, + "description": "If no memory connected, default BufferMemory will be used", + "id": "conversationalRetrievalQAChain_0-input-memory-DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory" } ], "inputs": { diff --git a/packages/server/marketplaces/chatflows/Github Repo QnA.json b/packages/server/marketplaces/chatflows/Github Repo QnA.json index 92867957..92ad0967 100644 --- a/packages/server/marketplaces/chatflows/Github Repo QnA.json +++ b/packages/server/marketplaces/chatflows/Github Repo QnA.json @@ -556,6 +556,14 @@ "name": "vectorStoreRetriever", "type": "BaseRetriever", "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + }, + { + "label": "Memory", + "name": "memory", + "type": "DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory", + "optional": true, + "description": "If no memory connected, default BufferMemory will be used", + "id": "conversationalRetrievalQAChain_0-input-memory-DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory" } ], "inputs": { diff --git a/packages/server/marketplaces/chatflows/Local QnA.json b/packages/server/marketplaces/chatflows/Local QnA.json index 9cfba954..5265c428 100644 --- a/packages/server/marketplaces/chatflows/Local QnA.json +++ b/packages/server/marketplaces/chatflows/Local QnA.json @@ -131,6 +131,14 @@ "name": "vectorStoreRetriever", "type": "BaseRetriever", "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + }, + { + "label": "Memory", + "name": "memory", + "type": "DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory", + "optional": true, + "description": "If no memory connected, default BufferMemory will be used", + "id": "conversationalRetrievalQAChain_0-input-memory-DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory" } ], "inputs": { diff --git a/packages/server/marketplaces/chatflows/Metadata Filter Load.json b/packages/server/marketplaces/chatflows/Metadata Filter Load.json index dfc6d6fb..61dad1c4 100644 --- a/packages/server/marketplaces/chatflows/Metadata Filter Load.json +++ b/packages/server/marketplaces/chatflows/Metadata Filter Load.json @@ -421,6 +421,14 @@ "name": "vectorStoreRetriever", "type": "BaseRetriever", "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + }, + { + "label": "Memory", + "name": "memory", + "type": "DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory", + "optional": true, + "description": "If no memory connected, default BufferMemory will be used", + "id": "conversationalRetrievalQAChain_0-input-memory-DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory" } ], "inputs": { diff --git a/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json b/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json index 87336654..f2273825 100644 --- a/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json +++ b/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json @@ -625,6 +625,14 @@ "name": "vectorStoreRetriever", "type": "BaseRetriever", "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + }, + { + "label": "Memory", + "name": "memory", + "type": "DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory", + "optional": true, + "description": "If no memory connected, default BufferMemory will be used", + "id": "conversationalRetrievalQAChain_0-input-memory-DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory" } ], "inputs": { From aeb143a04e95ec18928d83cf89e2ed5dd1a813f7 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 9 Jul 2023 16:30:19 +0100 Subject: [PATCH 207/398] update zep icon --- .../nodes/memory/ZepMemory/ZepMemory.ts | 2 +- .../nodes/memory/ZepMemory/memory.svg | 8 -------- .../components/nodes/memory/ZepMemory/zep.png | Bin 0 -> 17169 bytes 3 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 packages/components/nodes/memory/ZepMemory/memory.svg create mode 100644 packages/components/nodes/memory/ZepMemory/zep.png diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 2e7ba001..23691e30 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -18,7 +18,7 @@ class ZepMemory_Memory implements INode { this.label = 'Zep Memory' this.name = 'ZepMemory' this.type = 'ZepMemory' - this.icon = 'memory.svg' + this.icon = 'zep.png' this.category = 'Memory' this.description = 'Summarizes the conversation and stores the memory in zep server' this.baseClasses = [this.type, ...getBaseClasses(ZepMemory)] diff --git a/packages/components/nodes/memory/ZepMemory/memory.svg b/packages/components/nodes/memory/ZepMemory/memory.svg deleted file mode 100644 index ca8e17da..00000000 --- a/packages/components/nodes/memory/ZepMemory/memory.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/packages/components/nodes/memory/ZepMemory/zep.png b/packages/components/nodes/memory/ZepMemory/zep.png new file mode 100644 index 0000000000000000000000000000000000000000..293be6f6de9a238631d52059b16c90aa8e395fdb GIT binary patch literal 17169 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 Y|Gacjls{jx3;+NC07*qoM6N<$f`z|uM*si- literal 0 HcmV?d00001 From 46a1e43dc3438b51e142f7bcd5557c6493ac4fea Mon Sep 17 00:00:00 2001 From: vjsai Date: Mon, 10 Jul 2023 03:34:42 +0530 Subject: [PATCH 208/398] Added support for OpenSearch --- .../OpenSearch_existing.ts | 95 +++++++++++++++ .../OpenSearch_Existing/opensearch.png | Bin 0 -> 5216 bytes .../OpenSearch_Upsert/OpenSearch_Upsert.ts | 110 ++++++++++++++++++ .../OpenSearch_Upsert/opensearch.png | Bin 0 -> 5216 bytes packages/components/package.json | 1 + 5 files changed, 206 insertions(+) create mode 100644 packages/components/nodes/vectorstores/OpenSearch_Existing/OpenSearch_existing.ts create mode 100644 packages/components/nodes/vectorstores/OpenSearch_Existing/opensearch.png create mode 100644 packages/components/nodes/vectorstores/OpenSearch_Upsert/OpenSearch_Upsert.ts create mode 100644 packages/components/nodes/vectorstores/OpenSearch_Upsert/opensearch.png diff --git a/packages/components/nodes/vectorstores/OpenSearch_Existing/OpenSearch_existing.ts b/packages/components/nodes/vectorstores/OpenSearch_Existing/OpenSearch_existing.ts new file mode 100644 index 00000000..7aeac919 --- /dev/null +++ b/packages/components/nodes/vectorstores/OpenSearch_Existing/OpenSearch_existing.ts @@ -0,0 +1,95 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { OpenSearchVectorStore } from 'langchain/vectorstores/opensearch' +import { Embeddings } from 'langchain/embeddings/base' +import { Client } from '@opensearch-project/opensearch' +import { getBaseClasses } from '../../../src/utils' + +class OpenSearch_Existing_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'OpenSearch Load Existing Index' + this.name = 'openSearchExistingIndex' + this.type = 'OpenSearch' + this.icon = 'opensearch.png' + this.category = 'Vector Stores' + this.description = 'Load existing index from OpenSearch (i.e: Document has been upserted)' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'OpenSearch URL', + name: 'opensearchURL', + type: 'string', + placeholder: 'http://127.0.0.1:9200' + }, + { + label: 'Index Name', + name: 'indexName', + type: 'string' + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'OpenSearch Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'OpenSearch Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(OpenSearchVectorStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const embeddings = nodeData.inputs?.embeddings as Embeddings + const opensearchURL = nodeData.inputs?.opensearchURL as string + const indexName = nodeData.inputs?.indexName as string + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 + + const client = new Client({ + nodes: [opensearchURL] + }) + + const vectorStore = new OpenSearchVectorStore(embeddings, { + client, + indexName + }) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: OpenSearch_Existing_VectorStores } diff --git a/packages/components/nodes/vectorstores/OpenSearch_Existing/opensearch.png b/packages/components/nodes/vectorstores/OpenSearch_Existing/opensearch.png new file mode 100644 index 0000000000000000000000000000000000000000..3fdcfd3f09ed02c1478f690884ecafe844cb32ae GIT binary patch literal 5216 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 { + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const opensearchURL = nodeData.inputs?.opensearchURL as string + const indexName = nodeData.inputs?.indexName as string + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + finalDocs.push(new Document(flattenDocs[i])) + } + + const client = new Client({ + nodes: [opensearchURL] + }) + + const vectorStore = await OpenSearchVectorStore.fromDocuments(finalDocs, embeddings, { + client, + indexName: indexName + }) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: OpenSearchUpsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/OpenSearch_Upsert/opensearch.png b/packages/components/nodes/vectorstores/OpenSearch_Upsert/opensearch.png new file mode 100644 index 0000000000000000000000000000000000000000..3fdcfd3f09ed02c1478f690884ecafe844cb32ae GIT binary patch literal 5216 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 Date: Mon, 10 Jul 2023 00:36:52 -0700 Subject: [PATCH 209/398] fix lint error and update apikey and clientid attributes --- .../nodes/memory/MotorheadMemory/MotorheadMemory.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 383ad613..d50b7064 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -48,12 +48,16 @@ class MotorMemory_Memory implements INode { label: 'API Key', name: 'apiKey', type: 'string', + description: 'Only needed when using hosted solution - https://getmetal.io', + additionalParams: true, optional: true }, { label: 'Client ID', name: 'clientId', type: 'string', + description: 'Only needed when using hosted solution - https://getmetal.io', + additionalParams: true, optional: true } ] @@ -68,8 +72,6 @@ class MotorMemory_Memory implements INode { const chatId = options?.chatId as string - console.log(chatId) - let obj: MotorheadMemoryInput = { returnMessages: true, sessionId: sessionId ? sessionId : chatId, From 9dd19178ff30ababe681cf4e200f8a981f370d27 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Mon, 10 Jul 2023 21:19:22 +0800 Subject: [PATCH 210/398] modify password api config --- packages/server/src/utils/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 9e8c5c32..9bcf0f71 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -632,9 +632,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => { for (const flowNode of reactFlowNodes) { for (const inputParam of flowNode.data.inputParams) { let obj: IOverrideConfig - if (inputParam.type === 'password') { - continue - } else if (inputParam.type === 'file') { + if (inputParam.type === 'file') { obj = { node: flowNode.data.label, label: inputParam.label, @@ -659,7 +657,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => { node: flowNode.data.label, label: inputParam.label, name: inputParam.name, - type: inputParam.type + type: inputParam.type === 'password' ? 'string' : inputParam.type } } if (!configs.some((config) => JSON.stringify(config) === JSON.stringify(obj))) { From cf3bd72a982dea17b641f380c496d5ee563ed732 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 10 Jul 2023 16:51:36 +0100 Subject: [PATCH 211/398] update message schema --- .../ConversationalAgent/ConversationalAgent.ts | 6 +++--- .../OpenAIFunctionAgent/OpenAIFunctionAgent.ts | 6 +++--- .../ConversationChain/ConversationChain.ts | 6 +++--- .../ConversationalRetrievalQAChain.ts | 17 +++++------------ .../nodes/memory/ZepMemory/ZepMemory.ts | 4 ++-- packages/server/src/index.ts | 8 ++++---- 6 files changed, 20 insertions(+), 27 deletions(-) diff --git a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts index 363b3907..568ced0b 100644 --- a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts +++ b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts @@ -3,7 +3,7 @@ import { initializeAgentExecutorWithOptions, AgentExecutor, InitializeAgentExecu import { Tool } from 'langchain/tools' import { BaseChatMemory, ChatMessageHistory } from 'langchain/memory' import { getBaseClasses } from '../../../src/utils' -import { AIChatMessage, HumanChatMessage } from 'langchain/schema' +import { AIMessage, HumanMessage } from 'langchain/schema' import { BaseLanguageModel } from 'langchain/base_language' import { flatten } from 'lodash' @@ -99,9 +99,9 @@ class ConversationalAgent_Agents implements INode { for (const message of histories) { if (message.type === 'apiMessage') { - chatHistory.push(new AIChatMessage(message.message)) + chatHistory.push(new AIMessage(message.message)) } else if (message.type === 'userMessage') { - chatHistory.push(new HumanChatMessage(message.message)) + chatHistory.push(new HumanMessage(message.message)) } } memory.chatHistory = new ChatMessageHistory(chatHistory) diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index 1cbcb547..43de74cb 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -4,7 +4,7 @@ import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { flatten } from 'lodash' import { BaseChatMemory, ChatMessageHistory } from 'langchain/memory' -import { AIChatMessage, HumanChatMessage } from 'langchain/schema' +import { AIMessage, HumanMessage } from 'langchain/schema' class OpenAIFunctionAgent_Agents implements INode { label: string @@ -84,9 +84,9 @@ class OpenAIFunctionAgent_Agents implements INode { for (const message of histories) { if (message.type === 'apiMessage') { - chatHistory.push(new AIChatMessage(message.message)) + chatHistory.push(new AIMessage(message.message)) } else if (message.type === 'userMessage') { - chatHistory.push(new HumanChatMessage(message.message)) + chatHistory.push(new HumanMessage(message.message)) } } memory.chatHistory = new ChatMessageHistory(chatHistory) diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index 843e05fc..27af5b8e 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -4,7 +4,7 @@ import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts' import { BufferMemory, ChatMessageHistory } from 'langchain/memory' import { BaseChatModel } from 'langchain/chat_models/base' -import { AIChatMessage, HumanChatMessage } from 'langchain/schema' +import { AIMessage, HumanMessage } from 'langchain/schema' const systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.` @@ -81,9 +81,9 @@ class ConversationChain_Chains implements INode { for (const message of histories) { if (message.type === 'apiMessage') { - chatHistory.push(new AIChatMessage(message.message)) + chatHistory.push(new AIMessage(message.message)) } else if (message.type === 'userMessage') { - chatHistory.push(new HumanChatMessage(message.message)) + chatHistory.push(new HumanMessage(message.message)) } } memory.chatHistory = new ChatMessageHistory(chatHistory) diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 4872717f..2dccd012 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -6,33 +6,26 @@ import { AIMessage, BaseRetriever, HumanMessage } from 'langchain/schema' import { BaseChatMemory, BufferMemory, ChatMessageHistory } from 'langchain/memory' import { PromptTemplate } from 'langchain/prompts' -const default_qa_template = `Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. +const default_qa_template = `Use the following pieces of context to answer the question at the end, in its original language. If you don't know the answer, just say that you don't know in its original language, don't try to make up an answer. {context} Question: {question} Helpful Answer:` -const qa_template = `Use the following pieces of context to answer the question at the end. +const qa_template = `Use the following pieces of context to answer the question at the end, in its original language. {context} Question: {question} Helpful Answer:` -const CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT = `Given the following conversation and a follow up question, return the conversation history excerpt that includes any relevant context to the question if it exists and rephrase the follow up question to be a standalone question. +const CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language. include it in the standalone question. + Chat History: {chat_history} Follow Up Input: {question} -Your answer should follow the following format: -\`\`\` -Use the following pieces of context to answer the users question. -If you don't know the answer, just say that you don't know, don't try to make up an answer. ----------------- - -Standalone question: -\`\`\` -Your answer:` +Standalone question:` class ConversationalRetrievalQAChain_Chains implements INode { label: string diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 23691e30..89d07b8f 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -1,4 +1,4 @@ -import { SystemChatMessage } from 'langchain/schema' +import { SystemMessage } from 'langchain/schema' import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' @@ -123,7 +123,7 @@ class ZepMemory_Memory implements INode { let summary = autoSummaryTemplate.replace(/{summary}/g, memory.summary.content) // eslint-disable-next-line no-console console.log('[ZepMemory] auto summary:', summary) - data[zep.memoryKey].unshift(new SystemChatMessage(summary)) + data[zep.memoryKey].unshift(new SystemMessage(summary)) } } // for langchain zep memory compatibility, or we will get "Missing value for input variable chat_history" diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index cd4978a0..ef9bc42d 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -687,13 +687,13 @@ export class App { } } - /* Don't rebuild the flow (to avoid duplicated upsert, recomputation) when all these conditions met: + /* Reuse the flow without having to rebuild (to avoid duplicated upsert, recomputation) when all these conditions met: * - Node Data already exists in pool * - Still in sync (i.e the flow has not been modified since) * - Existing overrideConfig and new overrideConfig are the same * - Flow doesn't start with nodes that depend on incomingInput.question ***/ - const isRebuildNeeded = () => { + const isFlowReusable = () => { return ( Object.prototype.hasOwnProperty.call(this.chatflowPool.activeChatflows, chatflowid) && this.chatflowPool.activeChatflows[chatflowid].inSync && @@ -707,7 +707,7 @@ export class App { } if (process.env.EXECUTION_MODE === 'child') { - if (isRebuildNeeded()) { + if (isFlowReusable()) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData try { const result = await this.startChildProcess(chatflow, chatId, incomingInput, nodeToExecuteData) @@ -731,7 +731,7 @@ export class App { const nodes = parsedFlowData.nodes const edges = parsedFlowData.edges - if (isRebuildNeeded()) { + if (isFlowReusable()) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData isStreamValid = isFlowValidForStream(nodes, nodeToExecuteData) } else { From eb19c206cfddc9d3f62e98312a6bee7641ed84ce Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 01:53:22 +0100 Subject: [PATCH 212/398] add logs to component chains/agents --- .../nodes/agents/AutoGPT/AutoGPT.ts | 1 - .../OpenAIFunctionAgent.ts | 9 +- .../nodes/chains/ApiChain/GETApiChain.ts | 9 +- .../nodes/chains/ApiChain/OpenAPIChain.ts | 9 +- .../nodes/chains/ApiChain/POSTApiChain.ts | 9 +- .../ConversationChain/ConversationChain.ts | 9 +- .../ConversationalRetrievalQAChain.ts | 9 +- .../nodes/chains/LLMChain/LLMChain.ts | 40 ++-- .../MultiPromptChain/MultiPromptChain.ts | 9 +- .../MultiRetrievalQAChain.ts | 8 +- .../RetrievalQAChain/RetrievalQAChain.ts | 8 +- .../SqlDatabaseChain/SqlDatabaseChain.ts | 9 +- .../chains/VectorDBQAChain/VectorDBQAChain.ts | 9 +- packages/components/src/handler.ts | 180 ++++++++++++++++++ packages/components/src/utils.ts | 48 +---- packages/server/.env.example | 3 +- packages/server/src/ChatflowPool.ts | 4 + packages/server/src/ChildProcess.ts | 176 +++++++++-------- packages/server/src/commands/start.ts | 2 + packages/server/src/index.ts | 36 +++- packages/server/src/utils/config.ts | 4 +- packages/server/src/utils/index.ts | 8 +- packages/server/src/utils/logger.ts | 14 +- 23 files changed, 414 insertions(+), 199 deletions(-) create mode 100644 packages/components/src/handler.ts diff --git a/packages/components/nodes/agents/AutoGPT/AutoGPT.ts b/packages/components/nodes/agents/AutoGPT/AutoGPT.ts index ca118500..044b6f7b 100644 --- a/packages/components/nodes/agents/AutoGPT/AutoGPT.ts +++ b/packages/components/nodes/agents/AutoGPT/AutoGPT.ts @@ -90,7 +90,6 @@ class AutoGPT_Agents implements INode { const res = await executor.run([input]) return res || 'I have completed all my tasks.' } catch (e) { - console.error(e) throw new Error(e) } } diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index 43de74cb..f4d065d9 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -1,10 +1,11 @@ import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { flatten } from 'lodash' import { BaseChatMemory, ChatMessageHistory } from 'langchain/memory' import { AIMessage, HumanMessage } from 'langchain/schema' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class OpenAIFunctionAgent_Agents implements INode { label: string @@ -93,12 +94,14 @@ class OpenAIFunctionAgent_Agents implements INode { executor.memory = memory } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const result = await executor.run(input, [handler]) + const result = await executor.run(input, [loggerHandler, handler]) return result } else { - const result = await executor.run(input) + const result = await executor.run(input, [loggerHandler]) return result } } diff --git a/packages/components/nodes/chains/ApiChain/GETApiChain.ts b/packages/components/nodes/chains/ApiChain/GETApiChain.ts index 8e657749..373d0462 100644 --- a/packages/components/nodes/chains/ApiChain/GETApiChain.ts +++ b/packages/components/nodes/chains/ApiChain/GETApiChain.ts @@ -1,8 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { APIChain } from 'langchain/chains' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { PromptTemplate } from 'langchain/prompts' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' export const API_URL_RAW_PROMPT_TEMPLATE = `You are given the below API Documentation: {api_docs} @@ -95,12 +96,14 @@ class GETApiChain_Chains implements INode { const ansPrompt = nodeData.inputs?.ansPrompt as string const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt) + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts index ae1ae3c0..a231e80a 100644 --- a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -1,7 +1,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { APIChain, createOpenAPIChain } from 'langchain/chains' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { ChatOpenAI } from 'langchain/chat_models/openai' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class OpenApiChain_Chains implements INode { label: string @@ -57,12 +58,14 @@ class OpenApiChain_Chains implements INode { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = await initChain(nodeData) + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/ApiChain/POSTApiChain.ts b/packages/components/nodes/chains/ApiChain/POSTApiChain.ts index 3c6ea677..7189f1ad 100644 --- a/packages/components/nodes/chains/ApiChain/POSTApiChain.ts +++ b/packages/components/nodes/chains/ApiChain/POSTApiChain.ts @@ -1,8 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { PromptTemplate } from 'langchain/prompts' import { API_RESPONSE_RAW_PROMPT_TEMPLATE, API_URL_RAW_PROMPT_TEMPLATE, APIChain } from './postCore' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class POSTApiChain_Chains implements INode { label: string @@ -84,12 +85,14 @@ class POSTApiChain_Chains implements INode { const ansPrompt = nodeData.inputs?.ansPrompt as string const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt) + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index 27af5b8e..f1df0183 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -1,10 +1,11 @@ import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { ConversationChain } from 'langchain/chains' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts' import { BufferMemory, ChatMessageHistory } from 'langchain/memory' import { BaseChatModel } from 'langchain/chat_models/base' import { AIMessage, HumanMessage } from 'langchain/schema' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' const systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.` @@ -90,12 +91,14 @@ class ConversationChain_Chains implements INode { chain.memory = memory } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.call({ input }, [handler]) + const res = await chain.call({ input }, [loggerHandler, handler]) return res?.response } else { - const res = await chain.call({ input }) + const res = await chain.call({ input }, [loggerHandler]) return res?.response } } diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 2dccd012..24b40d48 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -1,10 +1,11 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { ConversationalRetrievalQAChain } from 'langchain/chains' import { AIMessage, BaseRetriever, HumanMessage } from 'langchain/schema' import { BaseChatMemory, BufferMemory, ChatMessageHistory } from 'langchain/memory' import { PromptTemplate } from 'langchain/prompts' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' const default_qa_template = `Use the following pieces of context to answer the question at the end, in its original language. If you don't know the answer, just say that you don't know in its original language, don't try to make up an answer. @@ -175,13 +176,15 @@ class ConversationalRetrievalQAChain_Chains implements INode { chain.memory = memory } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, undefined, returnSourceDocuments) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) if (res.text && res.sourceDocuments) return res return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) if (res.text && res.sourceDocuments) return res return res?.text } diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index 67c21ce4..1d0ccb92 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -1,7 +1,8 @@ import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { LLMChain } from 'langchain/chains' import { BaseLanguageModel } from 'langchain/base_language' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class LLMChain_Chains implements INode { label: string @@ -55,7 +56,7 @@ class LLMChain_Chains implements INode { ] } - async init(nodeData: INodeData, input: string): Promise { + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as BaseLanguageModel const prompt = nodeData.inputs?.prompt const output = nodeData.outputs?.output as string @@ -67,7 +68,7 @@ class LLMChain_Chains implements INode { } else if (output === 'outputPrediction') { const chain = new LLMChain({ llm: model, prompt, verbose: process.env.DEBUG === 'true' ? true : false }) const inputVariables = chain.prompt.inputVariables as string[] // ["product"] - const res = await runPrediction(inputVariables, chain, input, promptValues) + const res = await runPrediction(inputVariables, chain, input, promptValues, options) // eslint-disable-next-line no-console console.log('\x1b[92m\x1b[1m\n*****OUTPUT PREDICTION*****\n\x1b[0m\x1b[0m') // eslint-disable-next-line no-console @@ -81,9 +82,7 @@ class LLMChain_Chains implements INode { const chain = nodeData.instance as LLMChain const promptValues = nodeData.inputs?.prompt.promptValues as ICommonObject - const res = options.socketIO - ? await runPrediction(inputVariables, chain, input, promptValues, true, options.socketIO, options.socketIOClientId) - : await runPrediction(inputVariables, chain, input, promptValues) + const res = await runPrediction(inputVariables, chain, input, promptValues, options) // eslint-disable-next-line no-console console.log('\x1b[93m\x1b[1m\n*****FINAL RESULT*****\n\x1b[0m\x1b[0m') // eslint-disable-next-line no-console @@ -97,17 +96,20 @@ const runPrediction = async ( chain: LLMChain, input: string, promptValues: ICommonObject, - isStreaming?: boolean, - socketIO?: any, - socketIOClientId = '' + options: ICommonObject ) => { + const loggerHandler = new ConsoleCallbackHandler(options.logger) + const isStreaming = options.socketIO && options.socketIOClientId + const socketIO = isStreaming ? options.socketIO : undefined + const socketIOClientId = isStreaming ? options.socketIOClientId : '' + if (inputVariables.length === 1) { if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } else if (inputVariables.length > 1) { @@ -122,15 +124,13 @@ const runPrediction = async ( if (seen.length === 0) { // All inputVariables have fixed values specified - const options = { - ...promptValues - } + const options = { ...promptValues } if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.call(options, [handler]) + const res = await chain.call(options, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(options) + const res = await chain.call(options, [loggerHandler]) return res?.text } } else if (seen.length === 1) { @@ -143,10 +143,10 @@ const runPrediction = async ( } if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.call(options, [handler]) + const res = await chain.call(options, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(options) + const res = await chain.call(options, [loggerHandler]) return res?.text } } else { @@ -155,10 +155,10 @@ const runPrediction = async ( } else { if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts index 189f41f7..e9783639 100644 --- a/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts +++ b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts @@ -1,7 +1,8 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, INode, INodeData, INodeParams, PromptRetriever } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { MultiPromptChain } from 'langchain/chains' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class MultiPromptChain_Chains implements INode { label: string @@ -63,12 +64,14 @@ class MultiPromptChain_Chains implements INode { const chain = nodeData.instance as MultiPromptChain const obj = { input } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) return res?.text } } diff --git a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts index b3575a93..a1947faa 100644 --- a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts +++ b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts @@ -1,7 +1,8 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, INode, INodeData, INodeParams, VectorStoreRetriever } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { MultiRetrievalQAChain } from 'langchain/chains' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class MultiRetrievalQAChain_Chains implements INode { label: string @@ -71,14 +72,15 @@ class MultiRetrievalQAChain_Chains implements INode { const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean const obj = { input } + const loggerHandler = new ConsoleCallbackHandler(options.logger) if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2, returnSourceDocuments) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) if (res.text && res.sourceDocuments) return res return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) if (res.text && res.sourceDocuments) return res return res?.text } diff --git a/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts b/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts index 97fa51a1..eaf5a0a9 100644 --- a/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts +++ b/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts @@ -1,8 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { RetrievalQAChain } from 'langchain/chains' import { BaseRetriever } from 'langchain/schema' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class RetrievalQAChain_Chains implements INode { label: string @@ -49,13 +50,14 @@ class RetrievalQAChain_Chains implements INode { const obj = { query: input } + const loggerHandler = new ConsoleCallbackHandler(options.logger) if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) return res?.text } } diff --git a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts index 08d3eee5..5817264d 100644 --- a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts +++ b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts @@ -1,9 +1,10 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { SqlDatabaseChain, SqlDatabaseChainInput } from 'langchain/chains/sql_db' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { DataSource } from 'typeorm' import { SqlDatabase } from 'langchain/sql_db' import { BaseLanguageModel } from 'langchain/base_language' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class SqlDatabaseChain_Chains implements INode { label: string @@ -65,12 +66,14 @@ class SqlDatabaseChain_Chains implements INode { const dbFilePath = nodeData.inputs?.dbFilePath const chain = await getSQLDBChain(databaseType, dbFilePath, model) + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts b/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts index 5850ed7b..abe7aab3 100644 --- a/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts +++ b/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts @@ -1,8 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { VectorDBQAChain } from 'langchain/chains' import { BaseLanguageModel } from 'langchain/base_language' import { VectorStore } from 'langchain/vectorstores' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class VectorDBQAChain_Chains implements INode { label: string @@ -53,12 +54,14 @@ class VectorDBQAChain_Chains implements INode { query: input } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) return res?.text } } diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts new file mode 100644 index 00000000..8e363361 --- /dev/null +++ b/packages/components/src/handler.ts @@ -0,0 +1,180 @@ +import { BaseTracer, Run, BaseCallbackHandler } from 'langchain/callbacks' +import { AgentAction, ChainValues } from 'langchain/schema' +import { Logger } from 'winston' +import { Server } from 'socket.io' + +interface AgentRun extends Run { + actions: AgentAction[] +} + +function tryJsonStringify(obj: unknown, fallback: string) { + try { + return JSON.stringify(obj, null, 2) + } catch (err) { + return fallback + } +} + +function elapsed(run: Run): string { + if (!run.end_time) return '' + const elapsed = run.end_time - run.start_time + if (elapsed < 1000) { + return `${elapsed}ms` + } + return `${(elapsed / 1000).toFixed(2)}s` +} + +export class ConsoleCallbackHandler extends BaseTracer { + name = 'console_callback_handler' as const + logger: Logger + + protected persistRun(_run: Run) { + return Promise.resolve() + } + + constructor(logger: Logger) { + super() + this.logger = logger + } + + // utility methods + + getParents(run: Run) { + const parents: Run[] = [] + let currentRun = run + while (currentRun.parent_run_id) { + const parent = this.runMap.get(currentRun.parent_run_id) + if (parent) { + parents.push(parent) + currentRun = parent + } else { + break + } + } + return parents + } + + getBreadcrumbs(run: Run) { + const parents = this.getParents(run).reverse() + const string = [...parents, run] + .map((parent) => { + const name = `${parent.execution_order}:${parent.run_type}:${parent.name}` + return name + }) + .join(' > ') + return string + } + + // logging methods + + onChainStart(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose(`[chain/start] [${crumbs}] Entering Chain run with input: ${tryJsonStringify(run.inputs, '[inputs]')}`) + } + + onChainEnd(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[chain/end] [${crumbs}] [${elapsed(run)}] Exiting Chain run with output: ${tryJsonStringify(run.outputs, '[outputs]')}` + ) + } + + onChainError(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[chain/error] [${crumbs}] [${elapsed(run)}] Chain run errored with error: ${tryJsonStringify(run.error, '[error]')}` + ) + } + + onLLMStart(run: Run) { + const crumbs = this.getBreadcrumbs(run) + const inputs = 'prompts' in run.inputs ? { prompts: (run.inputs.prompts as string[]).map((p) => p.trim()) } : run.inputs + this.logger.verbose(`[llm/start] [${crumbs}] Entering LLM run with input: ${tryJsonStringify(inputs, '[inputs]')}`) + } + + onLLMEnd(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[llm/end] [${crumbs}] [${elapsed(run)}] Exiting LLM run with output: ${tryJsonStringify(run.outputs, '[response]')}` + ) + } + + onLLMError(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[llm/error] [${crumbs}] [${elapsed(run)}] LLM run errored with error: ${tryJsonStringify(run.error, '[error]')}` + ) + } + + onToolStart(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose(`[tool/start] [${crumbs}] Entering Tool run with input: "${run.inputs.input?.trim()}"`) + } + + onToolEnd(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose(`[tool/end] [${crumbs}] [${elapsed(run)}] Exiting Tool run with output: "${run.outputs?.output?.trim()}"`) + } + + onToolError(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[tool/error] [${crumbs}] [${elapsed(run)}] Tool run errored with error: ${tryJsonStringify(run.error, '[error]')}` + ) + } + + onAgentAction(run: Run) { + const agentRun = run as AgentRun + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[agent/action] [${crumbs}] Agent selected action: ${tryJsonStringify( + agentRun.actions[agentRun.actions.length - 1], + '[action]' + )}` + ) + } +} + +/** + * Custom chain handler class + */ +export class CustomChainHandler extends BaseCallbackHandler { + name = 'custom_chain_handler' + isLLMStarted = false + socketIO: Server + socketIOClientId = '' + skipK = 0 // Skip streaming for first K numbers of handleLLMStart + returnSourceDocuments = false + + constructor(socketIO: Server, socketIOClientId: string, skipK?: number, returnSourceDocuments?: boolean) { + super() + this.socketIO = socketIO + this.socketIOClientId = socketIOClientId + this.skipK = skipK ?? this.skipK + this.returnSourceDocuments = returnSourceDocuments ?? this.returnSourceDocuments + } + + handleLLMStart() { + if (this.skipK > 0) this.skipK -= 1 + } + + handleLLMNewToken(token: string) { + if (this.skipK === 0) { + if (!this.isLLMStarted) { + this.isLLMStarted = true + this.socketIO.to(this.socketIOClientId).emit('start', token) + } + this.socketIO.to(this.socketIOClientId).emit('token', token) + } + } + + handleLLMEnd() { + this.socketIO.to(this.socketIOClientId).emit('end') + } + + handleChainEnd(outputs: ChainValues): void | Promise { + if (this.returnSourceDocuments) { + this.socketIO.to(this.socketIOClientId).emit('sourceDocuments', outputs?.sourceDocuments) + } + } +} diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index e1399404..027ec8db 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -3,9 +3,6 @@ import { load } from 'cheerio' import * as fs from 'fs' import * as path from 'path' import { JSDOM } from 'jsdom' -import { BaseCallbackHandler } from 'langchain/callbacks' -import { Server } from 'socket.io' -import { ChainValues } from 'langchain/dist/schema' 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 @@ -350,50 +347,9 @@ export const getEnvironmentVariable = (name: string): string | undefined => { } } -/** - * Custom chain handler class +/* + * List of dependencies allowed to be import in vm2 */ -export class CustomChainHandler extends BaseCallbackHandler { - name = 'custom_chain_handler' - isLLMStarted = false - socketIO: Server - socketIOClientId = '' - skipK = 0 // Skip streaming for first K numbers of handleLLMStart - returnSourceDocuments = false - - constructor(socketIO: Server, socketIOClientId: string, skipK?: number, returnSourceDocuments?: boolean) { - super() - this.socketIO = socketIO - this.socketIOClientId = socketIOClientId - this.skipK = skipK ?? this.skipK - this.returnSourceDocuments = returnSourceDocuments ?? this.returnSourceDocuments - } - - handleLLMStart() { - if (this.skipK > 0) this.skipK -= 1 - } - - handleLLMNewToken(token: string) { - if (this.skipK === 0) { - if (!this.isLLMStarted) { - this.isLLMStarted = true - this.socketIO.to(this.socketIOClientId).emit('start', token) - } - this.socketIO.to(this.socketIOClientId).emit('token', token) - } - } - - handleLLMEnd() { - this.socketIO.to(this.socketIOClientId).emit('end') - } - - handleChainEnd(outputs: ChainValues): void | Promise { - if (this.returnSourceDocuments) { - this.socketIO.to(this.socketIOClientId).emit('sourceDocuments', outputs?.sourceDocuments) - } - } -} - export const availableDependencies = [ '@dqbd/tiktoken', '@getzep/zep-js', diff --git a/packages/server/.env.example b/packages/server/.env.example index 262e08a6..d9b2da76 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -5,4 +5,5 @@ PORT=3000 # DATABASE_PATH=/your_database_path/.flowise # APIKEY_PATH=/your_api_key_path/.flowise # LOG_PATH=/your_log_path/logs -# EXECUTION_MODE=child or main +# LOG_LEVEL=debug (error | warn | info | verbose | debug) +# EXECUTION_MODE=main (child | main) diff --git a/packages/server/src/ChatflowPool.ts b/packages/server/src/ChatflowPool.ts index 35b0d947..d296dcfe 100644 --- a/packages/server/src/ChatflowPool.ts +++ b/packages/server/src/ChatflowPool.ts @@ -1,5 +1,6 @@ import { ICommonObject } from 'flowise-components' import { IActiveChatflows, INodeData, IReactFlowNode } from './Interface' +import logger from './utils/logger' /** * This pool is to keep track of active chatflow pools @@ -22,6 +23,7 @@ export class ChatflowPool { inSync: true } if (overrideConfig) this.activeChatflows[chatflowid].overrideConfig = overrideConfig + logger.info(`[server]: Chatflow ${chatflowid} added into ChatflowPool`) } /** @@ -32,6 +34,7 @@ export class ChatflowPool { updateInSync(chatflowid: string, inSync: boolean) { if (Object.prototype.hasOwnProperty.call(this.activeChatflows, chatflowid)) { this.activeChatflows[chatflowid].inSync = inSync + logger.info(`[server]: Chatflow ${chatflowid} updated inSync=${inSync} in ChatflowPool`) } } @@ -42,6 +45,7 @@ export class ChatflowPool { async remove(chatflowid: string) { if (Object.prototype.hasOwnProperty.call(this.activeChatflows, chatflowid)) { delete this.activeChatflows[chatflowid] + logger.info(`[server]: Chatflow ${chatflowid} removed from ChatflowPool`) } } } diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index e8aeaff2..27629480 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -5,6 +5,7 @@ import { DataSource } from 'typeorm' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' import { Tool } from './entity/Tool' +import logger from './utils/logger' export class ChildProcess { /** @@ -27,99 +28,112 @@ export class ChildProcess { await sendToParentProcess('start', '_') - const childAppDataSource = await initDB() + try { + const childAppDataSource = await initDB() - // Create a Queue and add our initial node in it - const { endingNodeData, chatflow, chatId, incomingInput, componentNodes } = messageValue + // Create a Queue and add our initial node in it + const { endingNodeData, chatflow, chatId, incomingInput, componentNodes } = messageValue - let nodeToExecuteData: INodeData - let addToChatFlowPool: any = {} + let nodeToExecuteData: INodeData + let addToChatFlowPool: any = {} - /* Don't rebuild the flow (to avoid duplicated upsert, recomputation) when all these conditions met: - * - Node Data already exists in pool - * - Still in sync (i.e the flow has not been modified since) - * - Existing overrideConfig and new overrideConfig are the same - * - Flow doesn't start with nodes that depend on incomingInput.question - ***/ - if (endingNodeData) { - nodeToExecuteData = endingNodeData - } else { - /*** Get chatflows and prepare data ***/ - const flowData = chatflow.flowData - const parsedFlowData: IReactFlowObject = JSON.parse(flowData) - const nodes = parsedFlowData.nodes - const edges = parsedFlowData.edges + /* Don't rebuild the flow (to avoid duplicated upsert, recomputation) when all these conditions met: + * - Node Data already exists in pool + * - Still in sync (i.e the flow has not been modified since) + * - Existing overrideConfig and new overrideConfig are the same + * - Flow doesn't start with nodes that depend on incomingInput.question + ***/ + if (endingNodeData) { + nodeToExecuteData = endingNodeData + } else { + /*** Get chatflows and prepare data ***/ + const flowData = chatflow.flowData + const parsedFlowData: IReactFlowObject = JSON.parse(flowData) + const nodes = parsedFlowData.nodes + const edges = parsedFlowData.edges - /*** Get Ending Node with Directed Graph ***/ - const { graph, nodeDependencies } = constructGraphs(nodes, edges) - const directedGraph = graph - const endingNodeId = getEndingNode(nodeDependencies, directedGraph) - if (!endingNodeId) { - await sendToParentProcess('error', `Ending node must be either a Chain or Agent`) - return - } + /*** Get Ending Node with Directed Graph ***/ + const { graph, nodeDependencies } = constructGraphs(nodes, edges) + const directedGraph = graph + const endingNodeId = getEndingNode(nodeDependencies, directedGraph) + if (!endingNodeId) { + await sendToParentProcess('error', `Ending node ${endingNodeId} not found`) + return + } - const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data - if (!endingNodeData) { - await sendToParentProcess('error', `Ending node must be either a Chain or Agent`) - return - } + const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data + if (!endingNodeData) { + await sendToParentProcess('error', `Ending node ${endingNodeId} data not found`) + return + } - if ( - endingNodeData.outputs && - Object.keys(endingNodeData.outputs).length && - !Object.values(endingNodeData.outputs).includes(endingNodeData.name) - ) { - await sendToParentProcess( - 'error', - `Output of ${endingNodeData.label} (${endingNodeData.id}) must be ${endingNodeData.label}, can't be an Output Prediction` + if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { + await sendToParentProcess('error', `Ending node must be either a Chain or Agent`) + return + } + + if ( + endingNodeData.outputs && + Object.keys(endingNodeData.outputs).length && + !Object.values(endingNodeData.outputs).includes(endingNodeData.name) + ) { + await sendToParentProcess( + 'error', + `Output of ${endingNodeData.label} (${endingNodeData.id}) must be ${endingNodeData.label}, can't be an Output Prediction` + ) + return + } + + /*** Get Starting Nodes with Non-Directed Graph ***/ + const constructedObj = constructGraphs(nodes, edges, true) + const nonDirectedGraph = constructedObj.graph + const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) + + logger.debug(`[server] [mode:child]: Start building chatflow ${chatflow.id}`) + /*** BFS to traverse from Starting Nodes to Ending Node ***/ + const reactFlowNodes = await buildLangchain( + startingNodeIds, + nodes, + graph, + depthQueue, + componentNodes, + incomingInput.question, + chatId, + childAppDataSource, + incomingInput?.overrideConfig ) - return + + const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId) + if (!nodeToExecute) { + await sendToParentProcess('error', `Node ${endingNodeId} not found`) + return + } + + const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question) + nodeToExecuteData = reactFlowNodeData + + const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id)) + addToChatFlowPool = { + chatflowid: chatflow.id, + nodeToExecuteData, + startingNodes, + overrideConfig: incomingInput?.overrideConfig + } } - /*** Get Starting Nodes with Non-Directed Graph ***/ - const constructedObj = constructGraphs(nodes, edges, true) - const nonDirectedGraph = constructedObj.graph - const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) + const nodeInstanceFilePath = componentNodes[nodeToExecuteData.name].filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const nodeInstance = new nodeModule.nodeClass() - /*** BFS to traverse from Starting Nodes to Ending Node ***/ - const reactFlowNodes = await buildLangchain( - startingNodeIds, - nodes, - graph, - depthQueue, - componentNodes, - incomingInput.question, - chatId, - childAppDataSource, - incomingInput?.overrideConfig - ) + logger.debug(`[server] [mode:child]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) + const result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history }) + logger.debug(`[server] [mode:child]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) - const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId) - if (!nodeToExecute) { - await sendToParentProcess('error', `Node ${endingNodeId} not found`) - return - } - - const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question) - nodeToExecuteData = reactFlowNodeData - - const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id)) - addToChatFlowPool = { - chatflowid: chatflow.id, - nodeToExecuteData, - startingNodes, - overrideConfig: incomingInput?.overrideConfig - } + await sendToParentProcess('finish', { result, addToChatFlowPool }) + } catch (e: any) { + await sendToParentProcess('error', e.message) + logger.error('[server] [mode:child]: Error:', e) } - - const nodeInstanceFilePath = componentNodes[nodeToExecuteData.name].filePath as string - const nodeModule = await import(nodeInstanceFilePath) - const nodeInstance = new nodeModule.nodeClass() - - const result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history }) - - await sendToParentProcess('finish', { result, addToChatFlowPool }) } } diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index c05c042a..276a3036 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -23,6 +23,7 @@ export default class Start extends Command { DATABASE_PATH: Flags.string(), APIKEY_PATH: Flags.string(), LOG_PATH: Flags.string(), + LOG_LEVEL: Flags.string(), EXECUTION_MODE: Flags.string() } @@ -61,6 +62,7 @@ export default class Start extends Command { if (flags.DATABASE_PATH) process.env.DATABASE_PATH = flags.DATABASE_PATH if (flags.APIKEY_PATH) process.env.APIKEY_PATH = flags.APIKEY_PATH if (flags.LOG_PATH) process.env.LOG_PATH = flags.LOG_PATH + if (flags.LOG_LEVEL) process.env.LOG_LEVEL = flags.LOG_LEVEL if (flags.EXECUTION_MODE) process.env.EXECUTION_MODE = flags.EXECUTION_MODE if (flags.DEBUG) process.env.DEBUG = flags.DEBUG diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 0f87aeba..74c4d07e 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -283,10 +283,16 @@ export class App { const nodes = parsedFlowData.nodes const edges = parsedFlowData.edges const { graph, nodeDependencies } = constructGraphs(nodes, edges) + const endingNodeId = getEndingNode(nodeDependencies, graph) - if (!endingNodeId) return res.status(500).send(`Ending node must be either a Chain or Agent`) + if (!endingNodeId) return res.status(500).send(`Ending node ${endingNodeId} not found`) + const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data - if (!endingNodeData) return res.status(500).send(`Ending node must be either a Chain or Agent`) + if (!endingNodeData) return res.status(500).send(`Ending node ${endingNodeId} data not found`) + + if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { + return res.status(500).send(`Ending node must be either a Chain or Agent`) + } const obj = { isStreaming: isFlowValidForStream(nodes, endingNodeData) @@ -638,7 +644,7 @@ export class App { }) }) } catch (err) { - logger.error(err) + logger.error('[server] [mode:child]: Error:', err) } } @@ -714,9 +720,11 @@ export class App { if (process.env.EXECUTION_MODE === 'child') { if (isFlowReusable()) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData + logger.debug( + `[server] [mode:child]: Reuse existing chatflow ${chatflowid} with ending node ${nodeToExecuteData.label} (${nodeToExecuteData.id})` + ) try { const result = await this.startChildProcess(chatflow, chatId, incomingInput, nodeToExecuteData) - return res.json(result) } catch (error) { return res.status(500).send(error) @@ -739,15 +747,22 @@ export class App { if (isFlowReusable()) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData isStreamValid = isFlowValidForStream(nodes, nodeToExecuteData) + logger.debug( + `[server]: Reuse existing chatflow ${chatflowid} with ending node ${nodeToExecuteData.label} (${nodeToExecuteData.id})` + ) } else { /*** Get Ending Node with Directed Graph ***/ const { graph, nodeDependencies } = constructGraphs(nodes, edges) const directedGraph = graph const endingNodeId = getEndingNode(nodeDependencies, directedGraph) - if (!endingNodeId) return res.status(500).send(`Ending node must be either a Chain or Agent`) + if (!endingNodeId) return res.status(500).send(`Ending node ${endingNodeId} not found`) const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data - if (!endingNodeData) return res.status(500).send(`Ending node must be either a Chain or Agent`) + if (!endingNodeData) return res.status(500).send(`Ending node ${endingNodeId} data not found`) + + if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { + return res.status(500).send(`Ending node must be either a Chain or Agent`) + } if ( endingNodeData.outputs && @@ -768,6 +783,7 @@ export class App { const nonDirectedGraph = constructedObj.graph const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) + logger.debug(`[server]: Start building chatflow ${chatflowid}`) /*** BFS to traverse from Starting Nodes to Ending Node ***/ const reactFlowNodes = await buildLangchain( startingNodeIds, @@ -796,17 +812,21 @@ export class App { const nodeInstance = new nodeModule.nodeClass() isStreamValid = isStreamValid && !isVectorStoreFaiss(nodeToExecuteData) + logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) const result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, socketIO, - socketIOClientId: incomingInput.socketIOClientId + socketIOClientId: incomingInput.socketIOClientId, + logger }) - : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history }) + : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, logger }) + logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) return res.json(result) } } catch (e: any) { + logger.error('[server]: Error:', e) return res.status(500).send(e.message) } } diff --git a/packages/server/src/utils/config.ts b/packages/server/src/utils/config.ts index c38d5a0c..8540b3b1 100644 --- a/packages/server/src/utils/config.ts +++ b/packages/server/src/utils/config.ts @@ -9,12 +9,12 @@ dotenv.config({ path: path.join(__dirname, '..', '..', '.env'), override: true } const loggingConfig = { dir: process.env.LOG_PATH ?? path.join(__dirname, '..', '..', '..', '..', 'logs'), server: { - level: 'info', + level: process.env.LOG_LEVEL ?? 'info', filename: 'server.log', errorFilename: 'server-error.log' }, express: { - level: 'info', + level: process.env.LOG_LEVEL ?? 'info', format: 'jsonl', // can't be changed currently filename: 'server-requests.log.jsonl' // should end with .jsonl } diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index bc4d8188..b67f2796 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -180,6 +180,9 @@ export const getEndingNode = (nodeDependencies: INodeDependencies, graph: INodeD * @param {IDepthQueue} depthQueue * @param {IComponentNodes} componentNodes * @param {string} question + * @param {string} chatId + * @param {DataSource} appDataSource + * @param {ICommonObject} overrideConfig */ export const buildLangchain = async ( startingNodeIds: string[], @@ -222,11 +225,14 @@ export const buildLangchain = async ( if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig) const reactFlowNodeData: INodeData = resolveVariables(flowNodeData, flowNodes, question) + logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, { chatId, appDataSource, - databaseEntities + databaseEntities, + logger }) + logger.debug(`[server]: Finished initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) } catch (e: any) { logger.error(e) throw new Error(e) diff --git a/packages/server/src/utils/logger.ts b/packages/server/src/utils/logger.ts index 1c28b173..5d7ffedf 100644 --- a/packages/server/src/utils/logger.ts +++ b/packages/server/src/utils/logger.ts @@ -4,7 +4,7 @@ import config from './config' // should be replaced by node-config or similar import { createLogger, transports, format } from 'winston' import { NextFunction, Request, Response } from 'express' -const { combine, timestamp, printf } = format +const { combine, timestamp, printf, errors } = format // expect the log dir be relative to the projects root const logDir = config.logging.dir @@ -18,9 +18,11 @@ const logger = createLogger({ format: combine( timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.json(), - printf(({ level, message, timestamp }) => { - return `${timestamp} [${level.toUpperCase()}]: ${message}` - }) + printf(({ level, message, timestamp, stack }) => { + const text = `${timestamp} [${level.toUpperCase()}]: ${message}` + return stack ? text + '\n' + stack : text + }), + errors({ stack: true }) ), defaultMeta: { package: 'server' @@ -56,7 +58,7 @@ const logger = createLogger({ */ export function expressRequestLogger(req: Request, res: Response, next: NextFunction): void { const fileLogger = createLogger({ - format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.json()), + format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.json(), errors({ stack: true })), defaultMeta: { package: 'server', request: { @@ -71,7 +73,7 @@ export function expressRequestLogger(req: Request, res: Response, next: NextFunc transports: [ new transports.File({ filename: path.join(logDir, config.logging.express.filename ?? 'server-requests.log.jsonl'), - level: 'debug' + level: config.logging.express.level ?? 'debug' }) ] }) From 8ad870ba261c3c8a3493f2cc43e6e0ee9f5e4afe Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 02:12:54 +0100 Subject: [PATCH 213/398] update README --- README.md | 22 ++++++++++++++++++++++ packages/server/README.md | 22 ++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4613e19f..7792b969 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,28 @@ FLOWISE_USERNAME=user FLOWISE_PASSWORD=1234 ``` +## 🌱 Env Variables + +Flowise support different environment variables to configure your instance. You can specify the following variables in the `.env` file inside `packages/server` folder. + +| Variable | Description | Type | Default | +| ---------------- | ---------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------- | +| PORT | The HTTP port Flowise runs on | Number | 3000 | +| FLOWISE_USERNAME | Username to login | String | +| FLOWISE_PASSWORD | Password to login | String | +| DEBUG | Print logs from components | Boolean | +| LOG_PATH | Location where log files are stored | String | `your-path/Flowise/logs` | +| LOG_LEVEL | Different levels of logs | Enum String: `error`, `info`, `verbose`, `debug` | `info` | +| DATABASE_PATH | Location where database is saved | String | `your-home-dir/.flowise` | +| APIKEY_PATH | Location where api keys are saved | String | `your-path/Flowise/packages/server` | +| EXECUTION_MODE | Whether predictions run in their own process or the main process | Enum String: `child`, `main` | `main` | + +You can also specify the env variables when using `npx`. For example: + +``` +npx flowise start --PORT=3000 --DEBUG=true +``` + ## 📖 Documentation [Flowise Docs](https://docs.flowiseai.com/) diff --git a/packages/server/README.md b/packages/server/README.md index 7895bd90..fb3a0c12 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -29,9 +29,27 @@ FLOWISE_USERNAME=user FLOWISE_PASSWORD=1234 ``` -## 🔎 Debugging +## 🌱 Env Variables -You can set `DEBUG=true` to the `.env` file. Refer [here](https://docs.flowiseai.com/environment-variables) for full list of env variables +Flowise support different environment variables to configure your instance. You can specify the following variables in the `.env` file inside `packages/server` folder. + +| Variable | Description | Type | Default | +| ---------------- | ---------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------- | +| PORT | The HTTP port Flowise runs on | Number | 3000 | +| FLOWISE_USERNAME | Username to login | String | +| FLOWISE_PASSWORD | Password to login | String | +| DEBUG | Print logs from components | Boolean | +| LOG_PATH | Location where log files are stored | String | `your-path/Flowise/logs` | +| LOG_LEVEL | Different levels of logs | Enum String: `error`, `info`, `verbose`, `debug` | `info` | +| DATABASE_PATH | Location where database is saved | String | `your-home-dir/.flowise` | +| APIKEY_PATH | Location where api keys are saved | String | `your-path/Flowise/packages/server` | +| EXECUTION_MODE | Whether predictions run in their own process or the main process | Enum String: `child`, `main` | `main` | + +You can also specify the env variables when using `npx`. For example: + +``` +npx flowise start --PORT=3000 --DEBUG=true +``` ## 📖 Documentation From 81641f99836b1717fd0d027d5f38f8915eef07d7 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 12:07:43 +0100 Subject: [PATCH 214/398] update Motorhead Memory icon --- .../memory/MotorheadMemory/MotorheadMemory.ts | 2 +- .../nodes/memory/MotorheadMemory/memory.svg | 8 -------- .../nodes/memory/MotorheadMemory/motorhead.png | Bin 0 -> 9923 bytes 3 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 packages/components/nodes/memory/MotorheadMemory/memory.svg create mode 100644 packages/components/nodes/memory/MotorheadMemory/motorhead.png diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index d50b7064..01d57614 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -17,7 +17,7 @@ class MotorMemory_Memory implements INode { this.label = 'Motorhead Memory' this.name = 'motorheadMemory' this.type = 'MotorheadMemory' - this.icon = 'memory.svg' + this.icon = 'motorhead.png' this.category = 'Memory' this.description = 'Remembers previous conversational back and forths directly' this.baseClasses = [this.type, ...getBaseClasses(MotorheadMemory)] diff --git a/packages/components/nodes/memory/MotorheadMemory/memory.svg b/packages/components/nodes/memory/MotorheadMemory/memory.svg deleted file mode 100644 index ca8e17da..00000000 --- a/packages/components/nodes/memory/MotorheadMemory/memory.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/packages/components/nodes/memory/MotorheadMemory/motorhead.png b/packages/components/nodes/memory/MotorheadMemory/motorhead.png new file mode 100644 index 0000000000000000000000000000000000000000..e1dfbde08872a555e544b353b0ec096071a0dd4c GIT binary patch literal 9923 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 B5P|>z literal 0 HcmV?d00001 From b69ba48ad4604cec2118e331f67f750bd5f60d4b Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 12:29:32 +0100 Subject: [PATCH 215/398] update GithubLoader marketplace --- .../server/marketplaces/chatflows/Github Repo QnA.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/server/marketplaces/chatflows/Github Repo QnA.json b/packages/server/marketplaces/chatflows/Github Repo QnA.json index 92ad0967..73fbf304 100644 --- a/packages/server/marketplaces/chatflows/Github Repo QnA.json +++ b/packages/server/marketplaces/chatflows/Github Repo QnA.json @@ -98,6 +98,13 @@ "optional": true, "id": "github_1-input-accessToken-password" }, + { + "label": "Recursive", + "name": "recursive", + "type": "boolean", + "optional": true, + "id": "github_1-input-recursive-boolean" + }, { "label": "Metadata", "name": "metadata", From cf6ad5355962c32b3b719da3f69a5aaca554e80f Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 12:52:32 +0100 Subject: [PATCH 216/398] update Zep memory node --- .../nodes/memory/ZepMemory/ZepMemory.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 89d07b8f..6e1a14bd 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -44,6 +44,20 @@ class ZepMemory_Memory implements INode { additionalParams: true, optional: true }, + { + label: 'API Key', + name: 'apiKey', + type: 'string', + additionalParams: true, + optional: true + }, + { + label: 'Size', + name: 'k', + type: 'number', + default: '10', + description: 'Window of size k to surface the last k back-and-forths to use as memory.' + }, { label: 'Auto Summary Template', name: 'autoSummaryTemplate', @@ -98,6 +112,8 @@ class ZepMemory_Memory implements INode { const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string const autoSummary = nodeData.inputs?.autoSummary as boolean const sessionId = nodeData.inputs?.sessionId as string + const apiKey = nodeData.inputs?.apiKey as string + const k = nodeData.inputs?.k as string const chatId = options?.chatId as string @@ -110,6 +126,7 @@ class ZepMemory_Memory implements INode { memoryKey, inputKey } + if (apiKey) obj.apiKey = apiKey let zep = new ZepMemory(obj) @@ -118,7 +135,7 @@ class ZepMemory_Memory implements INode { zep.loadMemoryVariables = async (values) => { let data = await tmpFunc.bind(zep, values)() if (autoSummary && zep.returnMessages && data[zep.memoryKey] && data[zep.memoryKey].length) { - const memory = await zep.zepClient.getMemory(zep.sessionId, 10) + const memory = await zep.zepClient.getMemory(zep.sessionId, parseInt(k, 10) ?? 10) if (memory?.summary) { let summary = autoSummaryTemplate.replace(/{summary}/g, memory.summary.content) // eslint-disable-next-line no-console From 6827a13e37a0f7acbf5a67a7a6192b66944a9a15 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 16:29:30 +0100 Subject: [PATCH 217/398] clear session memory --- .../nodes/memory/DynamoDb/DynamoDb.ts | 66 +++++++++++-------- .../memory/MotorheadMemory/MotorheadMemory.ts | 63 ++++++++++-------- .../RedisBackedChatMemory.ts | 55 +++++++++------- .../nodes/memory/ZepMemory/ZepMemory.ts | 53 +++++++++------ packages/components/src/Interface.ts | 1 + packages/server/src/index.ts | 18 ++++- packages/server/src/utils/index.ts | 23 +++++++ 7 files changed, 178 insertions(+), 101 deletions(-) diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index 49d15cb6..2ea7ba04 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -65,37 +65,47 @@ class DynamoDb_Memory implements INode { } ] } + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const tableName = nodeData.inputs?.tableName as string - const partitionKey = nodeData.inputs?.partitionKey as string - const sessionId = nodeData.inputs?.sessionId as string - const region = nodeData.inputs?.region as string - const accessKey = nodeData.inputs?.accessKey as string - const secretAccessKey = nodeData.inputs?.secretAccessKey as string - const memoryKey = nodeData.inputs?.memoryKey as string + return initalizeDynamoDB(nodeData, options) + } - const chatId = options.chatId - - const dynamoDb = new DynamoDBChatMessageHistory({ - tableName, - partitionKey, - sessionId: sessionId ? sessionId : chatId, - config: { - region, - credentials: { - accessKeyId: accessKey, - secretAccessKey - } - } - }) - - const memory = new BufferMemory({ - memoryKey, - chatHistory: dynamoDb, - returnMessages: true - }) - return memory + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const dynamodbMemory = initalizeDynamoDB(nodeData, options) + dynamodbMemory.clear() } } +const initalizeDynamoDB = (nodeData: INodeData, options: ICommonObject): BufferMemory => { + const tableName = nodeData.inputs?.tableName as string + const partitionKey = nodeData.inputs?.partitionKey as string + const sessionId = nodeData.inputs?.sessionId as string + const region = nodeData.inputs?.region as string + const accessKey = nodeData.inputs?.accessKey as string + const secretAccessKey = nodeData.inputs?.secretAccessKey as string + const memoryKey = nodeData.inputs?.memoryKey as string + + const chatId = options.chatId + + const dynamoDb = new DynamoDBChatMessageHistory({ + tableName, + partitionKey, + sessionId: sessionId ? sessionId : chatId, + config: { + region, + credentials: { + accessKeyId: accessKey, + secretAccessKey + } + } + }) + + const memory = new BufferMemory({ + memoryKey, + chatHistory: dynamoDb, + returnMessages: true + }) + return memory +} + module.exports = { nodeClass: DynamoDb_Memory } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 01d57614..b710b30a 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -64,35 +64,44 @@ class MotorMemory_Memory implements INode { } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const baseURL = nodeData.inputs?.baseURL as string - const sessionId = nodeData.inputs?.sessionId as string - const apiKey = nodeData.inputs?.apiKey as string - const clientId = nodeData.inputs?.clientId as string + return initalizeMotorhead(nodeData, options) + } - const chatId = options?.chatId as string - - let obj: MotorheadMemoryInput = { - returnMessages: true, - sessionId: sessionId ? sessionId : chatId, - memoryKey - } - - if (baseURL) { - obj = { - ...obj, - url: baseURL - } - } else { - obj = { - ...obj, - apiKey, - clientId - } - } - - return new MotorheadMemory(obj) + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const motorhead = initalizeMotorhead(nodeData, options) + motorhead.clear() } } +const initalizeMotorhead = (nodeData: INodeData, options: ICommonObject): MotorheadMemory => { + const memoryKey = nodeData.inputs?.memoryKey as string + const baseURL = nodeData.inputs?.baseURL as string + const sessionId = nodeData.inputs?.sessionId as string + const apiKey = nodeData.inputs?.apiKey as string + const clientId = nodeData.inputs?.clientId as string + + const chatId = options?.chatId as string + + let obj: MotorheadMemoryInput = { + returnMessages: true, + sessionId: sessionId ? sessionId : chatId, + memoryKey + } + + if (baseURL) { + obj = { + ...obj, + url: baseURL + } + } else { + obj = { + ...obj, + apiKey, + clientId + } + } + + return new MotorheadMemory(obj) +} + module.exports = { nodeClass: MotorMemory_Memory } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 2b4e51c2..cd406f59 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -56,31 +56,40 @@ class RedisBackedChatMemory_Memory implements INode { } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const baseURL = nodeData.inputs?.baseURL as string - const sessionId = nodeData.inputs?.sessionId as string - const sessionTTL = nodeData.inputs?.sessionTTL as number - const memoryKey = nodeData.inputs?.memoryKey as string + return initalizeRedis(nodeData, options) + } - const chatId = options?.chatId as string - - const redisClient = createClient({ url: baseURL }) - let obj: RedisChatMessageHistoryInput = { - sessionId: sessionId ? sessionId : chatId, - client: redisClient - } - - if (sessionTTL) { - obj = { - ...obj, - sessionTTL - } - } - - let redisChatMessageHistory = new RedisChatMessageHistory(obj) - let redis = new BufferMemory({ memoryKey, chatHistory: redisChatMessageHistory, returnMessages: true }) - - return redis + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const redis = initalizeRedis(nodeData, options) + redis.clear() } } +const initalizeRedis = (nodeData: INodeData, options: ICommonObject): BufferMemory => { + const baseURL = nodeData.inputs?.baseURL as string + const sessionId = nodeData.inputs?.sessionId as string + const sessionTTL = nodeData.inputs?.sessionTTL as number + const memoryKey = nodeData.inputs?.memoryKey as string + + const chatId = options?.chatId as string + + const redisClient = createClient({ url: baseURL }) + let obj: RedisChatMessageHistoryInput = { + sessionId: sessionId ? sessionId : chatId, + client: redisClient + } + + if (sessionTTL) { + obj = { + ...obj, + sessionTTL + } + } + + let redisChatMessageHistory = new RedisChatMessageHistory(obj) + let redis = new BufferMemory({ memoryKey, chatHistory: redisChatMessageHistory, returnMessages: true }) + + return redis +} + module.exports = { nodeClass: RedisBackedChatMemory_Memory } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 6e1a14bd..d9bac948 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -104,31 +104,11 @@ class ZepMemory_Memory implements INode { } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const baseURL = nodeData.inputs?.baseURL as string - const aiPrefix = nodeData.inputs?.aiPrefix as string - const humanPrefix = nodeData.inputs?.humanPrefix as string - const memoryKey = nodeData.inputs?.memoryKey as string - const inputKey = nodeData.inputs?.inputKey as string const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string const autoSummary = nodeData.inputs?.autoSummary as boolean - const sessionId = nodeData.inputs?.sessionId as string - const apiKey = nodeData.inputs?.apiKey as string const k = nodeData.inputs?.k as string - const chatId = options?.chatId as string - - const obj: ZepMemoryInput = { - baseURL, - sessionId: sessionId ? sessionId : chatId, - aiPrefix, - humanPrefix, - returnMessages: true, - memoryKey, - inputKey - } - if (apiKey) obj.apiKey = apiKey - - let zep = new ZepMemory(obj) + let zep = initalizeZep(nodeData, options) // hack to support summary let tmpFunc = zep.loadMemoryVariables @@ -153,6 +133,37 @@ class ZepMemory_Memory implements INode { } return zep } + + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const zep = initalizeZep(nodeData, options) + zep.clear() + } +} + +const initalizeZep = (nodeData: INodeData, options: ICommonObject) => { + const baseURL = nodeData.inputs?.baseURL as string + const aiPrefix = nodeData.inputs?.aiPrefix as string + const humanPrefix = nodeData.inputs?.humanPrefix as string + const memoryKey = nodeData.inputs?.memoryKey as string + const inputKey = nodeData.inputs?.inputKey as string + + const sessionId = nodeData.inputs?.sessionId as string + const apiKey = nodeData.inputs?.apiKey as string + + const chatId = options?.chatId as string + + const obj: ZepMemoryInput = { + baseURL, + sessionId: sessionId ? sessionId : chatId, + aiPrefix, + humanPrefix, + returnMessages: true, + memoryKey, + inputKey + } + if (apiKey) obj.apiKey = apiKey + + return new ZepMemory(obj) } module.exports = { nodeClass: ZepMemory_Memory } diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index d9233e49..862b81ac 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -96,6 +96,7 @@ export interface INode extends INodeProperties { } init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise + clearSessionMemory?(nodeData: INodeData, options?: ICommonObject): Promise } export interface INodeData extends INodeProperties { diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 0f87aeba..e11fa283 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -39,7 +39,8 @@ import { isFlowValidForStream, isVectorStoreFaiss, databaseEntities, - getApiKey + getApiKey, + clearSessionMemory } from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' @@ -320,6 +321,19 @@ export class App { // Delete all chatmessages from chatflowid this.app.delete('/api/v1/chatmessage/:id', async (req: Request, res: Response) => { + const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ + id: req.params.id + }) + if (!chatflow) { + res.status(404).send(`Chatflow ${req.params.id} not found`) + return + } + const flowData = chatflow.flowData + const parsedFlowData: IReactFlowObject = JSON.parse(flowData) + const nodes = parsedFlowData.nodes + let chatId = await getChatId(chatflow.id) + if (!chatId) chatId = chatflow.id + clearSessionMemory(nodes, this.nodesPool.componentNodes, chatId, req.query.sessionId as string) const results = await this.AppDataSource.getRepository(ChatMessage).delete({ chatflowid: req.params.id }) return res.json(results) }) @@ -662,7 +676,7 @@ export class App { if (!chatflow) return res.status(404).send(`Chatflow ${chatflowid} not found`) let chatId = await getChatId(chatflow.id) - if (!chatId) chatId = Date.now().toString() + if (!chatId) chatId = chatflowid if (!isInternal) { await this.validateKey(req, res, chatflow) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 3ee7a25b..e13bca97 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -267,6 +267,29 @@ export const buildLangchain = async ( return flowNodes } +/** + * Clear memory + * @param {IReactFlowNode[]} reactFlowNodes + * @param {IComponentNodes} componentNodes + * @param {string} chatId + * @param {string} sessionId + */ +export const clearSessionMemory = async ( + reactFlowNodes: IReactFlowNode[], + componentNodes: IComponentNodes, + chatId: string, + sessionId?: string +) => { + for (const node of reactFlowNodes) { + if (node.data.category !== 'Memory') continue + const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const newNodeInstance = new nodeModule.nodeClass() + if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId + if (newNodeInstance.clearSessionMemory) await newNodeInstance?.clearSessionMemory(node.data, { chatId }) + } +} + /** * Get variable value from outputResponses.output * @param {string} paramValue From aee0a51f7397e23b516ff5cbb6bc6f7d757e5ac2 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 17:31:47 +0100 Subject: [PATCH 218/398] update zep and motorhead api key as password --- .../components/nodes/memory/MotorheadMemory/MotorheadMemory.ts | 2 +- packages/components/nodes/memory/ZepMemory/ZepMemory.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 01d57614..8a160223 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -47,7 +47,7 @@ class MotorMemory_Memory implements INode { { label: 'API Key', name: 'apiKey', - type: 'string', + type: 'password', description: 'Only needed when using hosted solution - https://getmetal.io', additionalParams: true, optional: true diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 6e1a14bd..5ca1310d 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -47,7 +47,7 @@ class ZepMemory_Memory implements INode { { label: 'API Key', name: 'apiKey', - type: 'string', + type: 'password', additionalParams: true, optional: true }, From 4c47beabbcaffba93a1c9c873cb59899531f0292 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 01:11:02 +0100 Subject: [PATCH 219/398] update claude v2 --- .../ConversationChain/ConversationChain.ts | 26 ++++++++++++++++++- .../chatmodels/ChatAnthropic/ChatAnthropic.ts | 10 +++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index f1df0183..7b6f002d 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -6,8 +6,10 @@ import { BufferMemory, ChatMessageHistory } from 'langchain/memory' import { BaseChatModel } from 'langchain/chat_models/base' import { AIMessage, HumanMessage } from 'langchain/schema' import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' +import { flatten } from 'lodash' +import { Document } from 'langchain/document' -const systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.` +let systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.` class ConversationChain_Chains implements INode { label: string @@ -38,6 +40,14 @@ class ConversationChain_Chains implements INode { name: 'memory', type: 'BaseMemory' }, + { + label: 'Document', + name: 'document', + type: 'Document', + description: 'Include whole document into the context window', + optional: true, + list: true + }, { label: 'System Message', name: 'systemMessagePrompt', @@ -54,6 +64,20 @@ class ConversationChain_Chains implements INode { const model = nodeData.inputs?.model as BaseChatModel const memory = nodeData.inputs?.memory as BufferMemory const prompt = nodeData.inputs?.systemMessagePrompt as string + const docs = nodeData.inputs?.document as Document[] + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + finalDocs.push(new Document(flattenDocs[i])) + } + + let finalText = '' + for (let i = 0; i < finalDocs.length; i += 1) { + finalText += finalDocs[i].pageContent + } + + if (finalText) systemMessage = `${systemMessage}\nThe AI has the following context:\n${finalText}` const obj: any = { llm: model, diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts index 3d861d24..b65c7bd8 100644 --- a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts +++ b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts @@ -31,6 +31,16 @@ class ChatAnthropic_ChatModels implements INode { name: 'modelName', type: 'options', options: [ + { + label: 'claude-2', + name: 'claude-2', + description: 'Claude 2 latest major version, automatically get updates to the model as they are released' + }, + { + label: 'claude-instant-1', + name: 'claude-instant-1', + description: 'Claude Instant latest major version, automatically get updates to the model as they are released' + }, { label: 'claude-v1', name: 'claude-v1' From f558c0374473d55e023aaf2b8b74dd5c7f684020 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 01:31:01 +0100 Subject: [PATCH 220/398] update marketplace --- .../chatflows/Simple Conversation Chain.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json index f7e654db..04009123 100644 --- a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json +++ b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json @@ -249,6 +249,15 @@ "name": "memory", "type": "BaseMemory", "id": "conversationChain_0-input-memory-BaseMemory" + }, + { + "label": "Document", + "name": "document", + "type": "Document", + "description": "Include whole document into the context window", + "optional": true, + "list": true, + "id": "conversationChain_0-input-document-Document" } ], "inputs": { From d023042158a26bd8b2c07d37f14d020b57c9f12b Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 02:38:59 +0100 Subject: [PATCH 221/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.2.1?= =?UTF-8?q?6=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 32a84610..3459a372 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.15", + "version": "1.2.16", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From 59a3f7ab66190f5d4dfbdff2d8110ebf865558b8 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 02:40:46 +0100 Subject: [PATCH 222/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.2.14=20rele?= =?UTF-8?q?ase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 2cadc89d..0df27569 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.13", + "version": "1.2.14", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From f8945c7fabd8a11668b105f3e28eaa432458489b Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 02:41:09 +0100 Subject: [PATCH 223/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.2.15=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 61ea436b..9d0d9d1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.14", + "version": "1.2.15", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index 44b7e991..daed16d8 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.14", + "version": "1.2.15", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From ab0534169ce01c406217b9fb22f9b7f2dada5e3f Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Thu, 13 Jul 2023 19:03:36 +0100 Subject: [PATCH 224/398] update README Railway Deploy --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7792b969..5023a1b7 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ npx flowise start --PORT=3000 --DEBUG=true ### [Railway](https://docs.flowiseai.com/deployment/railway) -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/YK7J0v) +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/pn4G8S?referralCode=WVNPD9) ### [Render](https://docs.flowiseai.com/deployment/render) From 880754358db6a32030f1ab820140c7bdebc6709f Mon Sep 17 00:00:00 2001 From: apeng-singlestore <127370261+apeng-singlestore@users.noreply.github.com> Date: Thu, 13 Jul 2023 16:54:48 -0700 Subject: [PATCH 225/398] Add singlestore upsert and existing --- package.json | 5 +- .../Singlestore_Existing.ts | 138 ++++++++++++++++ .../Singlestore_Existing/singlestore.svg | 20 +++ .../Singlestore_Upsert/Singlestore_Upsert.ts | 154 ++++++++++++++++++ .../Singlestore_Upsert/singlestore.svg | 20 +++ 5 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts create mode 100644 packages/components/nodes/vectorstores/Singlestore_Existing/singlestore.svg create mode 100644 packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts create mode 100644 packages/components/nodes/vectorstores/Singlestore_Upsert/singlestore.svg diff --git a/package.json b/package.json index 61ea436b..8f9004ba 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "*.{js,jsx,ts,tsx,json,md}": "eslint --fix" }, "devDependencies": { - "turbo": "1.7.4", "@babel/preset-env": "^7.19.4", "@babel/preset-typescript": "7.18.6", "@types/express": "^4.17.13", @@ -48,9 +47,13 @@ "pretty-quick": "^3.1.3", "rimraf": "^3.0.2", "run-script-os": "^1.1.6", + "turbo": "1.7.4", "typescript": "^4.8.4" }, "engines": { "node": ">=18.15.0" + }, + "dependencies": { + "mysql2": "^3.5.1" } } diff --git a/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts b/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts new file mode 100644 index 00000000..61ae84f6 --- /dev/null +++ b/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts @@ -0,0 +1,138 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams, INodeOptionsValue } from '../../../src/Interface' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { getBaseClasses } from '../../../src/utils' +import { ConnectionOptions, SingleStoreVectorStore, SingleStoreVectorStoreConfig } from 'langchain/vectorstores/singlestore' +import { flatten } from 'lodash' +import { integer } from '@opensearch-project/opensearch/api/types' + +class SingleStoreExisting_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'SingleStore Load Existing Table' + this.name = 'singlestoreExisting' + this.type = 'SingleStore' + this.icon = 'singlestore.svg' + this.category = 'Vector Stores' + this.description = 'Load existing document from SingleStore' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Host', + name: 'host', + type: 'string' + }, + { + label: 'Port', + name: 'port', + type: 'string' + }, + { + label: 'User', + name: 'user', + type: 'string' + }, + { + label: 'Password', + name: 'password', + type: 'password' + }, + { + label: 'Database', + name: 'database', + type: 'string' + }, + { + label: 'Table Name', + name: 'tableName', + type: 'string' + }, + { + label: 'Content Column Name', + name: 'contentColumnName', + type: 'string' + }, + { + label: 'Vector Column Name', + name: 'vectorColumnName', + type: 'string' + }, + { + label: 'Metadata Column Name', + name: 'metadataColumnName', + type: 'string' + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'SingleStore Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'SingleStore Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(SingleStoreVectorStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const singleStoreConnectionConfig = { + connectionOptions: { + host: nodeData.inputs?.host as string, + port: nodeData.inputs?.port as integer, + user: nodeData.inputs?.user as string, + password: nodeData.inputs?.password as string, + database: nodeData.inputs?.database as string + }, + tableName: nodeData.inputs?.tableName as string, + contentColumnName: nodeData.inputs?.contentColumnName as string, + vectorColumnName: nodeData.inputs?.vectorColumnName as string, + metadataColumnName: nodeData.inputs?.metadataColumnName as string + } as SingleStoreVectorStoreConfig + + const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 + + let vectorStore: SingleStoreVectorStore + + vectorStore = new SingleStoreVectorStore(embeddings, singleStoreConnectionConfig) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: SingleStoreExisting_VectorStores } diff --git a/packages/components/nodes/vectorstores/Singlestore_Existing/singlestore.svg b/packages/components/nodes/vectorstores/Singlestore_Existing/singlestore.svg new file mode 100644 index 00000000..bd8dc817 --- /dev/null +++ b/packages/components/nodes/vectorstores/Singlestore_Existing/singlestore.svg @@ -0,0 +1,20 @@ + + + SingleStore + + + + + + + + + + + + + + + + + diff --git a/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts b/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts new file mode 100644 index 00000000..b5f873a5 --- /dev/null +++ b/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts @@ -0,0 +1,154 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams, INodeOptionsValue } from '../../../src/Interface' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { getBaseClasses } from '../../../src/utils' +import { ConnectionOptions, SingleStoreVectorStore, SingleStoreVectorStoreConfig } from 'langchain/vectorstores/singlestore' +import { flatten } from 'lodash' +import { integer } from '@opensearch-project/opensearch/api/types' +import { MemoryVectorStore } from 'langchain/vectorstores/memory' + +class SingleStoreUpsert_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'SingleStore Upsert Document' + this.name = 'singlestoreUpsert' + this.type = 'SingleStore' + this.icon = 'singlestore.svg' + this.category = 'Vector Stores' + this.description = 'Upsert documents to SingleStore' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Document', + name: 'document', + type: 'Document', + list: true + }, + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Host', + name: 'host', + type: 'string' + }, + { + label: 'Port', + name: 'port', + type: 'string' + }, + { + label: 'User', + name: 'user', + type: 'string' + }, + { + label: 'Password', + name: 'password', + type: 'password' + }, + { + label: 'Database', + name: 'database', + type: 'string' + }, + { + label: 'Table Name', + name: 'tableName', + type: 'string' + }, + { + label: 'Content Column Name', + name: 'contentColumnName', + type: 'string' + }, + { + label: 'Vector Column Name', + name: 'vectorColumnName', + type: 'string' + }, + { + label: 'Metadata Column Name', + name: 'metadataColumnName', + type: 'string' + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'SingleStore Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'SingleStore Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(SingleStoreVectorStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const singleStoreConnectionConfig = { + connectionOptions: { + host: nodeData.inputs?.host as string, + port: nodeData.inputs?.port as integer, + user: nodeData.inputs?.user as string, + password: nodeData.inputs?.password as string, + database: nodeData.inputs?.database as string + }, + tableName: nodeData.inputs?.tableName as string, + contentColumnName: nodeData.inputs?.contentColumnName as string, + vectorColumnName: nodeData.inputs?.vectorColumnName as string, + metadataColumnName: nodeData.inputs?.metadataColumnName as string + } as SingleStoreVectorStoreConfig + + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + finalDocs.push(new Document(flattenDocs[i])) + } + + let vectorStore: SingleStoreVectorStore | MemoryVectorStore + + vectorStore = new SingleStoreVectorStore(embeddings, singleStoreConnectionConfig) + vectorStore.addDocuments.bind(vectorStore)(finalDocs) + + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: SingleStoreUpsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/Singlestore_Upsert/singlestore.svg b/packages/components/nodes/vectorstores/Singlestore_Upsert/singlestore.svg new file mode 100644 index 00000000..bd8dc817 --- /dev/null +++ b/packages/components/nodes/vectorstores/Singlestore_Upsert/singlestore.svg @@ -0,0 +1,20 @@ + + + SingleStore + + + + + + + + + + + + + + + + + From f893edcc02f9b830a5d51a26e2d77166f863ad48 Mon Sep 17 00:00:00 2001 From: Atish Amte Date: Sat, 15 Jul 2023 00:49:31 +0530 Subject: [PATCH 226/398] ChatMessage Order Fixed --- packages/server/src/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 74c4d07e..ff7d375d 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -306,8 +306,13 @@ export class App { // Get all chatmessages from chatflowid this.app.get('/api/v1/chatmessage/:id', async (req: Request, res: Response) => { - const chatmessages = await this.AppDataSource.getRepository(ChatMessage).findBy({ - chatflowid: req.params.id + const chatmessages = await this.AppDataSource.getRepository(ChatMessage).find({ + where: { + chatflowid: req.params.id + }, + order: { + createdDate: 'ASC' + }, }) return res.json(chatmessages) }) From 5ce082e34f8a9499cdf195dbc15ce0339a1d935d Mon Sep 17 00:00:00 2001 From: apeng-singlestore <127370261+apeng-singlestore@users.noreply.github.com> Date: Fri, 14 Jul 2023 15:24:24 -0700 Subject: [PATCH 227/398] remove redundant imports and move some options to additional --- .../Singlestore_Existing.ts | 43 +++++++++--------- .../Singlestore_Upsert/Singlestore_Upsert.ts | 44 ++++++++++--------- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts b/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts index 61ae84f6..3b880b24 100644 --- a/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts +++ b/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts @@ -1,10 +1,7 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams, INodeOptionsValue } from '../../../src/Interface' +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { Embeddings } from 'langchain/embeddings/base' -import { Document } from 'langchain/document' import { getBaseClasses } from '../../../src/utils' -import { ConnectionOptions, SingleStoreVectorStore, SingleStoreVectorStoreConfig } from 'langchain/vectorstores/singlestore' -import { flatten } from 'lodash' -import { integer } from '@opensearch-project/opensearch/api/types' +import { SingleStoreVectorStore, SingleStoreVectorStoreConfig } from 'langchain/vectorstores/singlestore' class SingleStoreExisting_VectorStores implements INode { label: string @@ -36,11 +33,6 @@ class SingleStoreExisting_VectorStores implements INode { name: 'host', type: 'string' }, - { - label: 'Port', - name: 'port', - type: 'string' - }, { label: 'User', name: 'user', @@ -59,27 +51,38 @@ class SingleStoreExisting_VectorStores implements INode { { label: 'Table Name', name: 'tableName', - type: 'string' + type: 'string', + placeholder: 'embeddings', + additionalParams: true, + optional: true }, { label: 'Content Column Name', name: 'contentColumnName', - type: 'string' + type: 'string', + placeholder: 'content', + additionalParams: true, + optional: true }, { label: 'Vector Column Name', name: 'vectorColumnName', - type: 'string' + type: 'string', + placeholder: 'vector', + additionalParams: true, + optional: true }, { label: 'Metadata Column Name', name: 'metadataColumnName', - type: 'string' + type: 'string', + placeholder: 'metadata', + additionalParams: true, + optional: true }, { label: 'Top K', name: 'topK', - description: 'Number of top results to fetch. Default to 4', placeholder: '4', type: 'number', additionalParams: true, @@ -104,15 +107,15 @@ class SingleStoreExisting_VectorStores implements INode { const singleStoreConnectionConfig = { connectionOptions: { host: nodeData.inputs?.host as string, - port: nodeData.inputs?.port as integer, + port: 3306, user: nodeData.inputs?.user as string, password: nodeData.inputs?.password as string, database: nodeData.inputs?.database as string }, - tableName: nodeData.inputs?.tableName as string, - contentColumnName: nodeData.inputs?.contentColumnName as string, - vectorColumnName: nodeData.inputs?.vectorColumnName as string, - metadataColumnName: nodeData.inputs?.metadataColumnName as string + ...(nodeData.inputs?.tableName ? { tableName: nodeData.inputs.tableName as string } : {}), + ...(nodeData.inputs?.contentColumnName ? { contentColumnName: nodeData.inputs.contentColumnName as string } : {}), + ...(nodeData.inputs?.vectorColumnName ? { vectorColumnName: nodeData.inputs.vectorColumnName as string } : {}), + ...(nodeData.inputs?.metadataColumnName ? { metadataColumnName: nodeData.inputs.metadataColumnName as string } : {}) } as SingleStoreVectorStoreConfig const embeddings = nodeData.inputs?.embeddings as Embeddings diff --git a/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts b/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts index b5f873a5..dc59edb6 100644 --- a/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts +++ b/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts @@ -2,9 +2,8 @@ import { INode, INodeData, INodeOutputsValue, INodeParams, INodeOptionsValue } f import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' import { getBaseClasses } from '../../../src/utils' -import { ConnectionOptions, SingleStoreVectorStore, SingleStoreVectorStoreConfig } from 'langchain/vectorstores/singlestore' +import { SingleStoreVectorStore, SingleStoreVectorStoreConfig } from 'langchain/vectorstores/singlestore' import { flatten } from 'lodash' -import { integer } from '@opensearch-project/opensearch/api/types' import { MemoryVectorStore } from 'langchain/vectorstores/memory' class SingleStoreUpsert_VectorStores implements INode { @@ -43,11 +42,6 @@ class SingleStoreUpsert_VectorStores implements INode { name: 'host', type: 'string' }, - { - label: 'Port', - name: 'port', - type: 'string' - }, { label: 'User', name: 'user', @@ -66,27 +60,38 @@ class SingleStoreUpsert_VectorStores implements INode { { label: 'Table Name', name: 'tableName', - type: 'string' + type: 'string', + placeholder: 'embeddings', + additionalParams: true, + optional: true }, { label: 'Content Column Name', name: 'contentColumnName', - type: 'string' + type: 'string', + placeholder: 'content', + additionalParams: true, + optional: true }, { label: 'Vector Column Name', name: 'vectorColumnName', - type: 'string' + type: 'string', + placeholder: 'vector', + additionalParams: true, + optional: true }, { label: 'Metadata Column Name', name: 'metadataColumnName', - type: 'string' + type: 'string', + placeholder: 'metadata', + additionalParams: true, + optional: true }, { label: 'Top K', name: 'topK', - description: 'Number of top results to fetch. Default to 4', placeholder: '4', type: 'number', additionalParams: true, @@ -111,15 +116,15 @@ class SingleStoreUpsert_VectorStores implements INode { const singleStoreConnectionConfig = { connectionOptions: { host: nodeData.inputs?.host as string, - port: nodeData.inputs?.port as integer, + port: 3306, user: nodeData.inputs?.user as string, password: nodeData.inputs?.password as string, database: nodeData.inputs?.database as string }, - tableName: nodeData.inputs?.tableName as string, - contentColumnName: nodeData.inputs?.contentColumnName as string, - vectorColumnName: nodeData.inputs?.vectorColumnName as string, - metadataColumnName: nodeData.inputs?.metadataColumnName as string + ...(nodeData.inputs?.tableName ? { tableName: nodeData.inputs.tableName as string } : {}), + ...(nodeData.inputs?.contentColumnName ? { contentColumnName: nodeData.inputs.contentColumnName as string } : {}), + ...(nodeData.inputs?.vectorColumnName ? { vectorColumnName: nodeData.inputs.vectorColumnName as string } : {}), + ...(nodeData.inputs?.metadataColumnName ? { metadataColumnName: nodeData.inputs.metadataColumnName as string } : {}) } as SingleStoreVectorStoreConfig const docs = nodeData.inputs?.document as Document[] @@ -134,11 +139,10 @@ class SingleStoreUpsert_VectorStores implements INode { finalDocs.push(new Document(flattenDocs[i])) } - let vectorStore: SingleStoreVectorStore | MemoryVectorStore - + let vectorStore: SingleStoreVectorStore + vectorStore = new SingleStoreVectorStore(embeddings, singleStoreConnectionConfig) vectorStore.addDocuments.bind(vectorStore)(finalDocs) - if (output === 'retriever') { const retriever = vectorStore.asRetriever(k) From 647bb1f8649d11f27686b827ae67d40e9644fae4 Mon Sep 17 00:00:00 2001 From: denchi Date: Sat, 15 Jul 2023 13:34:08 +0100 Subject: [PATCH 228/398] Added BraveSearch Api Tool --- .../tools/BraveSearchAPI/BraveSearchAPI.ts | 38 +++++++++++++++ .../nodes/tools/BraveSearchAPI/brave-logo.svg | 46 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts create mode 100644 packages/components/nodes/tools/BraveSearchAPI/brave-logo.svg diff --git a/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts b/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts new file mode 100644 index 00000000..75d0d5c5 --- /dev/null +++ b/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts @@ -0,0 +1,38 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { BraveSearch } from 'langchain/tools' + +class BraveSearchAPI_Tools implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'BraveSearch API' + this.name = 'braveSearchAPI' + this.type = 'BraveSearchAPI' + this.icon = 'brave-logo.svg' + this.category = 'Tools' + this.description = 'Wrapper around BraveSearch API - a real-time API to access Brave search results' + this.inputs = [ + { + label: 'BraveSearch API Key', + name: 'apiKey', + type: 'password' + } + ] + this.baseClasses = [this.type, ...getBaseClasses(BraveSearch)] + } + + async init(nodeData: INodeData): Promise { + const apiKey = nodeData.inputs?.apiKey as string + return new BraveSearch({apiKey}) + } +} + +module.exports = { nodeClass: BraveSearchAPI_Tools } diff --git a/packages/components/nodes/tools/BraveSearchAPI/brave-logo.svg b/packages/components/nodes/tools/BraveSearchAPI/brave-logo.svg new file mode 100644 index 00000000..686b5c41 --- /dev/null +++ b/packages/components/nodes/tools/BraveSearchAPI/brave-logo.svg @@ -0,0 +1,46 @@ + + + + Logotypes/bat/logo-dark@1x + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 413d654493df40924332666e6dd8a55587beb17c Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 15 Jul 2023 14:25:52 +0100 Subject: [PATCH 229/398] add credentials --- .../AzureChatOpenAI/AzureChatOpenAI.ts | 48 +-- .../AzureOpenAIApi.credential.ts | 45 +++ .../ChatAnthropic/AnthropicApi.credential.ts | 21 ++ .../chatmodels/ChatAnthropic/ChatAnthropic.ts | 22 +- .../nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts | 22 +- .../ChatOpenAI/OpenAIApi.credential.ts | 21 ++ .../components/nodes/llms/OpenAI/OpenAI.ts | 22 +- .../memory/MotorheadMemory/MotorheadMemory.ts | 33 +-- .../MotorheadMemoryApi.credential.ts | 29 ++ .../nodes/memory/ZepMemory/ZepMemory.ts | 23 +- .../ZepMemory/ZepMemoryApi.credential.ts | 24 ++ packages/components/src/Interface.ts | 10 + packages/components/src/utils.ts | 86 ++++++ packages/server/package.json | 2 + packages/server/src/ChildProcess.ts | 3 +- packages/server/src/DataSource.ts | 3 +- packages/server/src/Interface.ts | 27 ++ packages/server/src/NodesPool.ts | 48 ++- packages/server/src/entity/Credential.ts | 24 ++ packages/server/src/index.ts | 155 +++++++++- packages/server/src/utils/index.ts | 156 +++++++++- packages/server/src/utils/logger.ts | 3 +- packages/ui/src/api/credentials.js | 28 ++ .../ui/src/assets/images/credential_empty.svg | 1 + packages/ui/src/menu-items/dashboard.js | 12 +- packages/ui/src/routes/MainRoutes.js | 9 +- packages/ui/src/store/constant.js | 1 + .../ui-component/dropdown/AsyncDropdown.js | 35 ++- packages/ui/src/ui-component/input/Input.js | 2 +- packages/ui/src/utils/genericHelper.js | 19 +- .../views/canvas/CredentialInputHandler.js | 149 ++++++++++ .../ui/src/views/canvas/NodeInputHandler.js | 16 + packages/ui/src/views/canvas/index.js | 26 +- .../credentials/AddEditCredentialDialog.js | 276 ++++++++++++++++++ .../credentials/CredentialInputHandler.js | 137 +++++++++ .../views/credentials/CredentialListDialog.js | 172 +++++++++++ packages/ui/src/views/credentials/index.js | 274 +++++++++++++++++ 37 files changed, 1858 insertions(+), 126 deletions(-) create mode 100644 packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts create mode 100644 packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts create mode 100644 packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts create mode 100644 packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts create mode 100644 packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts create mode 100644 packages/server/src/entity/Credential.ts create mode 100644 packages/ui/src/api/credentials.js create mode 100644 packages/ui/src/assets/images/credential_empty.svg create mode 100644 packages/ui/src/views/canvas/CredentialInputHandler.js create mode 100644 packages/ui/src/views/credentials/AddEditCredentialDialog.js create mode 100644 packages/ui/src/views/credentials/CredentialInputHandler.js create mode 100644 packages/ui/src/views/credentials/CredentialListDialog.js create mode 100644 packages/ui/src/views/credentials/index.js diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts index 2cdb505d..0bff883f 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts @@ -1,6 +1,6 @@ import { OpenAIBaseInput } from 'langchain/dist/types/openai-types' -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { AzureOpenAIInput, ChatOpenAI } from 'langchain/chat_models/openai' class AzureChatOpenAI_ChatModels implements INode { @@ -11,6 +11,7 @@ class AzureChatOpenAI_ChatModels implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -21,12 +22,13 @@ class AzureChatOpenAI_ChatModels implements INode { this.category = 'Chat Models' this.description = 'Wrapper around Azure OpenAI large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatOpenAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['azureOpenAIApi'] + } this.inputs = [ - { - label: 'Azure OpenAI Api Key', - name: 'azureOpenAIApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -59,26 +61,6 @@ class AzureChatOpenAI_ChatModels implements INode { default: 0.9, optional: true }, - { - label: 'Azure OpenAI Api Instance Name', - name: 'azureOpenAIApiInstanceName', - type: 'string', - placeholder: 'YOUR-INSTANCE-NAME' - }, - { - label: 'Azure OpenAI Api Deployment Name', - name: 'azureOpenAIApiDeploymentName', - type: 'string', - placeholder: 'YOUR-DEPLOYMENT-NAME' - }, - { - label: 'Azure OpenAI Api Version', - name: 'azureOpenAIApiVersion', - type: 'string', - placeholder: '2023-06-01-preview', - description: - 'Description of Supported API Versions. Please refer examples' - }, { label: 'Max Tokens', name: 'maxTokens', @@ -110,19 +92,21 @@ class AzureChatOpenAI_ChatModels implements INode { ] } - async init(nodeData: INodeData): Promise { - const azureOpenAIApiKey = nodeData.inputs?.azureOpenAIApiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const modelName = nodeData.inputs?.modelName as string const temperature = nodeData.inputs?.temperature as string - const azureOpenAIApiInstanceName = nodeData.inputs?.azureOpenAIApiInstanceName as string - const azureOpenAIApiDeploymentName = nodeData.inputs?.azureOpenAIApiDeploymentName as string - const azureOpenAIApiVersion = nodeData.inputs?.azureOpenAIApiVersion as string const maxTokens = nodeData.inputs?.maxTokens as string const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string const presencePenalty = nodeData.inputs?.presencePenalty as string const timeout = nodeData.inputs?.timeout as string const streaming = nodeData.inputs?.streaming as boolean + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const azureOpenAIApiKey = getCredentialParam('azureOpenAIApiKey', credentialData, nodeData) + const azureOpenAIApiInstanceName = getCredentialParam('azureOpenAIApiInstanceName', credentialData, nodeData) + const azureOpenAIApiDeploymentName = getCredentialParam('azureOpenAIApiDeploymentName', credentialData, nodeData) + const azureOpenAIApiVersion = getCredentialParam('azureOpenAIApiVersion', credentialData, nodeData) + const obj: Partial & Partial = { temperature: parseFloat(temperature), modelName, diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts new file mode 100644 index 00000000..d48e0c88 --- /dev/null +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts @@ -0,0 +1,45 @@ +import { INodeParams, INodeCredential } from '../../../src/Interface' + +class AzureOpenAIApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Azure OpenAI API' + this.name = 'azureOpenAIApi' + this.description = + 'Refer to official guide of how to use Azure OpenAI service' + this.inputs = [ + { + label: 'Azure OpenAI Api Key', + name: 'azureOpenAIApiKey', + type: 'password', + description: `Refer to official guide on how to create API key on Azure OpenAI` + }, + { + label: 'Azure OpenAI Api Instance Name', + name: 'azureOpenAIApiInstanceName', + type: 'string', + placeholder: 'YOUR-INSTANCE-NAME' + }, + { + label: 'Azure OpenAI Api Deployment Name', + name: 'azureOpenAIApiDeploymentName', + type: 'string', + placeholder: 'YOUR-DEPLOYMENT-NAME' + }, + { + label: 'Azure OpenAI Api Version', + name: 'azureOpenAIApiVersion', + type: 'string', + placeholder: '2023-06-01-preview', + description: + 'Description of Supported API Versions. Please refer examples' + } + ] + } +} + +module.exports = { credClass: AzureOpenAIApi } diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts b/packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts new file mode 100644 index 00000000..607fa625 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts @@ -0,0 +1,21 @@ +import { INodeParams, INodeCredential } from '../../../src/Interface' + +class AnthropicApi implements INodeCredential { + label: string + name: string + inputs: INodeParams[] + + constructor() { + this.label = 'Anthropic API' + this.name = 'anthropicApi' + this.inputs = [ + { + label: 'Anthropic Api Key', + name: 'anthropicApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: AnthropicApi } diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts index 3d861d24..6581475e 100644 --- a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts +++ b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { AnthropicInput, ChatAnthropic } from 'langchain/chat_models/anthropic' class ChatAnthropic_ChatModels implements INode { @@ -10,6 +10,7 @@ class ChatAnthropic_ChatModels implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class ChatAnthropic_ChatModels implements INode { this.category = 'Chat Models' this.description = 'Wrapper around ChatAnthropic large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatAnthropic)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['anthropicApi'] + } this.inputs = [ - { - label: 'ChatAnthropic Api Key', - name: 'anthropicApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -110,15 +112,17 @@ class ChatAnthropic_ChatModels implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const temperature = nodeData.inputs?.temperature as string const modelName = nodeData.inputs?.modelName as string - const anthropicApiKey = nodeData.inputs?.anthropicApiKey as string const maxTokensToSample = nodeData.inputs?.maxTokensToSample as string const topP = nodeData.inputs?.topP as string const topK = nodeData.inputs?.topK as string const streaming = nodeData.inputs?.streaming as boolean + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const anthropicApiKey = getCredentialParam('anthropicApiKey', credentialData, nodeData) + const obj: Partial & { anthropicApiKey?: string } = { temperature: parseFloat(temperature), modelName, diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts index 955563ff..1339d1fe 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ChatOpenAI, OpenAIChatInput } from 'langchain/chat_models/openai' class ChatOpenAI_ChatModels implements INode { @@ -10,6 +10,7 @@ class ChatOpenAI_ChatModels implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class ChatOpenAI_ChatModels implements INode { this.category = 'Chat Models' this.description = 'Wrapper around OpenAI large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatOpenAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['openAIApi'] + } this.inputs = [ - { - label: 'OpenAI Api Key', - name: 'openAIApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -119,10 +121,9 @@ class ChatOpenAI_ChatModels implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const temperature = nodeData.inputs?.temperature as string const modelName = nodeData.inputs?.modelName as string - const openAIApiKey = nodeData.inputs?.openAIApiKey as string const maxTokens = nodeData.inputs?.maxTokens as string const topP = nodeData.inputs?.topP as string const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string @@ -131,6 +132,9 @@ class ChatOpenAI_ChatModels implements INode { const streaming = nodeData.inputs?.streaming as boolean const basePath = nodeData.inputs?.basepath as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) + const obj: Partial & { openAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts b/packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts new file mode 100644 index 00000000..96209a35 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts @@ -0,0 +1,21 @@ +import { INodeParams, INodeCredential } from '../../../src/Interface' + +class OpenAIApi implements INodeCredential { + label: string + name: string + inputs: INodeParams[] + + constructor() { + this.label = 'OpenAI API' + this.name = 'openAIApi' + this.inputs = [ + { + label: 'OpenAI Api Key', + name: 'openAIApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: OpenAIApi } diff --git a/packages/components/nodes/llms/OpenAI/OpenAI.ts b/packages/components/nodes/llms/OpenAI/OpenAI.ts index b0af867d..50aa1c60 100644 --- a/packages/components/nodes/llms/OpenAI/OpenAI.ts +++ b/packages/components/nodes/llms/OpenAI/OpenAI.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { OpenAI, OpenAIInput } from 'langchain/llms/openai' class OpenAI_LLMs implements INode { @@ -10,6 +10,7 @@ class OpenAI_LLMs implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class OpenAI_LLMs implements INode { this.category = 'LLMs' this.description = 'Wrapper around OpenAI large language models' this.baseClasses = [this.type, ...getBaseClasses(OpenAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['openAIApi'] + } this.inputs = [ - { - label: 'OpenAI Api Key', - name: 'openAIApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -117,10 +119,9 @@ class OpenAI_LLMs implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const temperature = nodeData.inputs?.temperature as string const modelName = nodeData.inputs?.modelName as string - const openAIApiKey = nodeData.inputs?.openAIApiKey as string const maxTokens = nodeData.inputs?.maxTokens as string const topP = nodeData.inputs?.topP as string const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string @@ -131,6 +132,9 @@ class OpenAI_LLMs implements INode { const streaming = nodeData.inputs?.streaming as boolean const basePath = nodeData.inputs?.basepath as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) + const obj: Partial & { openAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 8a160223..9caf604c 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -1,5 +1,5 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ICommonObject } from '../../../src' import { MotorheadMemory, MotorheadMemoryInput } from 'langchain/memory' @@ -11,6 +11,7 @@ class MotorMemory_Memory implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -21,6 +22,14 @@ class MotorMemory_Memory implements INode { this.category = 'Memory' this.description = 'Remembers previous conversational back and forths directly' this.baseClasses = [this.type, ...getBaseClasses(MotorheadMemory)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: true, + description: 'Only needed when using hosted solution - https://getmetal.io', + credentialNames: ['motorheadMemoryApi'] + } this.inputs = [ { label: 'Base URL', @@ -43,22 +52,6 @@ class MotorMemory_Memory implements INode { default: '', additionalParams: true, optional: true - }, - { - label: 'API Key', - name: 'apiKey', - type: 'password', - description: 'Only needed when using hosted solution - https://getmetal.io', - additionalParams: true, - optional: true - }, - { - label: 'Client ID', - name: 'clientId', - type: 'string', - description: 'Only needed when using hosted solution - https://getmetal.io', - additionalParams: true, - optional: true } ] } @@ -67,11 +60,13 @@ class MotorMemory_Memory implements INode { const memoryKey = nodeData.inputs?.memoryKey as string const baseURL = nodeData.inputs?.baseURL as string const sessionId = nodeData.inputs?.sessionId as string - const apiKey = nodeData.inputs?.apiKey as string - const clientId = nodeData.inputs?.clientId as string const chatId = options?.chatId as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + const clientId = getCredentialParam('clientId', credentialData, nodeData) + let obj: MotorheadMemoryInput = { returnMessages: true, sessionId: sessionId ? sessionId : chatId, diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts new file mode 100644 index 00000000..4563cda2 --- /dev/null +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts @@ -0,0 +1,29 @@ +import { INodeParams, INodeCredential } from '../../../src/Interface' + +class MotorheadMemoryApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Motorhead Memory API' + this.name = 'motorheadMemoryApi' + this.description = + 'Refer to official guide on how to create API key and Client ID on Motorhead Memory' + this.inputs = [ + { + label: 'Client ID', + name: 'clientId', + type: 'string' + }, + { + label: 'API Key', + name: 'apiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: MotorheadMemoryApi } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 5ca1310d..211f60be 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -1,6 +1,6 @@ import { SystemMessage } from 'langchain/schema' import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' import { ICommonObject } from '../../../src' @@ -12,6 +12,7 @@ class ZepMemory_Memory implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -22,6 +23,14 @@ class ZepMemory_Memory implements INode { this.category = 'Memory' this.description = 'Summarizes the conversation and stores the memory in zep server' this.baseClasses = [this.type, ...getBaseClasses(ZepMemory)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: true, + description: 'Configure JWT authentication on your Zep instance (Optional)', + credentialNames: ['zepMemoryApi'] + } this.inputs = [ { label: 'Base URL', @@ -44,18 +53,12 @@ class ZepMemory_Memory implements INode { additionalParams: true, optional: true }, - { - label: 'API Key', - name: 'apiKey', - type: 'password', - additionalParams: true, - optional: true - }, { label: 'Size', name: 'k', type: 'number', default: '10', + step: 1, description: 'Window of size k to surface the last k back-and-forths to use as memory.' }, { @@ -112,11 +115,13 @@ class ZepMemory_Memory implements INode { const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string const autoSummary = nodeData.inputs?.autoSummary as boolean const sessionId = nodeData.inputs?.sessionId as string - const apiKey = nodeData.inputs?.apiKey as string const k = nodeData.inputs?.k as string const chatId = options?.chatId as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + const obj: ZepMemoryInput = { baseURL, sessionId: sessionId ? sessionId : chatId, diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts b/packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts new file mode 100644 index 00000000..5e92ef5d --- /dev/null +++ b/packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts @@ -0,0 +1,24 @@ +import { INodeParams, INodeCredential } from '../../../src/Interface' + +class ZepMemoryApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Zep Memory Api' + this.name = 'zepMemoryApi' + this.description = + 'Refer to official guide on how to create API key on Zep' + this.inputs = [ + { + label: 'API Key', + name: 'apiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: ZepMemoryApi } diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index d9233e49..4a69a9a0 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -59,7 +59,9 @@ export interface INodeParams { description?: string warning?: string options?: Array + credentialNames?: Array optional?: boolean | INodeDisplay + step?: number rows?: number list?: boolean acceptVariable?: boolean @@ -102,10 +104,18 @@ export interface INodeData extends INodeProperties { id: string inputs?: ICommonObject outputs?: ICommonObject + credential?: string instance?: any loadMethod?: string // method to load async options } +export interface INodeCredential { + label: string + name: string + description?: string + inputs?: INodeParams[] +} + export interface IMessage { message: string type: MessageType diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index e1399404..3244422f 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -6,6 +6,9 @@ import { JSDOM } from 'jsdom' import { BaseCallbackHandler } from 'langchain/callbacks' import { Server } from 'socket.io' import { ChainValues } from 'langchain/dist/schema' +import { DataSource } from 'typeorm' +import { ICommonObject, IDatabaseEntity, INodeData } from './Interface' +import { AES, enc } from 'crypto-js' 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 @@ -350,6 +353,89 @@ export const getEnvironmentVariable = (name: string): string | undefined => { } } +/** + * Returns the path of encryption key + * @returns {string} + */ +const getEncryptionKeyFilePath = (): string => { + const checkPaths = [ + path.join(__dirname, '..', '..', 'server', 'encryption.key'), + path.join(__dirname, '..', '..', '..', 'server', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', 'server', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', '..', 'server', 'encryption.key') + ] + for (const checkPath of checkPaths) { + if (fs.existsSync(checkPath)) { + return checkPath + } + } + return '' +} + +const getEncryptionKeyPath = (): string => { + return process.env.SECRETKEY_PATH ? path.join(process.env.SECRETKEY_PATH, 'encryption.key') : getEncryptionKeyFilePath() +} + +/** + * Returns the encryption key + * @returns {Promise} + */ +const getEncryptionKey = async (): Promise => { + try { + return await fs.promises.readFile(getEncryptionKeyPath(), 'utf8') + } catch (error) { + throw new Error(error) + } +} + +/** + * Decrypt credential data + * @param {string} encryptedData + * @param {string} componentCredentialName + * @param {IComponentCredentials} componentCredentials + * @returns {Promise} + */ +const decryptCredentialData = async (encryptedData: string): Promise => { + const encryptKey = await getEncryptionKey() + const decryptedData = AES.decrypt(encryptedData, encryptKey) + try { + return JSON.parse(decryptedData.toString(enc.Utf8)) + } catch (e) { + console.error(e) + throw new Error('Credentials could not be decrypted.') + } +} + +/** + * Get credential data + * @param {string} selectedCredentialId + * @param {ICommonObject} options + * @returns {Promise} + */ +export const getCredentialData = async (selectedCredentialId: string, options: ICommonObject): Promise => { + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + + try { + const credential = await appDataSource.getRepository(databaseEntities['Credential']).findOneBy({ + id: selectedCredentialId + }) + + if (!credential) throw new Error(`Credential ${selectedCredentialId} not found`) + + // Decrpyt credentialData + const decryptedCredentialData = await decryptCredentialData(credential.encryptedData) + + return decryptedCredentialData + } catch (e) { + throw new Error(e) + } +} + +export const getCredentialParam = (paramName: string, credentialData: ICommonObject, nodeData: INodeData): any => { + return (nodeData.inputs as ICommonObject)[paramName] ?? credentialData[paramName] +} + /** * Custom chain handler class */ diff --git a/packages/server/package.json b/packages/server/package.json index 44b7e991..d04bcab2 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -48,6 +48,7 @@ "@oclif/core": "^1.13.10", "axios": "^0.27.2", "cors": "^2.8.5", + "crypto-js": "^4.1.1", "dotenv": "^16.0.0", "express": "^4.17.3", "express-basic-auth": "^1.2.1", @@ -63,6 +64,7 @@ }, "devDependencies": { "@types/cors": "^2.8.12", + "@types/crypto-js": "^4.1.1", "@types/multer": "^1.4.7", "concurrently": "^7.1.0", "nodemon": "^2.0.15", diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index e8aeaff2..1ac80557 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -5,6 +5,7 @@ import { DataSource } from 'typeorm' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' import { Tool } from './entity/Tool' +import { Credential } from './entity/Credential' export class ChildProcess { /** @@ -133,7 +134,7 @@ async function initDB() { type: 'sqlite', database: path.resolve(homePath, 'database.sqlite'), synchronize: true, - entities: [ChatFlow, ChatMessage, Tool], + entities: [ChatFlow, ChatMessage, Tool, Credential], migrations: [] }) return await childAppDataSource.initialize() diff --git a/packages/server/src/DataSource.ts b/packages/server/src/DataSource.ts index 03b9d5ce..fdf0c924 100644 --- a/packages/server/src/DataSource.ts +++ b/packages/server/src/DataSource.ts @@ -3,6 +3,7 @@ import path from 'path' import { DataSource } from 'typeorm' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' +import { Credential } from './entity/Credential' import { Tool } from './entity/Tool' import { getUserHome } from './utils' @@ -15,7 +16,7 @@ export const init = async (): Promise => { type: 'sqlite', database: path.resolve(homePath, 'database.sqlite'), synchronize: true, - entities: [ChatFlow, ChatMessage, Tool], + entities: [ChatFlow, ChatMessage, Tool, Credential], migrations: [] }) } diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 0c630490..49d61036 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -38,10 +38,23 @@ export interface ITool { createdDate: Date } +export interface ICredential { + id: string + name: string + credentialName: string + encryptedData: string + updatedDate: Date + createdDate: Date +} + export interface IComponentNodes { [key: string]: INode } +export interface IComponentCredentials { + [key: string]: INode +} + export interface IVariableDict { [key: string]: string } @@ -167,3 +180,17 @@ export interface IChildProcessMessage { key: string value?: any } + +export type ICredentialDataDecrypted = ICommonObject + +// Plain credential object sent to server +export interface ICredentialReqBody { + name: string + credentialName: string + plainDataObj: ICredentialDataDecrypted +} + +// Decrypted credential object sent back to client +export interface ICredentialReturnResponse extends ICredential { + plainDataObj: ICredentialDataDecrypted +} diff --git a/packages/server/src/NodesPool.ts b/packages/server/src/NodesPool.ts index 1ee506ea..e339f592 100644 --- a/packages/server/src/NodesPool.ts +++ b/packages/server/src/NodesPool.ts @@ -1,23 +1,33 @@ -import { IComponentNodes } from './Interface' - +import { IComponentNodes, IComponentCredentials } from './Interface' import path from 'path' import { Dirent } from 'fs' import { getNodeModulesPackagePath } from './utils' import { promises } from 'fs' +import { ICommonObject } from 'flowise-components' export class NodesPool { componentNodes: IComponentNodes = {} + componentCredentials: IComponentCredentials = {} + private credentialIconPath: ICommonObject = {} /** - * Initialize to get all nodes + * Initialize to get all nodes & credentials */ async initialize() { + await this.initializeNodes() + await this.initializeCrdentials() + } + + /** + * Initialize nodes + */ + private async initializeNodes() { const packagePath = getNodeModulesPackagePath('flowise-components') const nodesPath = path.join(packagePath, 'dist', 'nodes') const nodeFiles = await this.getFiles(nodesPath) return Promise.all( nodeFiles.map(async (file) => { - if (file.endsWith('.js')) { + if (file.endsWith('.js') && !file.endsWith('.credential.js')) { const nodeModule = await require(file) if (nodeModule.nodeClass) { @@ -37,6 +47,13 @@ export class NodesPool { filePath.pop() const nodeIconAbsolutePath = `${filePath.join('/')}/${newNodeInstance.icon}` this.componentNodes[newNodeInstance.name].icon = nodeIconAbsolutePath + + // Store icon path for componentCredentials + if (newNodeInstance.credential) { + for (const credName of newNodeInstance.credential.credentialNames) { + this.credentialIconPath[credName] = nodeIconAbsolutePath + } + } } } } @@ -44,12 +61,33 @@ export class NodesPool { ) } + /** + * Initialize credentials + */ + private async initializeCrdentials() { + const packagePath = getNodeModulesPackagePath('flowise-components') + const nodesPath = path.join(packagePath, 'dist', 'nodes') + const nodeFiles = await this.getFiles(nodesPath) + return Promise.all( + nodeFiles.map(async (file) => { + if (file.endsWith('.credential.js')) { + const credentialModule = await require(file) + if (credentialModule.credClass) { + const newCredInstance = new credentialModule.credClass() + newCredInstance.icon = this.credentialIconPath[newCredInstance.name] ?? '' + this.componentCredentials[newCredInstance.name] = newCredInstance + } + } + }) + ) + } + /** * Recursive function to get node files * @param {string} dir * @returns {string[]} */ - async getFiles(dir: string): Promise { + private async getFiles(dir: string): Promise { const dirents = await promises.readdir(dir, { withFileTypes: true }) const files = await Promise.all( dirents.map((dirent: Dirent) => { diff --git a/packages/server/src/entity/Credential.ts b/packages/server/src/entity/Credential.ts new file mode 100644 index 00000000..b724eed6 --- /dev/null +++ b/packages/server/src/entity/Credential.ts @@ -0,0 +1,24 @@ +/* eslint-disable */ +import { Entity, Column, PrimaryGeneratedColumn, Index, CreateDateColumn, UpdateDateColumn } from 'typeorm' +import { ICredential } from '../Interface' + +@Entity() +export class Credential implements ICredential { + @PrimaryGeneratedColumn('uuid') + id: string + + @Column() + name: string + + @Column() + credentialName: string + + @Column() + encryptedData: string + + @CreateDateColumn() + createdDate: Date + + @UpdateDateColumn() + updatedDate: Date +} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 0f87aeba..b4316b69 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -17,7 +17,8 @@ import { INodeData, IDatabaseExport, IRunChatflowMessageValue, - IChildProcessMessage + IChildProcessMessage, + ICredentialReturnResponse } from './Interface' import { getNodeModulesPackagePath, @@ -39,17 +40,20 @@ import { isFlowValidForStream, isVectorStoreFaiss, databaseEntities, - getApiKey + getApiKey, + transformToCredentialEntity, + decryptCredentialData } from './utils' -import { cloneDeep } from 'lodash' +import { cloneDeep, omit } from 'lodash' import { getDataSource } from './DataSource' import { NodesPool } from './NodesPool' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' +import { Credential } from './entity/Credential' +import { Tool } from './entity/Tool' import { ChatflowPool } from './ChatflowPool' import { ICommonObject, INodeOptionsValue } from 'flowise-components' import { fork } from 'child_process' -import { Tool } from './entity/Tool' export class App { app: express.Application @@ -70,10 +74,11 @@ export class App { .then(async () => { logger.info('📦 [server]: Data Source has been initialized!') - // Initialize pools + // Initialize nodes pool this.nodesPool = new NodesPool() await this.nodesPool.initialize() + // Initialize chatflow pool this.chatflowPool = new ChatflowPool() // Initialize API keys @@ -104,6 +109,7 @@ export class App { '/api/v1/public-chatflows', '/api/v1/prediction/', '/api/v1/node-icon/', + '/api/v1/components-credentials-icon/', '/api/v1/chatflows-streaming' ] this.app.use((req, res, next) => { @@ -116,7 +122,7 @@ export class App { const upload = multer({ dest: `${path.join(__dirname, '..', 'uploads')}/` }) // ---------------------------------------- - // Nodes + // Components // ---------------------------------------- // Get all component nodes @@ -129,6 +135,16 @@ export class App { return res.json(returnData) }) + // Get all component credentials + this.app.get('/api/v1/components-credentials', async (req: Request, res: Response) => { + const returnData = [] + for (const credName in this.nodesPool.componentCredentials) { + const clonedCred = cloneDeep(this.nodesPool.componentCredentials[credName]) + returnData.push(clonedCred) + } + return res.json(returnData) + }) + // Get specific component node via name this.app.get('/api/v1/nodes/:name', (req: Request, res: Response) => { if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, req.params.name)) { @@ -138,6 +154,27 @@ export class App { } }) + // Get component credential via name + this.app.get('/api/v1/components-credentials/:name', (req: Request, res: Response) => { + if (!req.params.name.includes('&')) { + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, req.params.name)) { + return res.json(this.nodesPool.componentCredentials[req.params.name]) + } else { + throw new Error(`Credential ${req.params.name} not found`) + } + } else { + const returnResponse = [] + for (const name of req.params.name.split('&')) { + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, name)) { + returnResponse.push(this.nodesPool.componentCredentials[name]) + } else { + throw new Error(`Credential ${name} not found`) + } + } + return res.json(returnResponse) + } + }) + // Returns specific component node icon via name this.app.get('/api/v1/node-icon/:name', (req: Request, res: Response) => { if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, req.params.name)) { @@ -157,6 +194,25 @@ export class App { } }) + // Returns specific component credential icon via name + this.app.get('/api/v1/components-credentials-icon/:name', (req: Request, res: Response) => { + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, req.params.name)) { + const credInstance = this.nodesPool.componentCredentials[req.params.name] + if (credInstance.icon === undefined) { + throw new Error(`Credential ${req.params.name} icon not found`) + } + + if (credInstance.icon.endsWith('.svg') || credInstance.icon.endsWith('.png') || credInstance.icon.endsWith('.jpg')) { + const filepath = credInstance.icon + res.sendFile(filepath) + } else { + throw new Error(`Credential ${req.params.name} icon is missing icon`) + } + } else { + throw new Error(`Credential ${req.params.name} not found`) + } + }) + // load async options this.app.post('/api/v1/node-load-method/:name', async (req: Request, res: Response) => { const nodeData: INodeData = req.body @@ -324,6 +380,91 @@ export class App { return res.json(results) }) + // ---------------------------------------- + // Credentials + // ---------------------------------------- + + // Create new credential + this.app.post('/api/v1/credentials', async (req: Request, res: Response) => { + const body = req.body + const newCredential = await transformToCredentialEntity(body) + const credential = this.AppDataSource.getRepository(Credential).create(newCredential) + const results = await this.AppDataSource.getRepository(Credential).save(credential) + return res.json(results) + }) + + // Get all credentials + this.app.get('/api/v1/credentials', async (req: Request, res: Response) => { + if (req.query.credentialName) { + let returnCredentials = [] + if (Array.isArray(req.query.credentialName)) { + for (let i = 0; i < req.query.credentialName.length; i += 1) { + const name = req.query.credentialName[i] as string + const credentials = await this.AppDataSource.getRepository(Credential).findBy({ + credentialName: name + }) + returnCredentials.push(...credentials) + } + } else { + const credentials = await this.AppDataSource.getRepository(Credential).findBy({ + credentialName: req.query.credentialName as string + }) + returnCredentials = [...credentials] + } + return res.json(returnCredentials) + } else { + const credentials = await this.AppDataSource.getRepository(Credential).find() + const returnCredentials = [] + for (const credential of credentials) { + returnCredentials.push(omit(credential, ['encryptedData'])) + } + return res.json(returnCredentials) + } + }) + + // Get specific credential + this.app.get('/api/v1/credentials/:id', async (req: Request, res: Response) => { + const credential = await this.AppDataSource.getRepository(Credential).findOneBy({ + id: req.params.id + }) + + if (!credential) return res.status(404).send(`Credential ${req.params.id} not found`) + + // Decrpyt credentialData + const decryptedCredentialData = await decryptCredentialData( + credential.encryptedData, + credential.credentialName, + this.nodesPool.componentCredentials + ) + const returnCredential: ICredentialReturnResponse = { + ...credential, + plainDataObj: decryptedCredentialData + } + return res.json(omit(returnCredential, ['encryptedData'])) + }) + + // Update credential + this.app.put('/api/v1/credentials/:id', async (req: Request, res: Response) => { + const credential = await this.AppDataSource.getRepository(Credential).findOneBy({ + id: req.params.id + }) + + if (!credential) return res.status(404).send(`Credential ${req.params.id} not found`) + + const body = req.body + const updateCredential = await transformToCredentialEntity(body) + this.AppDataSource.getRepository(Credential).merge(credential, updateCredential) + const result = await this.AppDataSource.getRepository(Credential).save(credential) + + return res.json(result) + }) + + // Delete all chatmessages from chatflowid + this.app.delete('/api/v1/credentials/:id', async (req: Request, res: Response) => { + const results = await this.AppDataSource.getRepository(Credential).delete({ id: req.params.id }) + return res.json(results) + }) + // ---------------------------------------- // Tools // ---------------------------------------- @@ -393,7 +534,7 @@ export class App { const flowData = chatflow.flowData const parsedFlowData: IReactFlowObject = JSON.parse(flowData) const nodes = parsedFlowData.nodes - const availableConfigs = findAvailableConfigs(nodes) + const availableConfigs = findAvailableConfigs(nodes, this.nodesPool.componentCredentials) return res.json(availableConfigs) }) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 3ee7a25b..77b047cc 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -13,18 +13,26 @@ import { IReactFlowNode, IVariableDict, INodeData, - IOverrideConfig + IOverrideConfig, + ICredentialDataDecrypted, + IComponentCredentials, + ICredentialReqBody } from '../Interface' import { cloneDeep, get, omit, merge } from 'lodash' import { ICommonObject, getInputVariables, IDatabaseEntity } from 'flowise-components' import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' +import { lib, PBKDF2, AES, enc } from 'crypto-js' + import { ChatFlow } from '../entity/ChatFlow' import { ChatMessage } from '../entity/ChatMessage' +import { Credential } from '../entity/Credential' import { Tool } from '../entity/Tool' import { DataSource } from 'typeorm' const QUESTION_VAR_PREFIX = 'question' -export const databaseEntities: IDatabaseEntity = { ChatFlow: ChatFlow, ChatMessage: ChatMessage, Tool: Tool } +const REDACTED_CREDENTIAL_VALUE = '_FLOWISE_BLANK_07167752-1a71-43b1-bf8f-4f32252165db' + +export const databaseEntities: IDatabaseEntity = { ChatFlow: ChatFlow, ChatMessage: ChatMessage, Tool: Tool, Credential: Credential } /** * Returns the home folder path of the user if @@ -399,9 +407,8 @@ export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig: const types = 'inputs' const getParamValues = (paramsObj: ICommonObject) => { - for (const key in paramsObj) { - const paramValue: string = paramsObj[key] - paramsObj[key] = overrideConfig[key] ?? paramValue + for (const config in overrideConfig) { + paramsObj[config] = overrideConfig[config] } } @@ -623,11 +630,12 @@ export const mapMimeTypeToInputField = (mimeType: string) => { } /** - * Find all available inpur params config + * Find all available input params config * @param {IReactFlowNode[]} reactFlowNodes - * @returns {Promise} + * @param {IComponentCredentials} componentCredentials + * @returns {IOverrideConfig[]} */ -export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => { +export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], componentCredentials: IComponentCredentials) => { const configs: IOverrideConfig[] = [] for (const flowNode of reactFlowNodes) { @@ -653,6 +661,23 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => { .join(', ') : 'string' } + } else if (inputParam.type === 'credential') { + // get component credential inputs + for (const name of inputParam.credentialNames ?? []) { + if (Object.prototype.hasOwnProperty.call(componentCredentials, name)) { + const inputs = componentCredentials[name]?.inputs ?? [] + for (const input of inputs) { + obj = { + node: flowNode.data.label, + label: input.label, + name: input.name, + type: input.type === 'password' ? 'string' : input.type + } + configs.push(obj) + } + } + } + continue } else { obj = { node: flowNode.data.label, @@ -705,3 +730,118 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod return isChatOrLLMsExist && isValidChainOrAgent && !isVectorStoreFaiss(endingNodeData) && process.env.EXECUTION_MODE !== 'child' } + +/** + * Returns the path of encryption key + * @returns {string} + */ +export const getEncryptionKeyPath = (): string => { + return process.env.SECRETKEY_PATH + ? path.join(process.env.SECRETKEY_PATH, 'encryption.key') + : path.join(__dirname, '..', '..', 'encryption.key') +} + +/** + * Generate an encryption key + * @returns {string} + */ +export const generateEncryptKey = (): string => { + const salt = lib.WordArray.random(128 / 8) + const key256Bits = PBKDF2(process.env.PASSPHRASE || 'MYPASSPHRASE', salt, { + keySize: 256 / 32, + iterations: 1000 + }) + return key256Bits.toString() +} + +/** + * Returns the encryption key + * @returns {Promise} + */ +export const getEncryptionKey = async (): Promise => { + try { + return await fs.promises.readFile(getEncryptionKeyPath(), 'utf8') + } catch (error) { + const encryptKey = generateEncryptKey() + await fs.promises.writeFile(getEncryptionKeyPath(), encryptKey) + return encryptKey + } +} + +/** + * Encrypt credential data + * @param {ICredentialDataDecrypted} plainDataObj + * @returns {Promise} + */ +export const encryptCredentialData = async (plainDataObj: ICredentialDataDecrypted): Promise => { + const encryptKey = await getEncryptionKey() + return AES.encrypt(JSON.stringify(plainDataObj), encryptKey).toString() +} + +/** + * Decrypt credential data + * @param {string} encryptedData + * @param {string} componentCredentialName + * @param {IComponentCredentials} componentCredentials + * @returns {Promise} + */ +export const decryptCredentialData = async ( + encryptedData: string, + componentCredentialName?: string, + componentCredentials?: IComponentCredentials +): Promise => { + const encryptKey = await getEncryptionKey() + const decryptedData = AES.decrypt(encryptedData, encryptKey) + try { + if (componentCredentialName && componentCredentials) { + const plainDataObj = JSON.parse(decryptedData.toString(enc.Utf8)) + return redactCredentialWithPasswordType(componentCredentialName, plainDataObj, componentCredentials) + } + return JSON.parse(decryptedData.toString(enc.Utf8)) + } catch (e) { + console.error(e) + throw new Error('Credentials could not be decrypted.') + } +} + +/** + * Transform ICredentialBody from req to Credential entity + * @param {ICredentialReqBody} body + * @returns {Credential} + */ +export const transformToCredentialEntity = async (body: ICredentialReqBody): Promise => { + const encryptedData = await encryptCredentialData(body.plainDataObj) + + const credentialBody = { + name: body.name, + credentialName: body.credentialName, + encryptedData + } + + const newCredential = new Credential() + Object.assign(newCredential, credentialBody) + + return newCredential +} + +/** + * Redact values that are of password type to avoid sending back to client + * @param {string} componentCredentialName + * @param {ICredentialDataDecrypted} decryptedCredentialObj + * @param {IComponentCredentials} componentCredentials + * @returns {ICredentialDataDecrypted} + */ +export const redactCredentialWithPasswordType = ( + componentCredentialName: string, + decryptedCredentialObj: ICredentialDataDecrypted, + componentCredentials: IComponentCredentials +): ICredentialDataDecrypted => { + const plainDataObj = cloneDeep(decryptedCredentialObj) + for (const cred in plainDataObj) { + const inputParam = componentCredentials[componentCredentialName].inputs?.find((inp) => inp.type === 'password' && inp.name === cred) + if (inputParam) { + plainDataObj[cred] = REDACTED_CREDENTIAL_VALUE + } + } + return plainDataObj +} diff --git a/packages/server/src/utils/logger.ts b/packages/server/src/utils/logger.ts index 1c28b173..c5ff26b0 100644 --- a/packages/server/src/utils/logger.ts +++ b/packages/server/src/utils/logger.ts @@ -81,7 +81,8 @@ export function expressRequestLogger(req: Request, res: Response, next: NextFunc GET: '⬇️', POST: '⬆️', PUT: '🖊', - DELETE: '❌' + DELETE: '❌', + OPTION: '🔗' } return requetsEmojis[method] || '?' diff --git a/packages/ui/src/api/credentials.js b/packages/ui/src/api/credentials.js new file mode 100644 index 00000000..9dbdcf7a --- /dev/null +++ b/packages/ui/src/api/credentials.js @@ -0,0 +1,28 @@ +import client from './client' + +const getAllCredentials = () => client.get('/credentials') + +const getCredentialsByName = (componentCredentialName) => client.get(`/credentials?credentialName=${componentCredentialName}`) + +const getAllComponentsCredentials = () => client.get('/components-credentials') + +const getSpecificCredential = (id) => client.get(`/credentials/${id}`) + +const getSpecificComponentCredential = (name) => client.get(`/components-credentials/${name}`) + +const createCredential = (body) => client.post(`/credentials`, body) + +const updateCredential = (id, body) => client.put(`/credentials/${id}`, body) + +const deleteCredential = (id) => client.delete(`/credentials/${id}`) + +export default { + getAllCredentials, + getCredentialsByName, + getAllComponentsCredentials, + getSpecificCredential, + getSpecificComponentCredential, + createCredential, + updateCredential, + deleteCredential +} diff --git a/packages/ui/src/assets/images/credential_empty.svg b/packages/ui/src/assets/images/credential_empty.svg new file mode 100644 index 00000000..0951ee07 --- /dev/null +++ b/packages/ui/src/assets/images/credential_empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/menu-items/dashboard.js b/packages/ui/src/menu-items/dashboard.js index 948b4e4a..87ef88f9 100644 --- a/packages/ui/src/menu-items/dashboard.js +++ b/packages/ui/src/menu-items/dashboard.js @@ -1,8 +1,8 @@ // assets -import { IconHierarchy, IconBuildingStore, IconKey, IconTool } from '@tabler/icons' +import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock } from '@tabler/icons' // constant -const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool } +const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock } // ==============================|| DASHBOARD MENU ITEMS ||============================== // @@ -35,6 +35,14 @@ const dashboard = { icon: icons.IconTool, breadcrumbs: true }, + { + id: 'credentials', + title: 'Credentials', + type: 'item', + url: '/credentials', + icon: icons.IconLock, + breadcrumbs: true + }, { id: 'apikey', title: 'API Keys', diff --git a/packages/ui/src/routes/MainRoutes.js b/packages/ui/src/routes/MainRoutes.js index 28e60287..9a1c29af 100644 --- a/packages/ui/src/routes/MainRoutes.js +++ b/packages/ui/src/routes/MainRoutes.js @@ -13,9 +13,12 @@ const Marketplaces = Loadable(lazy(() => import('views/marketplaces'))) // apikey routing const APIKey = Loadable(lazy(() => import('views/apikey'))) -// apikey routing +// tools routing const Tools = Loadable(lazy(() => import('views/tools'))) +// credentials routing +const Credentials = Loadable(lazy(() => import('views/credentials'))) + // ==============================|| MAIN ROUTING ||============================== // const MainRoutes = { @@ -41,6 +44,10 @@ const MainRoutes = { { path: '/tools', element: + }, + { + path: '/credentials', + element: } ] } diff --git a/packages/ui/src/store/constant.js b/packages/ui/src/store/constant.js index c3138257..c0fce49d 100644 --- a/packages/ui/src/store/constant.js +++ b/packages/ui/src/store/constant.js @@ -5,3 +5,4 @@ export const appDrawerWidth = 320 export const maxScroll = 100000 export const baseURL = process.env.NODE_ENV === 'production' ? window.location.origin : window.location.origin.replace(':8080', ':3000') export const uiBaseURL = window.location.origin +export const FLOWISE_CREDENTIAL_ID = 'FLOWISE_CREDENTIAL_ID' diff --git a/packages/ui/src/ui-component/dropdown/AsyncDropdown.js b/packages/ui/src/ui-component/dropdown/AsyncDropdown.js index 8dfd782d..b24fa02b 100644 --- a/packages/ui/src/ui-component/dropdown/AsyncDropdown.js +++ b/packages/ui/src/ui-component/dropdown/AsyncDropdown.js @@ -1,13 +1,17 @@ import { useState, useEffect, Fragment } from 'react' import { useSelector } from 'react-redux' - import PropTypes from 'prop-types' import axios from 'axios' +// Material import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete' import { Popper, CircularProgress, TextField, Box, Typography } from '@mui/material' import { styled } from '@mui/material/styles' +// API +import credentialsApi from 'api/credentials' + +// const import { baseURL } from 'store/constant' const StyledPopper = styled(Popper)({ @@ -49,6 +53,7 @@ export const AsyncDropdown = ({ onSelect, isCreateNewOption, onCreateNew, + credentialNames = [], disabled = false, disableClearable = false }) => { @@ -62,11 +67,36 @@ export const AsyncDropdown = ({ const addNewOption = [{ label: '- Create New -', name: '-create-' }] let [internalValue, setInternalValue] = useState(value ?? 'choose an option') + const fetchCredentialList = async () => { + try { + let names = '' + if (credentialNames.length > 1) { + names = credentialNames.join('&credentialName=') + } else { + names = credentialNames[0] + } + const resp = await credentialsApi.getCredentialsByName(names) + if (resp.data) { + const returnList = [] + for (let i = 0; i < resp.data.length; i += 1) { + const data = { + label: resp.data[i].name, + name: resp.data[i].id + } + returnList.push(data) + } + return returnList + } + } catch (error) { + console.error(error) + } + } + useEffect(() => { setLoading(true) ;(async () => { const fetchData = async () => { - let response = await fetchList({ name, nodeData }) + let response = credentialNames.length ? await fetchCredentialList() : await fetchList({ name, nodeData }) if (isCreateNewOption) setOptions([...response, ...addNewOption]) else setOptions([...response]) setLoading(false) @@ -142,6 +172,7 @@ AsyncDropdown.propTypes = { onSelect: PropTypes.func, onCreateNew: PropTypes.func, disabled: PropTypes.bool, + credentialNames: PropTypes.array, disableClearable: PropTypes.bool, isCreateNewOption: PropTypes.bool } diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index e7744764..8f2d55e0 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -37,7 +37,7 @@ export const Input = ({ inputParam, value, onChange, disabled = false, showDialo onChange(e.target.value) }} inputProps={{ - step: 0.1, + step: inputParam.step ?? 0.1, style: { height: inputParam.rows ? '90px' : 'inherit' } diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index 305326f7..eb3b08bc 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -41,6 +41,7 @@ export const initNode = (nodeData, newNodeId) => { const whitelistTypes = ['asyncOptions', 'options', 'string', 'number', 'boolean', 'password', 'json', 'code', 'date', 'file', 'folder'] + // Inputs for (let i = 0; i < incoming; i += 1) { const newInput = { ...nodeData.inputs[i], @@ -53,6 +54,16 @@ export const initNode = (nodeData, newNodeId) => { } } + // Credential + if (nodeData.credential) { + const newInput = { + ...nodeData.credential, + id: `${newNodeId}-input-${nodeData.credential.name}-${nodeData.credential.type}` + } + inputParams.unshift(newInput) + } + + // Outputs const outputAnchors = [] for (let i = 0; i < outgoing; i += 1) { if (nodeData.outputs && nodeData.outputs.length) { @@ -129,6 +140,8 @@ export const initNode = (nodeData, newNodeId) => { } ] */ + + // Inputs if (nodeData.inputs) { nodeData.inputAnchors = inputAnchors nodeData.inputParams = inputParams @@ -139,13 +152,17 @@ export const initNode = (nodeData, newNodeId) => { nodeData.inputs = {} } + // Outputs if (nodeData.outputs) { nodeData.outputs = initializeDefaultNodeData(outputAnchors) } else { nodeData.outputs = {} } - nodeData.outputAnchors = outputAnchors + + // Credential + if (nodeData.credential) nodeData.credential = '' + nodeData.id = newNodeId return nodeData diff --git a/packages/ui/src/views/canvas/CredentialInputHandler.js b/packages/ui/src/views/canvas/CredentialInputHandler.js new file mode 100644 index 00000000..4f874719 --- /dev/null +++ b/packages/ui/src/views/canvas/CredentialInputHandler.js @@ -0,0 +1,149 @@ +import PropTypes from 'prop-types' +import { useRef, useState } from 'react' + +// material-ui +import { IconButton } from '@mui/material' +import { IconEdit } from '@tabler/icons' + +// project import +import { AsyncDropdown } from 'ui-component/dropdown/AsyncDropdown' +import AddEditCredentialDialog from 'views/credentials/AddEditCredentialDialog' +import CredentialListDialog from 'views/credentials/CredentialListDialog' + +// API +import credentialsApi from 'api/credentials' + +// ===========================|| CredentialInputHandler ||=========================== // + +const CredentialInputHandler = ({ inputParam, data, onSelect, disabled = false }) => { + const ref = useRef(null) + const [credentialId, setCredentialId] = useState(data?.credential ?? '') + const [showCredentialListDialog, setShowCredentialListDialog] = useState(false) + const [credentialListDialogProps, setCredentialListDialogProps] = useState({}) + const [showSpecificCredentialDialog, setShowSpecificCredentialDialog] = useState(false) + const [specificCredentialDialogProps, setSpecificCredentialDialogProps] = useState({}) + const [reloadTimestamp, setReloadTimestamp] = useState(Date.now().toString()) + + const editCredential = (credentialId) => { + const dialogProp = { + type: 'EDIT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Save', + credentialId + } + setSpecificCredentialDialogProps(dialogProp) + setShowSpecificCredentialDialog(true) + } + + const addAsyncOption = async () => { + try { + let names = '' + if (inputParam.credentialNames.length > 1) { + names = inputParam.credentialNames.join('&') + } else { + names = inputParam.credentialNames[0] + } + const componentCredentialsResp = await credentialsApi.getSpecificComponentCredential(names) + if (componentCredentialsResp.data) { + if (Array.isArray(componentCredentialsResp.data)) { + const dialogProp = { + title: 'Add New Credential', + componentsCredentials: componentCredentialsResp.data + } + setCredentialListDialogProps(dialogProp) + setShowCredentialListDialog(true) + } else { + const dialogProp = { + type: 'ADD', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add', + credentialComponent: componentCredentialsResp.data + } + setSpecificCredentialDialogProps(dialogProp) + setShowSpecificCredentialDialog(true) + } + } + } catch (error) { + console.error(error) + } + } + + const onConfirmAsyncOption = (selectedCredentialId = '') => { + setCredentialId(selectedCredentialId) + setReloadTimestamp(Date.now().toString()) + setSpecificCredentialDialogProps({}) + setShowSpecificCredentialDialog(false) + onSelect(selectedCredentialId) + } + + const onCredentialSelected = (credentialComponent) => { + setShowCredentialListDialog(false) + const dialogProp = { + type: 'ADD', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add', + credentialComponent + } + setSpecificCredentialDialogProps(dialogProp) + setShowSpecificCredentialDialog(true) + } + + return ( +
+ {inputParam && ( + <> + {inputParam.type === 'credential' && ( + <> +
+
+ { + setCredentialId(newValue) + onSelect(newValue) + }} + onCreateNew={() => addAsyncOption(inputParam.name)} + /> + {credentialId && ( + editCredential(credentialId)}> + + + )} +
+ + )} + + )} + {showSpecificCredentialDialog && ( + setShowSpecificCredentialDialog(false)} + onConfirm={onConfirmAsyncOption} + > + )} + {showCredentialListDialog && ( + setShowCredentialListDialog(false)} + onCredentialSelected={onCredentialSelected} + > + )} +
+ ) +} + +CredentialInputHandler.propTypes = { + inputParam: PropTypes.object, + data: PropTypes.object, + onSelect: PropTypes.func, + disabled: PropTypes.bool +} + +export default CredentialInputHandler diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index ba72a4ce..176df52f 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -21,9 +21,14 @@ import { JsonEditorInput } from 'ui-component/json/JsonEditor' import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' import ToolDialog from 'views/tools/ToolDialog' import FormatPromptValuesDialog from 'ui-component/dialog/FormatPromptValuesDialog' +import CredentialInputHandler from './CredentialInputHandler' +// utils import { getInputVariables } from 'utils/genericHelper' +// const +import { FLOWISE_CREDENTIAL_ID } from 'store/constant' + const EDITABLE_TOOLS = ['selectedTool'] const CustomWidthTooltip = styled(({ className, ...props }) => )({ @@ -226,6 +231,17 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA {inputParam.warning}
)} + {inputParam.type === 'credential' && ( + { + data.credential = newValue + data.inputs[FLOWISE_CREDENTIAL_ID] = newValue // in case data.credential is not updated + }} + /> + )} {inputParam.type === 'file' && ( { const handleSaveFlow = (chatflowName) => { if (reactFlowInstance) { - setNodes((nds) => - nds.map((node) => { - node.data = { - ...node.data, - selected: false - } - return node - }) - ) + const nodes = reactFlowInstance.getNodes().map((node) => { + const nodeData = cloneDeep(node.data) + if (Object.prototype.hasOwnProperty.call(nodeData.inputs, FLOWISE_CREDENTIAL_ID)) { + nodeData.credential = nodeData.inputs[FLOWISE_CREDENTIAL_ID] + nodeData.inputs = omit(nodeData.inputs, [FLOWISE_CREDENTIAL_ID]) + } + node.data = { + ...nodeData, + selected: false + } + return node + }) const rfInstanceObject = reactFlowInstance.toObject() + rfInstanceObject.nodes = nodes const flowData = JSON.stringify(rfInstanceObject) if (!chatflow.id) { diff --git a/packages/ui/src/views/credentials/AddEditCredentialDialog.js b/packages/ui/src/views/credentials/AddEditCredentialDialog.js new file mode 100644 index 00000000..6a5c9568 --- /dev/null +++ b/packages/ui/src/views/credentials/AddEditCredentialDialog.js @@ -0,0 +1,276 @@ +import { createPortal } from 'react-dom' +import PropTypes from 'prop-types' +import { useState, useEffect } from 'react' +import { useDispatch } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import parser from 'html-react-parser' + +// Material +import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Box, Stack, OutlinedInput, Typography } from '@mui/material' + +// Project imports +import { StyledButton } from 'ui-component/button/StyledButton' +import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' +import CredentialInputHandler from './CredentialInputHandler' + +// Icons +import { IconX } from '@tabler/icons' + +// API +import credentialsApi from 'api/credentials' + +// Hooks +import useApi from 'hooks/useApi' + +// utils +import useNotifier from 'utils/useNotifier' + +// const +import { baseURL } from 'store/constant' + +const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const portalElement = document.getElementById('portal') + + const dispatch = useDispatch() + + // ==============================|| Snackbar ||============================== // + + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const getSpecificCredentialApi = useApi(credentialsApi.getSpecificCredential) + const getSpecificComponentCredentialApi = useApi(credentialsApi.getSpecificComponentCredential) + + const [credential, setCredential] = useState({}) + const [name, setName] = useState('') + const [credentialData, setCredentialData] = useState({}) + const [componentCredential, setComponentCredential] = useState({}) + + useEffect(() => { + if (getSpecificCredentialApi.data) { + setCredential(getSpecificCredentialApi.data) + if (getSpecificCredentialApi.data.name) { + setName(getSpecificCredentialApi.data.name) + } + if (getSpecificCredentialApi.data.plainDataObj) { + setCredentialData(getSpecificCredentialApi.data.plainDataObj) + } + getSpecificComponentCredentialApi.request(getSpecificCredentialApi.data.credentialName) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getSpecificCredentialApi.data]) + + useEffect(() => { + if (getSpecificComponentCredentialApi.data) { + setComponentCredential(getSpecificComponentCredentialApi.data) + } + }, [getSpecificComponentCredentialApi.data]) + + useEffect(() => { + if (dialogProps.type === 'EDIT' && dialogProps.data) { + // When credential dialog is opened from Credentials dashboard + getSpecificCredentialApi.request(dialogProps.data.id) + } else if (dialogProps.type === 'EDIT' && dialogProps.credentialId) { + // When credential dialog is opened from node in canvas + getSpecificCredentialApi.request(dialogProps.credentialId) + } else if (dialogProps.type === 'ADD' && dialogProps.credentialComponent) { + // When credential dialog is to add a new credential + setName('') + setCredential({}) + setCredentialData({}) + setComponentCredential(dialogProps.credentialComponent) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dialogProps]) + + const addNewCredential = async () => { + try { + const obj = { + name, + credentialName: componentCredential.name, + plainDataObj: credentialData + } + const createResp = await credentialsApi.createCredential(obj) + if (createResp.data) { + enqueueSnackbar({ + message: 'New Credential added', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm(createResp.data.id) + } + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to add new Credential: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + + const saveCredential = async () => { + try { + const saveResp = await credentialsApi.updateCredential(credential.id, { + name, + credentialName: componentCredential.name, + plainDataObj: credentialData + }) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Credential saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm(saveResp.data.id) + } + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to save Credential: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + + const component = show ? ( + + + {componentCredential && componentCredential.label && ( +
+
+ {componentCredential.name} +
+ {componentCredential.label} +
+ )} +
+ + {componentCredential && componentCredential.description && ( + +
+ {parser(componentCredential.description)} +
+
+ )} + {componentCredential && componentCredential.label && ( + + + + Credential Name +  * + + + setName(e.target.value)} + /> + + )} + {componentCredential && + componentCredential.inputs && + componentCredential.inputs.map((inputParam, index) => ( + + ))} +
+ + (dialogProps.type === 'ADD' ? addNewCredential() : saveCredential())} + > + {dialogProps.confirmButtonName} + + + +
+ ) : null + + return createPortal(component, portalElement) +} + +AddEditCredentialDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func, + onConfirm: PropTypes.func +} + +export default AddEditCredentialDialog diff --git a/packages/ui/src/views/credentials/CredentialInputHandler.js b/packages/ui/src/views/credentials/CredentialInputHandler.js new file mode 100644 index 00000000..30cc5746 --- /dev/null +++ b/packages/ui/src/views/credentials/CredentialInputHandler.js @@ -0,0 +1,137 @@ +import PropTypes from 'prop-types' +import { useRef, useState } from 'react' +import { useSelector } from 'react-redux' + +// material-ui +import { Box, Typography, IconButton } from '@mui/material' +import { IconArrowsMaximize, IconAlertTriangle } from '@tabler/icons' + +// project import +import { Dropdown } from 'ui-component/dropdown/Dropdown' +import { Input } from 'ui-component/input/Input' +import { SwitchInput } from 'ui-component/switch/Switch' +import { JsonEditorInput } from 'ui-component/json/JsonEditor' +import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' + +// ===========================|| NodeInputHandler ||=========================== // + +const CredentialInputHandler = ({ inputParam, data, disabled = false }) => { + const customization = useSelector((state) => state.customization) + const ref = useRef(null) + + const [showExpandDialog, setShowExpandDialog] = useState(false) + const [expandDialogProps, setExpandDialogProps] = useState({}) + + const onExpandDialogClicked = (value, inputParam) => { + const dialogProp = { + value, + inputParam, + disabled, + confirmButtonName: 'Save', + cancelButtonName: 'Cancel' + } + setExpandDialogProps(dialogProp) + setShowExpandDialog(true) + } + + const onExpandDialogSave = (newValue, inputParamName) => { + setShowExpandDialog(false) + data[inputParamName] = newValue + } + + return ( +
+ {inputParam && ( + <> + +
+ + {inputParam.label} + {!inputParam.optional &&  *} + {inputParam.description && } + +
+ {inputParam.type === 'string' && inputParam.rows && ( + onExpandDialogClicked(data[inputParam.name] ?? inputParam.default ?? '', inputParam)} + > + + + )} +
+ {inputParam.warning && ( +
+ + {inputParam.warning} +
+ )} + + {inputParam.type === 'boolean' && ( + (data[inputParam.name] = newValue)} + value={data[inputParam.name] ?? inputParam.default ?? false} + /> + )} + {(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && ( + (data[inputParam.name] = newValue)} + value={data[inputParam.name] ?? inputParam.default ?? ''} + showDialog={showExpandDialog} + dialogProps={expandDialogProps} + onDialogCancel={() => setShowExpandDialog(false)} + onDialogConfirm={(newValue, inputParamName) => onExpandDialogSave(newValue, inputParamName)} + /> + )} + {inputParam.type === 'json' && ( + (data[inputParam.name] = newValue)} + value={data[inputParam.name] ?? inputParam.default ?? ''} + isDarkMode={customization.isDarkMode} + /> + )} + {inputParam.type === 'options' && ( + (data[inputParam.name] = newValue)} + value={data[inputParam.name] ?? inputParam.default ?? 'choose an option'} + /> + )} +
+ + )} +
+ ) +} + +CredentialInputHandler.propTypes = { + inputAnchor: PropTypes.object, + inputParam: PropTypes.object, + data: PropTypes.object, + disabled: PropTypes.bool +} + +export default CredentialInputHandler diff --git a/packages/ui/src/views/credentials/CredentialListDialog.js b/packages/ui/src/views/credentials/CredentialListDialog.js new file mode 100644 index 00000000..9333db67 --- /dev/null +++ b/packages/ui/src/views/credentials/CredentialListDialog.js @@ -0,0 +1,172 @@ +import { useState, useEffect } from 'react' +import { createPortal } from 'react-dom' +import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' +import { + List, + ListItemButton, + ListItem, + ListItemAvatar, + ListItemText, + Dialog, + DialogContent, + DialogTitle, + Box, + OutlinedInput, + InputAdornment +} from '@mui/material' +import { useTheme } from '@mui/material/styles' +import { IconSearch, IconX } from '@tabler/icons' + +// const +import { baseURL } from 'store/constant' + +const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelected }) => { + const portalElement = document.getElementById('portal') + const customization = useSelector((state) => state.customization) + + const theme = useTheme() + const [searchValue, setSearchValue] = useState('') + const [componentsCredentials, setComponentsCredentials] = useState([]) + + const filterSearch = (value) => { + setSearchValue(value) + setTimeout(() => { + if (value) { + const searchData = dialogProps.componentsCredentials.filter((crd) => crd.name.toLowerCase().includes(value.toLowerCase())) + setComponentsCredentials(searchData) + } else if (value === '') { + setComponentsCredentials(dialogProps.componentsCredentials) + } + // scrollTop() + }, 500) + } + + useEffect(() => { + if (show && dialogProps.componentsCredentials) { + setComponentsCredentials(dialogProps.componentsCredentials) + } + }, [show, dialogProps]) + + const component = show ? ( + + + {dialogProps.title} + + filterSearch(e.target.value)} + placeholder='Search credential' + startAdornment={ + + + + } + endAdornment={ + + filterSearch('')} + style={{ + cursor: 'pointer' + }} + /> + + } + aria-describedby='search-helper-text' + inputProps={{ + 'aria-label': 'weight' + }} + /> + + + + + {[...componentsCredentials].map((componentCredential) => ( +
+ onCredentialSelected(componentCredential)} + sx={{ p: 0, borderRadius: `${customization.borderRadius}px` }} + > + + +
+ {componentCredential.name} +
+
+ +
+
+
+ ))} +
+
+
+ ) : null + + return createPortal(component, portalElement) +} + +CredentialListDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func, + onCredentialSelected: PropTypes.func +} + +export default CredentialListDialog diff --git a/packages/ui/src/views/credentials/index.js b/packages/ui/src/views/credentials/index.js new file mode 100644 index 00000000..bcd641ed --- /dev/null +++ b/packages/ui/src/views/credentials/index.js @@ -0,0 +1,274 @@ +import { useEffect, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import moment from 'moment' + +// material-ui +import { Button, Box, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, IconButton } from '@mui/material' +import { useTheme } from '@mui/material/styles' + +// project imports +import MainCard from 'ui-component/cards/MainCard' +import { StyledButton } from 'ui-component/button/StyledButton' +import CredentialListDialog from './CredentialListDialog' +import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' +import AddEditCredentialDialog from './AddEditCredentialDialog' + +// API +import credentialsApi from 'api/credentials' + +// Hooks +import useApi from 'hooks/useApi' +import useConfirm from 'hooks/useConfirm' + +// utils +import useNotifier from 'utils/useNotifier' + +// Icons +import { IconTrash, IconEdit, IconX, IconPlus } from '@tabler/icons' +import CredentialEmptySVG from 'assets/images/credential_empty.svg' + +// const +import { baseURL } from 'store/constant' + +// ==============================|| Credentials ||============================== // + +const Credentials = () => { + const theme = useTheme() + const customization = useSelector((state) => state.customization) + + const dispatch = useDispatch() + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [showCredentialListDialog, setShowCredentialListDialog] = useState(false) + const [credentialListDialogProps, setCredentialListDialogProps] = useState({}) + const [showSpecificCredentialDialog, setShowSpecificCredentialDialog] = useState(false) + const [specificCredentialDialogProps, setSpecificCredentialDialogProps] = useState({}) + const [credentials, setCredentials] = useState([]) + const [componentsCredentials, setComponentsCredentials] = useState([]) + + const { confirm } = useConfirm() + + const getAllCredentialsApi = useApi(credentialsApi.getAllCredentials) + const getAllComponentsCredentialsApi = useApi(credentialsApi.getAllComponentsCredentials) + + const listCredential = () => { + const dialogProp = { + title: 'Add New Credential', + componentsCredentials + } + setCredentialListDialogProps(dialogProp) + setShowCredentialListDialog(true) + } + + const addNew = (credentialComponent) => { + const dialogProp = { + type: 'ADD', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add', + credentialComponent + } + setSpecificCredentialDialogProps(dialogProp) + setShowSpecificCredentialDialog(true) + } + + const edit = (credential) => { + const dialogProp = { + type: 'EDIT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Save', + data: credential + } + setSpecificCredentialDialogProps(dialogProp) + setShowSpecificCredentialDialog(true) + } + + const deleteCredential = async (credential) => { + const confirmPayload = { + title: `Delete`, + description: `Delete credential ${credential.name}?`, + confirmButtonName: 'Delete', + cancelButtonName: 'Cancel' + } + const isConfirmed = await confirm(confirmPayload) + + if (isConfirmed) { + try { + const deleteResp = await credentialsApi.deleteCredential(credential.id) + if (deleteResp.data) { + enqueueSnackbar({ + message: 'Credential deleted', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm() + } + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to delete Credential: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + } + + const onCredentialSelected = (credentialComponent) => { + setShowCredentialListDialog(false) + addNew(credentialComponent) + } + + const onConfirm = () => { + setShowCredentialListDialog(false) + setShowSpecificCredentialDialog(false) + getAllCredentialsApi.request() + } + + useEffect(() => { + getAllCredentialsApi.request() + getAllComponentsCredentialsApi.request() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (getAllCredentialsApi.data) { + setCredentials(getAllCredentialsApi.data) + } + }, [getAllCredentialsApi.data]) + + useEffect(() => { + if (getAllComponentsCredentialsApi.data) { + setComponentsCredentials(getAllComponentsCredentialsApi.data) + } + }, [getAllComponentsCredentialsApi.data]) + + return ( + <> + + +

Credentials 

+ + + } + > + Add Credential + +
+ {credentials.length <= 0 && ( + + + CredentialEmptySVG + +
No Credentials Yet
+
+ )} + {credentials.length > 0 && ( + + + + + Name + Last Updated + Created + + + + + + {credentials.map((credential, index) => ( + + +
+
+ {credential.credentialName} +
+ {credential.name} +
+
+ {moment(credential.updatedDate).format('DD-MMM-YY')} + {moment(credential.createdDate).format('DD-MMM-YY')} + + edit(credential)}> + + + + + deleteCredential(credential)}> + + + +
+ ))} +
+
+
+ )} +
+ setShowCredentialListDialog(false)} + onCredentialSelected={onCredentialSelected} + > + setShowSpecificCredentialDialog(false)} + onConfirm={onConfirm} + > + + + ) +} + +export default Credentials From c4e4e92fb213d5a208474b60ca057306b84200d7 Mon Sep 17 00:00:00 2001 From: denchi Date: Sat, 15 Jul 2023 15:56:52 +0100 Subject: [PATCH 230/398] Fix linting error --- .../components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts b/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts index 75d0d5c5..76629392 100644 --- a/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts +++ b/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts @@ -31,7 +31,7 @@ class BraveSearchAPI_Tools implements INode { async init(nodeData: INodeData): Promise { const apiKey = nodeData.inputs?.apiKey as string - return new BraveSearch({apiKey}) + return new BraveSearch({ apiKey }) } } From ff755c3d1b3b58d58b87de2cbd6bab84473737c5 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 15 Jul 2023 19:43:09 +0100 Subject: [PATCH 231/398] update credentials --- .../AnthropicApi.credential.ts | 2 +- .../AzureOpenAIApi.credential.ts | 2 +- .../credentials/CohereApi.credential.ts | 21 ++++ .../credentials/ConfluenceApi.credential.ts | 31 ++++++ .../DynamodbMemoryApi.credential.ts | 27 +++++ .../credentials/FigmaApi.credential.ts | 25 +++++ .../credentials/GithubApi.credential.ts | 25 +++++ .../credentials/HuggingFaceApi.credential.ts | 21 ++++ .../MotorheadMemoryApi.credential.ts | 2 +- .../credentials/NotionApi.credential.ts | 24 +++++ .../OpenAIApi.credential.ts | 2 +- .../credentials/OpenAPIAuth.credential.ts | 23 ++++ .../credentials/PineconeApi.credential.ts | 27 +++++ .../credentials/QdrantApi.credential.ts | 22 ++++ .../credentials/SerpApi.credential.ts | 22 ++++ .../credentials/SerperApi.credential.ts | 22 ++++ .../credentials/SupabaseApi.credential.ts | 22 ++++ .../credentials/WeaviateApi.credential.ts | 22 ++++ .../credentials/ZapierNLAApi.credential.ts | 22 ++++ .../ZepMemoryApi.credential.ts | 4 +- .../OpenAIFunctionAgent.ts | 2 +- .../agents/OpenAIFunctionAgent/openai.png | Bin 3991 -> 0 bytes .../agents/OpenAIFunctionAgent/openai.svg | 1 + .../nodes/chains/ApiChain/OpenAPIChain.ts | 2 +- .../chatmodels/AzureChatOpenAI/Azure.svg | 6 +- .../ChatHuggingFace/ChatHuggingFace.ts | 41 ++++---- .../nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts | 2 +- .../nodes/chatmodels/ChatOpenAI/openai.png | Bin 3991 -> 0 bytes .../nodes/chatmodels/ChatOpenAI/openai.svg | 1 + .../documentloaders/Confluence/Confluence.ts | 38 +++---- .../nodes/documentloaders/Figma/Figma.ts | 38 ++++--- .../nodes/documentloaders/Figma/figma.png | Bin 176029 -> 0 bytes .../nodes/documentloaders/Figma/figma.svg | 1 + .../nodes/documentloaders/Github/Github.ts | 31 +++--- .../documentloaders/NotionDB/NotionDB.ts | 46 ++++---- .../documentloaders/NotionPage/NotionPage.ts | 99 ++++++++++++++++++ .../documentloaders/NotionPage/notion.png | Bin 0 -> 11406 bytes .../embeddings/AzureOpenAIEmbedding/Azure.svg | 6 +- .../AzureOpenAIEmbedding.ts | 48 +++------ .../CohereEmbedding/CohereEmbedding.ts | 24 +++-- .../HuggingFaceInferenceEmbedding.ts | 24 +++-- .../OpenAIEmbedding/OpenAIEmbedding.ts | 24 +++-- .../embeddings/OpenAIEmbedding/openai.png | Bin 3991 -> 0 bytes .../embeddings/OpenAIEmbedding/openai.svg | 1 + .../nodes/llms/Azure OpenAI/Azure.svg | 6 +- .../nodes/llms/Azure OpenAI/AzureOpenAI.ts | 48 +++------ .../components/nodes/llms/Cohere/Cohere.ts | 24 +++-- .../HuggingFaceInference.ts | 41 ++++---- .../components/nodes/llms/OpenAI/OpenAI.ts | 2 +- .../components/nodes/llms/OpenAI/openai.png | Bin 3991 -> 0 bytes .../components/nodes/llms/OpenAI/openai.svg | 1 + .../nodes/memory/DynamoDb/DynamoDb.ts | 42 ++++---- .../memory/MotorheadMemory/MotorheadMemory.ts | 15 +-- .../RedisBackedChatMemory.ts | 4 +- .../tools/OpenAPIToolkit/OpenAPIToolkit.ts | 29 +++-- .../components/nodes/tools/SerpAPI/SerpAPI.ts | 26 ++--- .../components/nodes/tools/Serper/Serper.ts | 26 ++--- .../nodes/tools/ZapierNLA/ZapierNLA.ts | 27 ++--- .../nodes/tools/ZapierNLA/zapier.png | Bin 502 -> 0 bytes .../nodes/tools/ZapierNLA/zapier.svg | 8 ++ .../Pinecone_Existing/Pinecone_Existing.ts | 29 +++-- .../Pinecone_Upsert/Pinecone_Upsert.ts | 29 +++-- .../Qdrant_Existing/Qdrant_Existing.ts | 28 ++--- .../vectorstores/Qdrant_Existing/qdrant.png | Bin 0 -> 11663 bytes .../Qdrant_Existing/qdrant_logo.svg | 27 ----- .../Qdrant_Upsert/Qdrant_Upsert.ts | 28 ++--- .../vectorstores/Qdrant_Upsert/qdrant.png | Bin 0 -> 11663 bytes .../Qdrant_Upsert/qdrant_logo.svg | 27 ----- .../Supabase_Existing/Supabase_Exisiting.ts | 22 ++-- .../Supabase_Upsert/Supabase_Upsert.ts | 22 ++-- .../Weaviate_Existing/Weaviate_Existing.ts | 25 +++-- .../Weaviate_Upsert/Weaviate_Upsert.ts | 25 +++-- packages/components/package.json | 2 + packages/components/tsconfig.json | 2 +- packages/server/src/NodesPool.ts | 4 +- 75 files changed, 911 insertions(+), 461 deletions(-) rename packages/components/{nodes/chatmodels/ChatAnthropic => credentials}/AnthropicApi.credential.ts (86%) rename packages/components/{nodes/chatmodels/AzureChatOpenAI => credentials}/AzureOpenAIApi.credential.ts (96%) create mode 100644 packages/components/credentials/CohereApi.credential.ts create mode 100644 packages/components/credentials/ConfluenceApi.credential.ts create mode 100644 packages/components/credentials/DynamodbMemoryApi.credential.ts create mode 100644 packages/components/credentials/FigmaApi.credential.ts create mode 100644 packages/components/credentials/GithubApi.credential.ts create mode 100644 packages/components/credentials/HuggingFaceApi.credential.ts rename packages/components/{nodes/memory/MotorheadMemory => credentials}/MotorheadMemoryApi.credential.ts (91%) create mode 100644 packages/components/credentials/NotionApi.credential.ts rename packages/components/{nodes/chatmodels/ChatOpenAI => credentials}/OpenAIApi.credential.ts (85%) create mode 100644 packages/components/credentials/OpenAPIAuth.credential.ts create mode 100644 packages/components/credentials/PineconeApi.credential.ts create mode 100644 packages/components/credentials/QdrantApi.credential.ts create mode 100644 packages/components/credentials/SerpApi.credential.ts create mode 100644 packages/components/credentials/SerperApi.credential.ts create mode 100644 packages/components/credentials/SupabaseApi.credential.ts create mode 100644 packages/components/credentials/WeaviateApi.credential.ts create mode 100644 packages/components/credentials/ZapierNLAApi.credential.ts rename packages/components/{nodes/memory/ZepMemory => credentials}/ZepMemoryApi.credential.ts (84%) delete mode 100644 packages/components/nodes/agents/OpenAIFunctionAgent/openai.png create mode 100644 packages/components/nodes/agents/OpenAIFunctionAgent/openai.svg delete mode 100644 packages/components/nodes/chatmodels/ChatOpenAI/openai.png create mode 100644 packages/components/nodes/chatmodels/ChatOpenAI/openai.svg delete mode 100644 packages/components/nodes/documentloaders/Figma/figma.png create mode 100644 packages/components/nodes/documentloaders/Figma/figma.svg create mode 100644 packages/components/nodes/documentloaders/NotionPage/NotionPage.ts create mode 100644 packages/components/nodes/documentloaders/NotionPage/notion.png delete mode 100644 packages/components/nodes/embeddings/OpenAIEmbedding/openai.png create mode 100644 packages/components/nodes/embeddings/OpenAIEmbedding/openai.svg delete mode 100644 packages/components/nodes/llms/OpenAI/openai.png create mode 100644 packages/components/nodes/llms/OpenAI/openai.svg delete mode 100644 packages/components/nodes/tools/ZapierNLA/zapier.png create mode 100644 packages/components/nodes/tools/ZapierNLA/zapier.svg create mode 100644 packages/components/nodes/vectorstores/Qdrant_Existing/qdrant.png delete mode 100644 packages/components/nodes/vectorstores/Qdrant_Existing/qdrant_logo.svg create mode 100644 packages/components/nodes/vectorstores/Qdrant_Upsert/qdrant.png delete mode 100644 packages/components/nodes/vectorstores/Qdrant_Upsert/qdrant_logo.svg diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts b/packages/components/credentials/AnthropicApi.credential.ts similarity index 86% rename from packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts rename to packages/components/credentials/AnthropicApi.credential.ts index 607fa625..448128f1 100644 --- a/packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts +++ b/packages/components/credentials/AnthropicApi.credential.ts @@ -1,4 +1,4 @@ -import { INodeParams, INodeCredential } from '../../../src/Interface' +import { INodeParams, INodeCredential } from '../src/Interface' class AnthropicApi implements INodeCredential { label: string diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts b/packages/components/credentials/AzureOpenAIApi.credential.ts similarity index 96% rename from packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts rename to packages/components/credentials/AzureOpenAIApi.credential.ts index d48e0c88..e880c91c 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts +++ b/packages/components/credentials/AzureOpenAIApi.credential.ts @@ -1,4 +1,4 @@ -import { INodeParams, INodeCredential } from '../../../src/Interface' +import { INodeParams, INodeCredential } from '../src/Interface' class AzureOpenAIApi implements INodeCredential { label: string diff --git a/packages/components/credentials/CohereApi.credential.ts b/packages/components/credentials/CohereApi.credential.ts new file mode 100644 index 00000000..488644a2 --- /dev/null +++ b/packages/components/credentials/CohereApi.credential.ts @@ -0,0 +1,21 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class CohereApi implements INodeCredential { + label: string + name: string + inputs: INodeParams[] + + constructor() { + this.label = 'Cohere API' + this.name = 'cohereApi' + this.inputs = [ + { + label: 'Cohere Api Key', + name: 'cohereApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: CohereApi } diff --git a/packages/components/credentials/ConfluenceApi.credential.ts b/packages/components/credentials/ConfluenceApi.credential.ts new file mode 100644 index 00000000..75ea1d88 --- /dev/null +++ b/packages/components/credentials/ConfluenceApi.credential.ts @@ -0,0 +1,31 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class ConfluenceApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Confluence API' + this.name = 'confluenceApi' + this.description = + 'Refer to official guide on how to get accessToken on Confluence' + this.inputs = [ + { + label: 'Access Token', + name: 'accessToken', + type: 'password', + placeholder: '' + }, + { + label: 'Username', + name: 'username', + type: 'string', + placeholder: '' + } + ] + } +} + +module.exports = { credClass: ConfluenceApi } diff --git a/packages/components/credentials/DynamodbMemoryApi.credential.ts b/packages/components/credentials/DynamodbMemoryApi.credential.ts new file mode 100644 index 00000000..5bdfce37 --- /dev/null +++ b/packages/components/credentials/DynamodbMemoryApi.credential.ts @@ -0,0 +1,27 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class DynamodbMemoryApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'DynamodbMemory API' + this.name = 'dynamodbMemoryApi' + this.inputs = [ + { + label: 'Access Key', + name: 'accessKey', + type: 'password' + }, + { + label: 'Secret Access Key', + name: 'secretAccessKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: DynamodbMemoryApi } diff --git a/packages/components/credentials/FigmaApi.credential.ts b/packages/components/credentials/FigmaApi.credential.ts new file mode 100644 index 00000000..49638885 --- /dev/null +++ b/packages/components/credentials/FigmaApi.credential.ts @@ -0,0 +1,25 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class FigmaApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Figma API' + this.name = 'figmaApi' + this.description = + 'Refer to official guide on how to get accessToken on Figma' + this.inputs = [ + { + label: 'Access Token', + name: 'accessToken', + type: 'password', + placeholder: '' + } + ] + } +} + +module.exports = { credClass: FigmaApi } diff --git a/packages/components/credentials/GithubApi.credential.ts b/packages/components/credentials/GithubApi.credential.ts new file mode 100644 index 00000000..ffbe4739 --- /dev/null +++ b/packages/components/credentials/GithubApi.credential.ts @@ -0,0 +1,25 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class GithubApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Github API' + this.name = 'githubApi' + this.description = + 'Refer to official guide on how to get accessToken on Github' + this.inputs = [ + { + label: 'Access Token', + name: 'accessToken', + type: 'password', + placeholder: '' + } + ] + } +} + +module.exports = { credClass: GithubApi } diff --git a/packages/components/credentials/HuggingFaceApi.credential.ts b/packages/components/credentials/HuggingFaceApi.credential.ts new file mode 100644 index 00000000..2dae4319 --- /dev/null +++ b/packages/components/credentials/HuggingFaceApi.credential.ts @@ -0,0 +1,21 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class HuggingFaceApi implements INodeCredential { + label: string + name: string + inputs: INodeParams[] + + constructor() { + this.label = 'HuggingFace API' + this.name = 'huggingFaceApi' + this.inputs = [ + { + label: 'HuggingFace Api Key', + name: 'huggingFaceApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: HuggingFaceApi } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts b/packages/components/credentials/MotorheadMemoryApi.credential.ts similarity index 91% rename from packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts rename to packages/components/credentials/MotorheadMemoryApi.credential.ts index 4563cda2..937a9402 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts +++ b/packages/components/credentials/MotorheadMemoryApi.credential.ts @@ -1,4 +1,4 @@ -import { INodeParams, INodeCredential } from '../../../src/Interface' +import { INodeParams, INodeCredential } from '../src/Interface' class MotorheadMemoryApi implements INodeCredential { label: string diff --git a/packages/components/credentials/NotionApi.credential.ts b/packages/components/credentials/NotionApi.credential.ts new file mode 100644 index 00000000..47d03f3e --- /dev/null +++ b/packages/components/credentials/NotionApi.credential.ts @@ -0,0 +1,24 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class NotionApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Notion API' + this.name = 'notionApi' + this.description = + 'You can find integration token here' + this.inputs = [ + { + label: 'Notion Integration Token', + name: 'notionIntegrationToken', + type: 'password' + } + ] + } +} + +module.exports = { credClass: NotionApi } diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts b/packages/components/credentials/OpenAIApi.credential.ts similarity index 85% rename from packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts rename to packages/components/credentials/OpenAIApi.credential.ts index 96209a35..9aebf049 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts +++ b/packages/components/credentials/OpenAIApi.credential.ts @@ -1,4 +1,4 @@ -import { INodeParams, INodeCredential } from '../../../src/Interface' +import { INodeParams, INodeCredential } from '../src/Interface' class OpenAIApi implements INodeCredential { label: string diff --git a/packages/components/credentials/OpenAPIAuth.credential.ts b/packages/components/credentials/OpenAPIAuth.credential.ts new file mode 100644 index 00000000..7cc2d318 --- /dev/null +++ b/packages/components/credentials/OpenAPIAuth.credential.ts @@ -0,0 +1,23 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class OpenAPIAuth implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'OpenAPI Auth Token' + this.name = 'openAPIAuth' + this.inputs = [ + { + label: 'OpenAPI Token', + name: 'openAPIToken', + type: 'password', + description: 'Auth Token. For example: Bearer ' + } + ] + } +} + +module.exports = { credClass: OpenAPIAuth } diff --git a/packages/components/credentials/PineconeApi.credential.ts b/packages/components/credentials/PineconeApi.credential.ts new file mode 100644 index 00000000..393bfd46 --- /dev/null +++ b/packages/components/credentials/PineconeApi.credential.ts @@ -0,0 +1,27 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class PineconeApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Pinecone API' + this.name = 'pineconeApi' + this.inputs = [ + { + label: 'Pinecone Api Key', + name: 'pineconeApiKey', + type: 'password' + }, + { + label: 'Pinecone Environment', + name: 'pineconeEnv', + type: 'string' + } + ] + } +} + +module.exports = { credClass: PineconeApi } diff --git a/packages/components/credentials/QdrantApi.credential.ts b/packages/components/credentials/QdrantApi.credential.ts new file mode 100644 index 00000000..1738cc45 --- /dev/null +++ b/packages/components/credentials/QdrantApi.credential.ts @@ -0,0 +1,22 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class QdrantApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Qdrant API' + this.name = 'qdrantApi' + this.inputs = [ + { + label: 'Qdrant API Key', + name: 'qdrantApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: QdrantApi } diff --git a/packages/components/credentials/SerpApi.credential.ts b/packages/components/credentials/SerpApi.credential.ts new file mode 100644 index 00000000..0c18b103 --- /dev/null +++ b/packages/components/credentials/SerpApi.credential.ts @@ -0,0 +1,22 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class SerpApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Serp API' + this.name = 'serpApi' + this.inputs = [ + { + label: 'Serp Api Key', + name: 'serpApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: SerpApi } diff --git a/packages/components/credentials/SerperApi.credential.ts b/packages/components/credentials/SerperApi.credential.ts new file mode 100644 index 00000000..71e61b32 --- /dev/null +++ b/packages/components/credentials/SerperApi.credential.ts @@ -0,0 +1,22 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class SerperApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Serper API' + this.name = 'serperApi' + this.inputs = [ + { + label: 'Serper Api Key', + name: 'serperApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: SerperApi } diff --git a/packages/components/credentials/SupabaseApi.credential.ts b/packages/components/credentials/SupabaseApi.credential.ts new file mode 100644 index 00000000..d485e401 --- /dev/null +++ b/packages/components/credentials/SupabaseApi.credential.ts @@ -0,0 +1,22 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class SupabaseApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Supabase API' + this.name = 'supabaseApi' + this.inputs = [ + { + label: 'Supabase API Key', + name: 'supabaseApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: SupabaseApi } diff --git a/packages/components/credentials/WeaviateApi.credential.ts b/packages/components/credentials/WeaviateApi.credential.ts new file mode 100644 index 00000000..3d5dd5b9 --- /dev/null +++ b/packages/components/credentials/WeaviateApi.credential.ts @@ -0,0 +1,22 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class WeaviateApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Weaviate API' + this.name = 'weaviateApi' + this.inputs = [ + { + label: 'Weaviate API Key', + name: 'weaviateApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: WeaviateApi } diff --git a/packages/components/credentials/ZapierNLAApi.credential.ts b/packages/components/credentials/ZapierNLAApi.credential.ts new file mode 100644 index 00000000..03cb01b8 --- /dev/null +++ b/packages/components/credentials/ZapierNLAApi.credential.ts @@ -0,0 +1,22 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class ZapierNLAApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Zapier NLA API' + this.name = 'zapierNLAApi' + this.inputs = [ + { + label: 'Zapier NLA Api Key', + name: 'zapierNLAApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: ZapierNLAApi } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts b/packages/components/credentials/ZepMemoryApi.credential.ts similarity index 84% rename from packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts rename to packages/components/credentials/ZepMemoryApi.credential.ts index 5e92ef5d..d886328b 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts +++ b/packages/components/credentials/ZepMemoryApi.credential.ts @@ -1,4 +1,4 @@ -import { INodeParams, INodeCredential } from '../../../src/Interface' +import { INodeParams, INodeCredential } from '../src/Interface' class ZepMemoryApi implements INodeCredential { label: string @@ -7,7 +7,7 @@ class ZepMemoryApi implements INodeCredential { inputs: INodeParams[] constructor() { - this.label = 'Zep Memory Api' + this.label = 'Zep Memory API' this.name = 'zepMemoryApi' this.description = 'Refer to official guide on how to create API key on Zep' diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index f4d065d9..9faf83dd 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -22,7 +22,7 @@ class OpenAIFunctionAgent_Agents implements INode { this.name = 'openAIFunctionAgent' this.type = 'AgentExecutor' this.category = 'Agents' - this.icon = 'openai.png' + this.icon = 'openai.svg' this.description = `An agent that uses OpenAI's Function Calling functionality to pick the tool and args to call` this.baseClasses = [this.type, ...getBaseClasses(AgentExecutor)] this.inputs = [ diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/openai.png b/packages/components/nodes/agents/OpenAIFunctionAgent/openai.png deleted file mode 100644 index de08a05b28979826c4cc669c4899789763a938a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3991 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 diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/openai.svg b/packages/components/nodes/agents/OpenAIFunctionAgent/openai.svg new file mode 100644 index 00000000..9d3261dd --- /dev/null +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/openai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts index a231e80a..583ca1f4 100644 --- a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -20,7 +20,7 @@ class OpenApiChain_Chains implements INode { this.type = 'openApiChain' this.icon = 'openapi.png' this.category = 'Chains' - this.description = 'Chain to run queries against OpenAPI' + this.description = 'Chain that automatically select and call APIs based only on an OpenAPI spec' this.baseClasses = [this.type, ...getBaseClasses(APIChain)] this.inputs = [ { diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/Azure.svg b/packages/components/nodes/chatmodels/AzureChatOpenAI/Azure.svg index 51eb6253..47ad8c44 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/Azure.svg +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/Azure.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts index d92dd1e0..f192fefd 100644 --- a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { HFInput, HuggingFaceInference } from './core' class ChatHuggingFace_ChatModels implements INode { @@ -10,6 +10,7 @@ class ChatHuggingFace_ChatModels implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,17 +21,28 @@ class ChatHuggingFace_ChatModels implements INode { this.category = 'Chat Models' this.description = 'Wrapper around HuggingFace large language models' this.baseClasses = [this.type, 'BaseChatModel', ...getBaseClasses(HuggingFaceInference)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['huggingFaceApi'] + } this.inputs = [ { label: 'Model', name: 'model', type: 'string', - placeholder: 'gpt2' + description: 'If using own inference endpoint, leave this blank', + placeholder: 'gpt2', + optional: true }, { - label: 'HuggingFace Api Key', - name: 'apiKey', - type: 'password' + label: 'Endpoint', + name: 'endpoint', + type: 'string', + placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2', + description: 'Using your own inference endpoint', + optional: true }, { label: 'Temperature', @@ -71,22 +83,12 @@ class ChatHuggingFace_ChatModels implements INode { description: 'Frequency Penalty parameter may not apply to certain model. Please check available model parameters', optional: true, additionalParams: true - }, - { - label: 'Endpoint', - name: 'endpoint', - type: 'string', - placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2', - description: 'Using your own inference endpoint', - optional: true, - additionalParams: true } ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as string - const apiKey = nodeData.inputs?.apiKey as string const temperature = nodeData.inputs?.temperature as string const maxTokens = nodeData.inputs?.maxTokens as string const topP = nodeData.inputs?.topP as string @@ -94,9 +96,12 @@ class ChatHuggingFace_ChatModels implements INode { const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string const endpoint = nodeData.inputs?.endpoint as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const huggingFaceApiKey = getCredentialParam('huggingFaceApiKey', credentialData, nodeData) + const obj: Partial = { model, - apiKey + apiKey: huggingFaceApiKey } if (temperature) obj.temperature = parseFloat(temperature) diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts index 1339d1fe..13262b5f 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts @@ -17,7 +17,7 @@ class ChatOpenAI_ChatModels implements INode { this.label = 'ChatOpenAI' this.name = 'chatOpenAI' this.type = 'ChatOpenAI' - this.icon = 'openai.png' + this.icon = 'openai.svg' this.category = 'Chat Models' this.description = 'Wrapper around OpenAI large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatOpenAI)] diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/openai.png b/packages/components/nodes/chatmodels/ChatOpenAI/openai.png deleted file mode 100644 index de08a05b28979826c4cc669c4899789763a938a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3991 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 diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/openai.svg b/packages/components/nodes/chatmodels/ChatOpenAI/openai.svg new file mode 100644 index 00000000..9d3261dd --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatOpenAI/openai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/documentloaders/Confluence/Confluence.ts b/packages/components/nodes/documentloaders/Confluence/Confluence.ts index 9a69be14..db992310 100644 --- a/packages/components/nodes/documentloaders/Confluence/Confluence.ts +++ b/packages/components/nodes/documentloaders/Confluence/Confluence.ts @@ -1,6 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { ConfluencePagesLoader, ConfluencePagesLoaderParams } from 'langchain/document_loaders/web/confluence' +import { getCredentialData, getCredentialParam } from '../../../src' class Confluence_DocumentLoaders implements INode { label: string @@ -10,6 +11,7 @@ class Confluence_DocumentLoaders implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,6 +22,12 @@ class Confluence_DocumentLoaders implements INode { this.category = 'Document Loaders' this.description = `Load data from a Confluence Document` this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['confluenceApi'] + } this.inputs = [ { label: 'Text Splitter', @@ -27,18 +35,6 @@ class Confluence_DocumentLoaders implements INode { type: 'TextSplitter', optional: true }, - { - label: 'Username', - name: 'username', - type: 'string', - placeholder: '' - }, - { - label: 'Access Token', - name: 'accessToken', - type: 'password', - placeholder: '' - }, { label: 'Base URL', name: 'baseUrl', @@ -49,7 +45,9 @@ class Confluence_DocumentLoaders implements INode { label: 'Space Key', name: 'spaceKey', type: 'string', - placeholder: '~EXAMPLE362906de5d343d49dcdbae5dEXAMPLE' + placeholder: '~EXAMPLE362906de5d343d49dcdbae5dEXAMPLE', + description: + 'Refer to official guide on how to get Confluence Space Key' }, { label: 'Limit', @@ -68,16 +66,18 @@ class Confluence_DocumentLoaders implements INode { ] } - async init(nodeData: INodeData): Promise { - const username = nodeData.inputs?.username as string - const accessToken = nodeData.inputs?.accessToken as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const spaceKey = nodeData.inputs?.spaceKey as string const baseUrl = nodeData.inputs?.baseUrl as string const limit = nodeData.inputs?.limit as number const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const options: ConfluencePagesLoaderParams = { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const accessToken = getCredentialParam('accessToken', credentialData, nodeData) + const username = getCredentialParam('username', credentialData, nodeData) + + const confluenceOptions: ConfluencePagesLoaderParams = { username, accessToken, baseUrl, @@ -85,7 +85,7 @@ class Confluence_DocumentLoaders implements INode { limit } - const loader = new ConfluencePagesLoader(options) + const loader = new ConfluencePagesLoader(confluenceOptions) let docs = [] diff --git a/packages/components/nodes/documentloaders/Figma/Figma.ts b/packages/components/nodes/documentloaders/Figma/Figma.ts index 388c4ee0..e570490e 100644 --- a/packages/components/nodes/documentloaders/Figma/Figma.ts +++ b/packages/components/nodes/documentloaders/Figma/Figma.ts @@ -1,4 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getCredentialData, getCredentialParam } from '../../../src' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { FigmaFileLoader, FigmaLoaderParams } from 'langchain/document_loaders/web/figma' class Figma_DocumentLoaders implements INode { @@ -9,34 +10,39 @@ class Figma_DocumentLoaders implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { this.label = 'Figma' this.name = 'figma' this.type = 'Document' - this.icon = 'figma.png' + this.icon = 'figma.svg' this.category = 'Document Loaders' this.description = 'Load data from a Figma file' this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['figmaApi'] + } this.inputs = [ - { - label: 'Access Token', - name: 'accessToken', - type: 'password', - placeholder: '' - }, { label: 'File Key', name: 'fileKey', type: 'string', - placeholder: 'key' + placeholder: 'key', + description: + 'The file key can be read from any Figma file URL: https://www.figma.com/file/:key/:title. For example, in https://www.figma.com/file/12345/Website, the file key is 12345' }, { label: 'Node IDs', name: 'nodeIds', type: 'string', - placeholder: '0, 1, 2' + placeholder: '0, 1, 2', + description: + 'A list of Node IDs, seperated by comma. Refer to official guide on how to get Node IDs' }, { label: 'Recursive', @@ -60,18 +66,20 @@ class Figma_DocumentLoaders implements INode { ] } - async init(nodeData: INodeData): Promise { - const accessToken = nodeData.inputs?.accessToken as string - const nodeIds = (nodeData.inputs?.nodeIds as string)?.split(',') || [] + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const nodeIds = (nodeData.inputs?.nodeIds as string)?.trim().split(',') || [] const fileKey = nodeData.inputs?.fileKey as string - const options: FigmaLoaderParams = { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const accessToken = getCredentialParam('accessToken', credentialData, nodeData) + + const figmaOptions: FigmaLoaderParams = { accessToken, nodeIds, fileKey } - const loader = new FigmaFileLoader(options) + const loader = new FigmaFileLoader(figmaOptions) const docs = await loader.load() return docs diff --git a/packages/components/nodes/documentloaders/Figma/figma.png b/packages/components/nodes/documentloaders/Figma/figma.png deleted file mode 100644 index 72372ddff0e6eec27448d24b10c46c987edf317d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176029 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

?1A{3edV5r?d-vCR~^xf@Hu&y&T1 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 V5p|!MT;T}c;(o \ No newline at end of file diff --git a/packages/components/nodes/documentloaders/Github/Github.ts b/packages/components/nodes/documentloaders/Github/Github.ts index bbaad3cb..4a968403 100644 --- a/packages/components/nodes/documentloaders/Github/Github.ts +++ b/packages/components/nodes/documentloaders/Github/Github.ts @@ -1,6 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { GithubRepoLoader, GithubRepoLoaderParams } from 'langchain/document_loaders/web/github' +import { getCredentialData, getCredentialParam } from '../../../src' class Github_DocumentLoaders implements INode { label: string @@ -10,6 +11,7 @@ class Github_DocumentLoaders implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,6 +22,14 @@ class Github_DocumentLoaders implements INode { this.category = 'Document Loaders' this.description = `Load data from a GitHub repository` this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + description: 'Only needed when accessing private repo', + optional: true, + credentialNames: ['githubApi'] + } this.inputs = [ { label: 'Repo Link', @@ -33,13 +43,6 @@ class Github_DocumentLoaders implements INode { type: 'string', default: 'main' }, - { - label: 'Access Token', - name: 'accessToken', - type: 'password', - placeholder: '', - optional: true - }, { label: 'Recursive', name: 'recursive', @@ -62,23 +65,25 @@ class Github_DocumentLoaders implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const repoLink = nodeData.inputs?.repoLink as string const branch = nodeData.inputs?.branch as string const recursive = nodeData.inputs?.recursive as boolean - const accessToken = nodeData.inputs?.accessToken as string const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const options: GithubRepoLoaderParams = { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const accessToken = getCredentialParam('accessToken', credentialData, nodeData) + + const githubOptions: GithubRepoLoaderParams = { branch, recursive, unknown: 'warn' } - if (accessToken) options.accessToken = accessToken + if (accessToken) githubOptions.accessToken = accessToken - const loader = new GithubRepoLoader(repoLink, options) + const loader = new GithubRepoLoader(repoLink, githubOptions) const docs = textSplitter ? await loader.loadAndSplit(textSplitter) : await loader.load() if (metadata) { diff --git a/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts b/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts index 71e5e507..5fec6083 100644 --- a/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts +++ b/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts @@ -1,6 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' -import { NotionDBLoader, NotionDBLoaderParams } from 'langchain/document_loaders/web/notiondb' +import { NotionAPILoader, NotionAPILoaderOptions } from 'langchain/document_loaders/web/notionapi' +import { getCredentialData, getCredentialParam } from '../../../src' class NotionDB_DocumentLoaders implements INode { label: string @@ -10,6 +11,7 @@ class NotionDB_DocumentLoaders implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -18,8 +20,14 @@ class NotionDB_DocumentLoaders implements INode { this.type = 'Document' this.icon = 'notion.png' this.category = 'Document Loaders' - this.description = 'Load data from Notion Database ID' + this.description = 'Load data from Notion Database (each row is a separate document with all properties as metadata)' this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['notionApi'] + } this.inputs = [ { label: 'Text Splitter', @@ -34,19 +42,6 @@ class NotionDB_DocumentLoaders implements INode { description: 'If your URL looks like - https://www.notion.so/?v=, then is the database ID' }, - { - label: 'Notion Integration Token', - name: 'notionIntegrationToken', - type: 'password', - description: - 'You can find integration token here' - }, - { - label: 'Page Size Limit', - name: 'pageSizeLimit', - type: 'number', - default: 10 - }, { label: 'Metadata', name: 'metadata', @@ -57,19 +52,22 @@ class NotionDB_DocumentLoaders implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const databaseId = nodeData.inputs?.databaseId as string - const notionIntegrationToken = nodeData.inputs?.notionIntegrationToken as string - const pageSizeLimit = nodeData.inputs?.pageSizeLimit as string const metadata = nodeData.inputs?.metadata - const obj: NotionDBLoaderParams = { - pageSizeLimit: pageSizeLimit ? parseInt(pageSizeLimit, 10) : 10, - databaseId, - notionIntegrationToken + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const notionIntegrationToken = getCredentialParam('notionIntegrationToken', credentialData, nodeData) + + const obj: NotionAPILoaderOptions = { + clientOptions: { + auth: notionIntegrationToken + }, + id: databaseId, + type: 'database' } - const loader = new NotionDBLoader(obj) + const loader = new NotionAPILoader(obj) let docs = [] if (textSplitter) { diff --git a/packages/components/nodes/documentloaders/NotionPage/NotionPage.ts b/packages/components/nodes/documentloaders/NotionPage/NotionPage.ts new file mode 100644 index 00000000..57da8aaa --- /dev/null +++ b/packages/components/nodes/documentloaders/NotionPage/NotionPage.ts @@ -0,0 +1,99 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { TextSplitter } from 'langchain/text_splitter' +import { NotionAPILoader, NotionAPILoaderOptions } from 'langchain/document_loaders/web/notionapi' +import { getCredentialData, getCredentialParam } from '../../../src' + +class NotionPage_DocumentLoaders implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'Notion Page' + this.name = 'notionPage' + this.type = 'Document' + this.icon = 'notion.png' + this.category = 'Document Loaders' + this.description = 'Load data from Notion Page (including child pages all as separate documents)' + this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['notionApi'] + } + this.inputs = [ + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + label: 'Notion Page Id', + name: 'pageId', + type: 'string', + description: + 'The last The 32 char hex in the url path. For example: https://www.notion.so/skarard/LangChain-Notion-API-b34ca03f219c4420a6046fc4bdfdf7b4, b34ca03f219c4420a6046fc4bdfdf7b4 is the Page ID' + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const textSplitter = nodeData.inputs?.textSplitter as TextSplitter + const pageId = nodeData.inputs?.pageId as string + const metadata = nodeData.inputs?.metadata + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const notionIntegrationToken = getCredentialParam('notionIntegrationToken', credentialData, nodeData) + + const obj: NotionAPILoaderOptions = { + clientOptions: { + auth: notionIntegrationToken + }, + id: pageId, + type: 'page' + } + const loader = new NotionAPILoader(obj) + + let docs = [] + if (textSplitter) { + docs = await loader.loadAndSplit(textSplitter) + } else { + docs = await loader.load() + } + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + let finaldocs = [] + for (const doc of docs) { + const newdoc = { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + finaldocs.push(newdoc) + } + return finaldocs + } + + return docs + } +} + +module.exports = { nodeClass: NotionPage_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/NotionPage/notion.png b/packages/components/nodes/documentloaders/NotionPage/notion.png new file mode 100644 index 0000000000000000000000000000000000000000..391051679c8cc33e7e52891593147283bf93dcb0 GIT binary patch literal 11406 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 - - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts index 4133539e..15c9f460 100644 --- a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts +++ b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts @@ -1,6 +1,6 @@ import { AzureOpenAIInput } from 'langchain/chat_models/openai' -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { OpenAIEmbeddings, OpenAIEmbeddingsParams } from 'langchain/embeddings/openai' class AzureOpenAIEmbedding_Embeddings implements INode { @@ -11,6 +11,7 @@ class AzureOpenAIEmbedding_Embeddings implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -21,32 +22,13 @@ class AzureOpenAIEmbedding_Embeddings implements INode { this.category = 'Embeddings' this.description = 'Azure OpenAI API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(OpenAIEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['azureOpenAIApi'] + } this.inputs = [ - { - label: 'Azure OpenAI Api Key', - name: 'azureOpenAIApiKey', - type: 'password' - }, - { - label: 'Azure OpenAI Api Instance Name', - name: 'azureOpenAIApiInstanceName', - type: 'string', - placeholder: 'YOUR-INSTANCE-NAME' - }, - { - label: 'Azure OpenAI Api Deployment Name', - name: 'azureOpenAIApiDeploymentName', - type: 'string', - placeholder: 'YOUR-DEPLOYMENT-NAME' - }, - { - label: 'Azure OpenAI Api Version', - name: 'azureOpenAIApiVersion', - type: 'string', - placeholder: '2023-03-15-preview', - description: - 'Description of Supported API Versions. Please refer examples' - }, { label: 'Batch Size', name: 'batchSize', @@ -65,14 +47,16 @@ class AzureOpenAIEmbedding_Embeddings implements INode { ] } - async init(nodeData: INodeData): Promise { - const azureOpenAIApiKey = nodeData.inputs?.azureOpenAIApiKey as string - const azureOpenAIApiInstanceName = nodeData.inputs?.azureOpenAIApiInstanceName as string - const azureOpenAIApiDeploymentName = nodeData.inputs?.azureOpenAIApiDeploymentName as string - const azureOpenAIApiVersion = nodeData.inputs?.azureOpenAIApiVersion as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const batchSize = nodeData.inputs?.batchSize as string const timeout = nodeData.inputs?.timeout as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const azureOpenAIApiKey = getCredentialParam('azureOpenAIApiKey', credentialData, nodeData) + const azureOpenAIApiInstanceName = getCredentialParam('azureOpenAIApiInstanceName', credentialData, nodeData) + const azureOpenAIApiDeploymentName = getCredentialParam('azureOpenAIApiDeploymentName', credentialData, nodeData) + const azureOpenAIApiVersion = getCredentialParam('azureOpenAIApiVersion', credentialData, nodeData) + const obj: Partial & Partial = { azureOpenAIApiKey, azureOpenAIApiInstanceName, diff --git a/packages/components/nodes/embeddings/CohereEmbedding/CohereEmbedding.ts b/packages/components/nodes/embeddings/CohereEmbedding/CohereEmbedding.ts index 344713a4..914d643d 100644 --- a/packages/components/nodes/embeddings/CohereEmbedding/CohereEmbedding.ts +++ b/packages/components/nodes/embeddings/CohereEmbedding/CohereEmbedding.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { CohereEmbeddings, CohereEmbeddingsParams } from 'langchain/embeddings/cohere' class CohereEmbedding_Embeddings implements INode { @@ -10,6 +10,7 @@ class CohereEmbedding_Embeddings implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class CohereEmbedding_Embeddings implements INode { this.category = 'Embeddings' this.description = 'Cohere API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(CohereEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['cohereApi'] + } this.inputs = [ - { - label: 'Cohere API Key', - name: 'cohereApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -50,12 +52,14 @@ class CohereEmbedding_Embeddings implements INode { ] } - async init(nodeData: INodeData): Promise { - const apiKey = nodeData.inputs?.cohereApiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const modelName = nodeData.inputs?.modelName as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const cohereApiKey = getCredentialParam('cohereApiKey', credentialData, nodeData) + const obj: Partial & { apiKey?: string } = { - apiKey + apiKey: cohereApiKey } if (modelName) obj.modelName = modelName diff --git a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts index d77d623f..bfbb93ed 100644 --- a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts +++ b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { HuggingFaceInferenceEmbeddings, HuggingFaceInferenceEmbeddingsParams } from './core' class HuggingFaceInferenceEmbedding_Embeddings implements INode { @@ -10,6 +10,7 @@ class HuggingFaceInferenceEmbedding_Embeddings implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class HuggingFaceInferenceEmbedding_Embeddings implements INode { this.category = 'Embeddings' this.description = 'HuggingFace Inference API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(HuggingFaceInferenceEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['huggingFaceApi'] + } this.inputs = [ - { - label: 'HuggingFace Api Key', - name: 'apiKey', - type: 'password' - }, { label: 'Model', name: 'modelName', @@ -43,13 +45,15 @@ class HuggingFaceInferenceEmbedding_Embeddings implements INode { ] } - async init(nodeData: INodeData): Promise { - const apiKey = nodeData.inputs?.apiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const modelName = nodeData.inputs?.modelName as string const endpoint = nodeData.inputs?.endpoint as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const huggingFaceApiKey = getCredentialParam('huggingFaceApiKey', credentialData, nodeData) + const obj: Partial = { - apiKey + apiKey: huggingFaceApiKey } if (modelName) obj.model = modelName diff --git a/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding.ts b/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding.ts index 0fd08973..f2f5eb43 100644 --- a/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding.ts +++ b/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { OpenAIEmbeddings, OpenAIEmbeddingsParams } from 'langchain/embeddings/openai' class OpenAIEmbedding_Embeddings implements INode { @@ -10,22 +10,24 @@ class OpenAIEmbedding_Embeddings implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { this.label = 'OpenAI Embeddings' this.name = 'openAIEmbeddings' this.type = 'OpenAIEmbeddings' - this.icon = 'openai.png' + this.icon = 'openai.svg' this.category = 'Embeddings' this.description = 'OpenAI API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(OpenAIEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['openAIApi'] + } this.inputs = [ - { - label: 'OpenAI Api Key', - name: 'openAIApiKey', - type: 'password' - }, { label: 'Strip New Lines', name: 'stripNewLines', @@ -57,13 +59,15 @@ class OpenAIEmbedding_Embeddings implements INode { ] } - async init(nodeData: INodeData): Promise { - const openAIApiKey = nodeData.inputs?.openAIApiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const stripNewLines = nodeData.inputs?.stripNewLines as boolean const batchSize = nodeData.inputs?.batchSize as string const timeout = nodeData.inputs?.timeout as string const basePath = nodeData.inputs?.basepath as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) + const obj: Partial & { openAIApiKey?: string } = { openAIApiKey } diff --git a/packages/components/nodes/embeddings/OpenAIEmbedding/openai.png b/packages/components/nodes/embeddings/OpenAIEmbedding/openai.png deleted file mode 100644 index de08a05b28979826c4cc669c4899789763a938a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3991 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 diff --git a/packages/components/nodes/embeddings/OpenAIEmbedding/openai.svg b/packages/components/nodes/embeddings/OpenAIEmbedding/openai.svg new file mode 100644 index 00000000..9d3261dd --- /dev/null +++ b/packages/components/nodes/embeddings/OpenAIEmbedding/openai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/llms/Azure OpenAI/Azure.svg b/packages/components/nodes/llms/Azure OpenAI/Azure.svg index 51eb6253..47ad8c44 100644 --- a/packages/components/nodes/llms/Azure OpenAI/Azure.svg +++ b/packages/components/nodes/llms/Azure OpenAI/Azure.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts index 130eed33..9729dd41 100644 --- a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts +++ b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { AzureOpenAIInput, OpenAI, OpenAIInput } from 'langchain/llms/openai' class AzureOpenAI_LLMs implements INode { @@ -10,6 +10,7 @@ class AzureOpenAI_LLMs implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class AzureOpenAI_LLMs implements INode { this.category = 'LLMs' this.description = 'Wrapper around Azure OpenAI large language models' this.baseClasses = [this.type, ...getBaseClasses(OpenAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['azureOpenAIApi'] + } this.inputs = [ - { - label: 'Azure OpenAI Api Key', - name: 'azureOpenAIApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -90,26 +92,6 @@ class AzureOpenAI_LLMs implements INode { default: 0.9, optional: true }, - { - label: 'Azure OpenAI Api Instance Name', - name: 'azureOpenAIApiInstanceName', - type: 'string', - placeholder: 'YOUR-INSTANCE-NAME' - }, - { - label: 'Azure OpenAI Api Deployment Name', - name: 'azureOpenAIApiDeploymentName', - type: 'string', - placeholder: 'YOUR-DEPLOYMENT-NAME' - }, - { - label: 'Azure OpenAI Api Version', - name: 'azureOpenAIApiVersion', - type: 'string', - placeholder: '2023-06-01-preview', - description: - 'Description of Supported API Versions. Please refer examples' - }, { label: 'Max Tokens', name: 'maxTokens', @@ -155,13 +137,9 @@ class AzureOpenAI_LLMs implements INode { ] } - async init(nodeData: INodeData): Promise { - const azureOpenAIApiKey = nodeData.inputs?.azureOpenAIApiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const temperature = nodeData.inputs?.temperature as string const modelName = nodeData.inputs?.modelName as string - const azureOpenAIApiInstanceName = nodeData.inputs?.azureOpenAIApiInstanceName as string - const azureOpenAIApiDeploymentName = nodeData.inputs?.azureOpenAIApiDeploymentName as string - const azureOpenAIApiVersion = nodeData.inputs?.azureOpenAIApiVersion as string const maxTokens = nodeData.inputs?.maxTokens as string const topP = nodeData.inputs?.topP as string const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string @@ -170,6 +148,12 @@ class AzureOpenAI_LLMs implements INode { const bestOf = nodeData.inputs?.bestOf as string const streaming = nodeData.inputs?.streaming as boolean + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const azureOpenAIApiKey = getCredentialParam('azureOpenAIApiKey', credentialData, nodeData) + const azureOpenAIApiInstanceName = getCredentialParam('azureOpenAIApiInstanceName', credentialData, nodeData) + const azureOpenAIApiDeploymentName = getCredentialParam('azureOpenAIApiDeploymentName', credentialData, nodeData) + const azureOpenAIApiVersion = getCredentialParam('azureOpenAIApiVersion', credentialData, nodeData) + const obj: Partial & Partial = { temperature: parseFloat(temperature), modelName, diff --git a/packages/components/nodes/llms/Cohere/Cohere.ts b/packages/components/nodes/llms/Cohere/Cohere.ts index 75151571..36bc077a 100644 --- a/packages/components/nodes/llms/Cohere/Cohere.ts +++ b/packages/components/nodes/llms/Cohere/Cohere.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { Cohere, CohereInput } from './core' class Cohere_LLMs implements INode { @@ -10,6 +10,7 @@ class Cohere_LLMs implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class Cohere_LLMs implements INode { this.category = 'LLMs' this.description = 'Wrapper around Cohere large language models' this.baseClasses = [this.type, ...getBaseClasses(Cohere)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['cohereApi'] + } this.inputs = [ - { - label: 'Cohere Api Key', - name: 'cohereApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -75,14 +77,16 @@ class Cohere_LLMs implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const temperature = nodeData.inputs?.temperature as string const modelName = nodeData.inputs?.modelName as string - const apiKey = nodeData.inputs?.cohereApiKey as string const maxTokens = nodeData.inputs?.maxTokens as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const cohereApiKey = getCredentialParam('cohereApiKey', credentialData, nodeData) + const obj: CohereInput = { - apiKey + apiKey: cohereApiKey } if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) diff --git a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts index 92eb46d5..fae1525f 100644 --- a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts +++ b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { HFInput, HuggingFaceInference } from './core' class HuggingFaceInference_LLMs implements INode { @@ -10,6 +10,7 @@ class HuggingFaceInference_LLMs implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,17 +21,28 @@ class HuggingFaceInference_LLMs implements INode { this.category = 'LLMs' this.description = 'Wrapper around HuggingFace large language models' this.baseClasses = [this.type, ...getBaseClasses(HuggingFaceInference)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['huggingFaceApi'] + } this.inputs = [ { label: 'Model', name: 'model', type: 'string', - placeholder: 'gpt2' + description: 'If using own inference endpoint, leave this blank', + placeholder: 'gpt2', + optional: true }, { - label: 'HuggingFace Api Key', - name: 'apiKey', - type: 'password' + label: 'Endpoint', + name: 'endpoint', + type: 'string', + placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2', + description: 'Using your own inference endpoint', + optional: true }, { label: 'Temperature', @@ -71,22 +83,12 @@ class HuggingFaceInference_LLMs implements INode { description: 'Frequency Penalty parameter may not apply to certain model. Please check available model parameters', optional: true, additionalParams: true - }, - { - label: 'Endpoint', - name: 'endpoint', - type: 'string', - placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2', - description: 'Using your own inference endpoint', - optional: true, - additionalParams: true } ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as string - const apiKey = nodeData.inputs?.apiKey as string const temperature = nodeData.inputs?.temperature as string const maxTokens = nodeData.inputs?.maxTokens as string const topP = nodeData.inputs?.topP as string @@ -94,9 +96,12 @@ class HuggingFaceInference_LLMs implements INode { const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string const endpoint = nodeData.inputs?.endpoint as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const huggingFaceApiKey = getCredentialParam('huggingFaceApiKey', credentialData, nodeData) + const obj: Partial = { model, - apiKey + apiKey: huggingFaceApiKey } if (temperature) obj.temperature = parseFloat(temperature) diff --git a/packages/components/nodes/llms/OpenAI/OpenAI.ts b/packages/components/nodes/llms/OpenAI/OpenAI.ts index 50aa1c60..884cf63f 100644 --- a/packages/components/nodes/llms/OpenAI/OpenAI.ts +++ b/packages/components/nodes/llms/OpenAI/OpenAI.ts @@ -17,7 +17,7 @@ class OpenAI_LLMs implements INode { this.label = 'OpenAI' this.name = 'openAI' this.type = 'OpenAI' - this.icon = 'openai.png' + this.icon = 'openai.svg' this.category = 'LLMs' this.description = 'Wrapper around OpenAI large language models' this.baseClasses = [this.type, ...getBaseClasses(OpenAI)] diff --git a/packages/components/nodes/llms/OpenAI/openai.png b/packages/components/nodes/llms/OpenAI/openai.png deleted file mode 100644 index de08a05b28979826c4cc669c4899789763a938a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3991 zcmV;I4`}d-P)gjhf~eq#s7M4i1UD8XqJW~vA_xftNZ1L8K*&N!(&?H%y1G)Gbam|=aK3jA=g{eX z@7=H7a^Jo8-Gcvf2q9|6MLi;kA{=m2P6cQ2)V1)TAs~(pT*(!*p&9W+1Lr8@H};dw zHug~X$0Z<~j`Sm$E?i7hfWKF8n(eG&Ik{BUEe-a=MOQM|iyKj+xXEW0-3hDfF58I& z#*>GLh)0tE?>F`_krs8`ZF?Zli!3TN1+P64R?{bBi?U;gWH|YTh4+>Hq!L-zB3MBb zV>qpA;HyoBGo%q+*J7AOBx5KtExwO}V$uTc8RtC&hEr%s{OVDVdLga_y~wvLzK??a z^sQ@gj3R+7Tg3NKu$q>k>9{@Whl|Gh`>Pxu-Wg^SlVy}NwlLW4Twghj6#mHhirCmm~&>D3b&!V;S94?d=RK$ zm*UB~=s*f7bfqyD1^9jm$Joe9zT=R}sBsiYlGd-CA*uv8` zKMGwKhufy*PekMhFLJ3|cWa(uw}INL_=N{(5K8gm_}Vt%i};Y<^0aKgz5Hn6RB@I? zbPgQ>S5aV#@Rh7(5HV7%5!}cUN=(wUoD6&QPY6>=!9usII*OSN;4%;Yvb;%^wNdhi0 zr39VgO}gQd>S)X({7RL@S+7<~8R;YeZP;h9LuD+dzpT*K<95F0oFmWPS2oesE^#A? zCxJw|a3xI*65v6kimg0EL#Z|wSMxTf9Ti?g#7&yINO})Ljp#&oy3m&9G$4{NGMHkB zJb^m0!|Z!DK@i3u7IE0@&m-x^1lDq*hLgi9zTOc~NG8|Iib*^p*`&j1 zVploKP_x`!!)y)&T%0EBCZL>e_$&3LI-|ISFDMO}@ZR#C8C!Ep(m9}7r9J{Y#1yf(U)Cd3yJ5ZTF_yw7nmxtCOht;i^v5v`AaL5#Oie195@ zL7;#|%wrb-N14WQ9^!AZwa^&i1CO7YA700^G_+BC^ETRIRx+FQQtXI;h{z7c@EM~? zHZh)}1Di+u324j&5^*AM##oJRe&RKjQw%@^y-8*nKT<^nS#0D^9yJ_OqN4`_PZ%(7 z>DvbLxCDR~b=T`}9)nI~P=LrGCeuOwv<(vto z1WXJ1tvx&=eGlMLUgU^I>-jvZI7)Y55(k5Rzm&U!i(j9`hQ!xPz#dHke&=<%$g{o) zkFi~sdCbj5Mi4LkE{q<$#~Iac@28X20=Q43K_>_(<#TS4BZBI4Cs~HfW2JmfisJVJ zSgo>;Es>AoD!AM53Ec<*0@DLL!A*>mpI{U{SU{n{K8T2%U^Z9CBd8hwBDGL=bYeygf8|aRgNq-z z@iu~PyunFR;){q>@;&#+9)Jk?vY2A&ZyqLT9j4=05h4Q0Si!9UqdXv*ek{|us|PB@ zd`w>=q}pN`!Vgp;lFQ{<6QH392bVqqauozr@r%MJuGW(W`Ne{h?bXV6Z z{&r{`XvjK;2-syh;ITdf_|}4}+}{(S3h(OZ=8Va1I)_r0Fqo& zy~6A2VG=$CA%_x22(SZ{tY$c)*kCd$xE@1UpcXaeBOsdslikxAoYuxb6bT4G5t$4k zoqUt^bZ0Ju11*U@0*>;tsfw#8cTjw|m{)mR+SLy-nSspXw268|+K|DZV2^7kXH9H_ z5!}VvAkmyT7B9mkkV7R|+#wsnjhMk|DoH`3##$LvhGxjWY(W~ijuEg!+STWCjh`88 zn_-1HVANRktSBO$8x6Q1TM$V;B|tIj`4$iD0_a{RSYT;+jb#{3fM~jsLOcg31j^V% z7H4UvW$E>U03;B{sz5F>fOc#)#HN3AZxqRVR?DoCO@amSm0@@uMBHpv7*WFL$s)rF zb1A9n&7~T)3l;G`H^}~_ct+F+eX);#Y5|mHuxjJE{>iXeJu)el_Y5yCB8Qo(u(-4( zU2#6JN0LVL7MI||o5g<~@ItEG>ph<@M8Z>H5;2sK0QBc#` z*M!GeOmm9_1ov0wO3!k#!JcLYbCiW~kD)o`U-FnJ2SE!YSiA?UMZkWkEu#eF(k@uD z0?7up#M+CDG7R1tT51rm&m;jw+~#c{u;L@Kiu+l}SyP=3<67o0U*YLJ{}AJ|6sv1~ z*^J^bw&KCek)}R(vWRJ1ZaLb-nM*GMiQdN(O!Y11Z3Z%#gC;xCl*jmlCoC?5PNA81 z8PwAK^GIh9nI>(90+%s`4;Uyb%;yiJs4?xsPZb*&#c;k+J3?q6g1)@P8wSndJ?MuE z5FESr744NhH~|-vhzls?Q-&~(Y|L3`8!&_qd0r9Z6b$W2XEC>!XvYY2MUADA!!ruC zu_G^W)YR7KyD##vZr4}_0?WBm#oRmzyVGg?7}~Bv~CU<`e#`@VgFXorz1$zH*YebeC)Mwbq?C{es?{ zCg5Ey9W}9r4t9*0ia47VJgG4_gG~mJb$<7|+cL3M`X#3cn5Z=YM)~>Wynfdl#jY;U znOFJE1O*|#*1Q4c9YH(zhwl4B(;OvWvDK(CB0O^{~(@5xY5g*h@jj>*H7jcq+Y%QmGGz)Z9q$hN_ zf;9@`d8F>t7%w?SfQRR_qsCV1t}a;UvWK0Fm92sTNxaN)o%MPN(7K&&hJc+~3m`P& zM?*s@aOmCZBf=`GoXjf-)XBYs3!7yk^;GpBY2>%at5u-8(9;q>MP)7PEdY~(e* z0I(?myTE>)W1+sQvtDFVV$qOkR{XuZ!vZb&D!V6f?^G5?ug(>yjyw|PvxWLQkFqzl%f#=ONpHAVts zCG(iIQtoWFNaZLow*`7JpLxvr%hcIh#i>4)=Ng|Qv#1oA` zIYpcxpKP{sKuUt;!&NNd66{UTmd{;mwXr^vh@t_FXi8HW6R&DN2xFp!kea|#?BAi# z0PI6cR@*lJJ&0tT7rq8V=xdWo?Lj1uUUe;waR{W^^eV2?48IUx#RXA}qu3G!9z=>5 za~_A`Yap6&7Dj>h>5sWE-$my`BqI$c?W!($47+fjz7GO@SZ!ictR#zG7v|irjTTIh z{Qi1h%9_Xc3vc5K1{d9!NuI9P^5&62SEtmTx*SsBbfiDYT%r16=2L9vYgUkp+o?{} z{hX@#YHpEo3i*wF>|h&volfvm_XK!x-oBju50C!=Nj{KH?md;N0000bbVXQnWMOn= zI%9HWVRU5xGB7eSEigGPF*Z~%IXW;nIx#mZFfckWFtzvO1ONa4C3HntbYx+4Wjbwd xWNBu305UK#GA%GUEipD!FgZFfI65&mD=;uRFfhcbT(|%L002ovPDHLkV1j%(J<0$8 diff --git a/packages/components/nodes/llms/OpenAI/openai.svg b/packages/components/nodes/llms/OpenAI/openai.svg new file mode 100644 index 00000000..9d3261dd --- /dev/null +++ b/packages/components/nodes/llms/OpenAI/openai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index 49d15cb6..d9fc75d0 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -1,4 +1,4 @@ -import { ICommonObject, INode, INodeData, INodeParams, getBaseClasses } from '../../../src' +import { ICommonObject, INode, INodeData, INodeParams, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src' import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' import { BufferMemory } from 'langchain/memory' @@ -10,6 +10,7 @@ class DynamoDb_Memory implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,6 +21,12 @@ class DynamoDb_Memory implements INode { this.category = 'Memory' this.description = 'Stores the conversation in dynamo db table' this.baseClasses = [this.type, ...getBaseClasses(BufferMemory)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['dynamodbMemoryApi'] + } this.inputs = [ { label: 'Table Name', @@ -31,6 +38,13 @@ class DynamoDb_Memory implements INode { name: 'partitionKey', type: 'string' }, + { + label: 'Region', + name: 'region', + type: 'string', + description: 'The aws region in which table is located', + placeholder: 'us-east-1' + }, { label: 'Session ID', name: 'sessionId', @@ -40,28 +54,12 @@ class DynamoDb_Memory implements INode { additionalParams: true, optional: true }, - { - label: 'Region', - name: 'region', - type: 'string', - description: 'The aws region in which table is located', - placeholder: 'us-east-1' - }, - { - label: 'Access Key', - name: 'accessKey', - type: 'password' - }, - { - label: 'Secret Access Key', - name: 'secretAccessKey', - type: 'password' - }, { label: 'Memory Key', name: 'memoryKey', type: 'string', - default: 'chat_history' + default: 'chat_history', + additionalParams: true } ] } @@ -70,12 +68,14 @@ class DynamoDb_Memory implements INode { const partitionKey = nodeData.inputs?.partitionKey as string const sessionId = nodeData.inputs?.sessionId as string const region = nodeData.inputs?.region as string - const accessKey = nodeData.inputs?.accessKey as string - const secretAccessKey = nodeData.inputs?.secretAccessKey as string const memoryKey = nodeData.inputs?.memoryKey as string const chatId = options.chatId + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const accessKey = getCredentialParam('accessKey', credentialData, nodeData) + const secretAccessKey = getCredentialParam('secretAccessKey', credentialData, nodeData) + const dynamoDb = new DynamoDBChatMessageHistory({ tableName, partitionKey, diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 9caf604c..904bd672 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -20,7 +20,7 @@ class MotorMemory_Memory implements INode { this.type = 'MotorheadMemory' this.icon = 'motorhead.png' this.category = 'Memory' - this.description = 'Remembers previous conversational back and forths directly' + this.description = 'Use Motorhead Memory to store chat conversations' this.baseClasses = [this.type, ...getBaseClasses(MotorheadMemory)] this.credential = { label: 'Connect Credential', @@ -38,12 +38,6 @@ class MotorMemory_Memory implements INode { optional: true, description: 'To use the online version, leave the URL blank. More details at https://getmetal.io.' }, - { - label: 'Memory Key', - name: 'memoryKey', - type: 'string', - default: 'chat_history' - }, { label: 'Session Id', name: 'sessionId', @@ -52,6 +46,13 @@ class MotorMemory_Memory implements INode { default: '', additionalParams: true, optional: true + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history', + additionalParams: true } ] } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 2b4e51c2..37f1cbe2 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -44,13 +44,15 @@ class RedisBackedChatMemory_Memory implements INode { name: 'sessionTTL', type: 'number', description: 'Omit this parameter to make sessions never expire', + additionalParams: true, optional: true }, { label: 'Memory Key', name: 'memoryKey', type: 'string', - default: 'chat_history' + default: 'chat_history', + additionalParams: true } ] } diff --git a/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts b/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts index d6168061..0de7151b 100644 --- a/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts +++ b/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts @@ -1,8 +1,9 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { OpenApiToolkit } from 'langchain/agents' import { JsonSpec, JsonObject } from 'langchain/tools' import { BaseLanguageModel } from 'langchain/base_language' import { load } from 'js-yaml' +import { getCredentialData, getCredentialParam } from '../../../src' class OpenAPIToolkit_Tools implements INode { label: string @@ -12,6 +13,7 @@ class OpenAPIToolkit_Tools implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -21,12 +23,15 @@ class OpenAPIToolkit_Tools implements INode { this.icon = 'openapi.png' this.category = 'Tools' this.description = 'Load OpenAPI specification' + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + description: 'Only needed if the YAML OpenAPI Spec requires authentication', + optional: true, + credentialNames: ['openAPIAuth'] + } this.inputs = [ - { - label: 'OpenAI API Key', - name: 'openAIApiKey', - type: 'password' - }, { label: 'Language Model', name: 'model', @@ -42,11 +47,13 @@ class OpenAPIToolkit_Tools implements INode { this.baseClasses = [this.type, 'Tool'] } - async init(nodeData: INodeData): Promise { - const openAIApiKey = nodeData.inputs?.openAIApiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as BaseLanguageModel const yamlFileBase64 = nodeData.inputs?.yamlFile as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const openAPIToken = getCredentialParam('openAPIToken', credentialData, nodeData) + const splitDataURI = yamlFileBase64.split(',') splitDataURI.pop() const bf = Buffer.from(splitDataURI.pop() || '', 'base64') @@ -56,10 +63,10 @@ class OpenAPIToolkit_Tools implements INode { throw new Error('Failed to load OpenAPI spec') } - const headers = { - 'Content-Type': 'application/json', - Authorization: `Bearer ${openAIApiKey}` + const headers: ICommonObject = { + 'Content-Type': 'application/json' } + if (openAPIToken) headers.Authorization = `Bearer ${openAPIToken}` const toolkit = new OpenApiToolkit(new JsonSpec(data), model, headers) return toolkit.tools diff --git a/packages/components/nodes/tools/SerpAPI/SerpAPI.ts b/packages/components/nodes/tools/SerpAPI/SerpAPI.ts index 69432408..7e87e9c1 100644 --- a/packages/components/nodes/tools/SerpAPI/SerpAPI.ts +++ b/packages/components/nodes/tools/SerpAPI/SerpAPI.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { SerpAPI } from 'langchain/tools' class SerpAPI_Tools implements INode { @@ -10,6 +10,7 @@ class SerpAPI_Tools implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -19,19 +20,20 @@ class SerpAPI_Tools implements INode { this.icon = 'serp.png' this.category = 'Tools' this.description = 'Wrapper around SerpAPI - a real-time API to access Google search results' - this.inputs = [ - { - label: 'Serp Api Key', - name: 'apiKey', - type: 'password' - } - ] + this.inputs = [] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['serpApi'] + } this.baseClasses = [this.type, ...getBaseClasses(SerpAPI)] } - async init(nodeData: INodeData): Promise { - const apiKey = nodeData.inputs?.apiKey as string - return new SerpAPI(apiKey) + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const serpApiKey = getCredentialParam('serpApiKey', credentialData, nodeData) + return new SerpAPI(serpApiKey) } } diff --git a/packages/components/nodes/tools/Serper/Serper.ts b/packages/components/nodes/tools/Serper/Serper.ts index 65dff57c..495ac8af 100644 --- a/packages/components/nodes/tools/Serper/Serper.ts +++ b/packages/components/nodes/tools/Serper/Serper.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { Serper } from 'langchain/tools' class Serper_Tools implements INode { @@ -10,6 +10,7 @@ class Serper_Tools implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -19,19 +20,20 @@ class Serper_Tools implements INode { this.icon = 'serper.png' this.category = 'Tools' this.description = 'Wrapper around Serper.dev - Google Search API' - this.inputs = [ - { - label: 'Serper Api Key', - name: 'apiKey', - type: 'password' - } - ] + this.inputs = [] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['serperApi'] + } this.baseClasses = [this.type, ...getBaseClasses(Serper)] } - async init(nodeData: INodeData): Promise { - const apiKey = nodeData.inputs?.apiKey as string - return new Serper(apiKey) + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const serperApiKey = getCredentialParam('serperApiKey', credentialData, nodeData) + return new Serper(serperApiKey) } } diff --git a/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts b/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts index d16e32e6..06d3dc5a 100644 --- a/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts +++ b/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts @@ -1,6 +1,7 @@ import { ZapierNLAWrapper, ZapierNLAWrapperParams } from 'langchain/tools' -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { ZapierToolKit } from 'langchain/agents' +import { getCredentialData, getCredentialParam } from '../../../src' class ZapierNLA_Tools implements INode { label: string @@ -11,29 +12,31 @@ class ZapierNLA_Tools implements INode { category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams constructor() { this.label = 'Zapier NLA' this.name = 'zapierNLA' this.type = 'ZapierNLA' - this.icon = 'zapier.png' + this.icon = 'zapier.svg' this.category = 'Tools' this.description = "Access to apps and actions on Zapier's platform through a natural language API interface" - this.inputs = [ - { - label: 'Zapier NLA Api Key', - name: 'apiKey', - type: 'password' - } - ] + this.inputs = [] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['zapierNLAApi'] + } this.baseClasses = [this.type, 'Tool'] } - async init(nodeData: INodeData): Promise { - const apiKey = nodeData.inputs?.apiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const zapierNLAApiKey = getCredentialParam('zapierNLAApiKey', credentialData, nodeData) const obj: Partial = { - apiKey + apiKey: zapierNLAApiKey } const zapier = new ZapierNLAWrapper(obj) const toolkit = await ZapierToolKit.fromZapierNLAWrapper(zapier) diff --git a/packages/components/nodes/tools/ZapierNLA/zapier.png b/packages/components/nodes/tools/ZapierNLA/zapier.png deleted file mode 100644 index 769716faaadca77fcd3e7ac06c22ba570d37d2e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 502 zcmeAS@N?(olHy`uVBq!ia0vp^?I6s-1SB`N-i-xPjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucL7@`Yh?3y^w370~qErU=qSVy9;*9)~xKIwD z7RFpp7srr_xVIM-MVTELj$9~>2wc9vtS{`rwEm`s`Qk!ziZ33Xs + + + + + + + \ No newline at end of file diff --git a/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts b/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts index e57da396..3d37d61e 100644 --- a/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts +++ b/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts @@ -1,8 +1,8 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { PineconeClient } from '@pinecone-database/pinecone' import { PineconeLibArgs, PineconeStore } from 'langchain/vectorstores/pinecone' import { Embeddings } from 'langchain/embeddings/base' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' class Pinecone_Existing_VectorStores implements INode { label: string @@ -13,6 +13,7 @@ class Pinecone_Existing_VectorStores implements INode { category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams outputs: INodeOutputsValue[] constructor() { @@ -23,22 +24,18 @@ class Pinecone_Existing_VectorStores implements INode { this.category = 'Vector Stores' this.description = 'Load existing index from Pinecone (i.e: Document has been upserted)' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['pineconeApi'] + } this.inputs = [ { label: 'Embeddings', name: 'embeddings', type: 'Embeddings' }, - { - label: 'Pinecone Api Key', - name: 'pineconeApiKey', - type: 'password' - }, - { - label: 'Pinecone Environment', - name: 'pineconeEnv', - type: 'string' - }, { label: 'Pinecone Index', name: 'pineconeIndex', @@ -83,9 +80,7 @@ class Pinecone_Existing_VectorStores implements INode { ] } - async init(nodeData: INodeData): Promise { - const pineconeApiKey = nodeData.inputs?.pineconeApiKey as string - const pineconeEnv = nodeData.inputs?.pineconeEnv as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const index = nodeData.inputs?.pineconeIndex as string const pineconeNamespace = nodeData.inputs?.pineconeNamespace as string const pineconeMetadataFilter = nodeData.inputs?.pineconeMetadataFilter @@ -94,6 +89,10 @@ class Pinecone_Existing_VectorStores implements INode { const topK = nodeData.inputs?.topK as string const k = topK ? parseInt(topK, 10) : 4 + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData) + const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData) + const client = new PineconeClient() await client.init({ apiKey: pineconeApiKey, diff --git a/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts b/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts index ad1767c2..a3cc9094 100644 --- a/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts +++ b/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts @@ -1,9 +1,9 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { PineconeClient } from '@pinecone-database/pinecone' import { PineconeLibArgs, PineconeStore } from 'langchain/vectorstores/pinecone' import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { flatten } from 'lodash' class PineconeUpsert_VectorStores implements INode { @@ -15,6 +15,7 @@ class PineconeUpsert_VectorStores implements INode { category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams outputs: INodeOutputsValue[] constructor() { @@ -25,6 +26,12 @@ class PineconeUpsert_VectorStores implements INode { this.category = 'Vector Stores' this.description = 'Upsert documents to Pinecone' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['pineconeApi'] + } this.inputs = [ { label: 'Document', @@ -37,16 +44,6 @@ class PineconeUpsert_VectorStores implements INode { name: 'embeddings', type: 'Embeddings' }, - { - label: 'Pinecone Api Key', - name: 'pineconeApiKey', - type: 'password' - }, - { - label: 'Pinecone Environment', - name: 'pineconeEnv', - type: 'string' - }, { label: 'Pinecone Index', name: 'pineconeIndex', @@ -84,9 +81,7 @@ class PineconeUpsert_VectorStores implements INode { ] } - async init(nodeData: INodeData): Promise { - const pineconeApiKey = nodeData.inputs?.pineconeApiKey as string - const pineconeEnv = nodeData.inputs?.pineconeEnv as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const index = nodeData.inputs?.pineconeIndex as string const pineconeNamespace = nodeData.inputs?.pineconeNamespace as string const docs = nodeData.inputs?.document as Document[] @@ -95,6 +90,10 @@ class PineconeUpsert_VectorStores implements INode { const topK = nodeData.inputs?.topK as string const k = topK ? parseInt(topK, 10) : 4 + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData) + const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData) + const client = new PineconeClient() await client.init({ apiKey: pineconeApiKey, diff --git a/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts index f1eef8f9..bb4ac6ed 100644 --- a/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts +++ b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts @@ -1,8 +1,8 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { QdrantClient } from '@qdrant/js-client-rest' import { QdrantVectorStore, QdrantLibArgs } from 'langchain/vectorstores/qdrant' import { Embeddings } from 'langchain/embeddings/base' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' class Qdrant_Existing_VectorStores implements INode { label: string @@ -13,16 +13,25 @@ class Qdrant_Existing_VectorStores implements INode { category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams outputs: INodeOutputsValue[] constructor() { this.label = 'Qdrant Load Existing Index' this.name = 'qdrantExistingIndex' this.type = 'Qdrant' - this.icon = 'qdrant_logo.svg' + this.icon = 'qdrant.png' this.category = 'Vector Stores' this.description = 'Load existing index from Qdrant (i.e., documents have been upserted)' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + description: 'Only needed when using Qdrant cloud hosted', + optional: true, + credentialNames: ['qdrantApi'] + } this.inputs = [ { label: 'Embeddings', @@ -40,12 +49,6 @@ class Qdrant_Existing_VectorStores implements INode { name: 'qdrantCollection', type: 'string' }, - { - label: 'Qdrant API Key', - name: 'qdrantApiKey', - type: 'password', - optional: true - }, { label: 'Qdrant Collection Cofiguration', name: 'qdrantCollectionCofiguration', @@ -77,17 +80,18 @@ class Qdrant_Existing_VectorStores implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const qdrantServerUrl = nodeData.inputs?.qdrantServerUrl as string const collectionName = nodeData.inputs?.qdrantCollection as string - const qdrantApiKey = nodeData.inputs?.qdrantApiKey as string let qdrantCollectionCofiguration = nodeData.inputs?.qdrantCollectionCofiguration const embeddings = nodeData.inputs?.embeddings as Embeddings const output = nodeData.outputs?.output as string const topK = nodeData.inputs?.topK as string const k = topK ? parseInt(topK, 10) : 4 - // connect to Qdrant Cloud + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const qdrantApiKey = getCredentialParam('qdrantApiKey', credentialData, nodeData) + const client = new QdrantClient({ url: qdrantServerUrl, apiKey: qdrantApiKey diff --git a/packages/components/nodes/vectorstores/Qdrant_Existing/qdrant.png b/packages/components/nodes/vectorstores/Qdrant_Existing/qdrant.png new file mode 100644 index 0000000000000000000000000000000000000000..ecb2a56d55fbaf6ccdc3640a3d8d3d6587dd701a GIT binary patch literal 11663 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

- )} - - - - Output - - - + } + placement='right-start' + > + +
+ +
+ Notification +
+
+ + + {data.label} + + + {warningMessage && ( + <> +
+ {warningMessage}} placement='top'> + + + + + + )} +
+ {(data.inputAnchors.length > 0 || data.inputParams.length > 0) && ( + <> + + + + Inputs + + + + + )} + {data.inputAnchors.map((inputAnchor, index) => ( + + ))} + {data.inputParams.map((inputParam, index) => ( + + ))} + {data.inputParams.find((param) => param.additionalParams) && ( +
param.additionalParams).length === + data.inputParams.length + data.inputAnchors.length + ? 20 + : 0 + }} + > + +
+ )} + + + + Output + + + - {data.outputAnchors.map((outputAnchor, index) => ( - - ))} -
+ {data.outputAnchors.map((outputAnchor, index) => ( + + ))} + + setShowDialog(false)} > + setShowInfoDialog(false)}> ) } diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index e58fdd00..b89af7bb 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -28,6 +28,9 @@ import useApi from 'hooks/useApi' // Const import { baseURL, maxScroll } from 'store/constant' +import robotPNG from 'assets/images/robot.png' +import userPNG from 'assets/images/account.png' + export const ChatMessage = ({ open, chatflowid, isDialog }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -281,21 +284,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { > {/* Display the correct icon depending on the message type */} {message.type === 'apiMessage' ? ( - AI + AI ) : ( - Me + Me )}
diff --git a/packages/ui/src/views/credentials/AddEditCredentialDialog.js b/packages/ui/src/views/credentials/AddEditCredentialDialog.js index 6a5c9568..65b72a5f 100644 --- a/packages/ui/src/views/credentials/AddEditCredentialDialog.js +++ b/packages/ui/src/views/credentials/AddEditCredentialDialog.js @@ -27,6 +27,7 @@ import useNotifier from 'utils/useNotifier' // const import { baseURL } from 'store/constant' +import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const portalElement = document.getElementById('portal') @@ -87,6 +88,12 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) => // eslint-disable-next-line react-hooks/exhaustive-deps }, [dialogProps]) + useEffect(() => { + if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) + else dispatch({ type: HIDE_CANVAS_DIALOG }) + return () => dispatch({ type: HIDE_CANVAS_DIALOG }) + }, [show, dispatch]) + const addNewCredential = async () => { try { const obj = { diff --git a/packages/ui/src/views/credentials/CredentialListDialog.js b/packages/ui/src/views/credentials/CredentialListDialog.js index 9333db67..e0a3e08d 100644 --- a/packages/ui/src/views/credentials/CredentialListDialog.js +++ b/packages/ui/src/views/credentials/CredentialListDialog.js @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react' import { createPortal } from 'react-dom' -import { useSelector } from 'react-redux' +import { useSelector, useDispatch } from 'react-redux' import PropTypes from 'prop-types' import { List, @@ -20,11 +20,12 @@ import { IconSearch, IconX } from '@tabler/icons' // const import { baseURL } from 'store/constant' +import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelected }) => { const portalElement = document.getElementById('portal') const customization = useSelector((state) => state.customization) - + const dispatch = useDispatch() const theme = useTheme() const [searchValue, setSearchValue] = useState('') const [componentsCredentials, setComponentsCredentials] = useState([]) @@ -43,10 +44,16 @@ const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelecte } useEffect(() => { - if (show && dialogProps.componentsCredentials) { + if (dialogProps.componentsCredentials) { setComponentsCredentials(dialogProps.componentsCredentials) } - }, [show, dialogProps]) + }, [dialogProps]) + + useEffect(() => { + if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) + else dispatch({ type: HIDE_CANVAS_DIALOG }) + return () => dispatch({ type: HIDE_CANVAS_DIALOG }) + }, [show, dispatch]) const component = show ? ( { useEffect(() => { if (getAllComponentsCredentialsApi.data) { setComponentsCredentials(getAllComponentsCredentialsApi.data) + dispatch({ type: SET_COMPONENT_CREDENTIALS, componentsCredentials: getAllComponentsCredentialsApi.data }) } - }, [getAllComponentsCredentialsApi.data]) + }, [getAllComponentsCredentialsApi.data, dispatch]) return ( <> diff --git a/packages/ui/src/views/tools/ToolDialog.js b/packages/ui/src/views/tools/ToolDialog.js index 77ef770d..5e286789 100644 --- a/packages/ui/src/views/tools/ToolDialog.js +++ b/packages/ui/src/views/tools/ToolDialog.js @@ -29,6 +29,7 @@ import useApi from 'hooks/useApi' // utils import useNotifier from 'utils/useNotifier' import { generateRandomGradient } from 'utils/genericHelper' +import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' const exampleAPIFunc = `/* * You can use any libraries imported in Flowise @@ -155,6 +156,12 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) = } } + useEffect(() => { + if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) + else dispatch({ type: HIDE_CANVAS_DIALOG }) + return () => dispatch({ type: HIDE_CANVAS_DIALOG }) + }, [show, dispatch]) + useEffect(() => { if (getSpecificToolApi.data) { setToolId(getSpecificToolApi.data.id) From 5af9b5f63ba14998fe88d89c631a1dcc6e3c0e41 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 28 Jul 2023 17:49:54 +0100 Subject: [PATCH 294/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.3.0?= =?UTF-8?q?=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 01738a7c..e41e5add 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.17", + "version": "1.3.0", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From d47835d383b7713c364179291d0b0c17514b0cb7 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 28 Jul 2023 17:57:50 +0100 Subject: [PATCH 295/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.3.0=20relea?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index bdb49846..a2eeec6f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.15", + "version": "1.3.0", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From 3ffc9050b24ef71f352976e0320eb14bbc81eb92 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 28 Jul 2023 17:58:13 +0100 Subject: [PATCH 296/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.3.0=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 27c5358e..220d5807 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.16", + "version": "1.3.0", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index b8b5fe37..56e47fe3 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.16", + "version": "1.3.0", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 722223a87ef4d5fabdcfe63dea7b6823a846fd44 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 28 Jul 2023 19:16:42 +0100 Subject: [PATCH 297/398] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13da9420..65249147 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Download and Install [NodeJS](https://nodejs.org/en/download) >= 18.15.0 ### Docker Compose 1. Go to `docker` folder at the root of the project -2. Create `.env` file and specify the `PORT` (refer to `.env.example`) +2. Copy `.env.example` file, paste it into the same location, and rename to `.env` 3. `docker-compose up -d` 4. Open [http://localhost:3000](http://localhost:3000) 5. You can bring the containers down by `docker-compose stop` From 2a2fc02c75265499009a8670337c788b73299f7e Mon Sep 17 00:00:00 2001 From: Rafael Reis Date: Fri, 28 Jul 2023 20:18:35 -0300 Subject: [PATCH 298/398] Update CONTRIBUTING.md fixed DATABASE_USER(NAME) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7c2222a7..524db215 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -135,7 +135,7 @@ Flowise support different environment variables to configure your instance. You | DATABASE_PATH | Location where database is saved (When DATABASE_TYPE is sqlite) | String | `your-home-dir/.flowise` | | DATABASE_HOST | Host URL or IP address (When DATABASE_TYPE is not sqlite) | String | | | DATABASE_PORT | Database port (When DATABASE_TYPE is not sqlite) | String | | -| DATABASE_USERNAME | Database username (When DATABASE_TYPE is not sqlite) | String | | +| DATABASE_USER | Database username (When DATABASE_TYPE is not sqlite) | String | | | DATABASE_PASSWORD | Database password (When DATABASE_TYPE is not sqlite) | String | | | DATABASE_NAME | Database name (When DATABASE_TYPE is not sqlite) | String | | | PASSPHRASE | Passphrase used to create encryption key | String | `MYPASSPHRASE` | From 6378e08f279ad8fccc335dbd8f2ea04c6755517a Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 29 Jul 2023 01:46:51 +0100 Subject: [PATCH 299/398] fix default encryption key file path - Error: Error: Error: ENOENT: no such file or directory, open '' --- packages/components/src/utils.ts | 8 ++++---- packages/server/src/index.ts | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 8167a350..bada7cc0 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -355,10 +355,10 @@ export const getEnvironmentVariable = (name: string): string | undefined => { */ const getEncryptionKeyFilePath = (): string => { const checkPaths = [ - path.join(__dirname, '..', '..', 'server', 'encryption.key'), - path.join(__dirname, '..', '..', '..', 'server', 'encryption.key'), - path.join(__dirname, '..', '..', '..', '..', 'server', 'encryption.key'), - path.join(__dirname, '..', '..', '..', '..', '..', 'server', 'encryption.key') + path.join(__dirname, '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', '..', 'encryption.key') ] for (const checkPath of checkPaths) { if (fs.existsSync(checkPath)) { diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index e1ac4724..34357ea9 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -44,7 +44,8 @@ import { transformToCredentialEntity, decryptCredentialData, clearSessionMemory, - replaceInputsWithConfig + replaceInputsWithConfig, + getEncryptionKey } from './utils' import { cloneDeep, omit } from 'lodash' import { getDataSource } from './DataSource' @@ -82,6 +83,9 @@ export class App { // Initialize API keys await getAPIKeys() + + // Initialize encryption key + await getEncryptionKey() }) .catch((err) => { logger.error('❌ [server]: Error during Data Source initialization:', err) From cec026a8895efdf7b58e6bc954cfcaf1eb644aff Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 29 Jul 2023 02:01:18 +0100 Subject: [PATCH 300/398] add check file paths --- packages/components/src/utils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index bada7cc0..363dd026 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -356,9 +356,13 @@ export const getEnvironmentVariable = (name: string): string | undefined => { const getEncryptionKeyFilePath = (): string => { const checkPaths = [ path.join(__dirname, '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', 'server', 'encryption.key'), path.join(__dirname, '..', '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', '..', 'server', 'encryption.key'), path.join(__dirname, '..', '..', '..', '..', 'encryption.key'), - path.join(__dirname, '..', '..', '..', '..', '..', 'encryption.key') + path.join(__dirname, '..', '..', '..', '..', 'server', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', '..', 'server', 'encryption.key') ] for (const checkPath of checkPaths) { if (fs.existsSync(checkPath)) { From 7511d82c83ad22a842b759710df27c0a0f012c38 Mon Sep 17 00:00:00 2001 From: denchi Date: Sat, 29 Jul 2023 10:10:58 +0100 Subject: [PATCH 301/398] Added new GoogleCustomSearch tool --- .../tools/GoogleSearchAPI/GoogleSearchAPI.ts | 43 ++++++++++++++++++ .../nodes/tools/GoogleSearchAPI/google.png | Bin 0 -> 16690 bytes 2 files changed, 43 insertions(+) create mode 100644 packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts create mode 100644 packages/components/nodes/tools/GoogleSearchAPI/google.png diff --git a/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts b/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts new file mode 100644 index 00000000..4fbf7e36 --- /dev/null +++ b/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts @@ -0,0 +1,43 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { GoogleCustomSearch } from 'langchain/tools' + + +class GoogleSearchAPI_Tools implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'Google Custom Search' + this.name = 'googleSearchAPI' + this.version = 1.0 + this.type = 'GoogleSearchAPI' + this.icon = 'google.png' + this.category = 'Tools' + this.description = 'Wrapper around Google Custom Search API - a real-time API to access Google search results' + this.inputs = [] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['googleSearchApi'] + } + this.baseClasses = [this.type, ...getBaseClasses(GoogleCustomSearch)] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const googleApiKey = getCredentialParam('googleApiKey', credentialData, nodeData) + return new GoogleCustomSearch({ apiKey: googleApiKey }) + } +} + +module.exports = { nodeClass: GoogleSearchAPI_Tools } diff --git a/packages/components/nodes/tools/GoogleSearchAPI/google.png b/packages/components/nodes/tools/GoogleSearchAPI/google.png new file mode 100644 index 0000000000000000000000000000000000000000..c7cd4ca111a53f1085db2a6070c718ad3e17cdd6 GIT binary patch literal 16690 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 IRP3Mp5020|NdN!< literal 0 HcmV?d00001 From 2fcb2ea88edf21ee561ad67df9bd67a48e7f644f Mon Sep 17 00:00:00 2001 From: denchi Date: Sat, 29 Jul 2023 11:20:00 +0100 Subject: [PATCH 302/398] Added google custom search credential --- .../credentials/GoogleSearchApi.credential.ts | 29 +++++++++++++++++++ .../tools/GoogleSearchAPI/GoogleSearchAPI.ts | 15 +++++----- 2 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 packages/components/credentials/GoogleSearchApi.credential.ts diff --git a/packages/components/credentials/GoogleSearchApi.credential.ts b/packages/components/credentials/GoogleSearchApi.credential.ts new file mode 100644 index 00000000..ab6d709e --- /dev/null +++ b/packages/components/credentials/GoogleSearchApi.credential.ts @@ -0,0 +1,29 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class GoogleSearchApi implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Google Custom Search API' + this.name = 'googleCustomSearchApi' + this.version = 1.0 + this.inputs = [ + { + label: 'Google Custom Search Api Key', + name: 'googleCustomSearchApiKey', + type: 'password' + }, + { + label: 'Programmable Search Engine ID', + name: 'googleCustomSearchApiId', + type: 'string' + } + ] + } +} + +module.exports = { credClass: GoogleSearchApi } diff --git a/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts b/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts index 4fbf7e36..b510b37b 100644 --- a/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts +++ b/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts @@ -3,7 +3,7 @@ import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../ import { GoogleCustomSearch } from 'langchain/tools' -class GoogleSearchAPI_Tools implements INode { +class GoogleCustomSearchAPI_Tools implements INode { label: string name: string version: number @@ -17,9 +17,9 @@ class GoogleSearchAPI_Tools implements INode { constructor() { this.label = 'Google Custom Search' - this.name = 'googleSearchAPI' + this.name = 'googleCustomSearch' this.version = 1.0 - this.type = 'GoogleSearchAPI' + this.type = 'GoogleCustomSearchAPI' this.icon = 'google.png' this.category = 'Tools' this.description = 'Wrapper around Google Custom Search API - a real-time API to access Google search results' @@ -28,16 +28,17 @@ class GoogleSearchAPI_Tools implements INode { label: 'Connect Credential', name: 'credential', type: 'credential', - credentialNames: ['googleSearchApi'] + credentialNames: ['googleCustomSearchApi'] } this.baseClasses = [this.type, ...getBaseClasses(GoogleCustomSearch)] } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const credentialData = await getCredentialData(nodeData.credential ?? '', options) - const googleApiKey = getCredentialParam('googleApiKey', credentialData, nodeData) - return new GoogleCustomSearch({ apiKey: googleApiKey }) + const googleApiKey = getCredentialParam('googleCustomSearchApiKey', credentialData, nodeData) + const googleCseId = getCredentialParam('googleCustomSearchApiId', credentialData, nodeData) + return new GoogleCustomSearch({ apiKey: googleApiKey, googleCSEId: googleCseId }) } } -module.exports = { nodeClass: GoogleSearchAPI_Tools } +module.exports = { nodeClass: GoogleCustomSearchAPI_Tools } From 56c5c0dace036035524c1837fc38d095374ad2f4 Mon Sep 17 00:00:00 2001 From: denchi Date: Sat, 29 Jul 2023 12:00:59 +0100 Subject: [PATCH 303/398] Removed new line --- .../components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts b/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts index b510b37b..29ebae8b 100644 --- a/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts +++ b/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts @@ -2,7 +2,6 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Inter import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { GoogleCustomSearch } from 'langchain/tools' - class GoogleCustomSearchAPI_Tools implements INode { label: string name: string From b2b924a38ab731f53d4b09123f0f46773de316bb Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 29 Jul 2023 12:44:52 +0100 Subject: [PATCH 304/398] minor patch bug fix to 1.3.1 --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 220d5807..b5c7c5fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.0", + "version": "1.3.1", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index 56e47fe3..a8cf1e65 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.0", + "version": "1.3.1", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 79cae6962b9afed1862f517b3880b3d330f830e7 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 29 Jul 2023 12:45:31 +0100 Subject: [PATCH 305/398] minor patch bug fix to 1.3.1 --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index e41e5add..8a3eca6e 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.3.0", + "version": "1.3.1", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From 1832eecde2a4d8cdced394ce04e3fe1ac186a198 Mon Sep 17 00:00:00 2001 From: Seif Date: Sat, 29 Jul 2023 12:35:31 -0700 Subject: [PATCH 306/398] Add credentials file --- .../credentials/VectaraApi.credential.ts | 34 +++++++++++++++++ .../Vectara_Existing/Vectara_Existing.ts | 38 +++++++++---------- .../Vectara_Upsert/Vectara_Upsert.ts | 38 +++++++++---------- 3 files changed, 68 insertions(+), 42 deletions(-) create mode 100644 packages/components/credentials/VectaraApi.credential.ts diff --git a/packages/components/credentials/VectaraApi.credential.ts b/packages/components/credentials/VectaraApi.credential.ts new file mode 100644 index 00000000..96ad29a6 --- /dev/null +++ b/packages/components/credentials/VectaraApi.credential.ts @@ -0,0 +1,34 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class VectaraAPI implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Vectara API' + this.name = 'vectaraApi' + this.version = 1.0 + this.inputs = [ + { + label: 'Vectara Customer ID', + name: 'customerID', + type: 'string' + }, + { + label: 'Vectara Corpus ID', + name: 'corpusID', + type: 'string' + }, + { + label: 'Vectara API Key', + name: 'apiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: VectaraAPI } diff --git a/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts b/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts index fcf0b1aa..725a9e32 100644 --- a/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts +++ b/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts @@ -1,42 +1,36 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { VectaraStore, VectaraLibArgs, VectaraFilter } from 'langchain/vectorstores/vectara' class VectaraExisting_VectorStores implements INode { label: string name: string + version: number description: string type: string icon: string category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams outputs: INodeOutputsValue[] constructor() { this.label = 'Vectara Load Existing Index' this.name = 'vectaraExistingIndex' + this.version = 1.0 this.type = 'Vectara' this.icon = 'vectara.png' this.category = 'Vector Stores' this.description = 'Load existing index from Vectara (i.e: Document has been upserted)' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['vectaraApi'] + } this.inputs = [ - { - label: 'Vectara Customer ID', - name: 'customerID', - type: 'string' - }, - { - label: 'Vectara Corpus ID', - name: 'corpusID', - type: 'string' - }, - { - label: 'Vectara API Key', - name: 'apiKey', - type: 'password' - }, { label: 'Vectara Metadata Filter', name: 'filter', @@ -74,10 +68,12 @@ class VectaraExisting_VectorStores implements INode { } ] } - async init(nodeData: INodeData): Promise { - const customerId = nodeData.inputs?.customerID as number - const corpusId = nodeData.inputs?.corpusID as number - const apiKey = nodeData.inputs?.apiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + const customerId = getCredentialParam('customerID', credentialData, nodeData) + const corpusId = getCredentialParam('corpusID', credentialData, nodeData) + const vectaraMetadatafilter = nodeData.inputs?.filter as VectaraFilter const lambda = nodeData.inputs?.lambda as number const output = nodeData.outputs?.output as string diff --git a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts index ea44f3c9..bc236060 100644 --- a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts +++ b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts @@ -1,6 +1,6 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { Embeddings } from 'langchain/embeddings/base' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { VectaraStore, VectaraLibArgs, VectaraFilter } from 'langchain/vectorstores/vectara' import { Document } from 'langchain/document' import { flatten } from 'lodash' @@ -8,38 +8,32 @@ import { flatten } from 'lodash' class VectaraExisting_VectorStores implements INode { label: string name: string + version: number description: string type: string icon: string category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams outputs: INodeOutputsValue[] constructor() { this.label = 'Vectara Upsert Document' this.name = 'vectaraExisting' + this.version = 1.0 this.type = 'Vectara' this.icon = 'vectara.png' this.category = 'Vector Stores' this.description = 'Upsert documents to Vectara' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['vectaraApi'] + } this.inputs = [ - { - label: 'Vectara Customer ID', - name: 'customerID', - type: 'string' - }, - { - label: 'Vectara Corpus ID', - name: 'corpusID', - type: 'string' - }, - { - label: 'Vectara API Key', - name: 'apiKey', - type: 'password' - }, { label: 'Document', name: 'document', @@ -83,10 +77,12 @@ class VectaraExisting_VectorStores implements INode { } ] } - async init(nodeData: INodeData): Promise { - const customerId = nodeData.inputs?.customerID as number - const corpusId = nodeData.inputs?.corpusID as number - const apiKey = nodeData.inputs?.apiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + const customerId = getCredentialParam('customerID', credentialData, nodeData) + const corpusId = getCredentialParam('corpusID', credentialData, nodeData) + const docs = nodeData.inputs?.document as Document[] const embeddings = {} as Embeddings const vectaraMetadatafilter = nodeData.inputs?.filter as VectaraFilter From 6cedcc352a036b9d88ff092f667890693e41fc36 Mon Sep 17 00:00:00 2001 From: denchi Date: Sat, 29 Jul 2023 21:31:10 +0100 Subject: [PATCH 307/398] Added credentials description --- packages/components/credentials/GoogleSearchApi.credential.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/components/credentials/GoogleSearchApi.credential.ts b/packages/components/credentials/GoogleSearchApi.credential.ts index ab6d709e..cb82b25a 100644 --- a/packages/components/credentials/GoogleSearchApi.credential.ts +++ b/packages/components/credentials/GoogleSearchApi.credential.ts @@ -11,6 +11,8 @@ class GoogleSearchApi implements INodeCredential { this.label = 'Google Custom Search API' this.name = 'googleCustomSearchApi' this.version = 1.0 + this.description = + 'Please refer to the Google Cloud Console for instructions on how to create an API key, and visit the Search Engine Creation page to learn how to generate your Search Engine ID.' this.inputs = [ { label: 'Google Custom Search Api Key', From 27660b8ed30a89582ca8bd95d9e19aacb95cf89b Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 30 Jul 2023 14:03:15 +0100 Subject: [PATCH 308/398] add fix --- packages/components/nodes/chains/LLMChain/LLMChain.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index cf9e4bc9..c9e49165 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -116,16 +116,7 @@ const runPrediction = async ( */ const promptValues = handleEscapeCharacters(promptValuesRaw, true) - if (inputVariables.length === 1) { - if (isStreaming) { - const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.run(input, [loggerHandler, handler]) - return res - } else { - const res = await chain.run(input, [loggerHandler]) - return res - } - } else if (inputVariables.length > 1) { + if (inputVariables.length > 0) { let seen: string[] = [] for (const variable of inputVariables) { From b5c9345b2b83e5cf3d1d295787628ec71a45a899 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 30 Jul 2023 17:41:30 +0100 Subject: [PATCH 309/398] add clearSessionMemory fix --- .../nodes/memory/DynamoDb/DynamoDb.ts | 4 ++++ .../memory/MotorheadMemory/MotorheadMemory.ts | 4 ++++ .../RedisBackedChatMemory.ts | 6 +++++- .../nodes/memory/ZepMemory/ZepMemory.ts | 4 ++++ packages/server/src/index.ts | 2 +- packages/server/src/utils/index.ts | 21 +++++++++++++++---- 6 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index a1c0fb1f..6926912f 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -72,7 +72,11 @@ class DynamoDb_Memory implements INode { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { const dynamodbMemory = await initalizeDynamoDB(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing DynamoDb memory session ${sessionId ? sessionId : chatId}`) await dynamodbMemory.clear() + options.logger.info(`Successfully cleared DynamoDb memory session ${sessionId ? sessionId : chatId}`) } } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 790c753c..bb23de9d 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -65,7 +65,11 @@ class MotorMemory_Memory implements INode { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { const motorhead = await initalizeMotorhead(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Motorhead memory session ${sessionId ? sessionId : chatId}`) await motorhead.clear() + options.logger.info(`Successfully cleared Motorhead memory session ${sessionId ? sessionId : chatId}`) } } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index e9c963dd..7b3e2cb5 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -65,7 +65,11 @@ class RedisBackedChatMemory_Memory implements INode { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { const redis = initalizeRedis(nodeData, options) - redis.clear() + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Redis memory session ${sessionId ? sessionId : chatId}`) + await redis.clear() + options.logger.info(`Successfully cleared Redis memory session ${sessionId ? sessionId : chatId}`) } } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index e64740b8..3faa29b9 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -141,7 +141,11 @@ class ZepMemory_Memory implements INode { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { const zep = await initalizeZep(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Zep memory session ${sessionId ? sessionId : chatId}`) await zep.clear() + options.logger.info(`Successfully cleared Zep memory session ${sessionId ? sessionId : chatId}`) } } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 34357ea9..545c75a7 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -405,7 +405,7 @@ export class App { const nodes = parsedFlowData.nodes let chatId = await getChatId(chatflow.id) if (!chatId) chatId = chatflow.id - clearSessionMemory(nodes, this.nodesPool.componentNodes, chatId, req.query.sessionId as string) + clearSessionMemory(nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, req.query.sessionId as string) const results = await this.AppDataSource.getRepository(ChatMessage).delete({ chatflowid: req.params.id }) return res.json(results) }) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index f92cc6dc..2a68be47 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -18,7 +18,7 @@ import { IComponentCredentials, ICredentialReqBody } from '../Interface' -import { cloneDeep, get, omit, merge } from 'lodash' +import { cloneDeep, get, omit, merge, isEqual } from 'lodash' import { ICommonObject, getInputVariables, IDatabaseEntity, handleEscapeCharacters } from 'flowise-components' import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' import { lib, PBKDF2, AES, enc } from 'crypto-js' @@ -182,7 +182,7 @@ export const getEndingNode = (nodeDependencies: INodeDependencies, graph: INodeD /** * Build langchain from start to end - * @param {string} startingNodeId + * @param {string[]} startingNodeIds * @param {IReactFlowNode[]} reactFlowNodes * @param {INodeDirectedGraph} graph * @param {IDepthQueue} depthQueue @@ -286,12 +286,14 @@ export const buildLangchain = async ( * @param {IReactFlowNode[]} reactFlowNodes * @param {IComponentNodes} componentNodes * @param {string} chatId + * @param {DataSource} appDataSource * @param {string} sessionId */ export const clearSessionMemory = async ( reactFlowNodes: IReactFlowNode[], componentNodes: IComponentNodes, chatId: string, + appDataSource: DataSource, sessionId?: string ) => { for (const node of reactFlowNodes) { @@ -300,7 +302,8 @@ export const clearSessionMemory = async ( const nodeModule = await import(nodeInstanceFilePath) const newNodeInstance = new nodeModule.nodeClass() if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId - if (newNodeInstance.clearSessionMemory) await newNodeInstance?.clearSessionMemory(node.data, { chatId }) + if (newNodeInstance.clearSessionMemory) + await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) } } @@ -495,7 +498,7 @@ export const isSameOverrideConfig = ( Object.keys(existingOverrideConfig).length && newOverrideConfig && Object.keys(newOverrideConfig).length && - JSON.stringify(existingOverrideConfig) === JSON.stringify(newOverrideConfig) + isEqual(existingOverrideConfig, newOverrideConfig) ) { return true } @@ -660,8 +663,18 @@ export const mapMimeTypeToInputField = (mimeType: string) => { 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 '' } From 8237ce1d2a7dcfc63a81a4046af56cc0331e7fa5 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 30 Jul 2023 18:28:23 +0100 Subject: [PATCH 310/398] minor patch bug fix to 1.3.2 --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 8a3eca6e..85267d38 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.3.1", + "version": "1.3.2", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From ae3d7e2e2d41f63874e9c70ebff9ffec4dbe92d7 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 30 Jul 2023 18:28:53 +0100 Subject: [PATCH 311/398] minor patch bug fix to 1.3.2 --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b5c7c5fe..650671f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.1", + "version": "1.3.2", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index a8cf1e65..0aac4ac5 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.1", + "version": "1.3.2", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From d2313a30f9fa4feebef7dd3d835bb0d7b116595f Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 30 Jul 2023 23:52:41 +0100 Subject: [PATCH 312/398] fix python API --- .../ui/src/views/chatflows/APICodeDialog.js | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/views/chatflows/APICodeDialog.js b/packages/ui/src/views/chatflows/APICodeDialog.js index 994f7552..6ca605cd 100644 --- a/packages/ui/src/views/chatflows/APICodeDialog.js +++ b/packages/ui/src/views/chatflows/APICodeDialog.js @@ -97,7 +97,7 @@ const getConfigExamplesForPython = (configData, bodyType) => { if (config.type === 'string') exampleVal = `"example"` else if (config.type === 'boolean') exampleVal = `true` else if (config.type === 'number') exampleVal = `1` - else if (config.name === 'files') exampleVal = `('example${config.type}', open('example${config.type}', 'rb'))` + else if (config.name === 'files') continue finalStr += bodyType === 'json' ? `\n "${config.name}": ${exampleVal},` : `\n "${config.name}": ${exampleVal},` if (i === loop - 1 && bodyType !== 'json') finalStr += `\n "question": "Hey, how are you?"\n` } @@ -289,15 +289,20 @@ query({"question": "Hey, how are you?"}).then((response) => { const getConfigCodeWithFormData = (codeLang, configData) => { if (codeLang === 'Python') { + configData = unshiftFiles(configData) + const fileType = configData[0].type return `import requests API_URL = "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}" # use form data to upload files -form_data = {${getConfigExamplesForPython(configData, 'formData')}} +form_data = { + "files": ${`('example${fileType}', open('example${fileType}', 'rb'))`} +} +body_data = {${getConfigExamplesForPython(configData, 'formData')}} def query(form_data): - response = requests.post(API_URL, files=form_data) + response = requests.post(API_URL, files=form_data, data=body_data) return response.json() output = query(form_data) @@ -334,16 +339,21 @@ query(formData).then((response) => { const getConfigCodeWithFormDataWithAuth = (codeLang, configData) => { if (codeLang === 'Python') { + configData = unshiftFiles(configData) + const fileType = configData[0].type return `import requests API_URL = "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}" headers = {"Authorization": "Bearer ${selectedApiKey?.apiKey}"} # use form data to upload files -form_data = {${getConfigExamplesForPython(configData, 'formData')}} +form_data = { + "files": ${`('example${fileType}', open('example${fileType}', 'rb'))`} +} +body_data = {${getConfigExamplesForPython(configData, 'formData')}} def query(form_data): - response = requests.post(API_URL, headers=headers, files=form_data) + response = requests.post(API_URL, headers=headers, files=form_data, data=body_data) return response.json() output = query(form_data) From 36d3709371a43d405764d1cb0b1869fc5fd8686d Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 31 Jul 2023 00:08:37 +0100 Subject: [PATCH 313/398] minor fix when promptValues is undefined --- packages/components/nodes/chains/LLMChain/LLMChain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index c9e49165..5088b34d 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -116,7 +116,7 @@ const runPrediction = async ( */ const promptValues = handleEscapeCharacters(promptValuesRaw, true) - if (inputVariables.length > 0) { + if (promptValues && inputVariables.length > 0) { let seen: string[] = [] for (const variable of inputVariables) { From 53cb3c3251b0d6e27678634a37ee492b372f4bbc Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 31 Jul 2023 23:18:07 +0100 Subject: [PATCH 314/398] add multi config --- packages/server/src/Interface.ts | 1 + packages/server/src/utils/index.ts | 12 ++ packages/ui/src/ui-component/table/Table.js | 8 +- .../ui/src/views/chatflows/APICodeDialog.js | 151 +++++++++++++++++- 4 files changed, 166 insertions(+), 6 deletions(-) diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 49d61036..8feb4272 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -157,6 +157,7 @@ export interface IActiveChatflows { export interface IOverrideConfig { node: string + nodeId: string label: string name: string type: string diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 2a68be47..245f0b9c 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -445,6 +445,14 @@ export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig: const getParamValues = (paramsObj: ICommonObject) => { for (const config in overrideConfig) { + // If overrideConfig[key] is object + if (overrideConfig[config] && typeof overrideConfig[config] === 'object') { + const nodeIds = Object.keys(overrideConfig[config]) + if (!nodeIds.includes(flowNodeData.id)) continue + else paramsObj[config] = overrideConfig[config][flowNodeData.id] + continue + } + let paramValue = overrideConfig[config] ?? paramsObj[config] // Check if boolean if (paramValue === 'true') paramValue = true @@ -695,6 +703,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component if (inputParam.type === 'file') { obj = { node: flowNode.data.label, + nodeId: flowNode.data.id, label: inputParam.label, name: 'files', type: inputParam.fileType ?? inputParam.type @@ -702,6 +711,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component } else if (inputParam.type === 'options') { obj = { node: flowNode.data.label, + nodeId: flowNode.data.id, label: inputParam.label, name: inputParam.name, type: inputParam.options @@ -720,6 +730,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component for (const input of inputs) { obj = { node: flowNode.data.label, + nodeId: flowNode.data.id, label: input.label, name: input.name, type: input.type === 'password' ? 'string' : input.type @@ -732,6 +743,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component } else { obj = { node: flowNode.data.label, + nodeId: flowNode.data.id, label: inputParam.label, name: inputParam.name, type: inputParam.type === 'password' ? 'string' : inputParam.type diff --git a/packages/ui/src/ui-component/table/Table.js b/packages/ui/src/ui-component/table/Table.js index a6ab312e..2cf39182 100644 --- a/packages/ui/src/ui-component/table/Table.js +++ b/packages/ui/src/ui-component/table/Table.js @@ -16,9 +16,11 @@ export const TableViewOnly = ({ columns, rows }) => { {rows.map((row, index) => ( - {Object.keys(row).map((key, index) => ( - {row[key]} - ))} + {Object.keys(row) + .slice(-3) + .map((key, index) => ( + {row[key]} + ))} ))} diff --git a/packages/ui/src/views/chatflows/APICodeDialog.js b/packages/ui/src/views/chatflows/APICodeDialog.js index 6ca605cd..0ae8a8f4 100644 --- a/packages/ui/src/views/chatflows/APICodeDialog.js +++ b/packages/ui/src/views/chatflows/APICodeDialog.js @@ -4,8 +4,20 @@ import { useState, useEffect } from 'react' import { useDispatch } from 'react-redux' import PropTypes from 'prop-types' -import { Tabs, Tab, Dialog, DialogContent, DialogTitle, Box } from '@mui/material' +import { + Tabs, + Tab, + Dialog, + DialogContent, + DialogTitle, + Box, + Accordion, + AccordionSummary, + AccordionDetails, + Typography +} from '@mui/material' import { CopyBlock, atomOneDark } from 'react-code-blocks' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' // Project import import { Dropdown } from 'ui-component/dropdown/Dropdown' @@ -33,6 +45,8 @@ import useApi from 'hooks/useApi' import { CheckboxInput } from 'ui-component/checkbox/Checkbox' import { TableViewOnly } from 'ui-component/table/Table' +import { IconBulb } from '@tabler/icons' + function TabPanel(props) { const { children, value, index, ...other } = props return ( @@ -82,7 +96,7 @@ const getConfigExamplesForJS = (configData, bodyType) => { else if (config.type === 'number') exampleVal = `1` else if (config.name === 'files') exampleVal = `input.files[0]` finalStr += bodyType === 'json' ? `\n "${config.name}": ${exampleVal},` : `formData.append("${config.name}", ${exampleVal})\n` - if (i === loop - 1 && bodyType !== 'json') `formData.append("question", "Hey, how are you?")\n` + if (i === loop - 1 && bodyType !== 'json') finalStr += `formData.append("question", "Hey, how are you?")\n` } return finalStr } @@ -134,6 +148,8 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { const [chatflowApiKeyId, setChatflowApiKeyId] = useState('') const [selectedApiKey, setSelectedApiKey] = useState({}) const [checkboxVal, setCheckbox] = useState(false) + const [nodeConfig, setNodeConfig] = useState({}) + const [nodeConfigExpanded, setNodeConfigExpanded] = useState({}) const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys) const updateChatflowApi = useApi(chatflowsApi.updateChatflow) @@ -160,12 +176,36 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { updateChatflowApi.request(dialogProps.chatflowid, updateBody) } + const groupByNodeLabel = (nodes, isFilter = false) => { + const accordianNodes = {} + const result = nodes.reduce(function (r, a) { + r[a.node] = r[a.node] || [] + r[a.node].push(a) + accordianNodes[a.node] = isFilter ? true : false + return r + }, Object.create(null)) + setNodeConfig(result) + setNodeConfigExpanded(accordianNodes) + } + + const handleAccordionChange = (nodeLabel) => (event, isExpanded) => { + const accordianNodes = { ...nodeConfigExpanded } + accordianNodes[nodeLabel] = isExpanded + setNodeConfigExpanded(accordianNodes) + } + useEffect(() => { if (updateChatflowApi.data) { dispatch({ type: SET_CHATFLOW, chatflow: updateChatflowApi.data }) } }, [updateChatflowApi.data, dispatch]) + useEffect(() => { + if (getConfigApi.data) { + groupByNodeLabel(getConfigApi.data) + } + }, [getConfigApi.data]) + const handleChange = (event, newValue) => { setValue(newValue) } @@ -493,6 +533,32 @@ query({ return '' } + const getMultiConfigCodeWithFormData = (codeLang) => { + if (codeLang === 'Python') { + return `body_data = { + "openAIApiKey[chatOpenAI_0]": "sk-my-openai-1st-key", + "openAIApiKey[openAIEmbeddings_0]": "sk-my-openai-2nd-key" +}` + } else if (codeLang === 'JavaScript') { + return `formData.append("openAIApiKey[chatOpenAI_0]", "sk-my-openai-1st-key") +formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")` + } else if (codeLang === 'cURL') { + return `-F "openAIApiKey[chatOpenAI_0]=sk-my-openai-1st-key" \\ +-F "openAIApiKey[openAIEmbeddings_0]=sk-my-openai-2nd-key" \\` + } + } + + const getMultiConfigCode = () => { + return `{ + "overrideConfig": { + "openAIApiKey": { + "chatOpenAI_0": "sk-my-openai-1st-key", + "openAIEmbeddings_0": "sk-my-openai-2nd-key" + } + } +}` + } + useEffect(() => { if (getAllAPIKeysApi.data) { const options = [ @@ -593,7 +659,49 @@ query({ {checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && ( <> - + {Object.keys(nodeConfig) + .sort() + .map((nodeLabel) => ( + + } + aria-controls={`nodes-accordian-${nodeLabel}`} + id={`nodes-accordian-header-${nodeLabel}`} + > +
+ {nodeLabel} +
+ + {nodeConfig[nodeLabel][0].nodeId} + +
+
+
+ + + +
+ ))} +
+
+ + + You can also specify multiple values for a config parameter by specifying the node id + +
+
+ +
+
)} {getIsChatflowStreamingApi.data?.isStreaming && ( From 3e87aba9c5d5466a18a0a664e6871331a93b0c34 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Tue, 1 Aug 2023 01:02:08 +0100 Subject: [PATCH 315/398] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac3abdab..483cdbb6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,7 @@ Flowise has 3 different modules in a single mono repository. #### Step by step -1. Fork the official [Flowise Github 仓库](https://github.com/FlowiseAI/Flowise). +1. Fork the official [Flowise Github Repository](https://github.com/FlowiseAI/Flowise). 2. Clone your forked repository. From 4fd26b99a4fc9d96aa11d13ce0c734b2a6f308f6 Mon Sep 17 00:00:00 2001 From: xiexin12138 Date: Tue, 1 Aug 2023 11:30:01 +0800 Subject: [PATCH 316/398] Update CODE_OF_CONDUCT-ZH.md --- CODE_OF_CONDUCT-ZH.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT-ZH.md b/CODE_OF_CONDUCT-ZH.md index 6b5d4979..e0537da6 100644 --- a/CODE_OF_CONDUCT-ZH.md +++ b/CODE_OF_CONDUCT-ZH.md @@ -44,6 +44,6 @@ ## 归属 -本行为准则改编自[贡献者公约][主页],版本1.4,可在[http://contributor-covenant.org/version/1/4][版本]上获取。 +该行为准则的内容来自于[贡献者公约](http://contributor-covenant.org/)1.4版,可在[http://contributor-covenant.org/version/1/4](http://contributor-covenant.org/version/1/4)上获取。 [主页]: http://contributor-covenant.org From ef1f16b7aa96291a219fbdc2498fb46031eb3bb1 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Tue, 1 Aug 2023 16:20:24 +0800 Subject: [PATCH 317/398] add hyperlink --- packages/ui/src/utils/genericHelper.js | 8 ++++++++ packages/ui/src/views/chatmessage/ChatMessage.js | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index 7bf8998e..324cc112 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -401,3 +401,11 @@ export const getInputVariables = (paramValue) => { } return inputVariables } + +export const isValidURL = (url) => { + try { + return new URL(url) + } catch (err) { + return undefined + } +} diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index b89af7bb..bf895eb3 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -30,6 +30,7 @@ import { baseURL, maxScroll } from 'store/constant' import robotPNG from 'assets/images/robot.png' import userPNG from 'assets/images/account.png' +import { isValidURL } from 'utils/genericHelper' export const ChatMessage = ({ open, chatflowid, isDialog }) => { const theme = useTheme() @@ -59,6 +60,10 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { setSourceDialogOpen(true) } + const onURLClick = (data) => { + window.open(data, '_blank') + } + const scrollToBottom = () => { if (ps.current) { ps.current.scrollTo({ top: maxScroll }) @@ -320,16 +325,23 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { {message.sourceDocuments && (
{message.sourceDocuments.map((source, index) => { + const URL = isValidURL(source.metadata.source) return ( onSourceDialogClick(source)} + onClick={() => + URL ? onURLClick(source.metadata.source) : onSourceDialogClick(source) + } /> ) })} From 3aa301119b578652f79c740dbadd39295800cea2 Mon Sep 17 00:00:00 2001 From: drobnikj Date: Tue, 1 Aug 2023 10:22:51 +0200 Subject: [PATCH 318/398] feat: add apifyApiToken credentials v1 --- packages/components/credentials/ApifyApi.ts | 26 +++++++++++++++++++ .../ApifyWebsiteContentCrawler.ts | 24 +++++++++++------ 2 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 packages/components/credentials/ApifyApi.ts diff --git a/packages/components/credentials/ApifyApi.ts b/packages/components/credentials/ApifyApi.ts new file mode 100644 index 00000000..d3e7e870 --- /dev/null +++ b/packages/components/credentials/ApifyApi.ts @@ -0,0 +1,26 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class ApifyApi implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Apify API' + this.name = 'apifyApi' + this.version = 1.0 + this.description = + 'You can find the Apify API token on your Apify account page.' + this.inputs = [ + { + label: 'Apify API', + name: 'apifyApiToken', + type: 'password' + } + ] + } +} + +module.exports = { credClass: ApifyApi } diff --git a/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts b/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts index f292ada3..1cf826cf 100644 --- a/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts +++ b/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts @@ -1,4 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { INode, INodeData, INodeParams, ICommonObject } from '../../../src/Interface' +import { getCredentialData, getCredentialParam } from '../../../src/utils' import { TextSplitter } from 'langchain/text_splitter' import { ApifyDatasetLoader } from 'langchain/document_loaders/web/apify_dataset' import { Document } from 'langchain/document' @@ -9,24 +10,22 @@ class ApifyWebsiteContentCrawler_DocumentLoaders implements INode { description: string type: string icon: string + version: number category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams constructor() { this.label = 'Apify Website Content Crawler' this.name = 'apifyWebsiteContentCrawler' this.type = 'Document' this.icon = 'apify-symbol-transparent.svg' + this.version = 1.0 this.category = 'Document Loaders' this.description = 'Load data from Apify Website Content Crawler' this.baseClasses = [this.type] this.inputs = [ - { - label: 'Apify API Token', - name: 'apifyApiToken', - type: 'password' - }, { label: 'Input', name: 'input', @@ -43,13 +42,22 @@ class ApifyWebsiteContentCrawler_DocumentLoaders implements INode { optional: true } ] + this.credential = { + label: 'Connect Apify API', + name: 'credential', + type: 'credential', + credentialNames: ['apifyApi'] + } } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter - const apifyApiToken = nodeData.inputs?.apifyApiToken as string const input = typeof nodeData.inputs?.input === 'object' ? nodeData.inputs?.input : JSON.parse(nodeData.inputs?.input as string) + // Get Apify API token from credential data + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apifyApiToken = getCredentialParam('apifyApiToken', credentialData, nodeData) + const loader = await ApifyDatasetLoader.fromActorCall('apify/website-content-crawler', input, { datasetMappingFunction: (item) => new Document({ From 5146f6bde375814d54eb6cbaf9ede7cc080a2c6c Mon Sep 17 00:00:00 2001 From: drobnikj Date: Tue, 1 Aug 2023 11:19:57 +0200 Subject: [PATCH 319/398] feat: improve apify content crawler input --- .../ApifyWebsiteContentCrawler.ts | 77 +++++++++++++++++-- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts b/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts index 1cf826cf..9fd0764c 100644 --- a/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts +++ b/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts @@ -27,13 +27,60 @@ class ApifyWebsiteContentCrawler_DocumentLoaders implements INode { this.baseClasses = [this.type] this.inputs = [ { - label: 'Input', - name: 'input', + label: 'Start URLs', + name: 'urls', + type: 'string', + description: 'One or more URLs of pages where the crawler will start, separated by commas.', + placeholder: 'https://js.langchain.com/docs/' + }, + { + label: 'Crawler type', + type: 'options', + name: 'crawlerType', + options: [ + { + label: 'Headless web browser (Chrome+Playwright)', + name: 'playwright:chrome' + }, + { + label: 'Stealthy web browser (Firefox+Playwright)', + name: 'playwright:firefox' + }, + { + label: 'Raw HTTP client (Cheerio)', + name: 'cheerio' + }, + { + label: 'Raw HTTP client with JavaScript execution (JSDOM) [experimental]', + name: 'jsdom' + } + ], + description: + 'Select the crawling engine, see documentation for additional information.', + default: 'playwright:firefox' + }, + { + label: 'Max crawling depth', + name: 'maxCrawlDepth', + type: 'number', + optional: true, + default: 1 + }, + { + label: 'Max crawl pages', + name: 'maxCrawlPages', + type: 'number', + optional: true, + default: 3 + }, + { + label: 'Additional input', + name: 'additionalInput', type: 'json', - default: JSON.stringify({ - startUrls: [{ url: 'https://js.langchain.com/docs/' }], - maxCrawlPages: 1 - }) + default: JSON.stringify({}), + description: + 'For additional input options for the crawler see documentation.', + optional: true }, { label: 'Text Splitter', @@ -52,7 +99,23 @@ class ApifyWebsiteContentCrawler_DocumentLoaders implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter - const input = typeof nodeData.inputs?.input === 'object' ? nodeData.inputs?.input : JSON.parse(nodeData.inputs?.input as string) + + // Get input options and merge with additional input + const urls = nodeData.inputs?.urls as string + const crawlerType = nodeData.inputs?.crawlerType as string + const maxCrawlDepth = nodeData.inputs?.maxCrawlDepth as number + const maxCrawlPages = nodeData.inputs?.maxCrawlPages as number + const additionalInput = + typeof nodeData.inputs?.additionalInput === 'object' + ? nodeData.inputs?.additionalInput + : JSON.parse(nodeData.inputs?.additionalInput as string) + const input = { + startUrls: urls.split(',').map((url) => ({ url: url.trim() })), + crawlerType, + maxCrawlDepth, + maxCrawlPages, + ...additionalInput + } // Get Apify API token from credential data const credentialData = await getCredentialData(nodeData.credential ?? '', options) From d8749bd495952359269b4c02055c92305555c6b2 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 1 Aug 2023 12:23:25 +0100 Subject: [PATCH 320/398] chroma auth --- .../credentials/ChromaApi.credential.ts | 24 +++++++ .../Chroma_Existing/Chroma_Existing.ts | 71 +++++++++++++++++-- packages/components/package.json | 2 +- 3 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 packages/components/credentials/ChromaApi.credential.ts diff --git a/packages/components/credentials/ChromaApi.credential.ts b/packages/components/credentials/ChromaApi.credential.ts new file mode 100644 index 00000000..759c113c --- /dev/null +++ b/packages/components/credentials/ChromaApi.credential.ts @@ -0,0 +1,24 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class ChromaApi implements INodeCredential { + label: string + name: string + description: string + version: number + inputs: INodeParams[] + + constructor() { + this.label = 'Chroma API' + this.name = 'chromaApi' + this.version = 1.0 + this.inputs = [ + { + label: 'Chroma Api Key', + name: 'chromaApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: ChromaApi } diff --git a/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts b/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts index 8b84d33e..47266fda 100644 --- a/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts +++ b/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts @@ -1,7 +1,8 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' -import { Chroma } from 'langchain/vectorstores/chroma' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { Chroma, ChromaLibArgs } from 'langchain/vectorstores/chroma' import { Embeddings } from 'langchain/embeddings/base' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import type { Collection } from 'chromadb' class Chroma_Existing_VectorStores implements INode { label: string @@ -13,6 +14,7 @@ class Chroma_Existing_VectorStores implements INode { category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams outputs: INodeOutputsValue[] constructor() { @@ -24,6 +26,14 @@ class Chroma_Existing_VectorStores implements INode { this.category = 'Vector Stores' this.description = 'Load existing index from Chroma (i.e: Document has been upserted)' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + description: 'Only needed if you have chroma on cloud services with X-Api-key', + optional: true, + credentialNames: ['chromaApi'] + } this.inputs = [ { label: 'Embeddings', @@ -65,7 +75,7 @@ class Chroma_Existing_VectorStores implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const collectionName = nodeData.inputs?.collectionName as string const embeddings = nodeData.inputs?.embeddings as Embeddings const chromaURL = nodeData.inputs?.chromaURL as string @@ -73,13 +83,18 @@ class Chroma_Existing_VectorStores implements INode { const topK = nodeData.inputs?.topK as string const k = topK ? parseFloat(topK) : 4 + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const chromaApiKey = getCredentialParam('chromaApiKey', credentialData, nodeData) + const obj: { collectionName: string url?: string + chromaApiKey?: string } = { collectionName } if (chromaURL) obj.url = chromaURL + if (chromaApiKey) obj.chromaApiKey = chromaApiKey - const vectorStore = await Chroma.fromExistingCollection(embeddings, obj) + const vectorStore = await ChromaExisting.fromExistingCollection(embeddings, obj) if (output === 'retriever') { const retriever = vectorStore.asRetriever(k) @@ -92,4 +107,50 @@ class Chroma_Existing_VectorStores implements INode { } } +interface ChromaAuth { + chromaApiKey?: string +} + +class ChromaExisting extends Chroma { + chromaApiKey?: string + + constructor(embeddings: Embeddings, args: ChromaLibArgs & Partial) { + super(embeddings, args) + this.chromaApiKey = args.chromaApiKey + } + + static async fromExistingCollection(embeddings: Embeddings, dbConfig: ChromaLibArgs & Partial): Promise { + const instance = new this(embeddings, dbConfig) + await instance.ensureCollection() + return instance + } + + async ensureCollection(): Promise { + if (!this.collection) { + if (!this.index) { + const { ChromaClient } = await Chroma.imports() + const obj: any = { + path: this.url + } + if (this.chromaApiKey) { + obj.fetchOptions = { + headers: { + 'X-Api-Key': this.chromaApiKey + } + } + } + this.index = new ChromaClient(obj) + } + try { + this.collection = await this.index.getOrCreateCollection({ + name: this.collectionName + }) + } catch (err) { + throw new Error(`Chroma getOrCreateCollection error: ${err}`) + } + } + return this.collection + } +} + module.exports = { nodeClass: Chroma_Existing_VectorStores } diff --git a/packages/components/package.json b/packages/components/package.json index 85267d38..374e7949 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -29,7 +29,7 @@ "@types/jsdom": "^21.1.1", "axios": "^0.27.2", "cheerio": "^1.0.0-rc.12", - "chromadb": "^1.4.2", + "chromadb": "^1.5.3", "cohere-ai": "^6.2.0", "d3-dsv": "2", "dotenv": "^16.0.0", From 850cbbf3d91a7927fd65f3e8764ecbc5ba004dc1 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Wed, 2 Aug 2023 19:38:50 +0900 Subject: [PATCH 321/398] Add Google Vertex AI Embedding SVG icon file --- .../nodes/embeddings/GoogleVertexAIEmbedding/vertexai.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/components/nodes/embeddings/GoogleVertexAIEmbedding/vertexai.svg diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/vertexai.svg b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/vertexai.svg new file mode 100644 index 00000000..efc3589c --- /dev/null +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/vertexai.svg @@ -0,0 +1 @@ + \ No newline at end of file From 2b8cf7acdc28e04388b2c666d9c7cd1d9909a717 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Wed, 2 Aug 2023 19:39:07 +0900 Subject: [PATCH 322/398] Add Google Vertex AI embedding icon --- .../nodes/embeddings/GoogleVertexAIEmbedding/vertexai.svg | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/vertexai.svg b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/vertexai.svg index efc3589c..31244412 100644 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/vertexai.svg +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/vertexai.svg @@ -1 +1,2 @@ + \ No newline at end of file From 129f411c26a3f21a27fe5973536a0911bdc05c29 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Wed, 2 Aug 2023 22:16:43 +0900 Subject: [PATCH 323/398] add google-auth-library in package --- packages/components/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/package.json b/packages/components/package.json index 85267d38..cb8d757a 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -36,6 +36,7 @@ "express": "^4.17.3", "faiss-node": "^0.2.2", "form-data": "^4.0.0", + "google-auth-library": "^9.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", "langchain": "^0.0.117", From 6f80f87c552c43a5d829e08d77018fc7b4180a59 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Wed, 2 Aug 2023 22:16:54 +0900 Subject: [PATCH 324/398] Add GoogleVertexAIEmbedding_Embeddings class for GoogleVertexAIEmbeddings --- .../GoogleVertexAIEmbedding.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts new file mode 100644 index 00000000..05ceb415 --- /dev/null +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts @@ -0,0 +1,56 @@ +import { GoogleVertexAIEmbeddings, GoogleVertexAIEmbeddingsParams} from "langchain/embeddings/googlevertexai"; +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses} from '../../../src/utils' + +class GoogleVertexAIEmbedding_Embeddings implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + + constructor() { + this.label = 'GoogleVertexAI Embeddings' + this.name = 'googlevertexaiEmbeddings' + this.version = 1.0 + this.type = 'GoogleVertexAIEmbeddings' + this.icon = 'vertexai.svg' + this.category = 'Embeddings' + this.description = 'Google vertexAI API to generate embeddings for a given text' + this.baseClasses = [this.type, ...getBaseClasses(GoogleVertexAIEmbeddings)] + this.inputs = [ + { + label: 'Model Name', + name: 'modelName', + type: 'options', + options: [ + { + label: 'textembedding-gecko', + name: 'textembedding-gecko' + } + ], + default: 'textembedding-gecko', + } + ] + } + + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + + const model = nodeData.inputs?.modelName as string + const obj: GoogleVertexAIEmbeddingsParams = { + model + } + + const embedding = new GoogleVertexAIEmbeddings(obj); + return embedding + } +} + +module.exports = { nodeClass: GoogleVertexAIEmbedding_Embeddings } \ No newline at end of file From 63d920158cb588881b7ff275bfa8120b941496f5 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Wed, 2 Aug 2023 22:28:49 +0900 Subject: [PATCH 325/398] lint-fix --- .../GoogleVertexAIEmbedding.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts index 05ceb415..d92bd7d8 100644 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts @@ -1,6 +1,6 @@ -import { GoogleVertexAIEmbeddings, GoogleVertexAIEmbeddingsParams} from "langchain/embeddings/googlevertexai"; +import { GoogleVertexAIEmbeddings, GoogleVertexAIEmbeddingsParams } from 'langchain/embeddings/googlevertexai' import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses} from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' class GoogleVertexAIEmbedding_Embeddings implements INode { label: string @@ -14,7 +14,6 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { credential: INodeParams inputs: INodeParams[] - constructor() { this.label = 'GoogleVertexAI Embeddings' this.name = 'googlevertexaiEmbeddings' @@ -35,22 +34,20 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { name: 'textembedding-gecko' } ], - default: 'textembedding-gecko', + default: 'textembedding-gecko' } ] } - async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const model = nodeData.inputs?.modelName as string const obj: GoogleVertexAIEmbeddingsParams = { model } - const embedding = new GoogleVertexAIEmbeddings(obj); + const embedding = new GoogleVertexAIEmbeddings(obj) return embedding } } -module.exports = { nodeClass: GoogleVertexAIEmbedding_Embeddings } \ No newline at end of file +module.exports = { nodeClass: GoogleVertexAIEmbedding_Embeddings } From 2f0c40e503287e69c27311e2af29ce415fd7f6a7 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Wed, 2 Aug 2023 22:32:42 +0900 Subject: [PATCH 326/398] Add GoogleVertexAI SVG icon --- packages/components/nodes/llms/GoogleVertexAI/vertexai.svg | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 packages/components/nodes/llms/GoogleVertexAI/vertexai.svg diff --git a/packages/components/nodes/llms/GoogleVertexAI/vertexai.svg b/packages/components/nodes/llms/GoogleVertexAI/vertexai.svg new file mode 100644 index 00000000..31244412 --- /dev/null +++ b/packages/components/nodes/llms/GoogleVertexAI/vertexai.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file From 5b5cec408ca16f6e265310274d400af7f2afc957 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Wed, 2 Aug 2023 23:19:05 +0900 Subject: [PATCH 327/398] Remove unnecessary import statement and parameter in the async init function --- .../GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts index d92bd7d8..894ccab5 100644 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts @@ -1,5 +1,5 @@ import { GoogleVertexAIEmbeddings, GoogleVertexAIEmbeddingsParams } from 'langchain/embeddings/googlevertexai' -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' class GoogleVertexAIEmbedding_Embeddings implements INode { @@ -39,7 +39,7 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { ] } - async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + async init(nodeData: INodeData, _: string): Promise { const model = nodeData.inputs?.modelName as string const obj: GoogleVertexAIEmbeddingsParams = { model From d408c06ab0906b1239d252d3c82f5515e4bc9d2a Mon Sep 17 00:00:00 2001 From: Yongtae Date: Wed, 2 Aug 2023 23:19:59 +0900 Subject: [PATCH 328/398] Add support for 'textembedding-gecko@001' in GoogleVertexAIEmbedding --- .../GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts index 894ccab5..c7079d05 100644 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts @@ -32,6 +32,10 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { { label: 'textembedding-gecko', name: 'textembedding-gecko' + }, + { + label: 'textembedding-gecko@001', + name: 'textembedding-gecko@001' } ], default: 'textembedding-gecko' From fd7ef866fb7e0698aa0bf23512f91593aaa751f5 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Wed, 2 Aug 2023 23:59:51 +0900 Subject: [PATCH 329/398] add google-auth-library --- packages/components/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/package.json b/packages/components/package.json index 85267d38..cb8d757a 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -36,6 +36,7 @@ "express": "^4.17.3", "faiss-node": "^0.2.2", "form-data": "^4.0.0", + "google-auth-library": "^9.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", "langchain": "^0.0.117", From a26f76c70505aa149941e58abc06e0f8ed31e962 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Thu, 3 Aug 2023 00:00:08 +0900 Subject: [PATCH 330/398] Add GoogleVertexAI_LLMs node class --- .../llms/GoogleVertexAI/googlevertexai.ts | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts diff --git a/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts b/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts new file mode 100644 index 00000000..8a76c6af --- /dev/null +++ b/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts @@ -0,0 +1,105 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { GoogleVertexAI, GoogleVertexAITextInput } from 'langchain/llms/googlevertexai' + +class GoogleVertexAI_LLMs implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'GoogleVertexAI' + this.name = 'googlevertexai' + this.version = 1.0 + this.type = 'GoogleVertexAI' + this.icon = 'vertexai.svg' + this.category = 'LLMs' + this.description = 'Wrapper around GoogleVertexAI large language models' + this.baseClasses = [this.type, ...getBaseClasses(GoogleVertexAI)] + this.inputs = [ + { + label: 'Model Name', + name: 'modelName', + type: 'options', + options: [ + { + label: 'text-bison', + name: 'text-bison' + }, + { + label: 'code-bison', + name: 'code-bison' + }, + { + label: 'code-gecko', + name: 'code-gecko' + }, + { + label: 'text-bison@001', + name: 'text-bison@001' + }, + { + label: 'code-bison@001', + name: 'code-bison@001' + }, + { + label: 'code-gecko@001', + name: 'code-gecko@001' + }, + ], + default: 'text-bison', + }, + { + label: 'Temperature', + name: 'temperature', + type: 'number', + step: 0.1, + default: 0.7, + optional: true + }, + { + label: 'max Output Tokens', + name: 'maxOutputTokens', + type: 'number', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Top Probability', + name: 'topP', + type: 'number', + step: 0.1, + optional: true, + additionalParams: true + }, + ] + } + + async init(nodeData: INodeData, _: string): Promise { + const temperature = nodeData.inputs?.temperature as string + const model = nodeData.inputs?.modelName as string + const maxOutputTokens = nodeData.inputs?.maxTokens as string + const topP = nodeData.inputs?.topP as string + + const obj: Partial = { + temperature: parseFloat(temperature), + model, + } + + if (maxOutputTokens) obj.maxOutputTokens = parseInt(maxOutputTokens, 10) + if (topP) obj.topP = parseFloat(topP) + + const llm_model = new GoogleVertexAI(obj,) + return llm_model + } +} + +module.exports = { nodeClass: GoogleVertexAI_LLMs } \ No newline at end of file From 45069e10f24806501abb067e1528093f8b10a62f Mon Sep 17 00:00:00 2001 From: Yongtae Date: Thu, 3 Aug 2023 00:04:22 +0900 Subject: [PATCH 331/398] lint-fix --- .../nodes/llms/GoogleVertexAI/googlevertexai.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts b/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts index 8a76c6af..faf8e78b 100644 --- a/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts +++ b/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts @@ -52,9 +52,9 @@ class GoogleVertexAI_LLMs implements INode { { label: 'code-gecko@001', name: 'code-gecko@001' - }, + } ], - default: 'text-bison', + default: 'text-bison' }, { label: 'Temperature', @@ -79,7 +79,7 @@ class GoogleVertexAI_LLMs implements INode { step: 0.1, optional: true, additionalParams: true - }, + } ] } @@ -91,15 +91,15 @@ class GoogleVertexAI_LLMs implements INode { const obj: Partial = { temperature: parseFloat(temperature), - model, + model } if (maxOutputTokens) obj.maxOutputTokens = parseInt(maxOutputTokens, 10) if (topP) obj.topP = parseFloat(topP) - const llm_model = new GoogleVertexAI(obj,) + const llm_model = new GoogleVertexAI(obj) return llm_model } } -module.exports = { nodeClass: GoogleVertexAI_LLMs } \ No newline at end of file +module.exports = { nodeClass: GoogleVertexAI_LLMs } From 24befbd5da9c423c6b17065ea14434660728104f Mon Sep 17 00:00:00 2001 From: Yongtae Date: Thu, 3 Aug 2023 00:08:24 +0900 Subject: [PATCH 332/398] Add GoogleVertexAI logo SVG file --- .../components/nodes/chatmodels/GoogleVertexAI/vertexai.svg | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 packages/components/nodes/chatmodels/GoogleVertexAI/vertexai.svg diff --git a/packages/components/nodes/chatmodels/GoogleVertexAI/vertexai.svg b/packages/components/nodes/chatmodels/GoogleVertexAI/vertexai.svg new file mode 100644 index 00000000..31244412 --- /dev/null +++ b/packages/components/nodes/chatmodels/GoogleVertexAI/vertexai.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file From d71a7d5790f17c2922cdfb56759ef01b30b52403 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Thu, 3 Aug 2023 00:09:26 +0900 Subject: [PATCH 333/398] Add google-auth-library dependency --- packages/components/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/package.json b/packages/components/package.json index 85267d38..cb8d757a 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -36,6 +36,7 @@ "express": "^4.17.3", "faiss-node": "^0.2.2", "form-data": "^4.0.0", + "google-auth-library": "^9.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", "langchain": "^0.0.117", From 16c008dba73fcb87be94d639bdf3f9eca428e2de Mon Sep 17 00:00:00 2001 From: Yongtae Date: Thu, 3 Aug 2023 00:21:13 +0900 Subject: [PATCH 334/398] Fix incorrect variable name for maxOutputTokens in GoogleVertexAI_LLMs class --- packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts b/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts index faf8e78b..754f3642 100644 --- a/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts +++ b/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts @@ -86,7 +86,7 @@ class GoogleVertexAI_LLMs implements INode { async init(nodeData: INodeData, _: string): Promise { const temperature = nodeData.inputs?.temperature as string const model = nodeData.inputs?.modelName as string - const maxOutputTokens = nodeData.inputs?.maxTokens as string + const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string const topP = nodeData.inputs?.topP as string const obj: Partial = { From fdb4450755de082551164d60571481a776175a76 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Thu, 3 Aug 2023 00:39:31 +0900 Subject: [PATCH 335/398] Add GoogleVertexAI chat model node class implementation --- .../GoogleVertexAI/GoogleVertexAI.ts | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts diff --git a/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts b/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts new file mode 100644 index 00000000..6ef96c21 --- /dev/null +++ b/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts @@ -0,0 +1,100 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { ChatGoogleVertexAI, GoogleVertexAIChatInput } from 'langchain/chat_models/googlevertexai' + +class GoogleVertexAI_ChatModels implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'ChatGoogleVertexAI' + this.name = 'chatGoogleVertexAI' + this.version = 1.0 + this.type = 'ChatGoogleVertexAI' + this.icon = 'vertexai.svg' + this.category = 'Chat Models' + this.description = 'Wrapper around VertexAI large language models that use the Chat endpoint' + this.baseClasses = [this.type, ...getBaseClasses(ChatGoogleVertexAI)] + this.inputs = [ + { + label: 'Model Name', + name: 'modelName', + type: 'options', + options: [ + { + label: 'chat-bison', + name: 'chat-bison' + }, + { + label: 'codechat-bison', + name: 'codechat-bison' + }, + { + label: 'chat-bison@001', + name: 'chat-bison@001' + }, + { + label: 'codechat-bison@001', + name: 'codechat-bison@001' + }, + ], + default: 'chat-bison', + optional: true + }, + { + label: 'Temperature', + name: 'temperature', + type: 'number', + step: 0.1, + default: 0.9, + optional: true + }, + { + label: 'Max Output Tokens', + name: 'maxOutputTokens', + type: 'number', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Top Probability', + name: 'topP', + type: 'number', + step: 0.1, + optional: true, + additionalParams: true + }, + ] + } + + async init(nodeData: INodeData, _: string,): Promise { + const temperature = nodeData.inputs?.temperature as string + const model = nodeData.inputs?.modelName as string + const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string + const topP = nodeData.inputs?.topP as string + + const obj: Partial = { + temperature: parseFloat(temperature), + model, + } + + if (maxOutputTokens) obj.maxOutputTokens = parseInt(maxOutputTokens, 10) + if (topP) obj.topP = parseFloat(topP) + + + + const chat_model = new ChatGoogleVertexAI(obj) + return chat_model + } +} + +module.exports = { nodeClass: GoogleVertexAI_ChatModels } From 7aaeab12c7e677ba7ae368f926516e48fc2b1093 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Thu, 3 Aug 2023 00:41:10 +0900 Subject: [PATCH 336/398] lint fix --- .../nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts b/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts index 6ef96c21..4ba2cf62 100644 --- a/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts +++ b/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts @@ -44,7 +44,7 @@ class GoogleVertexAI_ChatModels implements INode { { label: 'codechat-bison@001', name: 'codechat-bison@001' - }, + } ], default: 'chat-bison', optional: true @@ -72,11 +72,11 @@ class GoogleVertexAI_ChatModels implements INode { step: 0.1, optional: true, additionalParams: true - }, + } ] } - async init(nodeData: INodeData, _: string,): Promise { + async init(nodeData: INodeData, _: string): Promise { const temperature = nodeData.inputs?.temperature as string const model = nodeData.inputs?.modelName as string const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string @@ -84,14 +84,12 @@ class GoogleVertexAI_ChatModels implements INode { const obj: Partial = { temperature: parseFloat(temperature), - model, + model } if (maxOutputTokens) obj.maxOutputTokens = parseInt(maxOutputTokens, 10) if (topP) obj.topP = parseFloat(topP) - - const chat_model = new ChatGoogleVertexAI(obj) return chat_model } From a7fa827be7d0e64664e4b86cb2d11741b76f808e Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 2 Aug 2023 19:00:06 +0100 Subject: [PATCH 337/398] langsmith env variables --- docker/.env.example | 5 +++++ packages/server/.env.example | 5 +++++ packages/server/src/commands/start.ts | 12 +++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/docker/.env.example b/docker/.env.example index dd3b00fb..ec13bc0a 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -20,3 +20,8 @@ LOG_PATH=/root/.flowise/logs # EXECUTION_MODE=main (child | main) # TOOL_FUNCTION_BUILTIN_DEP=crypto,fs # TOOL_FUNCTION_EXTERNAL_DEP=moment,lodash + +# LANGCHAIN_TRACING_V2=true +# ANGCHAIN_ENDPOINT=https://api.smith.langchain.com +# LANGCHAIN_API_KEY=your_api_key +# LANGCHAIN_PROJECT=your_project \ No newline at end of file diff --git a/packages/server/.env.example b/packages/server/.env.example index 1e10452c..7ab05518 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -20,3 +20,8 @@ PASSPHRASE=MYPASSPHRASE # Passphrase used to create encryption key # EXECUTION_MODE=main (child | main) # TOOL_FUNCTION_BUILTIN_DEP=crypto,fs # TOOL_FUNCTION_EXTERNAL_DEP=moment,lodash + +# LANGCHAIN_TRACING_V2=true +# LANGCHAIN_ENDPOINT=https://api.smith.langchain.com +# LANGCHAIN_API_KEY=your_api_key +# LANGCHAIN_PROJECT=your_project diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index 6c6260b6..71459d17 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -35,7 +35,11 @@ export default class Start extends Command { DATABASE_HOST: Flags.string(), DATABASE_NAME: Flags.string(), DATABASE_USER: Flags.string(), - DATABASE_PASSWORD: Flags.string() + DATABASE_PASSWORD: Flags.string(), + LANGCHAIN_TRACING_V2: Flags.string(), + LANGCHAIN_ENDPOINT: Flags.string(), + LANGCHAIN_API_KEY: Flags.string(), + LANGCHAIN_PROJECT: Flags.string() } async stopProcess() { @@ -99,6 +103,12 @@ export default class Start extends Command { if (flags.DATABASE_USER) process.env.DATABASE_USER = flags.DATABASE_USER if (flags.DATABASE_PASSWORD) process.env.DATABASE_PASSWORD = flags.DATABASE_PASSWORD + // Langsmith tracing + if (flags.LANGCHAIN_TRACING_V2) process.env.LANGCHAIN_TRACING_V2 = flags.LANGCHAIN_TRACING_V2 + if (flags.LANGCHAIN_ENDPOINT) process.env.LANGCHAIN_ENDPOINT = flags.LANGCHAIN_ENDPOINT + if (flags.LANGCHAIN_API_KEY) process.env.LANGCHAIN_API_KEY = flags.LANGCHAIN_API_KEY + if (flags.LANGCHAIN_PROJECT) process.env.LANGCHAIN_PROJECT = flags.LANGCHAIN_PROJECT + await (async () => { try { logger.info('Starting Flowise...') From 0f3dc36131f0792ad68dbdbfdbe8e4dd9698f550 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 2 Aug 2023 19:53:24 +0100 Subject: [PATCH 338/398] langsmith env variables --- docker/.env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/.env.example b/docker/.env.example index ec13bc0a..66b0910d 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -22,6 +22,6 @@ LOG_PATH=/root/.flowise/logs # TOOL_FUNCTION_EXTERNAL_DEP=moment,lodash # LANGCHAIN_TRACING_V2=true -# ANGCHAIN_ENDPOINT=https://api.smith.langchain.com +# LANGCHAIN_ENDPOINT=https://api.smith.langchain.com # LANGCHAIN_API_KEY=your_api_key # LANGCHAIN_PROJECT=your_project \ No newline at end of file From 5a41993cda7a6498754de2c08752c97e8e980884 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 3 Aug 2023 01:47:13 +0100 Subject: [PATCH 339/398] add package to devDependency --- packages/ui/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/package.json b/packages/ui/package.json index a2eeec6f..8020d4b1 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -75,6 +75,7 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.15.8", + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@craco/craco": "^7.1.0", "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^14.0.0", From 64c0ecb075603615699fe3106686884082d3416c Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Thu, 3 Aug 2023 18:21:45 +0800 Subject: [PATCH 340/398] add removeDuplicateURL --- packages/ui/src/views/chatmessage/ChatMessage.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index bf895eb3..9cfc917b 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -64,6 +64,20 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { window.open(data, '_blank') } + const removeDuplicateURL = (message) => { + const visitedURLs = [] + const newSourceDocuments = [] + message.sourceDocuments.forEach((source) => { + if (isValidURL(source.metadata.source) && !visitedURLs.includes(source.metadata.source)) { + visitedURLs.push(source.metadata.source) + newSourceDocuments.push(source) + } else if (!isValidURL(source.metadata.source)) { + newSourceDocuments.push(source) + } + }) + return newSourceDocuments + } + const scrollToBottom = () => { if (ps.current) { ps.current.scrollTo({ top: maxScroll }) @@ -324,7 +338,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
{message.sourceDocuments && (
- {message.sourceDocuments.map((source, index) => { + {removeDuplicateURL(message).map((source, index) => { const URL = isValidURL(source.metadata.source) return ( Date: Thu, 3 Aug 2023 11:52:17 +0100 Subject: [PATCH 341/398] add auth to chroma --- .../Chroma_Existing.ts | 52 ++----------------- .../Chroma_Upsert.ts | 23 ++++++-- .../{Chroma_Existing => Chroma}/chroma.svg | 0 .../nodes/vectorstores/Chroma/core.ts | 49 +++++++++++++++++ .../vectorstores/Chroma_Upsert/chroma.svg | 7 --- 5 files changed, 71 insertions(+), 60 deletions(-) rename packages/components/nodes/vectorstores/{Chroma_Existing => Chroma}/Chroma_Existing.ts (69%) rename packages/components/nodes/vectorstores/{Chroma_Upsert => Chroma}/Chroma_Upsert.ts (76%) rename packages/components/nodes/vectorstores/{Chroma_Existing => Chroma}/chroma.svg (100%) create mode 100644 packages/components/nodes/vectorstores/Chroma/core.ts delete mode 100644 packages/components/nodes/vectorstores/Chroma_Upsert/chroma.svg diff --git a/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts b/packages/components/nodes/vectorstores/Chroma/Chroma_Existing.ts similarity index 69% rename from packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts rename to packages/components/nodes/vectorstores/Chroma/Chroma_Existing.ts index 47266fda..f55faa40 100644 --- a/packages/components/nodes/vectorstores/Chroma_Existing/Chroma_Existing.ts +++ b/packages/components/nodes/vectorstores/Chroma/Chroma_Existing.ts @@ -1,8 +1,8 @@ import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' -import { Chroma, ChromaLibArgs } from 'langchain/vectorstores/chroma' +import { Chroma } from 'langchain/vectorstores/chroma' import { Embeddings } from 'langchain/embeddings/base' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' -import type { Collection } from 'chromadb' +import { ChromaExtended } from './core' class Chroma_Existing_VectorStores implements INode { label: string @@ -94,7 +94,7 @@ class Chroma_Existing_VectorStores implements INode { if (chromaURL) obj.url = chromaURL if (chromaApiKey) obj.chromaApiKey = chromaApiKey - const vectorStore = await ChromaExisting.fromExistingCollection(embeddings, obj) + const vectorStore = await ChromaExtended.fromExistingCollection(embeddings, obj) if (output === 'retriever') { const retriever = vectorStore.asRetriever(k) @@ -107,50 +107,4 @@ class Chroma_Existing_VectorStores implements INode { } } -interface ChromaAuth { - chromaApiKey?: string -} - -class ChromaExisting extends Chroma { - chromaApiKey?: string - - constructor(embeddings: Embeddings, args: ChromaLibArgs & Partial) { - super(embeddings, args) - this.chromaApiKey = args.chromaApiKey - } - - static async fromExistingCollection(embeddings: Embeddings, dbConfig: ChromaLibArgs & Partial): Promise { - const instance = new this(embeddings, dbConfig) - await instance.ensureCollection() - return instance - } - - async ensureCollection(): Promise { - if (!this.collection) { - if (!this.index) { - const { ChromaClient } = await Chroma.imports() - const obj: any = { - path: this.url - } - if (this.chromaApiKey) { - obj.fetchOptions = { - headers: { - 'X-Api-Key': this.chromaApiKey - } - } - } - this.index = new ChromaClient(obj) - } - try { - this.collection = await this.index.getOrCreateCollection({ - name: this.collectionName - }) - } catch (err) { - throw new Error(`Chroma getOrCreateCollection error: ${err}`) - } - } - return this.collection - } -} - module.exports = { nodeClass: Chroma_Existing_VectorStores } diff --git a/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts b/packages/components/nodes/vectorstores/Chroma/Chroma_Upsert.ts similarity index 76% rename from packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts rename to packages/components/nodes/vectorstores/Chroma/Chroma_Upsert.ts index 2be1bb3a..0527b729 100644 --- a/packages/components/nodes/vectorstores/Chroma_Upsert/Chroma_Upsert.ts +++ b/packages/components/nodes/vectorstores/Chroma/Chroma_Upsert.ts @@ -1,9 +1,10 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { Chroma } from 'langchain/vectorstores/chroma' import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { flatten } from 'lodash' +import { ChromaExtended } from './core' class ChromaUpsert_VectorStores implements INode { label: string @@ -15,6 +16,7 @@ class ChromaUpsert_VectorStores implements INode { category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams outputs: INodeOutputsValue[] constructor() { @@ -26,6 +28,14 @@ class ChromaUpsert_VectorStores implements INode { this.category = 'Vector Stores' this.description = 'Upsert documents to Chroma' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + description: 'Only needed if you have chroma on cloud services with X-Api-key', + optional: true, + credentialNames: ['chromaApi'] + } this.inputs = [ { label: 'Document', @@ -73,7 +83,7 @@ class ChromaUpsert_VectorStores implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const collectionName = nodeData.inputs?.collectionName as string const docs = nodeData.inputs?.document as Document[] const embeddings = nodeData.inputs?.embeddings as Embeddings @@ -82,6 +92,9 @@ class ChromaUpsert_VectorStores implements INode { const topK = nodeData.inputs?.topK as string const k = topK ? parseFloat(topK) : 4 + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const chromaApiKey = getCredentialParam('chromaApiKey', credentialData, nodeData) + const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { @@ -91,10 +104,12 @@ class ChromaUpsert_VectorStores implements INode { const obj: { collectionName: string url?: string + chromaApiKey?: string } = { collectionName } if (chromaURL) obj.url = chromaURL + if (chromaApiKey) obj.chromaApiKey = chromaApiKey - const vectorStore = await Chroma.fromDocuments(finalDocs, embeddings, obj) + const vectorStore = await ChromaExtended.fromDocuments(finalDocs, embeddings, obj) if (output === 'retriever') { const retriever = vectorStore.asRetriever(k) diff --git a/packages/components/nodes/vectorstores/Chroma_Existing/chroma.svg b/packages/components/nodes/vectorstores/Chroma/chroma.svg similarity index 100% rename from packages/components/nodes/vectorstores/Chroma_Existing/chroma.svg rename to packages/components/nodes/vectorstores/Chroma/chroma.svg diff --git a/packages/components/nodes/vectorstores/Chroma/core.ts b/packages/components/nodes/vectorstores/Chroma/core.ts new file mode 100644 index 00000000..ccdbe03c --- /dev/null +++ b/packages/components/nodes/vectorstores/Chroma/core.ts @@ -0,0 +1,49 @@ +import { Chroma, ChromaLibArgs } from 'langchain/vectorstores/chroma' +import { Embeddings } from 'langchain/embeddings/base' +import type { Collection } from 'chromadb' + +interface ChromaAuth { + chromaApiKey?: string +} + +export class ChromaExtended extends Chroma { + chromaApiKey?: string + + constructor(embeddings: Embeddings, args: ChromaLibArgs & Partial) { + super(embeddings, args) + this.chromaApiKey = args.chromaApiKey + } + + static async fromExistingCollection(embeddings: Embeddings, dbConfig: ChromaLibArgs & Partial): Promise { + const instance = new this(embeddings, dbConfig) + await instance.ensureCollection() + return instance + } + + async ensureCollection(): Promise { + if (!this.collection) { + if (!this.index) { + const { ChromaClient } = await Chroma.imports() + const obj: any = { + path: this.url + } + if (this.chromaApiKey) { + obj.fetchOptions = { + headers: { + 'X-Api-Key': this.chromaApiKey + } + } + } + this.index = new ChromaClient(obj) + } + try { + this.collection = await this.index.getOrCreateCollection({ + name: this.collectionName + }) + } catch (err) { + throw new Error(`Chroma getOrCreateCollection error: ${err}`) + } + } + return this.collection + } +} diff --git a/packages/components/nodes/vectorstores/Chroma_Upsert/chroma.svg b/packages/components/nodes/vectorstores/Chroma_Upsert/chroma.svg deleted file mode 100644 index 64090685..00000000 --- a/packages/components/nodes/vectorstores/Chroma_Upsert/chroma.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - From d208eae868afac2afd4a7137ecfd09d1fe590d79 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 3 Aug 2023 16:21:48 +0100 Subject: [PATCH 342/398] update UI Chat Message source links --- packages/ui/src/views/chatmessage/ChatMessage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 9cfc917b..98eef72a 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -346,7 +346,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { key={index} label={ URL - ? `${(URL.hostname + URL.pathname).substring(0, 15)}...` + ? `${URL.pathname.substring(0, 15)}...` : `${source.pageContent.substring(0, 15)}...` } component='a' From 31cb4c1c64c83f4f13a7181652398a5da3a5b76c Mon Sep 17 00:00:00 2001 From: Yongtae Date: Sat, 5 Aug 2023 03:18:29 +0900 Subject: [PATCH 343/398] Add Google Vertex Auth credential and update langchain version --- .../credentials/GoogleAuth.credential.ts | 25 +++++++++++++++++++ .../GoogleVertexAIEmbedding.ts | 22 +++++++++++++--- packages/components/package.json | 2 +- 3 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 packages/components/credentials/GoogleAuth.credential.ts diff --git a/packages/components/credentials/GoogleAuth.credential.ts b/packages/components/credentials/GoogleAuth.credential.ts new file mode 100644 index 00000000..de4c8843 --- /dev/null +++ b/packages/components/credentials/GoogleAuth.credential.ts @@ -0,0 +1,25 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class GoogleVertexAuth implements INodeCredential { + label: string + name: string + version: number + inputs: INodeParams[] + + constructor() { + this.label = 'Google Vertex Auth' + this.name = 'googleVertexAuth' + this.version = 1.0 + this.inputs = [ + { + label: 'Google Application Credential File Path', + name: 'googleApplicationCredentialFilePath', + description: 'Path to your google application credential json file', + placeholder: 'your-path/application_default_credentials.json', + type: 'string' + } + ] + } +} + +module.exports = { credClass: GoogleVertexAuth } \ No newline at end of file diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts index c7079d05..1c491682 100644 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts @@ -1,6 +1,6 @@ import { GoogleVertexAIEmbeddings, GoogleVertexAIEmbeddingsParams } from 'langchain/embeddings/googlevertexai' -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' class GoogleVertexAIEmbedding_Embeddings implements INode { label: string @@ -23,6 +23,12 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { this.category = 'Embeddings' this.description = 'Google vertexAI API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(GoogleVertexAIEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['googleVertexAuth'] + } this.inputs = [ { label: 'Model Name', @@ -43,10 +49,18 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { ] } - async init(nodeData: INodeData, _: string): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const model = nodeData.inputs?.modelName as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const googleApplicationCredentialFilePath = getCredentialParam('googleApplicationCredentialFilePath', credentialData, nodeData) + + const authOptions = { + keyFile: googleApplicationCredentialFilePath, + }; const obj: GoogleVertexAIEmbeddingsParams = { - model + model:model, + authOptions, + } const embedding = new GoogleVertexAIEmbeddings(obj) diff --git a/packages/components/package.json b/packages/components/package.json index cb8d757a..afcfd361 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -39,7 +39,7 @@ "google-auth-library": "^9.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "^0.0.117", + "langchain": "^0.0.122", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", From 6ef37eade3443dc30f3704ccbe1a450cc9aae8ec Mon Sep 17 00:00:00 2001 From: Yongtae Date: Sat, 5 Aug 2023 03:19:35 +0900 Subject: [PATCH 344/398] remove model name --- .../GoogleVertexAIEmbedding.ts | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts index 1c491682..3d76585d 100644 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts @@ -29,28 +29,10 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { type: 'credential', credentialNames: ['googleVertexAuth'] } - this.inputs = [ - { - label: 'Model Name', - name: 'modelName', - type: 'options', - options: [ - { - label: 'textembedding-gecko', - name: 'textembedding-gecko' - }, - { - label: 'textembedding-gecko@001', - name: 'textembedding-gecko@001' - } - ], - default: 'textembedding-gecko' - } - ] + this.inputs = [] } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const model = nodeData.inputs?.modelName as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const googleApplicationCredentialFilePath = getCredentialParam('googleApplicationCredentialFilePath', credentialData, nodeData) @@ -58,7 +40,6 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { keyFile: googleApplicationCredentialFilePath, }; const obj: GoogleVertexAIEmbeddingsParams = { - model:model, authOptions, } From c3b90e38750802cfe322cafb7c39ff0bc2ffd788 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Sat, 5 Aug 2023 03:28:00 +0900 Subject: [PATCH 345/398] Add optional inputs for project ID and location in GoogleVertexAIEmbedding --- .../GoogleVertexAIEmbedding.ts | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts index 3d76585d..9a7525a6 100644 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts @@ -1,6 +1,7 @@ import { GoogleVertexAIEmbeddings, GoogleVertexAIEmbeddingsParams } from 'langchain/embeddings/googlevertexai' import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { GoogleAuthOptions } from 'google-auth-library' class GoogleVertexAIEmbedding_Embeddings implements INode { label: string @@ -29,20 +30,42 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { type: 'credential', credentialNames: ['googleVertexAuth'] } - this.inputs = [] + this.inputs = [ + { + label: 'Project ID', + name: 'projectID', + description: 'project id of GCP', + type: 'string', + additionalParams: true, + optional: true + }, + { + label: 'location', + name: 'location', + description: 'location of API', + type: 'string', + additionalParams: true, + optional: true + }, + ] } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const projectID = nodeData.inputs?.projectID as string + const location = nodeData.inputs?.location as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const googleApplicationCredentialFilePath = getCredentialParam('googleApplicationCredentialFilePath', credentialData, nodeData) - const authOptions = { + const authOptions:GoogleAuthOptions = { keyFile: googleApplicationCredentialFilePath, }; + + if (projectID) authOptions.projectId = projectID + const obj: GoogleVertexAIEmbeddingsParams = { authOptions, - } + if (location) obj.location = location const embedding = new GoogleVertexAIEmbeddings(obj) return embedding From 31543994040e6131b5de78cd2aa757025d7d6812 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Sat, 5 Aug 2023 03:29:03 +0900 Subject: [PATCH 346/398] lint fix GoogleAuth and GoogleVertexAIEmbedding code --- .../components/credentials/GoogleAuth.credential.ts | 2 +- .../GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/components/credentials/GoogleAuth.credential.ts b/packages/components/credentials/GoogleAuth.credential.ts index de4c8843..484ff522 100644 --- a/packages/components/credentials/GoogleAuth.credential.ts +++ b/packages/components/credentials/GoogleAuth.credential.ts @@ -22,4 +22,4 @@ class GoogleVertexAuth implements INodeCredential { } } -module.exports = { credClass: GoogleVertexAuth } \ No newline at end of file +module.exports = { credClass: GoogleVertexAuth } diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts index 9a7525a6..205e12ef 100644 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts @@ -46,7 +46,7 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { type: 'string', additionalParams: true, optional: true - }, + } ] } @@ -56,14 +56,14 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { const credentialData = await getCredentialData(nodeData.credential ?? '', options) const googleApplicationCredentialFilePath = getCredentialParam('googleApplicationCredentialFilePath', credentialData, nodeData) - const authOptions:GoogleAuthOptions = { - keyFile: googleApplicationCredentialFilePath, - }; + const authOptions: GoogleAuthOptions = { + keyFile: googleApplicationCredentialFilePath + } if (projectID) authOptions.projectId = projectID const obj: GoogleVertexAIEmbeddingsParams = { - authOptions, + authOptions } if (location) obj.location = location From adffc7cb1d64c6632e6b46ac2476a3786ac05363 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Sat, 5 Aug 2023 03:33:26 +0900 Subject: [PATCH 347/398] Add error handling for missing Google Application Credential file path --- .../GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts index 205e12ef..7b859e77 100644 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts @@ -55,6 +55,7 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { const location = nodeData.inputs?.location as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const googleApplicationCredentialFilePath = getCredentialParam('googleApplicationCredentialFilePath', credentialData, nodeData) + if (!googleApplicationCredentialFilePath) throw new Error('Please specify your Google Application Credential file path') const authOptions: GoogleAuthOptions = { keyFile: googleApplicationCredentialFilePath From d6844655ccc4cfc08faf73922d907cd98b760cf8 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Sat, 5 Aug 2023 12:08:39 +0900 Subject: [PATCH 348/398] Unify the names of what is being returned --- .../GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts index 7b859e77..dbcc9c62 100644 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts @@ -68,8 +68,8 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { } if (location) obj.location = location - const embedding = new GoogleVertexAIEmbeddings(obj) - return embedding + const model = new GoogleVertexAIEmbeddings(obj) + return model } } From 45ffa62f0ff0365701e5ea8100446cd9c84c77e9 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Sat, 5 Aug 2023 18:22:05 +0800 Subject: [PATCH 349/398] add additional systemMessagePrompt into CSV Agent --- .../components/nodes/agents/CSVAgent/CSVAgent.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/agents/CSVAgent/CSVAgent.ts b/packages/components/nodes/agents/CSVAgent/CSVAgent.ts index 9224a4c1..4a42592f 100644 --- a/packages/components/nodes/agents/CSVAgent/CSVAgent.ts +++ b/packages/components/nodes/agents/CSVAgent/CSVAgent.ts @@ -37,6 +37,16 @@ class CSV_Agents implements INode { label: 'Language Model', name: 'model', type: 'BaseLanguageModel' + }, + { + label: 'System Message', + name: 'systemMessagePrompt', + type: 'string', + rows: 4, + additionalParams: true, + optional: true, + placeholder: + 'I want you to act as a document that I am having a conversation with. Your name is "AI Assistant". You will provide me with answers from the given info. If the answer is not included, say exactly "Hmm, I am not sure." and stop after that. Refuse to answer any question not about the info. Never break character.' } ] } @@ -49,6 +59,7 @@ class CSV_Agents implements INode { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const csvFileBase64 = nodeData.inputs?.csvFile as string const model = nodeData.inputs?.model as BaseLanguageModel + const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string const loggerHandler = new ConsoleCallbackHandler(options.logger) const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) @@ -127,7 +138,9 @@ json.dumps(my_dict)` if (finalResult) { const chain = new LLMChain({ llm: model, - prompt: PromptTemplate.fromTemplate(finalSystemPrompt), + prompt: PromptTemplate.fromTemplate( + systemMessagePrompt ? `${systemMessagePrompt}\n${finalSystemPrompt}` : finalSystemPrompt + ), verbose: process.env.DEBUG === 'true' ? true : false }) const inputs = { From bb10d18c881c12d58d359fe2f11b241100a24a02 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 5 Aug 2023 23:37:51 +0100 Subject: [PATCH 350/398] add appdatasource and databaseEntities to run method --- packages/server/src/ChildProcess.ts | 9 +++++++-- packages/server/src/index.ts | 12 +++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index 2eae90f8..45509938 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -7,7 +7,8 @@ import { getStartingNodes, getUserHome, replaceInputsWithConfig, - resolveVariables + resolveVariables, + databaseEntities } from './utils' import { DataSource } from 'typeorm' import { ChatFlow } from './entity/ChatFlow' @@ -137,7 +138,11 @@ export class ChildProcess { const nodeInstance = new nodeModule.nodeClass() logger.debug(`[server] [mode:child]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) - const result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history }) + const result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, { + chatHistory: incomingInput.history, + appDataSource: childAppDataSource, + databaseEntities + }) logger.debug(`[server] [mode:child]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) await sendToParentProcess('finish', { result, addToChatFlowPool }) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 545c75a7..178218dc 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -993,10 +993,16 @@ export class App { chatHistory: incomingInput.history, socketIO, socketIOClientId: incomingInput.socketIOClientId, - logger + logger, + appDataSource: this.AppDataSource, + databaseEntities + }) + : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { + chatHistory: incomingInput.history, + logger, + appDataSource: this.AppDataSource, + databaseEntities }) - : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, logger }) - logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) return res.json(result) } From 0ae6f53295eaea615df7e75c8ea3a97d55ff6ffd Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 6 Aug 2023 19:45:21 +0100 Subject: [PATCH 351/398] add conversational retrieval agent --- .../ConversationalAgent.ts | 20 +- .../ConversationalRetrievalAgent.ts | 100 +++ .../ConversationalRetrievalAgent/agent.svg | 9 + .../OpenAIFunctionAgent.ts | 19 +- .../ConversationChain/ConversationChain.ts | 19 +- .../ConversationalRetrievalQAChain.ts | 44 +- .../nodes/memory/DynamoDb/DynamoDb.ts | 26 +- .../memory/MotorheadMemory/MotorheadMemory.ts | 48 +- .../RedisBackedChatMemory.ts | 32 +- .../nodes/memory/ZepMemory/ZepMemory.ts | 41 +- .../tools/RetrieverTool/RetrieverTool.ts | 65 ++ .../tools/RetrieverTool/retriever-tool.png | Bin 0 -> 13797 bytes packages/components/package.json | 2 +- packages/components/src/utils.ts | 46 +- .../Conversational Retrieval Agent.json | 607 ++++++++++++++++++ packages/server/src/ChildProcess.ts | 6 +- packages/server/src/index.ts | 23 +- packages/server/src/utils/index.ts | 14 +- 18 files changed, 1018 insertions(+), 103 deletions(-) create mode 100644 packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts create mode 100644 packages/components/nodes/agents/ConversationalRetrievalAgent/agent.svg create mode 100644 packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts create mode 100644 packages/components/nodes/tools/RetrieverTool/retriever-tool.png create mode 100644 packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json diff --git a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts index 005429d6..d8d8506c 100644 --- a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts +++ b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts @@ -1,9 +1,8 @@ -import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { initializeAgentExecutorWithOptions, AgentExecutor, InitializeAgentExecutorOptions } from 'langchain/agents' import { Tool } from 'langchain/tools' -import { BaseChatMemory, ChatMessageHistory } from 'langchain/memory' -import { getBaseClasses } from '../../../src/utils' -import { AIMessage, HumanMessage } from 'langchain/schema' +import { BaseChatMemory } from 'langchain/memory' +import { getBaseClasses, mapChatHistory } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { flatten } from 'lodash' @@ -93,19 +92,10 @@ class ConversationalAgent_Agents implements INode { const memory = nodeData.inputs?.memory as BaseChatMemory if (options && options.chatHistory) { - const chatHistory = [] - const histories: IMessage[] = options.chatHistory - - for (const message of histories) { - if (message.type === 'apiMessage') { - chatHistory.push(new AIMessage(message.message)) - } else if (message.type === 'userMessage') { - chatHistory.push(new HumanMessage(message.message)) - } - } - memory.chatHistory = new ChatMessageHistory(chatHistory) + memory.chatHistory = mapChatHistory(options) executor.memory = memory } + const result = await executor.call({ input }) return result?.output diff --git a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts new file mode 100644 index 00000000..01215fc2 --- /dev/null +++ b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts @@ -0,0 +1,100 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' +import { getBaseClasses, mapChatHistory } from '../../../src/utils' +import { flatten } from 'lodash' +import { BaseChatMemory } from 'langchain/memory' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' + +class ConversationalRetrievalAgent_Agents implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Conversational Retrieval Agent' + this.name = 'conversationalRetrievalAgent' + this.version = 1.0 + this.type = 'AgentExecutor' + this.category = 'Agents' + this.icon = 'agent.svg' + this.description = `An agent optimized for retrieval during conversation, answering questions based on past dialogue, all using OpenAI's Function Calling` + this.baseClasses = [this.type, ...getBaseClasses(AgentExecutor)] + this.inputs = [ + { + label: 'Allowed Tools', + name: 'tools', + type: 'Tool', + list: true + }, + { + label: 'Memory', + name: 'memory', + type: 'BaseChatMemory' + }, + { + label: 'OpenAI Chat Model', + name: 'model', + type: 'ChatOpenAI' + }, + { + label: 'System Message', + name: 'systemMessage', + type: 'string', + rows: 4, + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const model = nodeData.inputs?.model + const memory = nodeData.inputs?.memory as BaseChatMemory + const systemMessage = nodeData.inputs?.systemMessage as string + + let tools = nodeData.inputs?.tools + tools = flatten(tools) + + const executor = await initializeAgentExecutorWithOptions(tools, model, { + agentType: 'openai-functions', + verbose: process.env.DEBUG === 'true' ? true : false, + agentArgs: { + prefix: systemMessage ?? `You are a helpful AI assistant.` + }, + returnIntermediateSteps: true + }) + executor.memory = memory + return executor + } + + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { + const executor = nodeData.instance as AgentExecutor + + if (options && options.chatHistory) { + if (executor.memory) { + ;(executor.memory as any).memoryKey = 'chat_history' + ;(executor.memory as any).outputKey = 'output' + ;(executor.memory as any).chatHistory = mapChatHistory(options) + } + } + + const loggerHandler = new ConsoleCallbackHandler(options.logger) + + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const result = await executor.call({ input }, [loggerHandler, handler]) + return result?.output + } else { + const result = await executor.call({ input }, [loggerHandler]) + return result?.output + } + } +} + +module.exports = { nodeClass: ConversationalRetrievalAgent_Agents } diff --git a/packages/components/nodes/agents/ConversationalRetrievalAgent/agent.svg b/packages/components/nodes/agents/ConversationalRetrievalAgent/agent.svg new file mode 100644 index 00000000..c87861e5 --- /dev/null +++ b/packages/components/nodes/agents/ConversationalRetrievalAgent/agent.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index f3751f1f..8c182d1a 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -1,10 +1,9 @@ -import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, mapChatHistory } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { flatten } from 'lodash' -import { BaseChatMemory, ChatMessageHistory } from 'langchain/memory' -import { AIMessage, HumanMessage } from 'langchain/schema' +import { BaseChatMemory } from 'langchain/memory' import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class OpenAIFunctionAgent_Agents implements INode { @@ -82,17 +81,7 @@ class OpenAIFunctionAgent_Agents implements INode { const memory = nodeData.inputs?.memory as BaseChatMemory if (options && options.chatHistory) { - const chatHistory = [] - const histories: IMessage[] = options.chatHistory - - for (const message of histories) { - if (message.type === 'apiMessage') { - chatHistory.push(new AIMessage(message.message)) - } else if (message.type === 'userMessage') { - chatHistory.push(new HumanMessage(message.message)) - } - } - memory.chatHistory = new ChatMessageHistory(chatHistory) + memory.chatHistory = mapChatHistory(options) executor.memory = memory } diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index f08d430c..cd42a670 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -1,10 +1,9 @@ -import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { ConversationChain } from 'langchain/chains' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, mapChatHistory } from '../../../src/utils' import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts' -import { BufferMemory, ChatMessageHistory } from 'langchain/memory' +import { BufferMemory } from 'langchain/memory' import { BaseChatModel } from 'langchain/chat_models/base' -import { AIMessage, HumanMessage } from 'langchain/schema' import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' import { flatten } from 'lodash' import { Document } from 'langchain/document' @@ -106,17 +105,7 @@ class ConversationChain_Chains implements INode { const memory = nodeData.inputs?.memory as BufferMemory if (options && options.chatHistory) { - const chatHistory = [] - const histories: IMessage[] = options.chatHistory - - for (const message of histories) { - if (message.type === 'apiMessage') { - chatHistory.push(new AIMessage(message.message)) - } else if (message.type === 'userMessage') { - chatHistory.push(new HumanMessage(message.message)) - } - } - memory.chatHistory = new ChatMessageHistory(chatHistory) + memory.chatHistory = mapChatHistory(options) chain.memory = memory } diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index e7f6e826..c14b292d 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -1,10 +1,9 @@ import { BaseLanguageModel } from 'langchain/base_language' -import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, mapChatHistory } from '../../../src/utils' import { ConversationalRetrievalQAChain, QAChainParams } from 'langchain/chains' -import { AIMessage, HumanMessage } from 'langchain/schema' import { BaseRetriever } from 'langchain/schema/retriever' -import { BaseChatMemory, BufferMemory, ChatMessageHistory, BufferMemoryInput } from 'langchain/memory' +import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { PromptTemplate } from 'langchain/prompts' import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' import { @@ -105,7 +104,7 @@ class ConversationalRetrievalQAChain_Chains implements INode { const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean const chainOption = nodeData.inputs?.chainOption as string - const memory = nodeData.inputs?.memory + const externalMemory = nodeData.inputs?.memory const obj: any = { verbose: process.env.DEBUG === 'true' ? true : false, @@ -113,7 +112,9 @@ class ConversationalRetrievalQAChain_Chains implements INode { template: CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT } } + if (returnSourceDocuments) obj.returnSourceDocuments = returnSourceDocuments + if (chainOption === 'map_reduce') { obj.qaChainOptions = { type: 'map_reduce', @@ -142,20 +143,21 @@ class ConversationalRetrievalQAChain_Chains implements INode { } as QAChainParams } - if (memory) { - memory.inputKey = 'question' - memory.memoryKey = 'chat_history' - if (chainOption === 'refine') memory.outputKey = 'output_text' - else memory.outputKey = 'text' - obj.memory = memory + if (externalMemory) { + externalMemory.memoryKey = 'chat_history' + externalMemory.inputKey = 'question' + externalMemory.outputKey = 'text' + externalMemory.returnMessages = true + if (chainOption === 'refine') externalMemory.outputKey = 'output_text' + obj.memory = externalMemory } else { const fields: BufferMemoryInput = { memoryKey: 'chat_history', inputKey: 'question', + outputKey: 'text', returnMessages: true } if (chainOption === 'refine') fields.outputKey = 'output_text' - else fields.outputKey = 'text' obj.memory = new BufferMemory(fields) } @@ -166,7 +168,6 @@ class ConversationalRetrievalQAChain_Chains implements INode { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = nodeData.instance as ConversationalRetrievalQAChain const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean - const memory = nodeData.inputs?.memory const chainOption = nodeData.inputs?.chainOption as string let model = nodeData.inputs?.model @@ -177,21 +178,8 @@ class ConversationalRetrievalQAChain_Chains implements INode { const obj = { question: input } - // If external memory like Zep, Redis is being used, ignore below - if (!memory && chain.memory && options && options.chatHistory) { - const chatHistory = [] - const histories: IMessage[] = options.chatHistory - const memory = chain.memory as BaseChatMemory - - for (const message of histories) { - if (message.type === 'apiMessage') { - chatHistory.push(new AIMessage(message.message)) - } else if (message.type === 'userMessage') { - chatHistory.push(new HumanMessage(message.message)) - } - } - memory.chatHistory = new ChatMessageHistory(chatHistory) - chain.memory = memory + if (options && options.chatHistory && chain.memory) { + ;(chain.memory as any).chatHistory = mapChatHistory(options) } const loggerHandler = new ConsoleCallbackHandler(options.logger) diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index 6926912f..68b09b7b 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -1,6 +1,6 @@ import { ICommonObject, INode, INodeData, INodeParams, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src' import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' -import { BufferMemory } from 'langchain/memory' +import { BufferMemory, BufferMemoryInput } from 'langchain/memory' class DynamoDb_Memory implements INode { label: string @@ -51,7 +51,7 @@ class DynamoDb_Memory implements INode { label: 'Session ID', name: 'sessionId', type: 'string', - description: 'if empty, chatId will be used automatically', + description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', default: '', additionalParams: true, optional: true @@ -86,9 +86,11 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P const sessionId = nodeData.inputs?.sessionId as string const region = nodeData.inputs?.region as string const memoryKey = nodeData.inputs?.memoryKey as string - const chatId = options.chatId + let isSessionIdUsingChatMessageId = false + if (!sessionId && chatId) isSessionIdUsingChatMessageId = true + const credentialData = await getCredentialData(nodeData.credential ?? '', options) const accessKeyId = getCredentialParam('accessKey', credentialData, nodeData) const secretAccessKey = getCredentialParam('secretAccessKey', credentialData, nodeData) @@ -106,12 +108,26 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P } }) - const memory = new BufferMemory({ + const memory = new BufferMemoryExtended({ memoryKey, chatHistory: dynamoDb, - returnMessages: true + returnMessages: true, + isSessionIdUsingChatMessageId }) return memory } +interface BufferMemoryExtendedInput { + isSessionIdUsingChatMessageId: boolean +} + +class BufferMemoryExtended extends BufferMemory { + isSessionIdUsingChatMessageId? = false + + constructor(fields: BufferMemoryInput & Partial) { + super(fields) + this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId + } +} + module.exports = { nodeClass: DynamoDb_Memory } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index bb23de9d..0ec2f42a 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -2,6 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ICommonObject } from '../../../src' import { MotorheadMemory, MotorheadMemoryInput } from 'langchain/memory' +import fetch from 'node-fetch' class MotorMemory_Memory implements INode { label: string @@ -44,7 +45,7 @@ class MotorMemory_Memory implements INode { label: 'Session Id', name: 'sessionId', type: 'string', - description: 'if empty, chatId will be used automatically', + description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', default: '', additionalParams: true, optional: true @@ -77,14 +78,16 @@ const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject): const memoryKey = nodeData.inputs?.memoryKey as string const baseURL = nodeData.inputs?.baseURL as string const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string + let isSessionIdUsingChatMessageId = false + if (!sessionId && chatId) isSessionIdUsingChatMessageId = true + const credentialData = await getCredentialData(nodeData.credential ?? '', options) const apiKey = getCredentialParam('apiKey', credentialData, nodeData) const clientId = getCredentialParam('clientId', credentialData, nodeData) - let obj: MotorheadMemoryInput = { + let obj: MotorheadMemoryInput & Partial = { returnMessages: true, sessionId: sessionId ? sessionId : chatId, memoryKey @@ -103,7 +106,44 @@ const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject): } } - return new MotorheadMemory(obj) + if (isSessionIdUsingChatMessageId) obj.isSessionIdUsingChatMessageId = true + + const motorheadMemory = new MotorheadMemoryExtended(obj) + + // Get messages from sessionId + await motorheadMemory.init() + + return motorheadMemory +} + +interface MotorheadMemoryExtendedInput { + isSessionIdUsingChatMessageId: boolean +} + +class MotorheadMemoryExtended extends MotorheadMemory { + isSessionIdUsingChatMessageId? = false + + constructor(fields: MotorheadMemoryInput & Partial) { + super(fields) + this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId + } + + async clear(): Promise { + try { + await this.caller.call(fetch, `${this.url}/sessions/${this.sessionId}/memory`, { + //@ts-ignore + signal: this.timeout ? AbortSignal.timeout(this.timeout) : undefined, + headers: this._getHeaders() as ICommonObject, + method: 'DELETE' + }) + } catch (error) { + console.error('Error deleting session: ', error) + } + + // Clear the superclass's chat history + await this.chatHistory.clear() + await super.clear() + } } module.exports = { nodeClass: MotorMemory_Memory } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 7b3e2cb5..f10f25ce 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -1,7 +1,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' import { ICommonObject } from '../../../src' -import { BufferMemory } from 'langchain/memory' +import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/redis' import { createClient } from 'redis' @@ -36,7 +36,7 @@ class RedisBackedChatMemory_Memory implements INode { label: 'Session Id', name: 'sessionId', type: 'string', - description: 'if empty, chatId will be used automatically', + description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', default: '', additionalParams: true, optional: true @@ -78,9 +78,11 @@ const initalizeRedis = (nodeData: INodeData, options: ICommonObject): BufferMemo const sessionId = nodeData.inputs?.sessionId as string const sessionTTL = nodeData.inputs?.sessionTTL as number const memoryKey = nodeData.inputs?.memoryKey as string - const chatId = options?.chatId as string + let isSessionIdUsingChatMessageId = false + if (!sessionId && chatId) isSessionIdUsingChatMessageId = true + const redisClient = createClient({ url: baseURL }) let obj: RedisChatMessageHistoryInput = { sessionId: sessionId ? sessionId : chatId, @@ -94,10 +96,28 @@ const initalizeRedis = (nodeData: INodeData, options: ICommonObject): BufferMemo } } - let redisChatMessageHistory = new RedisChatMessageHistory(obj) - let redis = new BufferMemory({ memoryKey, chatHistory: redisChatMessageHistory, returnMessages: true }) + const redisChatMessageHistory = new RedisChatMessageHistory(obj) - return redis + const memory = new BufferMemoryExtended({ + memoryKey, + chatHistory: redisChatMessageHistory, + returnMessages: true, + isSessionIdUsingChatMessageId + }) + return memory +} + +interface BufferMemoryExtendedInput { + isSessionIdUsingChatMessageId: boolean +} + +class BufferMemoryExtended extends BufferMemory { + isSessionIdUsingChatMessageId? = false + + constructor(fields: BufferMemoryInput & Partial) { + super(fields) + this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId + } } module.exports = { nodeClass: RedisBackedChatMemory_Memory } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 3faa29b9..f2a47852 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -50,7 +50,7 @@ class ZepMemory_Memory implements INode { label: 'Session Id', name: 'sessionId', type: 'string', - description: 'if empty, chatId will be used automatically', + description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', default: '', additionalParams: true, optional: true @@ -156,13 +156,15 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis const memoryKey = nodeData.inputs?.memoryKey as string const inputKey = nodeData.inputs?.inputKey as string const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string + let isSessionIdUsingChatMessageId = false + if (!sessionId && chatId) isSessionIdUsingChatMessageId = true + const credentialData = await getCredentialData(nodeData.credential ?? '', options) const apiKey = getCredentialParam('apiKey', credentialData, nodeData) - const obj: ZepMemoryInput = { + const obj: ZepMemoryInput & Partial = { baseURL, sessionId: sessionId ? sessionId : chatId, aiPrefix, @@ -172,8 +174,39 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis inputKey } if (apiKey) obj.apiKey = apiKey + if (isSessionIdUsingChatMessageId) obj.isSessionIdUsingChatMessageId = true - return new ZepMemory(obj) + return new ZepMemoryExtended(obj) +} + +interface ZepMemoryExtendedInput { + isSessionIdUsingChatMessageId: boolean +} + +class ZepMemoryExtended extends ZepMemory { + isSessionIdUsingChatMessageId? = false + + constructor(fields: ZepMemoryInput & Partial) { + super(fields) + this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId + } + + async clear(): Promise { + // Only clear when sessionId is using chatId + // If sessionId is specified, clearing and inserting again will error because the sessionId has been soft deleted + // If using chatId, it will not be a problem because the sessionId will always be the new chatId + if (this.isSessionIdUsingChatMessageId) { + try { + await this.zepClient.deleteMemory(this.sessionId) + } catch (error) { + console.error('Error deleting session: ', error) + } + + // Clear the superclass's chat history + await super.clear() + } + await this.chatHistory.clear() + } } module.exports = { nodeClass: ZepMemory_Memory } diff --git a/packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts b/packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts new file mode 100644 index 00000000..6217ca6e --- /dev/null +++ b/packages/components/nodes/tools/RetrieverTool/RetrieverTool.ts @@ -0,0 +1,65 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { DynamicTool } from 'langchain/tools' +import { createRetrieverTool } from 'langchain/agents/toolkits' +import { BaseRetriever } from 'langchain/schema/retriever' + +class Retriever_Tools implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'Retriever Tool' + this.name = 'retrieverTool' + this.version = 1.0 + this.type = 'RetrieverTool' + this.icon = 'retriever-tool.png' + this.category = 'Tools' + this.description = 'Use a retriever as allowed tool for agent' + this.baseClasses = [this.type, 'DynamicTool', ...getBaseClasses(DynamicTool)] + this.inputs = [ + { + label: 'Retriever Name', + name: 'name', + type: 'string', + placeholder: 'search_state_of_union' + }, + { + label: 'Retriever Description', + name: 'description', + type: 'string', + description: 'When should agent uses to retrieve documents', + rows: 3, + placeholder: 'Searches and returns documents regarding the state-of-the-union.' + }, + { + label: 'Retriever', + name: 'retriever', + type: 'BaseRetriever' + } + ] + } + + async init(nodeData: INodeData): Promise { + const name = nodeData.inputs?.name as string + const description = nodeData.inputs?.description as string + const retriever = nodeData.inputs?.retriever as BaseRetriever + + const tool = createRetrieverTool(retriever, { + name, + description + }) + + return tool + } +} + +module.exports = { nodeClass: Retriever_Tools } diff --git a/packages/components/nodes/tools/RetrieverTool/retriever-tool.png b/packages/components/nodes/tools/RetrieverTool/retriever-tool.png new file mode 100644 index 0000000000000000000000000000000000000000..4814d007518b1cbb935e6dcb6cab877e3c91b55d GIT binary patch literal 13797 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 sTVAa0%?5eX)2$Aa0i*`A0ssI2 literal 0 HcmV?d00001 diff --git a/packages/components/package.json b/packages/components/package.json index 374e7949..7f55010d 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -38,7 +38,7 @@ "form-data": "^4.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "^0.0.117", + "langchain": "^0.0.122", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 363dd026..2f4e7f5b 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -4,8 +4,10 @@ import * as fs from 'fs' import * as path from 'path' import { JSDOM } from 'jsdom' import { DataSource } from 'typeorm' -import { ICommonObject, IDatabaseEntity, INodeData } from './Interface' +import { ICommonObject, IDatabaseEntity, IMessage, INodeData } from './Interface' import { AES, enc } from 'crypto-js' +import { ChatMessageHistory } from 'langchain/memory' +import { AIMessage, HumanMessage } from 'langchain/schema' 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 @@ -204,6 +206,9 @@ export const getAvailableURLs = async (url: string, limit: number) => { /** * Search for href through htmlBody string + * @param {string} htmlBody + * @param {string} baseURL + * @returns {string[]} */ function getURLsFromHTML(htmlBody: string, baseURL: string): string[] { const dom = new JSDOM(htmlBody) @@ -233,6 +238,8 @@ function getURLsFromHTML(htmlBody: string, baseURL: string): string[] { /** * Normalize URL to prevent crawling the same page + * @param {string} urlString + * @returns {string} */ function normalizeURL(urlString: string): string { const urlObj = new URL(urlString) @@ -246,6 +253,11 @@ function normalizeURL(urlString: string): string { /** * Recursive crawl using normalizeURL and getURLsFromHTML + * @param {string} baseURL + * @param {string} currentURL + * @param {string[]} pages + * @param {number} limit + * @returns {Promise} */ async function crawl(baseURL: string, currentURL: string, pages: string[], limit: number): Promise { const baseURLObj = new URL(baseURL) @@ -290,6 +302,9 @@ async function crawl(baseURL: string, currentURL: string, pages: string[], limit /** * Prep URL before passing into recursive carwl function + * @param {string} stringURL + * @param {number} limit + * @returns {Promise} */ export async function webCrawl(stringURL: string, limit: number): Promise { const URLObj = new URL(stringURL) @@ -336,10 +351,10 @@ export async function xmlScrape(currentURL: string, limit: number): Promise { try { @@ -470,6 +485,10 @@ export function handleEscapeCharacters(input: any, reverse: Boolean): any { return input } +/** + * Get user home dir + * @returns {string} + */ export const getUserHome = (): string => { let variableName = 'HOME' if (process.platform === 'win32') { @@ -482,3 +501,22 @@ export const getUserHome = (): string => { } return process.env[variableName] as string } + +/** + * Map incoming chat history to ChatMessageHistory + * @param {options} ICommonObject + * @returns {ChatMessageHistory} + */ +export const mapChatHistory = (options: ICommonObject): ChatMessageHistory => { + const chatHistory = [] + const histories: IMessage[] = options.chatHistory + + for (const message of histories) { + if (message.type === 'apiMessage') { + chatHistory.push(new AIMessage(message.message)) + } else if (message.type === 'userMessage') { + chatHistory.push(new HumanMessage(message.message)) + } + } + return new ChatMessageHistory(chatHistory) +} diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json new file mode 100644 index 00000000..dcf344d1 --- /dev/null +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json @@ -0,0 +1,607 @@ +{ + "description": "Agent optimized for vector retrieval during conversation and answering questions based on previous dialogue.", + "nodes": [ + { + "width": 300, + "height": 523, + "id": "chatOpenAI_0", + "position": { + "x": 1381.867549919116, + "y": 212.76900895393834 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "version": 1, + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["openAIApi"], + "id": "chatOpenAI_0-input-credential-credential" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_0-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "default": 0.9, + "optional": true, + "id": "chatOpenAI_0-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "gpt-3.5-turbo-16k", + "temperature": "0", + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1381.867549919116, + "y": 212.76900895393834 + }, + "dragging": false + }, + { + "width": 300, + "height": 329, + "id": "openAIEmbeddings_0", + "position": { + "x": 954.0674802999345, + "y": -196.7034956445692 + }, + "type": "customNode", + "data": { + "id": "openAIEmbeddings_0", + "label": "OpenAI Embeddings", + "version": 1, + "name": "openAIEmbeddings", + "type": "OpenAIEmbeddings", + "baseClasses": ["OpenAIEmbeddings", "Embeddings"], + "category": "Embeddings", + "description": "OpenAI API to generate embeddings for a given text", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["openAIApi"], + "id": "openAIEmbeddings_0-input-credential-credential" + }, + { + "label": "Strip New Lines", + "name": "stripNewLines", + "type": "boolean", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-stripNewLines-boolean" + }, + { + "label": "Batch Size", + "name": "batchSize", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-batchSize-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-basepath-string" + } + ], + "inputAnchors": [], + "inputs": { + "stripNewLines": "", + "batchSize": "", + "timeout": "", + "basepath": "" + }, + "outputAnchors": [ + { + "id": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "name": "openAIEmbeddings", + "label": "OpenAIEmbeddings", + "type": "OpenAIEmbeddings | Embeddings" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 954.0674802999345, + "y": -196.7034956445692 + }, + "dragging": false + }, + { + "width": 300, + "height": 505, + "id": "pineconeExistingIndex_0", + "position": { + "x": 1362.0018461011314, + "y": -334.0373537488481 + }, + "type": "customNode", + "data": { + "id": "pineconeExistingIndex_0", + "label": "Pinecone Load Existing Index", + "version": 1, + "name": "pineconeExistingIndex", + "type": "Pinecone", + "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], + "category": "Vector Stores", + "description": "Load existing index from Pinecone (i.e: Document has been upserted)", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["pineconeApi"], + "id": "pineconeExistingIndex_0-input-credential-credential" + }, + { + "label": "Pinecone Index", + "name": "pineconeIndex", + "type": "string", + "id": "pineconeExistingIndex_0-input-pineconeIndex-string" + }, + { + "label": "Pinecone Namespace", + "name": "pineconeNamespace", + "type": "string", + "placeholder": "my-first-namespace", + "additionalParams": true, + "optional": true, + "id": "pineconeExistingIndex_0-input-pineconeNamespace-string" + }, + { + "label": "Pinecone Metadata Filter", + "name": "pineconeMetadataFilter", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "pineconeExistingIndex_0-input-pineconeMetadataFilter-json" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pineconeExistingIndex_0-input-topK-number" + } + ], + "inputAnchors": [ + { + "label": "Embeddings", + "name": "embeddings", + "type": "Embeddings", + "id": "pineconeExistingIndex_0-input-embeddings-Embeddings" + } + ], + "inputs": { + "embeddings": "{{openAIEmbeddings_0.data.instance}}", + "pineconeIndex": "newindex", + "pineconeNamespace": "", + "pineconeMetadataFilter": "", + "topK": "" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "pineconeExistingIndex_0-output-retriever-Pinecone|VectorStoreRetriever|BaseRetriever", + "name": "retriever", + "label": "Pinecone Retriever", + "type": "Pinecone | VectorStoreRetriever | BaseRetriever" + }, + { + "id": "pineconeExistingIndex_0-output-vectorStore-Pinecone|VectorStore", + "name": "vectorStore", + "label": "Pinecone Vector Store", + "type": "Pinecone | VectorStore" + } + ], + "default": "retriever" + } + ], + "outputs": { + "output": "retriever" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1362.0018461011314, + "y": -334.0373537488481 + }, + "dragging": false + }, + { + "width": 300, + "height": 383, + "id": "conversationalRetrievalAgent_0", + "position": { + "x": 2345.912948267881, + "y": 357.97363342258217 + }, + "type": "customNode", + "data": { + "id": "conversationalRetrievalAgent_0", + "label": "Conversational Retrieval Agent", + "version": 1, + "name": "conversationalRetrievalAgent", + "type": "AgentExecutor", + "baseClasses": ["AgentExecutor", "BaseChain", "Runnable"], + "category": "Agents", + "description": "An agent optimized for retrieval during conversation, answering questions based on past dialogue, all using OpenAI's Function Calling", + "inputParams": [ + { + "label": "System Message", + "name": "systemMessage", + "type": "string", + "rows": 4, + "optional": true, + "additionalParams": true, + "id": "conversationalRetrievalAgent_0-input-systemMessage-string" + } + ], + "inputAnchors": [ + { + "label": "Allowed Tools", + "name": "tools", + "type": "Tool", + "list": true, + "id": "conversationalRetrievalAgent_0-input-tools-Tool" + }, + { + "label": "Memory", + "name": "memory", + "type": "BaseChatMemory", + "id": "conversationalRetrievalAgent_0-input-memory-BaseChatMemory" + }, + { + "label": "OpenAI Chat Model", + "name": "model", + "type": "ChatOpenAI", + "id": "conversationalRetrievalAgent_0-input-model-ChatOpenAI" + } + ], + "inputs": { + "tools": ["{{retrieverTool_0.data.instance}}"], + "memory": "{{bufferMemory_0.data.instance}}", + "model": "{{chatOpenAI_0.data.instance}}", + "systemMessage": "" + }, + "outputAnchors": [ + { + "id": "conversationalRetrievalAgent_0-output-conversationalRetrievalAgent-AgentExecutor|BaseChain|Runnable", + "name": "conversationalRetrievalAgent", + "label": "AgentExecutor", + "type": "AgentExecutor | BaseChain | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 2345.912948267881, + "y": 357.97363342258217 + }, + "dragging": false + }, + { + "width": 300, + "height": 505, + "id": "retrieverTool_0", + "position": { + "x": 1770.1485365275375, + "y": -321.14473253480946 + }, + "type": "customNode", + "data": { + "id": "retrieverTool_0", + "label": "Retriever Tool", + "version": 1, + "name": "retrieverTool", + "type": "RetrieverTool", + "baseClasses": ["RetrieverTool", "DynamicTool", "Tool", "StructuredTool", "Runnable"], + "category": "Tools", + "description": "Use a retriever as allowed tool for agent", + "inputParams": [ + { + "label": "Retriever Name", + "name": "name", + "type": "string", + "placeholder": "search_state_of_union", + "id": "retrieverTool_0-input-name-string" + }, + { + "label": "Retriever Description", + "name": "description", + "type": "string", + "description": "When should agent uses to retrieve documents", + "rows": 3, + "placeholder": "Searches and returns documents regarding the state-of-the-union.", + "id": "retrieverTool_0-input-description-string" + } + ], + "inputAnchors": [ + { + "label": "Retriever", + "name": "retriever", + "type": "BaseRetriever", + "id": "retrieverTool_0-input-retriever-BaseRetriever" + } + ], + "inputs": { + "name": "search_website", + "description": "Searches and return documents regarding Jane - a culinary institution that offers top quality coffee, pastries, breakfast, lunch, and a variety of baked goods. They have multiple locations, including Jane on Fillmore, Jane on Larkin, Jane the Bakery, Toy Boat By Jane, and Little Jane on Grant. They emphasize healthy eating with a focus on flavor and quality ingredients. They bake everything in-house and work with local suppliers to source ingredients directly from farmers. They also offer catering services and delivery options.", + "retriever": "{{pineconeExistingIndex_0.data.instance}}" + }, + "outputAnchors": [ + { + "id": "retrieverTool_0-output-retrieverTool-RetrieverTool|DynamicTool|Tool|StructuredTool|Runnable", + "name": "retrieverTool", + "label": "RetrieverTool", + "type": "RetrieverTool | DynamicTool | Tool | StructuredTool | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "dragging": false, + "positionAbsolute": { + "x": 1770.1485365275375, + "y": -321.14473253480946 + } + }, + { + "width": 300, + "height": 376, + "id": "bufferMemory_0", + "position": { + "x": 1771.396291209036, + "y": 216.94151328212496 + }, + "type": "customNode", + "data": { + "id": "bufferMemory_0", + "label": "Buffer Memory", + "version": 1, + "name": "bufferMemory", + "type": "BufferMemory", + "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], + "category": "Memory", + "description": "Remembers previous conversational back and forths directly", + "inputParams": [ + { + "label": "Memory Key", + "name": "memoryKey", + "type": "string", + "default": "chat_history", + "id": "bufferMemory_0-input-memoryKey-string" + }, + { + "label": "Input Key", + "name": "inputKey", + "type": "string", + "default": "input", + "id": "bufferMemory_0-input-inputKey-string" + } + ], + "inputAnchors": [], + "inputs": { + "memoryKey": "chat_history", + "inputKey": "input" + }, + "outputAnchors": [ + { + "id": "bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory", + "name": "bufferMemory", + "label": "BufferMemory", + "type": "BufferMemory | BaseChatMemory | BaseMemory" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1771.396291209036, + "y": 216.94151328212496 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "openAIEmbeddings_0", + "sourceHandle": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "target": "pineconeExistingIndex_0", + "targetHandle": "pineconeExistingIndex_0-input-embeddings-Embeddings", + "type": "buttonedge", + "id": "openAIEmbeddings_0-openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings-pineconeExistingIndex_0-pineconeExistingIndex_0-input-embeddings-Embeddings", + "data": { + "label": "" + } + }, + { + "source": "pineconeExistingIndex_0", + "sourceHandle": "pineconeExistingIndex_0-output-retriever-Pinecone|VectorStoreRetriever|BaseRetriever", + "target": "retrieverTool_0", + "targetHandle": "retrieverTool_0-input-retriever-BaseRetriever", + "type": "buttonedge", + "id": "pineconeExistingIndex_0-pineconeExistingIndex_0-output-retriever-Pinecone|VectorStoreRetriever|BaseRetriever-retrieverTool_0-retrieverTool_0-input-retriever-BaseRetriever", + "data": { + "label": "" + } + }, + { + "source": "retrieverTool_0", + "sourceHandle": "retrieverTool_0-output-retrieverTool-RetrieverTool|DynamicTool|Tool|StructuredTool|Runnable", + "target": "conversationalRetrievalAgent_0", + "targetHandle": "conversationalRetrievalAgent_0-input-tools-Tool", + "type": "buttonedge", + "id": "retrieverTool_0-retrieverTool_0-output-retrieverTool-RetrieverTool|DynamicTool|Tool|StructuredTool|Runnable-conversationalRetrievalAgent_0-conversationalRetrievalAgent_0-input-tools-Tool", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "target": "conversationalRetrievalAgent_0", + "targetHandle": "conversationalRetrievalAgent_0-input-model-ChatOpenAI", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalAgent_0-conversationalRetrievalAgent_0-input-model-ChatOpenAI", + "data": { + "label": "" + } + }, + { + "source": "bufferMemory_0", + "sourceHandle": "bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory", + "target": "conversationalRetrievalAgent_0", + "targetHandle": "conversationalRetrievalAgent_0-input-memory-BaseChatMemory", + "type": "buttonedge", + "id": "bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-conversationalRetrievalAgent_0-conversationalRetrievalAgent_0-input-memory-BaseChatMemory", + "data": { + "label": "" + } + } + ] +} diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index 2eae90f8..6cc98e02 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -2,6 +2,7 @@ import path from 'path' import { IChildProcessMessage, IReactFlowNode, IReactFlowObject, IRunChatflowMessageValue, INodeData } from './Interface' import { buildLangchain, + checkMemorySessionId, constructGraphs, getEndingNode, getStartingNodes, @@ -137,7 +138,10 @@ export class ChildProcess { const nodeInstance = new nodeModule.nodeClass() logger.debug(`[server] [mode:child]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) - const result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history }) + + if (nodeToExecuteData.instance) checkMemorySessionId(nodeToExecuteData.instance, chatId) + + const result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, chatId }) logger.debug(`[server] [mode:child]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) await sendToParentProcess('finish', { result, addToChatFlowPool }) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 545c75a7..803dd175 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -45,7 +45,8 @@ import { decryptCredentialData, clearSessionMemory, replaceInputsWithConfig, - getEncryptionKey + getEncryptionKey, + checkMemorySessionId } from './utils' import { cloneDeep, omit } from 'lodash' import { getDataSource } from './DataSource' @@ -661,7 +662,11 @@ export class App { templates.push(template) }) const FlowiseDocsQnA = templates.find((tmp) => tmp.name === 'Flowise Docs QnA') - if (FlowiseDocsQnA) templates.unshift(FlowiseDocsQnA) + const FlowiseDocsQnAIndex = templates.findIndex((tmp) => tmp.name === 'Flowise Docs QnA') + if (FlowiseDocsQnA && FlowiseDocsQnAIndex > 0) { + templates.splice(FlowiseDocsQnAIndex, 1) + templates.unshift(FlowiseDocsQnA) + } return res.json(templates) }) @@ -988,14 +993,24 @@ export class App { isStreamValid = isStreamValid && !isVectorStoreFaiss(nodeToExecuteData) logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) + + if (nodeToExecuteData.instance) checkMemorySessionId(nodeToExecuteData.instance, chatId) + const result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, socketIO, socketIOClientId: incomingInput.socketIOClientId, - logger + logger, + appDataSource: this.AppDataSource, + databaseEntities + }) + : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { + chatHistory: incomingInput.history, + logger, + appDataSource: this.AppDataSource, + databaseEntities }) - : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, logger }) logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) return res.json(result) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 245f0b9c..12dd6545 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -787,7 +787,7 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod isValidChainOrAgent = !blacklistChains.includes(endingNodeData.name) } else if (endingNodeData.category === 'Agents') { // Agent that are available to stream - const whitelistAgents = ['openAIFunctionAgent', 'csvAgent', 'airtableAgent'] + const whitelistAgents = ['openAIFunctionAgent', 'csvAgent', 'airtableAgent', 'conversationalRetrievalAgent'] isValidChainOrAgent = whitelistAgents.includes(endingNodeData.name) } @@ -908,3 +908,15 @@ export const redactCredentialWithPasswordType = ( } return plainDataObj } + +/** + * Replace sessionId with new chatId + * Ex: after clear chat history, use the new chatId as sessionId + * @param {any} instance + * @param {string} chatId + */ +export const checkMemorySessionId = (instance: any, chatId: string) => { + if (instance.memory && instance.memory.isSessionIdUsingChatMessageId && chatId) { + instance.memory.sessionId = chatId + } +} From 5ef0324304a1a78ebd68477a91fd76a50c91c5cb Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 7 Aug 2023 00:14:00 +0100 Subject: [PATCH 352/398] add output keys fix --- .../ConversationalRetrievalAgent.ts | 10 ++++------ packages/components/src/utils.ts | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts index 01215fc2..ed39fbc8 100644 --- a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts +++ b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts @@ -76,12 +76,10 @@ class ConversationalRetrievalAgent_Agents implements INode { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const executor = nodeData.instance as AgentExecutor - if (options && options.chatHistory) { - if (executor.memory) { - ;(executor.memory as any).memoryKey = 'chat_history' - ;(executor.memory as any).outputKey = 'output' - ;(executor.memory as any).chatHistory = mapChatHistory(options) - } + if (executor.memory) { + ;(executor.memory as any).memoryKey = 'chat_history' + ;(executor.memory as any).outputKey = 'output' + ;(executor.memory as any).chatHistory = mapChatHistory(options) } const loggerHandler = new ConsoleCallbackHandler(options.logger) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 2f4e7f5b..bcca834a 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -509,7 +509,7 @@ export const getUserHome = (): string => { */ export const mapChatHistory = (options: ICommonObject): ChatMessageHistory => { const chatHistory = [] - const histories: IMessage[] = options.chatHistory + const histories: IMessage[] = options.chatHistory ?? [] for (const message of histories) { if (message.type === 'apiMessage') { From 68b6d4f30a1f00c29ca135c12fabecf56d3c7f7d Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 7 Aug 2023 01:45:40 +0100 Subject: [PATCH 353/398] add override customToolFunc --- packages/components/nodes/tools/CustomTool/CustomTool.ts | 6 ++++-- packages/ui/src/views/tools/ToolDialog.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/tools/CustomTool/CustomTool.ts b/packages/components/nodes/tools/CustomTool/CustomTool.ts index 26b30627..c070df31 100644 --- a/packages/components/nodes/tools/CustomTool/CustomTool.ts +++ b/packages/components/nodes/tools/CustomTool/CustomTool.ts @@ -36,7 +36,7 @@ class CustomTool_Tools implements INode { //@ts-ignore loadMethods = { - async listTools(nodeData: INodeData, options: ICommonObject): Promise { + async listTools(_: INodeData, options: ICommonObject): Promise { const returnData: INodeOptionsValue[] = [] const appDataSource = options.appDataSource as DataSource @@ -60,8 +60,9 @@ class CustomTool_Tools implements INode { } } - async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const selectedToolId = nodeData.inputs?.selectedTool as string + const customToolFunc = nodeData.inputs?.customToolFunc as string const appDataSource = options.appDataSource as DataSource const databaseEntities = options.databaseEntities as IDatabaseEntity @@ -78,6 +79,7 @@ class CustomTool_Tools implements INode { schema: z.object(convertSchemaToZod(tool.schema)), code: tool.func } + if (customToolFunc) obj.code = customToolFunc return new DynamicStructuredTool(obj) } catch (e) { throw new Error(e) diff --git a/packages/ui/src/views/tools/ToolDialog.js b/packages/ui/src/views/tools/ToolDialog.js index 5e286789..2b67f6d4 100644 --- a/packages/ui/src/views/tools/ToolDialog.js +++ b/packages/ui/src/views/tools/ToolDialog.js @@ -495,7 +495,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) = Javascript Function From a0d0d5e6e3ddfe22a00b4083ff8d5c7fcb01f2d0 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Mon, 7 Aug 2023 12:11:35 +0900 Subject: [PATCH 354/398] move project id and remove location --- .../credentials/GoogleAuth.credential.ts | 8 +++++++ .../GoogleVertexAIEmbedding.ts | 23 ++----------------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/packages/components/credentials/GoogleAuth.credential.ts b/packages/components/credentials/GoogleAuth.credential.ts index 484ff522..d06d32b1 100644 --- a/packages/components/credentials/GoogleAuth.credential.ts +++ b/packages/components/credentials/GoogleAuth.credential.ts @@ -17,6 +17,14 @@ class GoogleVertexAuth implements INodeCredential { description: 'Path to your google application credential json file', placeholder: 'your-path/application_default_credentials.json', type: 'string' + }, + { + label: 'Project ID', + name: 'projectID', + description: 'Project ID of GCP. If not provided, it will be read from the credential file', + type: 'string', + optional: true, + additionalParams: true } ] } diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts index dbcc9c62..d68911a9 100644 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts @@ -30,32 +30,14 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { type: 'credential', credentialNames: ['googleVertexAuth'] } - this.inputs = [ - { - label: 'Project ID', - name: 'projectID', - description: 'project id of GCP', - type: 'string', - additionalParams: true, - optional: true - }, - { - label: 'location', - name: 'location', - description: 'location of API', - type: 'string', - additionalParams: true, - optional: true - } - ] + this.inputs = [] } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const projectID = nodeData.inputs?.projectID as string - const location = nodeData.inputs?.location as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const googleApplicationCredentialFilePath = getCredentialParam('googleApplicationCredentialFilePath', credentialData, nodeData) if (!googleApplicationCredentialFilePath) throw new Error('Please specify your Google Application Credential file path') + const projectID = getCredentialParam('projectID', credentialData, nodeData) const authOptions: GoogleAuthOptions = { keyFile: googleApplicationCredentialFilePath @@ -66,7 +48,6 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { const obj: GoogleVertexAIEmbeddingsParams = { authOptions } - if (location) obj.location = location const model = new GoogleVertexAIEmbeddings(obj) return model From e09bad04403441f272ed0780e839b2bc1922dc28 Mon Sep 17 00:00:00 2001 From: drobnikj Date: Mon, 7 Aug 2023 16:57:44 +0200 Subject: [PATCH 355/398] fix: account link --- packages/components/credentials/ApifyApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/credentials/ApifyApi.ts b/packages/components/credentials/ApifyApi.ts index d3e7e870..c7e7322a 100644 --- a/packages/components/credentials/ApifyApi.ts +++ b/packages/components/credentials/ApifyApi.ts @@ -12,7 +12,7 @@ class ApifyApi implements INodeCredential { this.name = 'apifyApi' this.version = 1.0 this.description = - 'You can find the Apify API token on your Apify account page.' + 'You can find the Apify API token on your Apify account page.' this.inputs = [ { label: 'Apify API', From 9e0d2ccb3ac084d45e9ddb41a8f6854ee6cd138a Mon Sep 17 00:00:00 2001 From: Yongtae Date: Tue, 8 Aug 2023 10:39:13 +0900 Subject: [PATCH 356/398] Refactor GoogleAuth.credential to support both file path and JSON object credentials. Update GoogleVertexAIEmbedding to handle both cases --- .../credentials/GoogleAuth.credential.ts | 26 +++++++++++++++++-- .../GoogleVertexAIEmbedding.ts | 14 +++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/packages/components/credentials/GoogleAuth.credential.ts b/packages/components/credentials/GoogleAuth.credential.ts index d06d32b1..16b8e3b3 100644 --- a/packages/components/credentials/GoogleAuth.credential.ts +++ b/packages/components/credentials/GoogleAuth.credential.ts @@ -14,9 +14,31 @@ class GoogleVertexAuth implements INodeCredential { { label: 'Google Application Credential File Path', name: 'googleApplicationCredentialFilePath', - description: 'Path to your google application credential json file', + description: + 'Path to your google application credential json file. You can also use the credential JSON object (either one)', placeholder: 'your-path/application_default_credentials.json', - type: 'string' + type: 'string', + optional: true + }, + { + label: 'Google Credential JSON Object', + name: 'googleApplicationCredential', + description: 'JSON object of your google application credential. You can also use the file path (either one)', + placeholder: `{ + "type": ..., + "project_id": ..., + "private_key_id": ..., + "private_key": ..., + "client_email": ..., + "client_id": ..., + "auth_uri": ..., + "token_uri": ..., + "auth_provider_x509_cert_url": ..., + "client_x509_cert_url": ... +}`, + type: 'string', + rows: 4, + optional: true }, { label: 'Project ID', diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts index d68911a9..23bd3565 100644 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts @@ -36,12 +36,18 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const credentialData = await getCredentialData(nodeData.credential ?? '', options) const googleApplicationCredentialFilePath = getCredentialParam('googleApplicationCredentialFilePath', credentialData, nodeData) - if (!googleApplicationCredentialFilePath) throw new Error('Please specify your Google Application Credential file path') + const googleApplicationCredential = getCredentialParam('googleApplicationCredential', credentialData, nodeData) const projectID = getCredentialParam('projectID', credentialData, nodeData) - const authOptions: GoogleAuthOptions = { - keyFile: googleApplicationCredentialFilePath - } + if (!googleApplicationCredentialFilePath && !googleApplicationCredential) + throw new Error('Please specify your Google Application Credential') + if (googleApplicationCredentialFilePath && googleApplicationCredential) + throw new Error('Please use either Google Application Credential File Path or Google Credential JSON Object') + + const authOptions: GoogleAuthOptions = {} + if (googleApplicationCredentialFilePath && !googleApplicationCredential) authOptions.keyFile = googleApplicationCredentialFilePath + else if (!googleApplicationCredentialFilePath && googleApplicationCredential) + authOptions.credentials = JSON.parse(googleApplicationCredential) if (projectID) authOptions.projectId = projectID From 701005ecb3cc79238be4af276d3e151730db3f8f Mon Sep 17 00:00:00 2001 From: Yongtae Date: Tue, 8 Aug 2023 12:36:48 +0900 Subject: [PATCH 357/398] remove un-use model name --- .../nodes/llms/GoogleVertexAI/googlevertexai.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts b/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts index 754f3642..023b80ad 100644 --- a/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts +++ b/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts @@ -40,18 +40,6 @@ class GoogleVertexAI_LLMs implements INode { { label: 'code-gecko', name: 'code-gecko' - }, - { - label: 'text-bison@001', - name: 'text-bison@001' - }, - { - label: 'code-bison@001', - name: 'code-bison@001' - }, - { - label: 'code-gecko@001', - name: 'code-gecko@001' } ], default: 'text-bison' From 7d6828a3613f292827a32c22a2f86a4720ce00fd Mon Sep 17 00:00:00 2001 From: Yongtae Date: Tue, 8 Aug 2023 12:46:12 +0900 Subject: [PATCH 358/398] Add Google Vertex Auth credential and make necessary changes for GoogleVertexAI --- .../credentials/GoogleAuth.credential.ts | 55 +++++++++++++++++++ .../llms/GoogleVertexAI/googlevertexai.ts | 38 ++++++++++--- packages/components/package.json | 2 +- 3 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 packages/components/credentials/GoogleAuth.credential.ts diff --git a/packages/components/credentials/GoogleAuth.credential.ts b/packages/components/credentials/GoogleAuth.credential.ts new file mode 100644 index 00000000..16b8e3b3 --- /dev/null +++ b/packages/components/credentials/GoogleAuth.credential.ts @@ -0,0 +1,55 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class GoogleVertexAuth implements INodeCredential { + label: string + name: string + version: number + inputs: INodeParams[] + + constructor() { + this.label = 'Google Vertex Auth' + this.name = 'googleVertexAuth' + this.version = 1.0 + this.inputs = [ + { + label: 'Google Application Credential File Path', + name: 'googleApplicationCredentialFilePath', + description: + 'Path to your google application credential json file. You can also use the credential JSON object (either one)', + placeholder: 'your-path/application_default_credentials.json', + type: 'string', + optional: true + }, + { + label: 'Google Credential JSON Object', + name: 'googleApplicationCredential', + description: 'JSON object of your google application credential. You can also use the file path (either one)', + placeholder: `{ + "type": ..., + "project_id": ..., + "private_key_id": ..., + "private_key": ..., + "client_email": ..., + "client_id": ..., + "auth_uri": ..., + "token_uri": ..., + "auth_provider_x509_cert_url": ..., + "client_x509_cert_url": ... +}`, + type: 'string', + rows: 4, + optional: true + }, + { + label: 'Project ID', + name: 'projectID', + description: 'Project ID of GCP. If not provided, it will be read from the credential file', + type: 'string', + optional: true, + additionalParams: true + } + ] + } +} + +module.exports = { credClass: GoogleVertexAuth } diff --git a/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts b/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts index 023b80ad..4d9b3aed 100644 --- a/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts +++ b/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts @@ -1,6 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { GoogleVertexAI, GoogleVertexAITextInput } from 'langchain/llms/googlevertexai' +import { GoogleAuthOptions } from 'google-auth-library' class GoogleVertexAI_LLMs implements INode { label: string @@ -23,6 +24,12 @@ class GoogleVertexAI_LLMs implements INode { this.category = 'LLMs' this.description = 'Wrapper around GoogleVertexAI large language models' this.baseClasses = [this.type, ...getBaseClasses(GoogleVertexAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['googleVertexAuth'] + } this.inputs = [ { label: 'Model Name', @@ -71,22 +78,39 @@ class GoogleVertexAI_LLMs implements INode { ] } - async init(nodeData: INodeData, _: string): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const googleApplicationCredentialFilePath = getCredentialParam('googleApplicationCredentialFilePath', credentialData, nodeData) + const googleApplicationCredential = getCredentialParam('googleApplicationCredential', credentialData, nodeData) + const projectID = getCredentialParam('projectID', credentialData, nodeData) + + if (!googleApplicationCredentialFilePath && !googleApplicationCredential) + throw new Error('Please specify your Google Application Credential') + if (googleApplicationCredentialFilePath && googleApplicationCredential) + throw new Error('Please use either Google Application Credential File Path or Google Credential JSON Object') + + const authOptions: GoogleAuthOptions = {} + if (googleApplicationCredentialFilePath && !googleApplicationCredential) authOptions.keyFile = googleApplicationCredentialFilePath + else if (!googleApplicationCredentialFilePath && googleApplicationCredential) + authOptions.credentials = JSON.parse(googleApplicationCredential) + if (projectID) authOptions.projectId = projectID + const temperature = nodeData.inputs?.temperature as string - const model = nodeData.inputs?.modelName as string + const modelName = nodeData.inputs?.modelName as string const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string const topP = nodeData.inputs?.topP as string const obj: Partial = { temperature: parseFloat(temperature), - model + model: modelName, + authOptions } if (maxOutputTokens) obj.maxOutputTokens = parseInt(maxOutputTokens, 10) if (topP) obj.topP = parseFloat(topP) - const llm_model = new GoogleVertexAI(obj) - return llm_model + const model = new GoogleVertexAI(obj) + return model } } diff --git a/packages/components/package.json b/packages/components/package.json index cb8d757a..149b3259 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -39,7 +39,7 @@ "google-auth-library": "^9.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "^0.0.117", + "langchain": "0.0.122", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", From fc3238348ff51d244f9b805c54118e69fc5edf13 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Tue, 8 Aug 2023 12:47:55 +0900 Subject: [PATCH 359/398] Update langchain dependency version to ^0.0.122 --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 149b3259..afcfd361 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -39,7 +39,7 @@ "google-auth-library": "^9.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "0.0.122", + "langchain": "^0.0.122", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", From 812077c2aa19f64cff0e5f42ed59d7d8bf1a8731 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Tue, 8 Aug 2023 12:51:59 +0900 Subject: [PATCH 360/398] remove un-use model name --- .../nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts b/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts index 4ba2cf62..693cb2bc 100644 --- a/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts +++ b/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts @@ -36,14 +36,6 @@ class GoogleVertexAI_ChatModels implements INode { { label: 'codechat-bison', name: 'codechat-bison' - }, - { - label: 'chat-bison@001', - name: 'chat-bison@001' - }, - { - label: 'codechat-bison@001', - name: 'codechat-bison@001' } ], default: 'chat-bison', From f7894ed4bf86a149d3e17c46186dd69bc4e69c1a Mon Sep 17 00:00:00 2001 From: Yongtae Date: Tue, 8 Aug 2023 13:06:38 +0900 Subject: [PATCH 361/398] Add Google Vertex AI credential support to GoogleVertexAI node --- .../credentials/GoogleAuth.credential.ts | 55 +++++++++++++++++++ .../GoogleVertexAI/GoogleVertexAI.ts | 39 ++++++++++--- packages/components/package.json | 2 +- 3 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 packages/components/credentials/GoogleAuth.credential.ts diff --git a/packages/components/credentials/GoogleAuth.credential.ts b/packages/components/credentials/GoogleAuth.credential.ts new file mode 100644 index 00000000..16b8e3b3 --- /dev/null +++ b/packages/components/credentials/GoogleAuth.credential.ts @@ -0,0 +1,55 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class GoogleVertexAuth implements INodeCredential { + label: string + name: string + version: number + inputs: INodeParams[] + + constructor() { + this.label = 'Google Vertex Auth' + this.name = 'googleVertexAuth' + this.version = 1.0 + this.inputs = [ + { + label: 'Google Application Credential File Path', + name: 'googleApplicationCredentialFilePath', + description: + 'Path to your google application credential json file. You can also use the credential JSON object (either one)', + placeholder: 'your-path/application_default_credentials.json', + type: 'string', + optional: true + }, + { + label: 'Google Credential JSON Object', + name: 'googleApplicationCredential', + description: 'JSON object of your google application credential. You can also use the file path (either one)', + placeholder: `{ + "type": ..., + "project_id": ..., + "private_key_id": ..., + "private_key": ..., + "client_email": ..., + "client_id": ..., + "auth_uri": ..., + "token_uri": ..., + "auth_provider_x509_cert_url": ..., + "client_x509_cert_url": ... +}`, + type: 'string', + rows: 4, + optional: true + }, + { + label: 'Project ID', + name: 'projectID', + description: 'Project ID of GCP. If not provided, it will be read from the credential file', + type: 'string', + optional: true, + additionalParams: true + } + ] + } +} + +module.exports = { credClass: GoogleVertexAuth } diff --git a/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts b/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts index 693cb2bc..a06ce0c9 100644 --- a/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts +++ b/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts @@ -1,6 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ChatGoogleVertexAI, GoogleVertexAIChatInput } from 'langchain/chat_models/googlevertexai' +import { GoogleAuthOptions } from 'google-auth-library' class GoogleVertexAI_ChatModels implements INode { label: string @@ -23,6 +24,12 @@ class GoogleVertexAI_ChatModels implements INode { this.category = 'Chat Models' this.description = 'Wrapper around VertexAI large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatGoogleVertexAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['googleVertexAuth'] + } this.inputs = [ { label: 'Model Name', @@ -68,22 +75,40 @@ class GoogleVertexAI_ChatModels implements INode { ] } - async init(nodeData: INodeData, _: string): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const googleApplicationCredentialFilePath = getCredentialParam('googleApplicationCredentialFilePath', credentialData, nodeData) + const googleApplicationCredential = getCredentialParam('googleApplicationCredential', credentialData, nodeData) + const projectID = getCredentialParam('projectID', credentialData, nodeData) + + if (!googleApplicationCredentialFilePath && !googleApplicationCredential) + throw new Error('Please specify your Google Application Credential') + if (googleApplicationCredentialFilePath && googleApplicationCredential) + throw new Error('Please use either Google Application Credential File Path or Google Credential JSON Object') + + const authOptions: GoogleAuthOptions = {} + if (googleApplicationCredentialFilePath && !googleApplicationCredential) authOptions.keyFile = googleApplicationCredentialFilePath + else if (!googleApplicationCredentialFilePath && googleApplicationCredential) + authOptions.credentials = JSON.parse(googleApplicationCredential) + + if (projectID) authOptions.projectId = projectID + const temperature = nodeData.inputs?.temperature as string - const model = nodeData.inputs?.modelName as string + const modelName = nodeData.inputs?.modelName as string const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string const topP = nodeData.inputs?.topP as string const obj: Partial = { temperature: parseFloat(temperature), - model + model: modelName, + authOptions } if (maxOutputTokens) obj.maxOutputTokens = parseInt(maxOutputTokens, 10) if (topP) obj.topP = parseFloat(topP) - const chat_model = new ChatGoogleVertexAI(obj) - return chat_model + const model = new ChatGoogleVertexAI(obj) + return model } } diff --git a/packages/components/package.json b/packages/components/package.json index cb8d757a..afcfd361 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -39,7 +39,7 @@ "google-auth-library": "^9.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "^0.0.117", + "langchain": "^0.0.122", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", From 95a40ea116579a8b1a86ce5119049a86c3f27673 Mon Sep 17 00:00:00 2001 From: Yongtae Date: Wed, 9 Aug 2023 10:00:21 +0900 Subject: [PATCH 362/398] Rename GoogleVertexAI.ts to ChatGoogleVertexAI.ts in chatmodels folder --- .../GoogleVertexAI/{GoogleVertexAI.ts => ChatGoogleVertexAI.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/components/nodes/chatmodels/GoogleVertexAI/{GoogleVertexAI.ts => ChatGoogleVertexAI.ts} (100%) diff --git a/packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts b/packages/components/nodes/chatmodels/GoogleVertexAI/ChatGoogleVertexAI.ts similarity index 100% rename from packages/components/nodes/chatmodels/GoogleVertexAI/GoogleVertexAI.ts rename to packages/components/nodes/chatmodels/GoogleVertexAI/ChatGoogleVertexAI.ts From 74abaa367c1b9a3033c7d8ee626b76ab8d7ce67f Mon Sep 17 00:00:00 2001 From: Yongtae Date: Wed, 9 Aug 2023 10:29:04 +0900 Subject: [PATCH 363/398] Rename GoogleVertexAI file --- .../llms/GoogleVertexAI/{googlevertexai.ts => GoogleVertexAI.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/components/nodes/llms/GoogleVertexAI/{googlevertexai.ts => GoogleVertexAI.ts} (100%) diff --git a/packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts b/packages/components/nodes/llms/GoogleVertexAI/GoogleVertexAI.ts similarity index 100% rename from packages/components/nodes/llms/GoogleVertexAI/googlevertexai.ts rename to packages/components/nodes/llms/GoogleVertexAI/GoogleVertexAI.ts From 83d8e96f9c94f4374a6dcdc45a78e28183b64188 Mon Sep 17 00:00:00 2001 From: drobnikj Date: Wed, 9 Aug 2023 09:49:39 +0200 Subject: [PATCH 364/398] fix: fix credentials and parsing of numbers --- .../credentials/{ApifyApi.ts => ApifyApi.credential.ts} | 4 ++-- .../ApifyWebsiteContentCrawler.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) rename packages/components/credentials/{ApifyApi.ts => ApifyApi.credential.ts} (85%) diff --git a/packages/components/credentials/ApifyApi.ts b/packages/components/credentials/ApifyApi.credential.ts similarity index 85% rename from packages/components/credentials/ApifyApi.ts rename to packages/components/credentials/ApifyApi.credential.ts index c7e7322a..c961fd38 100644 --- a/packages/components/credentials/ApifyApi.ts +++ b/packages/components/credentials/ApifyApi.credential.ts @@ -1,6 +1,6 @@ import { INodeParams, INodeCredential } from '../src/Interface' -class ApifyApi implements INodeCredential { +class ApifyApiCredential implements INodeCredential { label: string name: string version: number @@ -23,4 +23,4 @@ class ApifyApi implements INodeCredential { } } -module.exports = { credClass: ApifyApi } +module.exports = { credClass: ApifyApiCredential } diff --git a/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts b/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts index 9fd0764c..a5e6a6e0 100644 --- a/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts +++ b/packages/components/nodes/documentloaders/ApifyWebsiteContentCrawler/ApifyWebsiteContentCrawler.ts @@ -103,8 +103,8 @@ class ApifyWebsiteContentCrawler_DocumentLoaders implements INode { // Get input options and merge with additional input const urls = nodeData.inputs?.urls as string const crawlerType = nodeData.inputs?.crawlerType as string - const maxCrawlDepth = nodeData.inputs?.maxCrawlDepth as number - const maxCrawlPages = nodeData.inputs?.maxCrawlPages as number + const maxCrawlDepth = nodeData.inputs?.maxCrawlDepth as string + const maxCrawlPages = nodeData.inputs?.maxCrawlPages as string const additionalInput = typeof nodeData.inputs?.additionalInput === 'object' ? nodeData.inputs?.additionalInput @@ -112,8 +112,8 @@ class ApifyWebsiteContentCrawler_DocumentLoaders implements INode { const input = { startUrls: urls.split(',').map((url) => ({ url: url.trim() })), crawlerType, - maxCrawlDepth, - maxCrawlPages, + maxCrawlDepth: parseInt(maxCrawlDepth, 10), + maxCrawlPages: parseInt(maxCrawlPages, 10), ...additionalInput } From 7b420eefec59efda8fe164259995803720f29af5 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Wed, 9 Aug 2023 13:00:41 +0100 Subject: [PATCH 365/398] Add HuggingFace Spaces to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index cd231b16..36c53b4b 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,10 @@ Flowise support different environment variables to configure your instance. You [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) +### [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face) + +HuggingFace Spaces + ### [AWS](https://docs.flowiseai.com/deployment/aws) ### [Azure](https://docs.flowiseai.com/deployment/azure) From 8af61f7785cafb730f15d3ee9600000c09556a13 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Wed, 9 Aug 2023 13:01:23 +0100 Subject: [PATCH 366/398] Add HuggingFace Spaces to README-ZH.md --- README-ZH.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README-ZH.md b/README-ZH.md index 019a414c..10efce8b 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -153,6 +153,10 @@ Flowise 支持不同的环境变量来配置您的实例。您可以在 `package [![部署到 Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) +### [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face) + +HuggingFace Spaces + ### [AWS](https://docs.flowiseai.com/deployment/aws) ### [Azure](https://docs.flowiseai.com/deployment/azure) @@ -181,4 +185,4 @@ Flowise 支持不同的环境变量来配置您的实例。您可以在 `package ## 📄 许可证 -此代码库中的源代码在[MIT许可证](LICENSE.md)下提供。 \ No newline at end of file +此代码库中的源代码在[MIT许可证](LICENSE.md)下提供。 From 56c4ca5cb3c0042df9f4af06a50edfca221a3f3e Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 10 Aug 2023 12:35:34 +0100 Subject: [PATCH 367/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.3.3?= =?UTF-8?q?=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 4933bd79..bad9fb74 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.3.2", + "version": "1.3.3", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From 17aa6684df12dfd4346882299b95f5975b670bd5 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 10 Aug 2023 12:36:30 +0100 Subject: [PATCH 368/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.3.1=20relea?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 8020d4b1..4468de91 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.3.0", + "version": "1.3.1", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From cd5f086b884c9f2f74ad0f9684f1c25d296c14f9 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 10 Aug 2023 12:36:54 +0100 Subject: [PATCH 369/398] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.3.3=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 650671f7..d5af440d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.2", + "version": "1.3.3", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index 0aac4ac5..f2388b01 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.2", + "version": "1.3.3", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 7ff9a52cd7250460ae571ae1d993a67d1e5f5f5f Mon Sep 17 00:00:00 2001 From: incorporAI <141642148+incorporAI@users.noreply.github.com> Date: Fri, 11 Aug 2023 02:12:33 +0200 Subject: [PATCH 370/398] Update SendGrid Email.json --- packages/server/marketplaces/tools/SendGrid Email.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/marketplaces/tools/SendGrid Email.json b/packages/server/marketplaces/tools/SendGrid Email.json index 18f6dad8..8a6bf993 100644 --- a/packages/server/marketplaces/tools/SendGrid Email.json +++ b/packages/server/marketplaces/tools/SendGrid Email.json @@ -3,6 +3,6 @@ "description": "Send email using SendGrid", "color": "linear-gradient(rgb(230,108,70), rgb(222,4,98))", "iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/sendgrid-icon.svg", - "schema": "[{\"id\":0,\"property\":\"fromEmail\",\"description\":\"Email address used to send the message\",\"type\":\"string\",\"required\":true},{\"id\":1,\"property\":\"toEmail \",\"description\":\"The intended recipient's email address\",\"type\":\"string\",\"required\":true},{\"id\":2,\"property\":\"subject\",\"description\":\"The subject of email\",\"type\":\"string\",\"required\":true},{\"id\":3,\"property\":\"content\",\"description\":\"Content of email\",\"type\":\"string\",\"required\":true}]", + "schema": "[{\"id\":0,\"property\":\"fromEmail\",\"description\":\"Email address used to send the message\",\"type\":\"string\",\"required\":true},{\"id\":1,\"property\":\"toEmail\",\"description\":\"The intended recipient's email address\",\"type\":\"string\",\"required\":true},{\"id\":2,\"property\":\"subject\",\"description\":\"The subject of email\",\"type\":\"string\",\"required\":true},{\"id\":3,\"property\":\"content\",\"description\":\"Content of email\",\"type\":\"string\",\"required\":true}]", "func": "const fetch = require('node-fetch');\nconst url = 'https://api.sendgrid.com/v3/mail/send';\nconst api_key = 'YOUR-API-KEY';\n\nconst body = {\n \"personalizations\": [\n {\n \"to\": [{ \"email\": $toEmail }]\n }\n ],\n\t\"from\": {\n\t \"email\": $fromEmail\n\t},\n\t\"subject\": $subject,\n\t\"content\": [\n\t {\n\t \"type\": 'text/plain',\n\t \"value\": $content\n\t }\n\t]\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t 'Authorization': `Bearer ${api_key}`,\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" } From 177e7f5c0fa83ecbae2509f09c3101cb20381c27 Mon Sep 17 00:00:00 2001 From: rkeshwani Date: Fri, 11 Aug 2023 00:20:04 +0000 Subject: [PATCH 371/398] Add additional optional input parameter for adding additional file loaders. --- .../nodes/documentloaders/Folder/Folder.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/documentloaders/Folder/Folder.ts b/packages/components/nodes/documentloaders/Folder/Folder.ts index 83bffd18..4ffdce91 100644 --- a/packages/components/nodes/documentloaders/Folder/Folder.ts +++ b/packages/components/nodes/documentloaders/Folder/Folder.ts @@ -46,6 +46,13 @@ class Folder_DocumentLoaders implements INode { type: 'json', optional: true, additionalParams: true + }, + { + label: 'Additional File Loaders', + name: 'additionalLoaders', + type: 'json', + optional: true, + additionalParams: true } ] } @@ -54,6 +61,8 @@ class Folder_DocumentLoaders implements INode { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const folderPath = nodeData.inputs?.folderPath as string const metadata = nodeData.inputs?.metadata + const additionalLoaders = nodeData.inputs?.additionalLoaders + const parsedLoaders = additionalLoaders ? ( typeof metadata === 'object' ? additionalLoaders: JSON.parse( additionalLoaders ) ) : [] const loader = new DirectoryLoader(folderPath, { '.json': (path) => new JSONLoader(path), @@ -61,7 +70,8 @@ class Folder_DocumentLoaders implements INode { '.csv': (path) => new CSVLoader(path), '.docx': (path) => new DocxLoader(path), // @ts-ignore - '.pdf': (path) => new PDFLoader(path, { pdfjs: () => import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js') }) + '.pdf': (path) => new PDFLoader(path, { pdfjs: () => import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js') }), + ...parsedLoaders }) let docs = [] From 99c068d508c17e226d85eb37f5c289e10f6cf03f Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 11 Aug 2023 14:27:14 +0100 Subject: [PATCH 372/398] remove make tool --- .../nodes/tools/MakeWebhook/MakeWebhook.ts | 50 ------------------ .../nodes/tools/MakeWebhook/core.ts | 41 -------------- .../nodes/tools/MakeWebhook/make.png | Bin 33392 -> 0 bytes .../marketplaces/tools/Make Webhook.json | 8 +++ 4 files changed, 8 insertions(+), 91 deletions(-) delete mode 100644 packages/components/nodes/tools/MakeWebhook/MakeWebhook.ts delete mode 100644 packages/components/nodes/tools/MakeWebhook/core.ts delete mode 100644 packages/components/nodes/tools/MakeWebhook/make.png create mode 100644 packages/server/marketplaces/tools/Make Webhook.json diff --git a/packages/components/nodes/tools/MakeWebhook/MakeWebhook.ts b/packages/components/nodes/tools/MakeWebhook/MakeWebhook.ts deleted file mode 100644 index e30e3874..00000000 --- a/packages/components/nodes/tools/MakeWebhook/MakeWebhook.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' -import { MakeWebhookTool } from './core' - -class MakeWebhook_Tools implements INode { - label: string - name: string - version: number - description: string - type: string - icon: string - category: string - baseClasses: string[] - inputs: INodeParams[] - - constructor() { - this.label = 'Make.com Webhook' - this.name = 'makeWebhook' - this.version = 1.0 - this.type = 'MakeWebhook' - this.icon = 'make.png' - this.category = 'Tools' - this.description = 'Execute webhook calls on Make.com' - this.inputs = [ - { - label: 'Webhook Url', - name: 'url', - type: 'string', - placeholder: 'https://hook.eu1.make.com/abcdefg' - }, - { - label: 'Tool Description', - name: 'desc', - type: 'string', - rows: 4, - placeholder: 'Useful when need to send message to Discord' - } - ] - this.baseClasses = [this.type, ...getBaseClasses(MakeWebhookTool)] - } - - async init(nodeData: INodeData): Promise { - const url = nodeData.inputs?.url as string - const desc = nodeData.inputs?.desc as string - - return new MakeWebhookTool(url, desc, 'GET') - } -} - -module.exports = { nodeClass: MakeWebhook_Tools } diff --git a/packages/components/nodes/tools/MakeWebhook/core.ts b/packages/components/nodes/tools/MakeWebhook/core.ts deleted file mode 100644 index 8b04ecb9..00000000 --- a/packages/components/nodes/tools/MakeWebhook/core.ts +++ /dev/null @@ -1,41 +0,0 @@ -import axios, { AxiosRequestConfig, Method } from 'axios' -import { Tool } from 'langchain/tools' -import { ICommonObject } from '../../../src/Interface' - -export class MakeWebhookTool extends Tool { - private url: string - - name: string - - description: string - - method: string - - headers: ICommonObject - - constructor(url: string, description: string, method = 'POST', headers: ICommonObject = {}) { - super() - this.url = url - this.name = 'make_webhook' - this.description = description ?? `useful for when you need to execute tasks on Make` - this.method = method - this.headers = headers - } - - async _call(): Promise { - try { - const axiosConfig: AxiosRequestConfig = { - method: this.method as Method, - url: this.url, - headers: { - ...this.headers, - 'Content-Type': 'application/json' - } - } - const response = await axios(axiosConfig) - return typeof response.data === 'object' ? JSON.stringify(response.data) : response.data - } catch (error) { - throw new Error(`HTTP error ${error}`) - } - } -} diff --git a/packages/components/nodes/tools/MakeWebhook/make.png b/packages/components/nodes/tools/MakeWebhook/make.png deleted file mode 100644 index 968afcb581aee3f3fef53ada08bdaada95c8feed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33392 zcmd41bx>SQw>LV2ySonV?hb)q0fM``yXy=D2@XMn1Pd12-Q5%1-Q6MBe}wwiVA{eP7bUl=1!&-tlkdJ@6-T*u$Z^AiJ6^+ zJIK_+%EnQI^1QW^5@cg8LaD{0$gb!tX<=<6>+5Qv=BuP`=4)prU`{C}3KI4fdtA33`iA{s*q$d;OosY?Pq?AaS=7p_KS%K#;bgGDyFh_-ooLX z%F&_JWk}AG?fw_sh$^VSl z$=t@$=YK+~s3<7s=;m(XXl5ZNB|`bm6swJmxgaMWpCyj~uPKYU2`?uLF9$ad3%|L! z2@8)II~R|DxhXdvzr}y>_1<5~$;{&)<^9wDKb_Xx$?P5D-}(`>G_~L~qfA>vJZf;HleX4WQ-Ru=Cela2BpQP}}oCT<>T$uT-@}JyAZdjtwDNBE-(G?Ma{D2*H-vMa0p$?2GqQ~h2QFZ^F+ z88H2y#{XVc!{@=?1qIjFK_fG%4{Qoh`e+u}&6Uu_SQYZLl^+rT&-Y>N+J>|jc z7c|()bGu9kKGdsIgL4oOdXdJY8N2o5K+%gg{P-P{xadR@6(v|yyC&< zBskh>=N`4VE`atl=Ws|e0r74egds14wed8B=KPF&Ool>x_E?)P7I^Ve`_^!(GlT#^ zz`zt29#gulIt-rUQ}XQ{3U4st_1zx&T6bU4Ms~l>f4fC&_;w)UE3#@C4aB_d*2et+ z_!=C6Q4HSbz4Jxz#q*2>;@Pj9nQfuG9yPeluY0brb_8wh%ux}0)t{|8mo|6ShKXcL z#@#inzWnS>6$#uwS?n%|ma)r+QPH6F@RQNVE=pIw1p!el> z8SKkvd*&N_*+pJ&e(B@`)kfU)S+%6TZScU0&I_(V`#NssBa6cz0HH!D;IwmPM6|iW zFQ=E+`8PFAKWD%0U7~;I7!%c)+di|St9-aR7j@ z?qaB6=X(v0-nNV(+sr2KByC9ow`_K_5ffX>_mYoQ|_!_9q{Q%OSg( z%NQ*Z1e7I;q}r5FQY!cQSlofVkDan-{XZAw9j%a=TZv%^_a$KSuI`9E4FX?xiC;^G zSy`s;0xJ9-bI->N)^yJyEZua*yd7;&n!#WRTZ%C-yEcmykZX%~@x<}Gxv|sjsu{gA z=xn^`RdXMDIq;|3_5tGBD-R)~M{=AnLFQ3CqWS0{6s2oL0}>1#Nf<&Nk}2wZY;F#Q zJ+r#7_L0E7m~k;dS_-=3El8dnDuN!0V}o$@`2f8ra5Etz?j=|oT@!*TL@@Yc_4Q8* zm(1DXW!VJFu~!2b$@1J%?V(g)UHe+bT?)ob=fw}Qr=S)QN5cHHS^xwIJ#c4r_KGqm zppqSyHuEv?@Jyoz1`pid?o-|vjM z4S6dAAQ&i^2zW$fBpRXdu(j}ZSyog7(93`=%qi#%WOpSD1uWXYhTG&UHm4xpr%y7K zghFd&CH6Bj*~ZuShK)|^z3ZYVK`Y{ZFzo#mK@sdFLCC-|g!y3~VQRb)9CBg&C%-Bp zaRfADb>m?dG;`E6oK=i2ZL1st$Si9_G;(7L(jnGf0WzdtwkYP7&a3M7W)5{QYbkJ} z$*rxmA#<_g_<>9MOeZL>)4I>!OI7ggm9SAvxY^0lZq)mr1V{l$QX~5`RvCc@<$AMD zJ){mVnF&n)2_S~U5bv=~JJ@5C#?T)4vFc{1MpujBw?V~Kc`R!3!%Ubw4~ibb5AFi< zxTwTP`eBA*LUSOQkv1>Z#Ku(t0YADj2&Wrw4c!Oh_6=`_=!%Sdt~AW%v;T%&{%XHv zjn=&d)i$=?PKCI!vq0CVH$C!i0-5tg2O&w5)!#@$tY}}d0bY)QhAD?E z)co+z!pp5@%agGIL8Cfr26WkCo_bax>GM4NH_pyTP-{rHztuoFWJ@Ll{1J={yJog- z%%2e1!9Bs<#t6Oz%BtQ_>;xk2E*IVJuafKf#Q@LVY`Z-}C5ZUT1$FWAloX1p#Lzkr zN}Iq-P8gP<3j+ZY3R7DZU$X@0!Qwq5PQdTq+IDgAxybNgL#D{kZ*G}xt^FnWH0Va! zTLb@=Gyq2kZIv&)8k<0363BqtcRAq(l!8@2iM7V%p_s8gIcsh8MwbHo3VX(bgo;a3 zXiAh~;1%lv4UoGfi$YDCG$@APO_|B3} z;|kG>xw$3?`>BXYYO~k=_1tRPZBMnH($nKaBkwtpPt_a;pC$-M02L4Xg$`+IPqRNZ+b<=3($x5tpxQVHOS64}3s&v6XOk{+O7(gUhA^9b1%i}s(ll67cbLx-Rl-u*= zzn}J)$~|67$D~QytnQe@K`^R=#nxM~nImaZCTb6Ea3GZwL>SaH`_jkl1j?1(Ds08eHFU+Z|AM3(2qw zG}Mq7fa$zwD1X}yeS2*ZGw3V{sQW{#;za<9k`IWA5%a5K-9a+zx&7mPF14=4>yAW@ z|HcQcdA6R2m~mwoivuD;hnSpI)1vs$paj99AoYsb_@;(gPU|tNy}#s7-xSViI*^hO z?N<#JSiH8d{+PDqNL12TH;Rxvm^k-0u2;Re@paxE$dLcNNni?*bJtw{OVZ?brZ5dR zvj?GBdLG6Ar-vh9hXtJE><_<+`c_g>EF=V@ge=2Ze))ZcqMHpFJPNpVB7fr}Z@SrJ zBM+!+bzpZ=379!@&ByGT@mXmjNcrfVD4VchLqt4s7u4bP*sW)2WIab*?9uN@Qbhg) zFzXrb5?sk04x3@YXi@e)#nUAQuNwXEUA%w0ow4e;J%WC_*r2njsHv@C6#lbDAr$y< zcBYfP4@o!SDfXy}nzKxk<0!Ve+C`!BuAWRFj| zXhwhhblB?eN%Pmhw>GOV6a4S^om;@+)I$Gs1yn`23jS!~T zae88ZDIQ#ri(k-<6x6cWGs^1oihtu5ca#JJ9rop`n1GDR?D!dS&@Q zqn%ZRkMqC9jX`)9Wk5Lq7HqA=RQ_C?sOLjHnCZYyN$x$MG;HK33IAO?T)&ecT!CJo zm&#wG4kqZyiSRk-EyTk^LWXWo*^@U(250vWg{_+^FFOkTh4y=FGzz--@%lI${<^to zXH{{a#LBin@-RFDJ98yoYD~|Z9AGF%dPM1z$LwAL4MO&Knm&)}&67Y5gJo!5!RJas zktH>SLm;dSqR69?1_rh%FPkVeh%o1OC& zRs6lvLowr=;nxgoUqIzyZ_2IXQ83%=1|J;h zfc*&n?ZZ?(wdeXbq@;XHD=lq$XONSu$s$x90&VLy*OPmQADYn~vg4xRPe`9f6Xi!6 z2S%#Lt;&8b2XV{IIS~Q{;mfc2otxFbsHWuWo)9+7V{%7j3oT?*dUQ$+(&~>a^|oD5 z7@thJJ{Z=@zm|{HG}h)IHPUp%=ldNTt7{CTpxm1O~zmf-*+lx2uLAw(r*_-c!JS? zLSi7<3V8H*Vtq-Fb32MPg>`b2XGD6i9O6`m~wJDR3~?uG9N5ds4)^cgDg~ z6JLbbYsW&q{d`Pl&VD$s$yLP-Re7^0#R1xqjg}Ci?D7{P`i05JeN7I+z z+3vo^sr_{WzvJo;^t`Bl?}^1~zF>>C-OY}J8$Qek9X6EqQ4hvoyNnH*^igGqCVAw~ zgVtY?s&q#4+TZ~4@-bF$VgCMWMLWFx=T*EnJ4 z@tNc(2@LYKTEAFNASVi2S)m0C;)E1aZy59E8g;!kM-e%L#O7b(zZCg5SE_~Z`fO$p z*7vYCn62bnQ4rqeiScyY!s(N!$cw8ZEQq3b|8H~>A%fk;)tWR8t(>J>(XnMBj)Ld4 zTc?T7MmI=tJ@hFT5g4{fu@lcM0Sy(0F+l5o(qXe_VH9-yI_ZDQcmLAc{zKR|d2CE9 zdg(f3!zx$+*&_5OUIfqC*m$65qhY4+EyF&z$>S%<ljvh`)-{8FV#y>m~06Xwn3+3>$9~p87ri{y9w&+mv3j6)_7Zx_G<>RtOzGMdlTL zg*5X$ZKE7EN5?|G5h^&U&rw2s`@16R?#+GNSJ+uBRaHL}>KPY9ba<5;_J!@-rnvLS zg?^2tiUd3(?u+(i%oo}W_bG6y{e;*_i748x3lF~y6OH&04|JegyqRqa2{I!ynP`Bl$b(mfWVgnl8NtDt8ke!+6F(EkBf z6xrIUT53uA*!B{IA4){x6(GaIkU3;qt)lIspD2a;)rB!N6D^A4LJ;YpCg2TypY`d= z?#J^%=W}B-Ky2x`^#%B2wAcJZuE2+jeuV8iIEAq4l{jVPX>dIN*jv$OMlPjfJ@2uT{b6!?V! z1|NB}j+x}c>|T^wg^34aQJ_q2BuR)#j!CHsA_hh@6b5oW(yxlhfJi} zvr*&oZnqZ-%=U8Y{y13`dzdL%Asbixoqo-auXcB&xc9g&72Yg#ZKMgi*P17BNxL5^6*j*3lDhC-0Ja&29kf$3PwFPrJ}?=OEpR-X#72#rj> zgyPSe3ia0dT*}MIYzuyw>nub_JpGdiR~U))2!F(&cDT)TfRf+&?SE$pBvqJ0V}zgr z?0T_$KWM>bosyk-pYQ0AJ^dZ)*EsjNN;qSXnEYZP3{1k6)=N(Q^-`9|0*jk_VK;gVjmlg425kcUGNiL_+hb?h>|ov?f4tMhT zyd2M?uUyQ4?1`LeMgu*FUPxSB$}*A?Lk5LONc=jfD+R!$cmA^TTPx*CQ^wr=2yt<* z{sXSyWzLo^5)Lo!K+;@JJj(A;s$@fPUh~onBG#)G{c9$O2zH1Gb=60Ea(PqX!f=tqtN{U@~(^!C3YgPd57Ddll^=J^hFk!OHYMQE84` z#!Dc}fnex0`qfmVaOgp#XALwy8m@3+>0gZlT}`dz zXtSvGe{y97$A~mhSqY(tFrcQz5W;W<5_y;ADJy6z3+?t-cu&D| z1j$7zJn{lm(PUQOLL5t%x z^zn9;fgzh2#w|&`lkM*?pScidxG#lukI_a$KlfIT8q@o(WG8B9 z2`EY(j5EeA^G1Fx&uZ@_X*8*cKUDYCEunuBncZ411=Bs%duJ;`b0RLkg*K1It>Ef! zc_HM1ExV~C0QdWhzro`qv(3tz{Z=d!Ei+Ne*j~aVbJz?*3XZb&{Doig)f=J0fOi-Q zIwvirtNU%5+1%;NPJARSW_Kjhp5Jd!f(K7%0YmY*sBrKeQGz)VdpD?jmo& zg<14ftVIw~Djju~Zalc@zS+4mW$TZK9O`z2O;NG#0HazKQ=On(p*eFGsRUd0+gI1` zq!Dcm4#bg)K}pIvBn!?1YV?%EX7hRpuJup&IiKmRob8LFWo>wEz^yDN)c80k8mX!c zWmRPxI>uZ2*QaVh7B>SQ*!GN_rn6hED%eSzI_NdDys&>xGL<^~0$V!Mx&Mt%3agi# z8&Jo+`M4Gh?B@HTb852OZ01$A{710O8k|^uz=H5gf8bN9;zS)V{bSuKd3HeWj%0h3 z@D<>&C_;cpsSt+o;G=wSNRhZkj0I=@@6^oSreGwQR!b@qMQ#O4Vk%^wFUalWbXBwG z82A|Q#!!7$-pbz6zcY@u+V~Xxtxu?fKL6fNU!}{~zhjT)xMXhyCB&vEV4E`>{sJTQ zQz#egQzpSvCji-#zY+|D0{fd;{4wKyE9%E-{Rp|qf6Mqmb?5L=m*)okl zP*|C#j9ilWu;Id0!aX^z{Q9d2^7GkF#m3xLoVMYt+t~DcaNqEh7(K;ds9L*9-5DlS zL$%^9!ktVFvCgo#3%5r)x$VMp z43dRJF$pcqu`wt+Q;}_>aOQxM=Jy@Q8MHgcQ$=Z`7~5AQGeflVFCTT{cLw;N&rs#q ziIRGD^R(Mx{KHBFHC4L=`}rukB7VVhfHFOI53B~f{>CjjUWy91Yn|KQk(iwDreH67 zCJ&y>Trz$KeG`vIO^pms*;;)Ogs`aYXqCl^w!*@$!+z3mthKD&TH-pt_J9DpS_T>(54^09dv`XoZwL+x>S5yb^OWCsN)-g`0^}xg zBbQCn(Him9VGC&6O7!d(B;wE{16+qabgdCSGNI_IA5V*AHKz^wF5kVq@BHd;!7V7* z7&yQ*;I~a|SHlmmi@Rvfs2B9sU(CnfT9gjHsrM4o=1-%OuRMbg0dL;d`EKhShe7Hu zr@8RK(rQCHaGgG@0>?4}5 zL)lKI+5L2k24OnV87a>-HN4KwbCzS7`hHP$n|aqJLCZAWf)N)b|LIbb7KX|9{Igy> zBARh;_Wi_b1E1-8`agG%wL64ULUucp75;nR-)3Lq$BmB6$D8FDl}5hw`B-qRm?!lW z5(1HX_1VmvmzM#L?`qDikh_dC>rB*OQj<1jll`w&u6Qt!R1iM$vP1F&vgQ zxA2g^X$YE-YM1lhYCo2neHiZdujFjeeh?5hbZ^)obAEo^K4IEBOO8_b)Sa@~e`p2n zw<*;gAz$j4u~;|RQ9u&V5u$Q=i5$q9F6m7cAP_*D{O%P)J$xkCRDurv+Cq|Fkxh*t zCIj=q9)?Q>5x>o1^+~$H7aqQRy}Na90%9ZI`VLFGSG7Mh^g|T1lyDv2 zw{f1I7JWop^FAChe)&&g!uQgS&A%yJe#cWrUq8J~tV-~Z#tdBA=qv`hl1I>HQ~mX; zh;ZK5>d$~FdjQe#Q_U&2r~$ir;vrdZ4F(G>HcwG99m3WxduVXjtI#dY*36$U9~Jt( z!cA^r6q1Q?0_@=r#&(R%oDpQI;)`v6H+|@eDY|<3)GT%!cQS7GYWdcPMKU%`Y~C|6 z27O-J$#WmyJ_Ly#Sd%I1<29Bj$#Z7)N}a6>pC*?UC%l=@Wz$SvHc~g+lK<1I`(t6- zwD|o?O;i>e`P)rP)|y$!+F*xX3;u-SaIHvwiE`l?NeLq><0ucVNJF9-5rz^TM3)Bq z@lT?>V+t;l(rfxx@`@A|11$k3n$tsAPSJu!x`noL!6#KLy0NsCn*=+f9k`U_Q}pWv zVSq}7E#p(;qT?}T&Q~;pIgRo}dFOmp06Rq*wS7ml92)4onmVnl70duM0Yis2Nx zbqUj^sumj(g4*!e$VpR)1$IZy7Q8+z@r*=~DIi}ruV<9=e3%Fgg2}BuBf_Vm1nluO z!2o{vvn&yjG}^8mF0trt7zYEnu>%)c%q1MTw7t1dkK&3miMS|E=RCZD>#AM-w+7sM zZW9F5Lf2-itXg`y~&gG-Vk*Bm+*Me4F;z?7;D!6DcD?1JcK{Qlu1)FNA4c0 zh>03;lZO|f<6i>B3515`?PmBGT z&zuPhFQ!YoTd>>x1{%Dd@LTZ1ORN~-2#m|42Lp8>V+m|9skdj-$YZh#2*n*42!yn9 zf6cAybbhJ!y`XY7Bu}NZjm9wX?e@)fn$tp>5*g1 z7#a=%H7B*CzrTVSndFJ_8l?)*+Hs?^y2SXm74ylP2jFKA{OK;!XM1)=a_ zx911`mc)QhJwNgce+ixz|V*E;k?bH-vIF=tdCLnM5<3_aG2Htba<249u;Ucz>PM6Ir0Z!H+d64iP5TlPuyGi6Vb!xg{IMVeL) z_X&Rn1L!iv1ldHTv1@Qo!b;|aQ@-j*xA0D`O+%Uc#tk>TXl>VL?@O%eKq{dyzrttX zuF8#iryqDdcFGmz{WR8^%3}`T?aw&Xlb;`MYR#kRDn{1`bpjxQZ>{{+g)dWujLL4m zvxGOvkT(=rZ$$n9o02gw%_`H1eBaf9`wGFwTptroYx?Owq<#|^HK(m%k_Jkta;yvS zSZ-g9YUaC^JTK1a`1ps9h~>_aC|*Zm`DuMWa9s(mwT=hZwy+ADYO6!aye2T1dwfz# zqguzb5&c8bWiXabiFP5EFKWz>pX{Sj>Il|4$LdR|7) z#{`)_P_MKE+1`3efBw>y-piYM1IkMG+EMz2bsI|<8wu^bz zyVn~x(bj$LJmh9Q@{LI94o!D7Pkz_d$uaP7IWKH9Z|{>vD0`PQsFZ*6`ObkVqrG>u z)@_%;4erMk$nE0gYA+rB(%zOK8j2D)1n9&2p61MdP=$8I(S?u+VCjJNix;g_l1x zJ!G3-pcek!&8!~u@i;p~)!Zs;WZpJ4XsX*2dMcW82;l_9WQ(xeDf6=M5*-RWe`UAM zX+<_qTKfc5ydHwIU&sThC9}DGarLxaX!Rj1PWJf@lZ0gbaXjwhtpd@NfH|uFZS`f+ z1Xa{tdJJal(tp0kd=^k186fu?dVessijBvBinUns#$c4s}im?(iDHloO zqw4eHZ^>=BoCkHhYyOEW;T_g;@*#+h>$eahQt(kqfdCT zrG#2qn0*er8hhUmC10AJB^rcRo^`A(1G*Ry$N(KDFcR_Jhi0o`44kw32vvuT$E zlrBYX%OMNpY`!;_E{dF&yK}*v?V1rOR;O=b-Q0H*PT`5{BC=my{JTG1wACGYDnAU2 zWBQq+T47xdJ*GxvPFsm;$a|kA5+$74<3WZPl9ZcU6R;z%w)5K>jwF8Gj^ela!H%sS ze+VH~ucUvil`B%}W-cgAwUWjI|MP<^ZT`pY>`b>swkk;JsNrIK2*fIroDy&Pl5eie|}U;4SixPpe3;+ zI*!~SafMn6vD`2p@WPSj!f_{{qhpW?n!g|UoBizzk_Vp1g1F1v@?5*s&BI*xW7**= z`Gm;wwea96MiIiaAVp-D!LigXSg3e36CSKXNTH7#iA2*&>&XZDQb7QH0HoT6TV^9@ zEsy9j{7#krY%6X#t8=fi>bcW$rtzK%@ZCI6`*x?TYNnL`N_|K^rEtJKG)-@-`io1l zosAUG&JBkRu$>Zh9kiimT`ILrpal@`mV$~b?|wXT37k-^!5FRFUsPzk$o68WkySO^xqG%seXQ11%#4bzWY5yJ%+rYrj&!=@J|%|RaniA=?zICn1FQYDV8akOOR=^i%S() z{R=gijeqwi)x2jc*YzGw?AokI2LHh^8CQtK)OXd7{7K4)nc>Cigw6W+)~fBFX7+=$ zVQv4Kq{Br{z`doETUpwg5B9**3G zIl%&LvuZ7WRN zSsxN1f{zfaiK(64x&P4B);-JT*6qJZQ0ZnrrnH8$)@s`^KjuI=)?<0l$3}gvQE+sV zD|ap~J2h5D{$S$t2g5|2kz|xKMYOVD7`ea9X(CzFrbWBi*GX_t6}S!`kMdUTF>5%JVUnC|`jRop~3GHa4p z@8SWQ0%hY@*slG%r2<*^JpJZwih{NNG=V^{gkjaZcs56F95z>cP?4h?NayFIk2f9LKki^qK5Yt}5HGHqOnwJvT&>SyVtL9jbP%Puc z!!zsg3|9?J`V59&SLR1UacJ0qC8c&LyM207%4)KJYM5zDdl9GIUEX=l1z`*Ey$jU4 z$VK>D5=SO_XM-MTf>J!nEpFjNVzu5@ElRcR2i%E*ylZObnyF-iWCcN7T_`0Tz@|CB zhVpU3NkD1hZBk#BYMSv7GLtJLtCQzWbXr%NW1zd$*DC)P`@oli;?VdmWS{8blzwqF z4;BygQ`L;$C{VMu0@La;UOo1l)Z5$(Out62R4`IVo9`=+0@rLFpBgjgn;i~gGa7Tu z78IDYX^%yim*ZXx&b_QdLceTmMMS=``^5Ksu?v!P-Gof6I>3KX`<}Ku!#2gVXEkzm z;&Ikfam(Nm(N}U>efOd2QXi8Leo7!DX1`Ii^CfZu71>}t7Yx7RMb$8bL4idGfG7+@ z=Mr%ov7N;p;|=(Aj=Beb$pVb;>dsW2OfUZkC(u-A1PGOBZGZS~$~G9VsGF;$(IRv& zL_xqV{8Jf8GG`{Fxcd%S+EK5}Ou7^n#(Bn0S`vV=2Rnp1G&Z077Ih|cTa^vRmS;yp zqCa5F+KQ3+>tLKkp$3-7dgN8vHG?0$-(GK-^if8a;S zABKAlPL^ z5ErAD8+YxrLERRN#osjyiF}Jv=?-9X`Ggz~{;v{Qy-67RN>JDdAA0I42|i%Z&t>)U zop@V3kpsIPkj>z&yhi61Me1Uf@F1QPV5(`IEIf4BQ2630wcIu+gWZ`iiBNayYs%#X^8fF`1<ZikO-|Ex_F22Qs_ATda-;COUc4j$2oc5yuGIU93){w!R2kpXtGoS>;js7l*= zPEI8AdFe9F)z5{NMCtUCZi?f~*KJrMSp^2y{r3Tl$|R&QQRpp!0xNr)>4g zvfSZO{0e59amw{?@uk`~(dV28%88h)*8SEk|cyHRDFEX%y&;(-EQEW1lzTKnug5-uM zulR5OQZy#8v~WsY7*DAN6QQqyx(3|Zh(G(=Jhk-e?&rtB(0P&9cP*s zatLG7_qzK!@6Gwid{A^XKIMC&;`N8ITd5Zbq!-bTdk`~{>*I>%ky<&Xq~ODg|B`B{lLbQ1qDdvW1`(n5#d@K zPF3GUCAvZlO|GX2qw`l#XM(OMp03}7K(5q<7fI$PleXBVS?^uKAil`VB-l*kkFP_x zvK^GURO{svisyZU(yK^d5szutru|`Y0LT`%cxH4wzNG@mkO{v)wWI>BdtZOMd*pBE zWUh@91yRW_3WNl@E_LuNetpIAb6!Fv^Y=!62R$`(m?Bf0uN|BeIJv z?Q$Nk8MH3-@z=#jn|YIY^2Tvf$2uflVR6+gmglC>V;7O$HAhwkVl?7mCAgB0Ge#Fe zryPEqK5hua!P|;G6xveli~{vdJsACc6iN-9#b=4QET+8WSu=^dL>U|z7GL3tKTd8x zV1_0_a0iAfiC?Vkpr)hh_qwSEiq0F~3N3`S6Pj9uu85M9=_w8Gt5Dywdr(@70^7Ti zw({M9cD@Swrc#9<0V?lW=R-BwNh>*0wW=0CTd5Zz+&C zly0+TlSlg#zo+ZWk=){<%9H0;9%igBB>FI;-lUmwswQlqCK5G=W}t)Pzqv-J?uO|% zQBqd$9V1jdQoem3?>q={;F$N_%W+%^+GlQ4dLWB|HrE*JXCJaS*H*Lm`_W(4Wv*BZ z6Rh%^JKZ<7j#?^lU6xlA&mfgkeENVVX_F(Tqn`vuZ}KK+iRijD%6Ge(;){#t~SnF3$$6h(H(Y~+ymX;B)#pV2)G+C2%vD^7k%{$-FN0hQmweyW1 zKrW20WIoNxW*nuyOudCIUtBCy%c*xs{j#@eJXZ0R9LO_1^V~y=PwAC$QSwA0ydSrc z4s<&#@Bh=z#cy^e?MK)I`$JzAr}>yRg4Tl0|+5L50nZ%cD#4%y)_8EmnKX)0bUyO)8C%-qd8_ z`Fc@%c)0SZoLUiDp|=bclRs(Fsm8I}Li~!ZV4RU2M4%C2jvIi)O61M>0O6oZ)6~$} zC7!E{Pma^%^IBf#13uiPQ#qe6q&bslQ7rf~qi}@AVRZ-_TbnB5Q!{@iUEoB5DHzYV zHdm-=ShR-2)wH4@U#IK&h8c}xkl-X0Qy#=?;7yQjt$G*7$`A^|J(mY#wW9r(h(CH& z&p$0lx<WkQ(XiEb@H3<&rZgIU)Vp*OJHKkrB+qJKuJ0zJv_6NCN;SgxcEB;Jj@hK<@J`I9+WXMd-mvd583I#t)4ZCopyRzI-UfdAmSj&-ycH zurb=2>dAhK1xo*SX&(vh_Y;N?uUYt$VYN3d?~Kkh01%@~e7LFPD}CjWA~fFKkl~bL27f{=^aeC*Z~S(!1nyQsoe$a(_Ig>&%_PSHE+8=wXb{c z7i#t6u;yWK7&q(f2^?)Ay;;=B6@ZB-fRyjs=5QF@XU!;S`V#pOU1e1EVtyyAfQu!B z6e#^tG_x$|<~dHqUu5RAZwhbPXr}N9usUqn6sLla<{qv?a@&d_3>Da8u^+=w3_VSj z-3BYqX##aFmdtRPE`6Sj;c9Ebmq4e?lmeggq0>9z2=&tC-4BCA6bgNB`^wq zIxvcsLq%xIwjo&8Eo8{~h?fwXldq^AI+ZI=v0)%hV*TzXj@rZ)-zrr(<>(HJlBrz0 z@&7xZTK)tf9~VJ>KSQuxpdYd$J7W)Ri%IY^YzZA9fIvMe^$;FWcNHLq6?<2nwvA%~ zh83jpoqt~&iRqVOlpL(sB)mPFuS!zHOY61WN%Qv@+!B>tY`2q|VDwha=3N#gK zc}B*DHX^qLnc^$>8hi(1F3!YSJzsa6fm6YF>=3!Sqnso3(g6*FL}y?E?2X(uT6`?% zOh;pz_29p&S{+^)IamvG%QuU`le=X52uM7QP`c77l)B~<(<-&Sk<)$;PsaUnwsn2! z&gr!837Rhn<)`iCvE+idO^s;RLR&8 z?VE~kOU6?#V?M#L-`{yI=^SdK{ps_S^DMH)^H4GP^ty2%zU7H={zr>^=FeA{SwRsB zj0Au^kT%nMVu5tD{oKHN{>^DML}zb&44?hx5Wp#y@fzQzLbjh3*b0jvF)SGSf#05d z2uIZhcqqfke%!aH>ZC}E^fU3tR|}5qaXsbsUrNL@axiojp!^R!8d0+DeX|fL+ajHQ+j2y%n6*FptyE&kT<%z@1lvd9IWJ2np5)vWOp(W5)3yjTK@v&Hrn{Np zi6~hL1JI1>bDx4W3#%rFK`8tY@+C}ik;?9)?P44-n!pnHTfYU@+-8g464V8Uf66g! zb~|M4WRrUW)fskbBb!Pe;goQE{gS-N0$RWd&e*JoqT!=HGo%%HqIo@4R9s2rKM+Ua z;^}8n(~J0|zV2@(CV8MpWzt0&Xw~Bvd8HFd+qX3%K;3{ZYsXOsJ4fDR0N~E1=$tWu zIuZBf6WF_Ahz#6Zft6kA6|zaL$(&m}QB8)%%BS=}wUfi7uNn+Vy%P6j%4oft7hs{h z>>g$lVj(ue`|{+Kr)rp@6pe5DPF2W@t)t!NW%BXqFE=ZrA#@humx?Vb!YSsm$9I;% z{0O^TBu84W>JPatBZ}Wn#O!_-ghECw zMWa3XV|hG%0=rd`p)0v9TPO)BQrfi`wq(D~4_BE61X9S{4RBdbKAmJOf%mE76KqUv zlU|-&-NmrO@dc0e6iq3Vehn-YQgNSgNQwkSnBJ{0Oz& z@v2Wr+irNWytRT1@bU^3aomuYFFDo(o=Vj(Mlo#}f(R+Dr$uDDq;5~AObc4Yxa`!iajd^A6Hn~B{z|kva%8bd zX9^H0v#T>hZ|8U?o*bykPrcQZzYR1N!d*Ae5i%)c#sZnYXa zL+f&YBZU6B_^0(3Co0Z?66sOm6sN#A$l#k=T<>s*GNzGabB40&vW%R`J_4OV-PzvH zZQT)(RKcKc_wFgQ_ysKwldYxVmU{->H(1<_%FRZo`m=y^HmSXKA zm*S1~_Z}2Q@vyL^o}iK(rJ~rPnM$Pzas-U-QV2eKY*4^4)tWSkfor^v@WSs5*VPXcVGk@2-|D{hER zaIySRIHmNv44nZb8@zbsW&dT#ON zA_Qq{p$S;~T*mw=O;bHrkjB}E@cetb`D*rF1N;}NoAhKaZ_!Vz74qx)Kc zs%9aPKyli(y_KI>en{rp2#p;<+)hUWJMwlR`mji^(beGrs39{on{YsSsef`J(!TV>Xr-%efsxgL)v z5^%CBrU>&-tN^f1>?~MgCP~@f@UA5xJVlSMGYo8Nv{NPP790u!)pduh0TJPrwOmRCgrL$MUVolLIIGVO0}-k8i0DM5 zh>{Z))rvF$f_#MOAoA|V<8fo`nG+@Y77&cO=LwR)qLnWRooo_tmklAzkRgN?*r={O0Eg_~RPWQwJ@CnRW&5f&}=M+F{ByIfu0YEq~|DeaqAX>j- zU%ecHgZ{RoHbGn-cSz8!QwXwVLvwvA66z(dAvlpqJ5f|Ns8|O?47e|3rbPrwq^y{2 zuS&kAF3jb)iC5ngTHC->`+*D858o&sGL5(lg>`^eA$hCw>O%LH&9&zBJAmi#d*Zm- z3ooM0dSZsaMJM>eWca@EB)~=DUxyzS+bo|TfhRl#ftR30287jmIW3vF#}BW#fSMYC zjIQ5LKeu)lf4-WLY!fUb*{uz_gN9sdb1cIrOJn|uFLZ2h45mw&Gl(3LSygSVh^0uW z+=x)VB!f^01RRc`tX`kedLZeya&y5)eU-B~H5QjslVS;d@e7udorSkusBYmxm{#Q}VLi zbOqXd|M_@y)(;aAILme59(|M)%9mv_g|%fiokg1<1LEbXXYDsS1!h z(9BB=8$V+~U6>>QUJ%fcA$9Toa&*y$*zuO>7^v_XAL;Z6>UzIxZNQJy&&9q5f~qffIPFACq--mdI4 z<143)9llYW9^5QMDZo`k;jqgKsj^A= z1MQQ(Q?k0&9>nOKKY3{2Ci~6V4*XM?Hz_BrMpu%yj05DvHx-m*wJ?aBPy~uNSw&Ec zFLa~)*{-|tryoJ7Icwu|D$!%nge0j6_ft?S=e&#oOXlRi^?%m5M7he!1x06QX#_ zN>&ipFZ;8_b|ih76esJXuMt~6Zf_%v;FF$$B*qVtgbr>W{3{+fCHt6>>-+O|=WGx+ z!E>Y=tUV%6C7*zbP=iBF1j1VAEVZaEFFV56(0cE<UQp-ifraLqp(x`d%7VnP!E!(G{T%dS|J1G)B!=nXCLjb1{r#Il0m zQCWU5Jd^~ZKA5WDjS}Bom4%QJAPfbRitS&LqaCs+EJKxWRm^%ssY)35;h+F5NsaxQ zA%(W?%EyS5c6^EJs~j`lG&1Qrv0T>wMmd`D<9%>iOd2i7o-+&}uQ53bBT`eJ}F1D}{-k1zo-Og%x|3p}J*l0YrkT^G_T&e&?%vKe=_D;tti zoR(+%zZA@9XpEDbI-yU__~JBUax@r8Jux?zM|OIzmY+AWN1*aY?a|f|S{hgak^>W}cHD zOwN8CmblDe(c(UV0s#k<;ftcy`>Xj}zO=P{{}$^`|IzaO!Rj5R^H1IJIh>hHwh4|t z5&6rRdWTr$doskv~Ji4t00B^yqI zflfs;J%aEwx)ymOx5kNg)el%|gF6wofLxLs@0^rPY?X5tT^N;!?LXRrEfua4dmpw{ zdP)6!U*45AfEjzN>3A+Ts^A@paH4?_6vXu7_c6J|nBmX>Hih5D%O-OPAQ6-Mb}@rk zPCy^Knvk=J7vNReH{R`%BxtVrIVf95gb_kKJBgL2dq#y;la@T z-<`u*+zuPE0TUvs^N`YJtBg`h2AuJ6J5Guhh*!BGXgJnnmYXdhjFaBdM(KldDq}%G zCai3|Zr#6>RE4E$zK;%=PmpWJ{*7C*54-7J5g4qyd*lcG(IdBvz%bTK$Fbn+jvg03 z>wBM|8g*QDx30`;5B2k1Kg7vMUk$@i=&&LgQo~G$B8!}jlHiU#+yt{&Zs^vDSfW|P z2!MHJDg9Qe@0vdd6+k9NYJPd;*fvvUduEI zj={u1)Ot#yz}S)-+^5%8mp zjho9O75;nKj;8%dShSmD0b!7U0S0L(%pfdf6kqrIva63A)*&+Lu+eJ8cOBJ!Pqs1z z+?}$R603sS{gv3MvJeqrzb6S*kdRS2A6*1qv)T*E&usfgIH^7vmElRbj)l82l`Sle zc%Ch}3o_mA-k_LV$^Z(NF0JUgR0l*PdU1gCiu1+C9XR7V=H?ysO9;|=L_qbt$B|80V%J6c zGLl(jzq0gS39Ixk$Ms}5jO0aKcnZLLaG-WIxc}fRRQJrsk}HFaURPrA!DW!A_A}x@ zGb(5w!Htspm@0*%y(Eg+74-3`ecF0*28aoBrr<)1AyFoU*r)8mR;e(+BqUsp3?ZDC zl6Dz6wk!Yh(CoyRnlq#F$m#E8WjT{Oq|)8r<`S?B^r)@kv~`9jda(5~$%=q_psGA% z(72^VQX`IA$Q16=!VYjQXcqMiMoO}6ECpo7vt&1@XT_BgRn8`=t##>9SGJ@O zOjnk)6aS~Va|Cu>BYRz`_zVxppNbF+CKODcZJ;9@3t}dLMB=0EWb%DSF$oU={qjBy z_0=kANtjd-V+cy7aO(U9AUbbPAVAIZjHz`cHy_pN686Rtd~M!PX{h)wOd3CR-Cq!M z9B$}vFc0)%SS0^;Z3gr7M9Z=3hwEH3Q+uWH5@|4&#;KL-$lM~z`w{=7M%BOo2=lT* zalHHf(%q|W2QrRd*O=lK_zOvUEcrDzK3;^zlw5lRwbR|AG-7W>Zc_*`$K$(=h0R7y z%(w11N|Y?=z~i{}XSCOO+K1`7xhu!g2W_=!3HP8FRI=LE3>H>`-+)MBnww?G<`X~t$n4LMyud~Zs39rsYw+nKuo>SpMVK*tV6 zFhNNzTv*gRiHp&6g~T4MzTQ8sJNBHY{&q{xkwa^d>>*PaF<}@^Y2}zz$0f8YYqNM2 z*OJR_GzSXjYX%afy9!f0zy5k1B>Qa09lBoHH3pw|A%WyIu4hw(()x1jS6`>eAy&*N zC*_^6$PRu(29*M4U%`@nWH2PlNA+24hZA-tN+tpxcf%?t0l#2)ti|`J^>ru_GJlfV zW^Y>?yccG&YqjTv*_3Z4ut9RQhkq!nW=GrAxeI&sqk{!7Gd!c!01TrhqpG*&>@V^i;Pwp)MnJsGAPaYa#R7FR&(p{ zw+ph2Xq~X?ygA3l!jRWK=#(xcM9)_H3rW1r(jG zw3OwQkLZ%ZY2A$(hC5Q4s!faxFQrpK1aeQ<0B9Wr_X)vc2~QxsI*np^j|3E{GXP6v zwyPA$zqn#BXRQ3A__yL=5o%rOU$yF+4@1>!mxxbCrWWvoLZ)`z!jgAJ3qc_cyt8zyy* zKP5yCD2cG30RezzmK*j(kwb=i{uw)MCx%j%d>r#{%^c3?sHj$<<~`BU#n2I_|ROy*F;GcY1aJ1)lI7QSx$K%;o`UVsKX(^>am z@URClzXC=Sny!Qtoxrr09wV!6KZILU!eS9m;k@27q~Og#J2FDQW~?eX!6lh6cL|Wl zDGb)5lATcDNNroY&@=*?(`u<_+S_$SwsX<|ZF?2J&G?8I@M$Vkd~Z%!QbWVVKV|4x zBZkjNN|Z@TrO=c~QvPHd$+t8;x6x@+Aa9^?0(n^G@eJZWeb%Vjjgm0jU-szVW{G#9 zKuh@y+SK2ZzNZ2n7}Yy|r||&qHEyq_K%~u|+cF+i;kg8pI!SwDp4}XLq}X@8v7Be9 zwJazlI$!HgaWg;4k&a-Lvfi-iqz@BOjio{ziZ2>(Bdh{0Mp}ywGLv7uD;y;vSyNNcot{V2$0cYubj`K-szTNAmj3@D1VuK z)ai(zmrv~{JO%m(!_yY~QTcjj4V2OGZkG0DH79KR&B#Zcf(oNLoi>RheRjHs4KXMv zgW&vti!?A;WQe(4@2{<1l6ysyMG%?_9ciZDbk)RwJw6`o z`9S1Jg2bC9S6Gkbj7d*h#~F4sJSGcalVBmN%{yOn?Q~?h$@z&3qMJ-iF@uK)5j=v7 z^l2;m!b5WtJuu93R#zWs`-H?5VKg|0fxLS3D~jx>coN>j9$G{gvHQiLoirF5mQ(7W zvDVm6If{Idym1(y1IjnBKKg_)s-X2HWPi`MRcpnD%7?Qzqq2o{9cB#>x#5bM-Y9O) z$FK|N6~Q=cdVu~LA512s*rd!T2kDZ1r%K(CMJ`D{YkRW(k6}K)0QZRWP$Ra1n=2$y z`jsHIr}DCl_59ZusbaHlKrL1W=&mju4p%zT_010?15<%$RSJXqj$zjD75-(O{j#n4RCT(KW>Gm?S_g||nUun{w?EqYB#hC|gL>U9wz^Bwzx zHwv8j?c@!j5>UNa3@nduL!B|LY2BK)!9-Y2H$O8Dh*5QynW#pzp&pm=Cg9h61C%*w zIFGs%@k5GHdo5Tr)e{or)0=#5_VkS)&)#Ekt9nYhEhc;PnqBZ0iEbqUP>1hRw=Aw7 zT}`ClBNjtUZ;hxkEONl(4=yEsB{{Va1%)e4;AOAdn1Lg<+bo+ji~G}^v^nTd`R@Z` z=byG}h#rtzFn6%0fU@8#1WRTI+m@@6%8NBZW>dhyrk+4B|B?C3`LyT=?iEc#j-r`a zIG;T>vY0}Jx0wUT1!tHQ$y2gK({7`rTpOIrxT15&jINBH({m5CPnv+WuY_((V1BB- zrGLBJ=~WZ+{7&Pe00@FJu9gZ%9ateG9-tQ1Byk^+rri3mgGz_6py^A0-@YQ!(b4LY z5)DKgD1e~DI7BzRPA7P&Ys9m*7kV;6D(-hX( zKdUXJ<8!UMGES(+7>WWaGT7It&savsg`~ngP}k_Me2CRS)<>PytOTx5U9tsKK|3Tn zX!()v_9R{qL>yE2TsJDmRJKgz<$0c+j!5cgc(jOJ+YRg|?)XpL@cGz3C@=hm6(+a# zMWRFy{X^@gqq$smUzM{T89Js*kWya7K;&xTv?1aMdD=bhTjB$@t9kTVQ!MvodYpj_ zxvl|8B5#24I4TMkK8#xy&{Xn1;YbaJsjE=bHlD&jb_EVlVy>~_4IBd(Z*AJ2pJ;&v6V|d6z2nWWX zidmHQkF634+sx`;F@k6&LKw8lO2xS`c+$x^(>U-pz#N5-{SN0Nf_cuNq}mO(s0YLx z1uO93u40+HxN6>?JB?_m)b*HYr0?+HhDI93!jHb55Y+!{fYD^BA_qdlbe}EJitg?q z635^eCAFNIwR=Y5n{0JU_V)6}%?g(ZZ}~?TaeUmu5v(^7BM}@iBAhbjbOnr`55Y^N zih`FPK`UICDvYv)-d@^BCR%25j)@y%vWA4=Rzu9yVbT0*fh90w&sif{ukP)aWsjC< zCi&;+nl?n4AVTS^*Cc!jji-UEe5>)jhxev*%_k$VjvA<=-kcRCQMK5>LgXBiA8LBg z7{QoBc>YlCN%GhMM>Q8gP$(e8NC&w5h%4!gzu+Lgl71a+!m$z9n${c0mQzI9dwciG z=ew=}+U}UBYf0Y2KTI3?IY=8k5?7Cp~z0>EtrW-uQ=5DRnBX9J{LVQc?noy~Dbvn!cn@N6_7 zx!I20Tivmwyf>JB4Wiu&De(Y>Rv&Dcz+bO#_+u34Xh_?9f@b9f~HDn{09W zu|SQ>R`689ultXD2s<5`C>xZ@R_0G0?icGz9VA-;&6ng#U{!)&w6-y}W<1q}ULaZ> zmHJl!57;b#5Y(5Z z-KyA?sQkSEnM~NWf8MzxrYpNLUBF%ImXa!+An~`djn8t;cna)m7wUcz@rJZzjgDqP z%Z!~QOo;=x(p4R!LCD8f~7Kv{~BIp%?u$vM|e1xxk`AI3B zth?9f&ct^NH>5hr`1HShyQcRC4kJVwc=5B7K}S}y3a#ybx}w1F&=u0#pNZ9DgoKl^ zR!p0LO-RzNUK9Ahy> z5CpNlJgRdN3Wps{N6SqG*$@4b4saR`u@fsHuD|g1P}xn=svFMOg`vZQ)dD3+i2@D4 zFRXp-E!!6+8_^BGAZ`Io26vT4sD#Z`Ftg63a5S+TjWdbPUH7m>6ZEa%C?zd|p_>&WC@1&pKW1wsH7Gg?9q@YdMtBXPI#UYQ9_m^0s zWH0&5hx#(bB=8p&j=VzwpHW=(s-k3m3Xu_(RLy{fd3P8c$Huvm~t__+w1;e8VPLuPf0<}>WI1NX(Iga*l6qVCX} zQ8X>As=pan<{RNXmu@M?DH16U{JxHdSa*%Ii<-(ux~9aK87OiA6YbHfT?Y``(b ztIf{H%Z@cTR+qt#gOleyRDBFW2L&Yz9Mm}Af;FjkN?sXr>VyK!vck)N@}tK;u)Fc5 zdNRw%{Gk*QNB;ThQG7u!W~~&Gu*oJbhoi+3)By^k6?2b^b$pyv`z5>^ofIE7pE%>| z$cT8s(_xjIO>T-++(gis-nW)oLxf76iVzD0&U>Rn5L-`j;U(9oS%8Nk7M9n={c`ut zZ-lz7qo>pL3KB2}3~7uY4(Xq%nN98GK2<7?j^}PppZ=mnL_eboNi23>M{_-|l>zZP z#A6y(O~`B7aAelj?BVD%r0Y6YJ04_a==SngSix8S?g zX}J}nzF+Ad70}7rtVB}O`-?B1PvO27`Apcc=p+;h`BG1~bga^of)+JG30aO%UuoT#PVB_EN6i3!^M+eB8+U~(_*$?)Zm~w>WKZ%`-_v2bK-`8K zgP1svxe|h!Cm38=lZP9;H$RmwhGhC>1v84V0@DgHEJ5m@T`&GRca8Q@P2)yHlM~VzSn}8^+Q7;1-c0|*c-9rVSJKYYJkt?czV=z{17p??g zubz2IViOq#ZTn$qsD_jpiG16Zx~Ma@Bg14{&W;SBO2N6pmWb!Kx+8_sJj+K&6|x$) z=+Phwx@7=+ky~~)jau_g2?|U(Z!$G8@`Wt+IB-M-WZI7kIJ-wHUU;Q*m(j>p=Ryil zPct&M44ae-ZOAMT|B6dL^n9bc_C9H7$^9?luhYhxz2hxTDfRq>o&8LV@&tTB)P$ju z`zlo=6pi=cfIQQK^DFN+a*2g@9kNknwcIYjB{5DgQ80wbmMvwFsjDrvYVn{86%T0& zu{xMm)B#Tis`a;d#IP{k9wsMUI+Qiq4)FKj`I5@BYMXVu2ggn*77tL~ z05YJMF|&P71`{K7?o-MOzro$jS-u7RLkT=lnIId*4lH@KpkY5qD&Pm&;l?Yl>KWu~ zS);pmwe6jnh#&?)4y*)A2!%@+AzJiqF`wM}vNbv1tF?#XTZ=TG7^g{ksP{0R^AG}> z*GGuv8`|ot}M10 zPRfWB%4AHzf=^tGufEJY9wM6wktZiI2O|?z39D(btm82F^LzFbuL@4l8EXbCaOn?d z2ABH>lMfs4zxV)<6h?-*MTbz_&?F_q=3mX`0<{U)Xz^55eZdxLl;pWq(^FknpUJ;3 zCrI6~6^8QXk=>Kw->#FtTT`!yo8lhi!D%G9vIR}^i*J$-FO=Nq*|2Jlup@=@%kjYh z>qi%i2h``dEfi$&$w&^ZQa9gZ*+P@u$jrSm;xKeI!m8!X_wdK^pGfEoVha4{`i2@rRcWB*_f ziZHn*tkBg^M^Onwq=yLs=WDIZEsf42Qsxi}fCD}Ux*-%FER=#_?(bYW+i5|jMg#f} z?w-q``M7z4ds)1MMDb{3e)qubP)rlF&6WzctCH)_Uzmhzqe*E8XkBv>;A-@iExe61 zD+jcy9kh+3ZRgbAkILziTJ%)-^cg#3bYzxQkh{*vZ3j%rL{1sfJ*h(bSb>C>loT3d zv%&b7y%r5Wl<@7Uf0@Z4VL`>TcRM_Ekn_hRh<)z+i*1ffKC}8@>hUu@=ZQem-s?}q z`4b*2A!6Ry&~c;U9nfA3MI$!=+cKZO+i3*pB*+ljZRY4{&pm@rqH8tu&_QS z-P#`C_eOq^LomNt+>QAM_}^zwVas)l_!nlL7>8&7$vyUx2?sKO8ot8UwZx9~mij6hNIaoEN| z>N5c1rjA0JJXS(vwCqbxo6K2cJJia=q7jh2{3;gDD?o}22BVI2+Cl3L8}iMn3@UeR zxd={ZJKWFYtep!`DA?=C1kC}yU_Oz41ML~BxqarOxE9ZI_N@^K<*@${d=NzP2Ym^P zTg;o9N>%;=c>O`DGOp+^I@!rGnQt+?A!*kEmspfYppmyKJrElAEm)|Ls3lEF9H1+a zv=@Y61BK5G<95O&cUccV6^qiUB1UtljbtN+K|{7?+-cNt7xa#}E8*iZ6P0q3pj>}; zqqyg+w}_VG9=RR5uE3!hxY3IN(fkSj#w=+?wy|j&56A#8zdtmEHK9s+`kGisn9w2FT@zKu8^N@dV?SkBNN z2m)MZu8g2A7c=7B27MwHmMe=JINeCr^l$c+u!4BqRS$X-CdFIo;f;ytw z^;1_No)j0Qel-YdM}gfDKTrZt1W<*{-$qQyxO^PmaJ=#b!?*aiQ zNQ@w@IEF*?_r5HXOI6^e%N+f21K_T4@ZiRbGwxp6+@jjL#sk1Q>Yx7J#uug zw%ra~zF?hEjVjplHU6oMNS2+Ty;lWCTTBU&6#I*9wz3N2j31gFH1pC4oWR)d2NbMa zvwB1Neqg&W#!?Vup!9O637r5JIb=PzOAvox7J0g=C(b0snF+wygtO_YZ3J5aT6Q=C zAt~JunIDg9sg(b)%4(89x~8kBR%?6XR#;$B;G{=D$D- z{P-o=u`q2ZGdo@EbU|HUbTQ?J1y3hUcZp1z{wfxvMn}Y5f2!ml_Es4^5Ji|nuX??~ z&?Rn5yP?XY&1&FiqXFaeF@&NuUQ5sOk0CO(lYU4-TjV`qL9wm&Ra!1CW}mW~YKW06*VEV>gVa@+PLM?PrXK@e>0C*yPHw*%3XYE$8y)dqmXR_IU*c!^;dJe&**eN z*O>i$>tN^jU=k`A9$p#}Kak1czTIU>9<@-GsXat4&LMG%vf(Y%7Hs+VO6seG(ReWI zdlvSxn86SQJi&@9+~Gfp%htaklJ9zJvTAFDEBBb+6X7ZABinWkVvOu7A?qru&{gb# z53(YvFMfN*oKOq$xsJG9La*zN4;SdV|58d~YadEESO7>;r!`leu&3_mp_pW*tF>?aRhSe(OexVV zX*MT^p`I8!;cnP?Vv)}^d;s0)u@>1t_LzA%i7hZ(5N=9gZ*>@pa4y}EXRlsF5zaT7 z63nKaeqbYIhYtPG0%~9v73Ix1aH<()b&$Jr$Vq}y644I-+BeoApCQ)rCjpL_HTX0u;yc+m>gB+$Ln~4pp zk+LFL(rT2gDiO!nxT4#1wfq!R^+l|9Lpr4TF?%suk4y*xc-eg=f5ZMprccOHIV?CwKgpCwmhTAn@=_lMBp#uaYe_N)&A9Yi$`M zo>$zXjRmQ#W_4}UD1CMk&6)+m=;e>r%rP-=`>)w;#Y7ee6Jay2nLK3p%)*i86Xu&( zFV3JXZC-AX+)@F^yxfAQ`V$Zq{cfyX*_QO=+N^JOmOo@-m~A5mtO*SJgJrz)?9R)L z&{^Z|A6LT4@m#)AB*!cjzO!FXHplBFeL_58J80F$tDC)uQqnYQj=O&bsj`UQtoL8S zfGAUjD{dJcNzOZ`aV&bZzLELqA#r^&`{<7YuUa+7Ls1Bd|LHSMGd!YQA)h^`|3!WL z_XZfb>qA+lYzF5R$+|`lpS)4TZITPpu>#0YP4EYJWV$dw2NM_B<)MdNNI$0HHTye+ z)*=Hl6%Mt=$#BWzACLbE+jx!jzx%boTxDHYYN?b|iU(=kqP*3#rLvc`Q!#H6)$JAw zpm5<8FMW1@Eb?E{R$7(>nzRxH$U(e;50J-yKd9bzZ{&EvY>$DwGApy14Z{l{sS&)4 zo+w$)3$Mr-7E3Ew%uAMo_W31L$f`vTe#fkVujYxkRZobNhxep?ed(nuzE}g7ODE^M zvdU;Io;dOKi1zs#thzYSz=$lk9I3HNgeBf(7(uvY=R^XArKl~5y^_($%L05CZj2J+ zagqu^G50ZTi(zg+35wS&om}glR&}wxp7giPK>9h1p4r+PjhBETQA1Tqa99rqAR&RF zc{&bh^k}xQQj29vyNj~I%$!7?xj#x4{#DiI^w*F@g-=;3H`F@gix#~~^ZMB{7a(SI zQ^TOq6G+=uwbC#L((8#Ej6UBs-2wUV<(%!>>-@%M2Za{E(vh&Tk|vAU0+l#Kk14`N z!x~J=V3=p5eS~PlL1$#~20R#hXVCwxLtPz-?>inWn}E1v(Zos(^u3l^p+s?g6q+a; z2?*;&Y-zs3R!eB#;LfP5r}Pkf^Sc&G-iyhoNU$^iw(enPqAcrfe6Ubf<78Ycz$Tdj zltNCc2QFcq+somBevo%!yX|{_!FZPO;_RxB=U2_A+O#j#htGw&v4PEFG^s<-3S(VE z+*6KBIh@lvq5B9*%VsnWG6wY959_}%+=T&Ff9c7ZF2#*M?I`6K)BAOA;7U2Rkbx%w)HN3z5N-OQ#*#b2+SjdpJqGt?nh@z)CI!P z??O}$)!ZcRtFwp-8hm=Sul?(czC(s3hNazuwnySO@=ke_bm7@iN+D$A7^N3f1wlm~ z2h9plT{&*+iLDSXJ(Nj>%qmB`+<~|~D&uXwPPvm)Ged$xbWw7!tuyfnNl9tvgn94l zd9L4fKzgeoq6{`fRMVt{AhMTt*E;{!B$@NrGT0|3nPFjdXn50A&8eHdllUa1t8h&L zfU$i>Ux@25i(BM3NPW7KySQp`=ikt){g0OMq5DZ8j!xqunwRp+Uzn>$z8FfDI7u&V zPv0Xd}CMauhf1xy?9@JlXE?MLFQUuK}LH!WCdM+SGAB_^kJfo#< z%_nD-s1JmXS-!cyUFiNgg2M&V@Z~40)&Y^rmwByDwoqqPsIEq7bm^B$&axm=W9T*5 zE)|+9x(Alsa(icAjlRC&-?L}av<;#-#uey;0$@`ezYsRSJXz_d3>h^|#9;-2hH8g| zd{}${3QRV6krE$=n_iN3ycun=JhI%J+q~tRwUJR&)-Oegx$VsIr7vJZSaQPajC4H|dcF+i3qs^A5Id327j7>nHy9sXKc2XxYQf>qa70gZ zGCth4tL&TJ&dy1YBa!55sSsO-vVo4 zrWftkY)7~iqvN|dw##+Sc3*BtIE$*zC+1tiBJ#maMLfO>>Zs_F>t3^0%x z?#z+C(eLGpziYSpo_u^^y;Vw;QVCOxQo`!OC~gD3ZNG!xR^BHl2S{PEH49u||47G8 zOdV&>mTCr=FtHY4WcJZ! zLu(tK~Y-9{_cT5Ji1cgk7jofx1M!PEFa*26t@Y+opH3o!Wln>D89+l7hoeLUl@0-8qAI|$ zq<;_{ewPm8{z%VjLsEv$vY1mm%O;qb4dT)qyI*D8q2viy97o)A5aydIiP~8FiC{&m82=@0D6DIdi$shAXyYZO(z$Bex&Ap0?ew z*QvfI{jNLwR(!`sG2Z=He=EOR`>7{z3oqh=}_`afJQnX=qf>FrR=A>!J9&-(gVN zFc6GIgw)YZ!O4G6+^8QhasJ&D0~s-FS3*OAT51~?NP5KoP)LZ>$dyTC9jy_J1!y(z zkN5@WpT5jG2|;_j8YM&s`3Z%Kgh4eU1vAcJkB!(6fb5117H5Z|k{b(PD|q@QNyHXm zCvw=*4%D56U$%~&py@}v#!SHKu*!4FwXD*AKX^RFFHl6N89d1@S}>U$@YASg*;Z_E zENuOX+#1J7=ecEVfSa@4?%mgF#{W9Z~KvF!<`?sC%&qguc~JQ6yz6WJ%U9@ zfsP^gQ4t_WLFc1kZM+6`u;L?Xg8D!3KlN^`-<__Lj@6#5yH~w67@S-Jp%}qUYnGab ze&3HR+ZhlCcgu)_CsG}wZdDMTV#41OWV!8ISzpNCx*eik?m&8@+#t$stZp5?RAo^7 z+T22E_BrJ09~D>Fr`2yCP2q`-%XY0Kl|9U?>?sl|~|7w==iOyKSGB%3r@u#w)smjXb<(%P6aPqdiXzMUb_;N$KT&fvU`xX* zc3eCZD2Y3sikM2NZfc!P7<`tB!fgf({5+y7Vj8HZ>_()@r-$PT*X!316$!!|ZuTE) z_Ur+9)pb%XRmtR|7G`|eJZ?=-l!W+dx0T~*bfLMir<|{#e{@eB&DIapo_pnClH@HS zYX{VC3NI)giV7zC=$wxEMoC2u@4zRC=~*?EiVPoZWl47^yh(F=;HK28-KTCnT2>QE zva$lw6a>9JUOPt$XAc!WwIpZO^nP6WHa;;!C~D9p;5~{)MQf9UCtEm)E!^>BB^}sr zRu)rQ$OuF9i{29e-7RAL!G~Lnp7#e?&2{xt$=B8UQ{UO4gU2a|2??F%1#dHBloE_7 zcu9#1uRLdNJEyL+zH@GxxXRnL-FoGLXHomgP(cU2{yl-)=WW!t>84+)8bjngemB_l z=;l$`lkgSOUI&axiGZKP3R_Nwq+%50DunEs@a}-0yq}ccI^pEO&;KAdJRe&3x=o%Z zYR@2oRDyy!t)38f7Wsfr6Y8VdNp5%+T& zu_bsQsyFw2&VT0dZE)D-zH;$4iQ665{m^UU_Yxwa5gpUkPbfJzqd!@BII<+i;?jTN z$pe}sZDO$_1td9bpt0_1h!nheGY{esJ;627(Z~4pxcjy|n^v>Ax}#tt>~RquePHbi z!XK(}2;tlyU2aAaE<_e0$HVolXMGzw|>7*FrsF zX(3c`&r59#hN+K}^JVK-4+b&pU(RWEy%w|D_)U(C-0n4H?tI_trE1q`Zt%Nf{DM!Q zt8gSG{1bsfXfweEY?FI_(e*ge zQD2bHd%VOUm0d8N^YovU9Z26o->7q?d8~}}(`%(L^oug9wT{=AqyLyP(l?4W;kWJQ zc$~xdp4WSRW5iCT01G2R=G(_68l}O5b%E;`KnW=n1Ppra^zq(*56-eCjzTxMFfa|V?Kbr5)T(xdmT(xw;?_y=U9M$gGdd0K5 zT0E%zmPV3A4(t!)7PLub^bP!O$mv^q%c{Y6-E~lRu*2j-Tb{@9vesfG*_j4ubvx3J zGC_d4Bns$@VDy!T(ecaO{3<_VuUht(HHl$c#RODu58VCSXo4{RCz`;Gv;JQ(hK+DZ z^XJ+BJ+A)u?f*UgPlErZ`F|w)KNI{n+5f*Z|DERljHyQI{TnDLii|1= diff --git a/packages/server/marketplaces/tools/Make Webhook.json b/packages/server/marketplaces/tools/Make Webhook.json new file mode 100644 index 00000000..24d00900 --- /dev/null +++ b/packages/server/marketplaces/tools/Make Webhook.json @@ -0,0 +1,8 @@ +{ + "name": "make_webhook", + "description": "Useful when you need to send message to Discord", + "color": "linear-gradient(rgb(19,94,2), rgb(19,124,59))", + "iconSrc": "https://github.com/FlowiseAI/Flowise/assets/26460777/517fdab2-8a6e-4781-b3c8-fb92cc78aa0b", + "schema": "[{\"id\":0,\"property\":\"message\",\"description\":\"Message to send\",\"type\":\"string\",\"required\":true}]", + "func": "/*\n* You can use any libraries imported in Flowise\n* You can use properties specified in Output Schema as variables. Ex: Property = userid, Variable = $userid\n* Must return a string value at the end of function\n*/\n\nconst fetch = require('node-fetch');\nconst webhookUrl = 'https://hook.eu1.make.com/abcdefg';\nconst body = {\n\t\"message\": $message\n};\nconst options = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(body)\n};\ntry {\n const response = await fetch(webhookUrl, options);\n const text = await response.text();\n return text;\n} catch (error) {\n console.error(error);\n return '';\n}" +} From 9daaef75837aa9150e0a30fd2f6ca249ff606faf Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 11 Aug 2023 19:06:05 +0100 Subject: [PATCH 373/398] removing child mode --- CONTRIBUTING-ZH.md | 121 ++++++------ CONTRIBUTING.md | 3 +- docker/.env.example | 1 - docker/docker-compose.yml | 1 - packages/server/.env.example | 1 - packages/server/README-ZH.md | 57 +++--- packages/server/src/ChildProcess.ts | 253 ------------------------- packages/server/src/Interface.ts | 13 -- packages/server/src/commands/start.ts | 2 - packages/server/src/index.ts | 263 +++++++++----------------- packages/server/src/utils/index.ts | 2 +- 11 files changed, 178 insertions(+), 539 deletions(-) delete mode 100644 packages/server/src/ChildProcess.ts diff --git a/CONTRIBUTING-ZH.md b/CONTRIBUTING-ZH.md index b0176e4e..bec081f4 100644 --- a/CONTRIBUTING-ZH.md +++ b/CONTRIBUTING-ZH.md @@ -1,22 +1,22 @@ -# 贡献给Flowise +# 贡献给 Flowise -[English](<./CONTRIBUTING.md>) | 中文 +[English](./CONTRIBUTING.md) | 中文 我们欢迎任何形式的贡献。 ## ⭐ 点赞 -点赞并分享[Github仓库](https://github.com/FlowiseAI/Flowise)。 +点赞并分享[Github 仓库](https://github.com/FlowiseAI/Flowise)。 ## 🙋 问题和回答 在[问题和回答](https://github.com/FlowiseAI/Flowise/discussions/categories/q-a)部分搜索任何问题,如果找不到,可以毫不犹豫地创建一个。这可能会帮助到其他有类似问题的人。 -## 🙌 分享Chatflow +## 🙌 分享 Chatflow -是的!分享你如何使用Flowise是一种贡献方式。将你的Chatflow导出为JSON,附上截图并在[展示和分享](https://github.com/FlowiseAI/Flowise/discussions/categories/show-and-tell)部分分享。 +是的!分享你如何使用 Flowise 是一种贡献方式。将你的 Chatflow 导出为 JSON,附上截图并在[展示和分享](https://github.com/FlowiseAI/Flowise/discussions/categories/show-and-tell)部分分享。 ## 💡 想法 @@ -30,75 +30,75 @@ 不确定要贡献什么?一些想法: -- 从Langchain创建新组件 -- 更新现有组件,如扩展功能、修复错误 -- 添加新的Chatflow想法 +- 从 Langchain 创建新组件 +- 更新现有组件,如扩展功能、修复错误 +- 添加新的 Chatflow 想法 ### 开发人员 -Flowise在一个单一的单体存储库中有3个不同的模块。 +Flowise 在一个单一的单体存储库中有 3 个不同的模块。 -- `server`:用于提供API逻辑的Node后端 -- `ui`:React前端 -- `components`:Langchain组件 +- `server`:用于提供 API 逻辑的 Node 后端 +- `ui`:React 前端 +- `components`:Langchain 组件 #### 先决条件 -- 安装 [Yarn v1](https://classic.yarnpkg.com/en/docs/install) - ```bash - npm i -g yarn - ``` +- 安装 [Yarn v1](https://classic.yarnpkg.com/en/docs/install) + ```bash + npm i -g yarn + ``` #### 逐步指南 -1. Fork官方的[Flowise Github 仓库](https://github.com/FlowiseAI/Flowise)。 +1. Fork 官方的[Flowise Github 仓库](https://github.com/FlowiseAI/Flowise)。 -2. 克隆你fork的存储库。 +2. 克隆你 fork 的存储库。 3. 创建一个新的分支,参考[指南](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-and-deleting-branches-within-your-repository)。命名约定: - - 对于功能分支:`feature/<你的新功能>` - - 对于bug修复分支:`bugfix/<你的新bug修复>`。 + - 对于功能分支:`feature/<你的新功能>` + - 对于 bug 修复分支:`bugfix/<你的新bug修复>`。 4. 切换到新创建的分支。 5. 进入存储库文件夹 - ```bash - cd Flowise - ``` + ```bash + cd Flowise + ``` 6. 安装所有模块的依赖项: - ```bash - yarn install - ``` + ```bash + yarn install + ``` 7. 构建所有代码: - ```bash - yarn build - ``` + ```bash + yarn build + ``` 8. 在[http://localhost:3000](http://localhost:3000)上启动应用程序 - ```bash - yarn start - ``` + ```bash + yarn start + ``` 9. 开发时: - - 在`packages/ui`中创建`.env`文件并指定`PORT`(参考`.env.example`) - - 在`packages/server`中创建`.env`文件并指定`PORT`(参考`.env.example`) - - 运行 + - 在`packages/ui`中创建`.env`文件并指定`PORT`(参考`.env.example`) + - 在`packages/server`中创建`.env`文件并指定`PORT`(参考`.env.example`) + - 运行 - ```bash - yarn dev - ``` + ```bash + yarn dev + ``` - 对`packages/ui`或`packages/server`进行的任何更改都将反映在[http://localhost:8080](http://localhost:8080)上 + 对`packages/ui`或`packages/server`进行的任何更改都将反映在[http://localhost:8080](http://localhost:8080)上 - 对于`packages/components`中进行的更改,再次运行`yarn build`以应用更改。 + 对于`packages/components`中进行的更改,再次运行`yarn build`以应用更改。 10. 做完所有的更改后,运行以下命令来确保在生产环境中一切正常: @@ -118,26 +118,25 @@ Flowise在一个单一的单体存储库中有3个不同的模块。 Flowise 支持不同的环境变量来配置您的实例。您可以在 `packages/server` 文件夹中的 `.env` 文件中指定以下变量。阅读[更多信息](https://docs.flowiseai.com/environment-variables) -| 变量名 | 描述 | 类型 | 默认值 | -| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------- | ----------------------------------- | -| PORT | Flowise 运行的 HTTP 端口 | 数字 | 3000 | -| FLOWISE_USERNAME | 登录用户名 | 字符串 | | -| FLOWISE_PASSWORD | 登录密码 | 字符串 | | -| DEBUG | 打印组件的日志 | 布尔值 | | -| LOG_PATH | 存储日志文件的位置 | 字符串 | `your-path/Flowise/logs` | -| LOG_LEVEL | 日志的不同级别 | 枚举字符串: `error`, `info`, `verbose`, `debug` | `info` | -| APIKEY_PATH | 存储 API 密钥的位置 | 字符串 | `your-path/Flowise/packages/server` | -| EXECUTION_MODE | 预测是否在独立进程中运行还是在主进程中运行 | 枚举字符串: `child`, `main` | `main` | -| TOOL_FUNCTION_BUILTIN_DEP | 用于工具函数的 NodeJS 内置模块 | 字符串 | | -| TOOL_FUNCTION_EXTERNAL_DEP | 用于工具函数的外部模块 | 字符串 | | -| OVERRIDE_DATABASE | 是否使用默认值覆盖当前数据库 | 枚举字符串: `true`, `false` | `true` | -| DATABASE_TYPE | 存储 flowise 数据的数据库类型 | 枚举字符串: `sqlite`, `mysql`, `postgres` | `sqlite` | -| DATABASE_PATH | 数据库保存的位置(当 DATABASE_TYPE 是 sqlite 时) | 字符串 | `your-home-dir/.flowise` | -| DATABASE_HOST | 主机 URL 或 IP 地址(当 DATABASE_TYPE 不是 sqlite 时) | 字符串 | | -| DATABASE_PORT | 数据库端口(当 DATABASE_TYPE 不是 sqlite 时) | 字符串 | | -| DATABASE_USERNAME | 数据库用户名(当 DATABASE_TYPE 不是 sqlite 时) | 字符串 | | -| DATABASE_PASSWORD | 数据库密码(当 DATABASE_TYPE 不是 sqlite 时) | 字符串 | | -| DATABASE_NAME | 数据库名称(当 DATABASE_TYPE 不是 sqlite 时) | 字符串 | | +| 变量名 | 描述 | 类型 | 默认值 | +| -------------------------- | ------------------------------------------------------ | ----------------------------------------------- | ----------------------------------- | +| PORT | Flowise 运行的 HTTP 端口 | 数字 | 3000 | +| FLOWISE_USERNAME | 登录用户名 | 字符串 | | +| FLOWISE_PASSWORD | 登录密码 | 字符串 | | +| DEBUG | 打印组件的日志 | 布尔值 | | +| LOG_PATH | 存储日志文件的位置 | 字符串 | `your-path/Flowise/logs` | +| LOG_LEVEL | 日志的不同级别 | 枚举字符串: `error`, `info`, `verbose`, `debug` | `info` | +| APIKEY_PATH | 存储 API 密钥的位置 | 字符串 | `your-path/Flowise/packages/server` | +| TOOL_FUNCTION_BUILTIN_DEP | 用于工具函数的 NodeJS 内置模块 | 字符串 | | +| TOOL_FUNCTION_EXTERNAL_DEP | 用于工具函数的外部模块 | 字符串 | | +| OVERRIDE_DATABASE | 是否使用默认值覆盖当前数据库 | 枚举字符串: `true`, `false` | `true` | +| DATABASE_TYPE | 存储 flowise 数据的数据库类型 | 枚举字符串: `sqlite`, `mysql`, `postgres` | `sqlite` | +| DATABASE_PATH | 数据库保存的位置(当 DATABASE_TYPE 是 sqlite 时) | 字符串 | `your-home-dir/.flowise` | +| DATABASE_HOST | 主机 URL 或 IP 地址(当 DATABASE_TYPE 不是 sqlite 时) | 字符串 | | +| DATABASE_PORT | 数据库端口(当 DATABASE_TYPE 不是 sqlite 时) | 字符串 | | +| DATABASE_USERNAME | 数据库用户名(当 DATABASE_TYPE 不是 sqlite 时) | 字符串 | | +| DATABASE_PASSWORD | 数据库密码(当 DATABASE_TYPE 不是 sqlite 时) | 字符串 | | +| DATABASE_NAME | 数据库名称(当 DATABASE_TYPE 不是 sqlite 时) | 字符串 | | 您也可以在使用 `npx` 时指定环境变量。例如: @@ -153,4 +152,4 @@ npx flowise start --PORT=3000 --DEBUG=true 当您打开一个 Pull Request 时,FlowiseAI 团队的成员将自动收到通知/指派。您也可以在 [Discord](https://discord.gg/jbaHfsRVBW) 上联系我们。 -## \ No newline at end of file +## diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 74865d48..90ba5498 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ # Contributing to Flowise -English | [中文](<./CONTRIBUTING-ZH.md>) +English | [中文](./CONTRIBUTING-ZH.md) We appreciate any form of contributions. @@ -129,7 +129,6 @@ Flowise support different environment variables to configure your instance. You | LOG_PATH | Location where log files are stored | String | `your-path/Flowise/logs` | | LOG_LEVEL | Different levels of logs | Enum String: `error`, `info`, `verbose`, `debug` | `info` | | APIKEY_PATH | Location where api keys are saved | String | `your-path/Flowise/packages/server` | -| EXECUTION_MODE | Whether predictions run in their own process or the main process | Enum String: `child`, `main` | `main` | | TOOL_FUNCTION_BUILTIN_DEP | NodeJS built-in modules to be used for Tool Function | String | | | TOOL_FUNCTION_EXTERNAL_DEP | External modules to be used for Tool Function | String | | | OVERRIDE_DATABASE | Override current database with default | Enum String: `true`, `false` | `true` | diff --git a/docker/.env.example b/docker/.env.example index 66b0910d..16b19cdc 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -17,7 +17,6 @@ LOG_PATH=/root/.flowise/logs # FLOWISE_PASSWORD=1234 # DEBUG=true # LOG_LEVEL=debug (error | warn | info | verbose | debug) -# EXECUTION_MODE=main (child | main) # TOOL_FUNCTION_BUILTIN_DEP=crypto,fs # TOOL_FUNCTION_EXTERNAL_DEP=moment,lodash diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index e9b2824d..4a03bcf3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -15,7 +15,6 @@ services: - SECRETKEY_PATH=${SECRETKEY_PATH} - LOG_LEVEL=${LOG_LEVEL} - LOG_PATH=${LOG_PATH} - - EXECUTION_MODE=${EXECUTION_MODE} ports: - '${PORT}:${PORT}' volumes: diff --git a/packages/server/.env.example b/packages/server/.env.example index 7ab05518..bedbf638 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -17,7 +17,6 @@ PASSPHRASE=MYPASSPHRASE # Passphrase used to create encryption key # FLOWISE_PASSWORD=1234 # DEBUG=true # LOG_LEVEL=debug (error | warn | info | verbose | debug) -# EXECUTION_MODE=main (child | main) # TOOL_FUNCTION_BUILTIN_DEP=crypto,fs # TOOL_FUNCTION_EXTERNAL_DEP=moment,lodash diff --git a/packages/server/README-ZH.md b/packages/server/README-ZH.md index d32096d3..e58f08bf 100644 --- a/packages/server/README-ZH.md +++ b/packages/server/README-ZH.md @@ -1,20 +1,20 @@ -# Flowise - 低代码LLM应用程序构建器 +# Flowise - 低代码 LLM 应用程序构建器 -[English](<./README.md>) | 中文 +[English](./README.md) | 中文 ![Flowise](https://github.com/FlowiseAI/Flowise/blob/main/images/flowise.gif?raw=true) -拖放界面来构建自定义的LLM流程 +拖放界面来构建自定义的 LLM 流程 -## ⚡快速入门 +## ⚡ 快速入门 -1. 安装Flowise +1. 安装 Flowise ```bash npm install -g flowise ``` -2. 启动Flowise +2. 启动 Flowise ```bash npx flowise start @@ -33,28 +33,27 @@ FLOWISE_PASSWORD=1234 ## 🌱 环境变量 -Flowise支持不同的环境变量来配置您的实例。您可以在`packages/server`文件夹中的`.env`文件中指定以下变量。阅读[更多](https://docs.flowiseai.com/environment-variables) +Flowise 支持不同的环境变量来配置您的实例。您可以在`packages/server`文件夹中的`.env`文件中指定以下变量。阅读[更多](https://docs.flowiseai.com/environment-variables) -| 变量 | 描述 | 类型 | 默认值 | -| ---------------- | ---------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------- | -| PORT | Flowise运行的HTTP端口 | 数字 | 3000 | -| FLOWISE_USERNAME | 登录的用户名 | 字符串 | | -| FLOWISE_PASSWORD | 登录的密码 | 字符串 | | -| DEBUG | 打印组件的日志 | 布尔值 | | -| LOG_PATH | 存储日志文件的位置 | 字符串 | `your-path/Flowise/logs` | -| LOG_LEVEL | 日志的不同级别 | 枚举字符串:`error`、`info`、`verbose`、`debug` | `info` | -| APIKEY_PATH | 存储API密钥的位置 | 字符串 | `your-path/Flowise/packages/server` | -| EXECUTION_MODE | 预测是在其自己的进程中运行还是在主进程中运行 | 枚举字符串:`child`、`main` | `main` | -| TOOL_FUNCTION_BUILTIN_DEP | 用于工具函数的NodeJS内置模块 | 字符串 | | -| TOOL_FUNCTION_EXTERNAL_DEP | 用于工具函数的外部模块 | 字符串 | | -| OVERRIDE_DATABASE | 使用默认值覆盖当前数据库 | 枚举字符串:`true`、`false` | `true` | -| DATABASE_TYPE | 存储flowise数据的数据库类型 | 枚举字符串:`sqlite`、`mysql`、`postgres` | `sqlite` | -| DATABASE_PATH | 数据库的保存位置(当DATABASE_TYPE为sqlite时) | 字符串 | `your-home-dir/.flowise` | -| DATABASE_HOST | 主机URL或IP地址(当DATABASE_TYPE不为sqlite时) | 字符串 | | -| DATABASE_PORT | 数据库端口(当DATABASE_TYPE不为sqlite时) | 字符串 | | -| DATABASE_USERNAME | 数据库用户名(当DATABASE_TYPE不为sqlite时) | 字符串 | | -| DATABASE_PASSWORD | 数据库密码(当DATABASE_TYPE不为sqlite时) | 字符串 | | -| DATABASE_NAME | 数据库名称(当DATABASE_TYPE不为sqlite时) | 字符串 | | +| 变量 | 描述 | 类型 | 默认值 | +| -------------------------- | ------------------------------------------------------ | ----------------------------------------------- | ----------------------------------- | +| PORT | Flowise 运行的 HTTP 端口 | 数字 | 3000 | +| FLOWISE_USERNAME | 登录的用户名 | 字符串 | | +| FLOWISE_PASSWORD | 登录的密码 | 字符串 | | +| DEBUG | 打印组件的日志 | 布尔值 | | +| LOG_PATH | 存储日志文件的位置 | 字符串 | `your-path/Flowise/logs` | +| LOG_LEVEL | 日志的不同级别 | 枚举字符串:`error`、`info`、`verbose`、`debug` | `info` | +| APIKEY_PATH | 存储 API 密钥的位置 | 字符串 | `your-path/Flowise/packages/server` | +| TOOL_FUNCTION_BUILTIN_DEP | 用于工具函数的 NodeJS 内置模块 | 字符串 | | +| TOOL_FUNCTION_EXTERNAL_DEP | 用于工具函数的外部模块 | 字符串 | | +| OVERRIDE_DATABASE | 使用默认值覆盖当前数据库 | 枚举字符串:`true`、`false` | `true` | +| DATABASE_TYPE | 存储 flowise 数据的数据库类型 | 枚举字符串:`sqlite`、`mysql`、`postgres` | `sqlite` | +| DATABASE_PATH | 数据库的保存位置(当 DATABASE_TYPE 为 sqlite 时) | 字符串 | `your-home-dir/.flowise` | +| DATABASE_HOST | 主机 URL 或 IP 地址(当 DATABASE_TYPE 不为 sqlite 时) | 字符串 | | +| DATABASE_PORT | 数据库端口(当 DATABASE_TYPE 不为 sqlite 时) | 字符串 | | +| DATABASE_USERNAME | 数据库用户名(当 DATABASE_TYPE 不为 sqlite 时) | 字符串 | | +| DATABASE_PASSWORD | 数据库密码(当 DATABASE_TYPE 不为 sqlite 时) | 字符串 | | +| DATABASE_NAME | 数据库名称(当 DATABASE_TYPE 不为 sqlite 时) | 字符串 | | 您还可以在使用`npx`时指定环境变量。例如: @@ -64,7 +63,7 @@ npx flowise start --PORT=3000 --DEBUG=true ## 📖 文档 -[Flowise文档](https://docs.flowiseai.com/) +[Flowise 文档](https://docs.flowiseai.com/) ## 🌐 自托管 @@ -98,4 +97,4 @@ npx flowise start --PORT=3000 --DEBUG=true ## 📄 许可证 -本仓库中的源代码在[MIT许可证](https://github.com/FlowiseAI/Flowise/blob/master/LICENSE.md)下提供。 \ No newline at end of file +本仓库中的源代码在[MIT 许可证](https://github.com/FlowiseAI/Flowise/blob/master/LICENSE.md)下提供。 diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts deleted file mode 100644 index 0112f840..00000000 --- a/packages/server/src/ChildProcess.ts +++ /dev/null @@ -1,253 +0,0 @@ -import path from 'path' -import { IChildProcessMessage, IReactFlowNode, IReactFlowObject, IRunChatflowMessageValue, INodeData } from './Interface' -import { - buildLangchain, - checkMemorySessionId, - constructGraphs, - getEndingNode, - getStartingNodes, - getUserHome, - replaceInputsWithConfig, - resolveVariables, - databaseEntities -} from './utils' -import { DataSource } from 'typeorm' -import { ChatFlow } from './entity/ChatFlow' -import { ChatMessage } from './entity/ChatMessage' -import { Tool } from './entity/Tool' -import { Credential } from './entity/Credential' -import logger from './utils/logger' - -export class ChildProcess { - /** - * Stop child process when app is killed - */ - static async stopChildProcess() { - setTimeout(() => { - process.exit(0) - }, 50000) - } - - /** - * Process prediction - * @param {IRunChatflowMessageValue} messageValue - * @return {Promise} - */ - async runChildProcess(messageValue: IRunChatflowMessageValue): Promise { - process.on('SIGTERM', ChildProcess.stopChildProcess) - process.on('SIGINT', ChildProcess.stopChildProcess) - - await sendToParentProcess('start', '_') - - try { - const childAppDataSource = await initDB() - - // Create a Queue and add our initial node in it - const { endingNodeData, chatflow, chatId, incomingInput, componentNodes } = messageValue - - let nodeToExecuteData: INodeData - let addToChatFlowPool: any = {} - - /* Don't rebuild the flow (to avoid duplicated upsert, recomputation) when all these conditions met: - * - Node Data already exists in pool - * - Still in sync (i.e the flow has not been modified since) - * - Existing overrideConfig and new overrideConfig are the same - * - Flow doesn't start with nodes that depend on incomingInput.question - ***/ - if (endingNodeData) { - nodeToExecuteData = endingNodeData - } else { - /*** Get chatflows and prepare data ***/ - const flowData = chatflow.flowData - const parsedFlowData: IReactFlowObject = JSON.parse(flowData) - const nodes = parsedFlowData.nodes - const edges = parsedFlowData.edges - - /*** Get Ending Node with Directed Graph ***/ - const { graph, nodeDependencies } = constructGraphs(nodes, edges) - const directedGraph = graph - const endingNodeId = getEndingNode(nodeDependencies, directedGraph) - if (!endingNodeId) { - await sendToParentProcess('error', `Ending node ${endingNodeId} not found`) - return - } - - const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data - if (!endingNodeData) { - await sendToParentProcess('error', `Ending node ${endingNodeId} data not found`) - return - } - - if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { - await sendToParentProcess('error', `Ending node must be either a Chain or Agent`) - return - } - - if ( - endingNodeData.outputs && - Object.keys(endingNodeData.outputs).length && - !Object.values(endingNodeData.outputs).includes(endingNodeData.name) - ) { - await sendToParentProcess( - 'error', - `Output of ${endingNodeData.label} (${endingNodeData.id}) must be ${endingNodeData.label}, can't be an Output Prediction` - ) - return - } - - /*** Get Starting Nodes with Non-Directed Graph ***/ - const constructedObj = constructGraphs(nodes, edges, true) - const nonDirectedGraph = constructedObj.graph - const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) - - logger.debug(`[server] [mode:child]: Start building chatflow ${chatflow.id}`) - /*** BFS to traverse from Starting Nodes to Ending Node ***/ - const reactFlowNodes = await buildLangchain( - startingNodeIds, - nodes, - graph, - depthQueue, - componentNodes, - incomingInput.question, - chatId, - childAppDataSource, - incomingInput?.overrideConfig - ) - - const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId) - if (!nodeToExecute) { - await sendToParentProcess('error', `Node ${endingNodeId} not found`) - return - } - - if (incomingInput.overrideConfig) - nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig) - const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question) - nodeToExecuteData = reactFlowNodeData - - const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id)) - addToChatFlowPool = { - chatflowid: chatflow.id, - nodeToExecuteData, - startingNodes, - overrideConfig: incomingInput?.overrideConfig - } - } - - const nodeInstanceFilePath = componentNodes[nodeToExecuteData.name].filePath as string - const nodeModule = await import(nodeInstanceFilePath) - const nodeInstance = new nodeModule.nodeClass() - - logger.debug(`[server] [mode:child]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) - - if (nodeToExecuteData.instance) checkMemorySessionId(nodeToExecuteData.instance, chatId) - - const result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, { - chatHistory: incomingInput.history, - appDataSource: childAppDataSource, - databaseEntities - }) - - logger.debug(`[server] [mode:child]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) - - await sendToParentProcess('finish', { result, addToChatFlowPool }) - } catch (e: any) { - await sendToParentProcess('error', e.message) - logger.error('[server] [mode:child]: Error:', e) - } - } -} - -/** - * Initialize DB in child process - * @returns {DataSource} - */ -async function initDB() { - let childAppDataSource - let homePath - const synchronize = process.env.OVERRIDE_DATABASE === 'false' ? false : true - switch (process.env.DATABASE_TYPE) { - case 'sqlite': - homePath = process.env.DATABASE_PATH ?? path.join(getUserHome(), '.flowise') - childAppDataSource = new DataSource({ - type: 'sqlite', - database: path.resolve(homePath, 'database.sqlite'), - synchronize, - entities: [ChatFlow, ChatMessage, Tool, Credential], - migrations: [] - }) - break - case 'mysql': - childAppDataSource = new DataSource({ - type: 'mysql', - host: process.env.DATABASE_HOST, - port: parseInt(process.env.DATABASE_PORT || '3306'), - username: process.env.DATABASE_USER, - password: process.env.DATABASE_PASSWORD, - database: process.env.DATABASE_NAME, - charset: 'utf8mb4', - synchronize, - entities: [ChatFlow, ChatMessage, Tool, Credential], - migrations: [] - }) - break - case 'postgres': - childAppDataSource = new DataSource({ - type: 'postgres', - host: process.env.DATABASE_HOST, - port: parseInt(process.env.DATABASE_PORT || '5432'), - username: process.env.DATABASE_USER, - password: process.env.DATABASE_PASSWORD, - database: process.env.DATABASE_NAME, - synchronize, - entities: [ChatFlow, ChatMessage, Tool, Credential], - migrations: [] - }) - break - default: - homePath = process.env.DATABASE_PATH ?? path.join(getUserHome(), '.flowise') - childAppDataSource = new DataSource({ - type: 'sqlite', - database: path.resolve(homePath, 'database.sqlite'), - synchronize, - entities: [ChatFlow, ChatMessage, Tool, Credential], - migrations: [] - }) - break - } - - return await childAppDataSource.initialize() -} - -/** - * Send data back to parent process - * @param {string} key Key of message - * @param {*} value Value of message - * @returns {Promise} - */ -async function sendToParentProcess(key: string, value: any): Promise { - // tslint:disable-line:no-any - return new Promise((resolve, reject) => { - process.send!( - { - key, - value - }, - (error: Error) => { - if (error) { - return reject(error) - } - resolve() - } - ) - }) -} - -const childProcess = new ChildProcess() - -process.on('message', async (message: IChildProcessMessage) => { - if (message.key === 'start') { - await childProcess.runChildProcess(message.value) - process.exit() - } -}) diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 8feb4272..92e3054d 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -169,19 +169,6 @@ export interface IDatabaseExport { apikeys: ICommonObject[] } -export interface IRunChatflowMessageValue { - chatflow: IChatFlow - chatId: string - incomingInput: IncomingInput - componentNodes: IComponentNodes - endingNodeData?: INodeData -} - -export interface IChildProcessMessage { - key: string - value?: any -} - export type ICredentialDataDecrypted = ICommonObject // Plain credential object sent to server diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index 71459d17..4b58ae7c 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -25,7 +25,6 @@ export default class Start extends Command { SECRETKEY_PATH: Flags.string(), LOG_PATH: Flags.string(), LOG_LEVEL: Flags.string(), - EXECUTION_MODE: Flags.string(), TOOL_FUNCTION_BUILTIN_DEP: Flags.string(), TOOL_FUNCTION_EXTERNAL_DEP: Flags.string(), OVERRIDE_DATABASE: Flags.string(), @@ -73,7 +72,6 @@ export default class Start extends Command { const { flags } = await this.parse(Start) if (flags.PORT) process.env.PORT = flags.PORT - if (flags.EXECUTION_MODE) process.env.EXECUTION_MODE = flags.EXECUTION_MODE if (flags.DEBUG) process.env.DEBUG = flags.DEBUG // Authorization diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 803dd175..b7289149 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -16,8 +16,6 @@ import { IReactFlowObject, INodeData, IDatabaseExport, - IRunChatflowMessageValue, - IChildProcessMessage, ICredentialReturnResponse } from './Interface' import { @@ -57,7 +55,6 @@ import { Credential } from './entity/Credential' import { Tool } from './entity/Tool' import { ChatflowPool } from './ChatflowPool' import { ICommonObject, INodeOptionsValue } from 'flowise-components' -import { fork } from 'child_process' export class App { app: express.Application @@ -764,68 +761,6 @@ export class App { } } - /** - * Start child process - * @param {ChatFlow} chatflow - * @param {IncomingInput} incomingInput - * @param {INodeData} endingNodeData - */ - async startChildProcess(chatflow: ChatFlow, chatId: string, incomingInput: IncomingInput, endingNodeData?: INodeData) { - try { - const controller = new AbortController() - const { signal } = controller - - let childpath = path.join(__dirname, '..', 'dist', 'ChildProcess.js') - if (!fs.existsSync(childpath)) childpath = 'ChildProcess.ts' - - const childProcess = fork(childpath, [], { signal }) - - const value = { - chatflow, - chatId, - incomingInput, - componentNodes: cloneDeep(this.nodesPool.componentNodes), - endingNodeData - } as IRunChatflowMessageValue - childProcess.send({ key: 'start', value } as IChildProcessMessage) - - let childProcessTimeout: NodeJS.Timeout - - return new Promise((resolve, reject) => { - childProcess.on('message', async (message: IChildProcessMessage) => { - if (message.key === 'finish') { - const { result, addToChatFlowPool } = message.value as ICommonObject - if (childProcessTimeout) { - clearTimeout(childProcessTimeout) - } - if (Object.keys(addToChatFlowPool).length) { - const { chatflowid, nodeToExecuteData, startingNodes, overrideConfig } = addToChatFlowPool - this.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, overrideConfig) - } - resolve(result) - } - if (message.key === 'start') { - if (process.env.EXECUTION_TIMEOUT) { - childProcessTimeout = setTimeout(async () => { - childProcess.kill() - resolve(undefined) - }, parseInt(process.env.EXECUTION_TIMEOUT, 10)) - } - } - if (message.key === 'error') { - let errMessage = message.value as string - if (childProcessTimeout) { - clearTimeout(childProcessTimeout) - } - reject(errMessage) - } - }) - }) - } catch (err) { - logger.error('[server] [mode:child]: Error:', err) - } - } - /** * Process Prediction * @param {Request} req @@ -895,126 +830,104 @@ export class App { ) } - if (process.env.EXECUTION_MODE === 'child') { - if (isFlowReusable()) { - nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData - logger.debug( - `[server] [mode:child]: Reuse existing chatflow ${chatflowid} with ending node ${nodeToExecuteData.label} (${nodeToExecuteData.id})` - ) - try { - const result = await this.startChildProcess(chatflow, chatId, incomingInput, nodeToExecuteData) - return res.json(result) - } catch (error) { - return res.status(500).send(error) - } - } else { - try { - const result = await this.startChildProcess(chatflow, chatId, incomingInput) - return res.json(result) - } catch (error) { - return res.status(500).send(error) - } - } + /*** Get chatflows and prepare data ***/ + const flowData = chatflow.flowData + const parsedFlowData: IReactFlowObject = JSON.parse(flowData) + const nodes = parsedFlowData.nodes + const edges = parsedFlowData.edges + + if (isFlowReusable()) { + nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData + isStreamValid = isFlowValidForStream(nodes, nodeToExecuteData) + logger.debug( + `[server]: Reuse existing chatflow ${chatflowid} with ending node ${nodeToExecuteData.label} (${nodeToExecuteData.id})` + ) } else { - /*** Get chatflows and prepare data ***/ - const flowData = chatflow.flowData - const parsedFlowData: IReactFlowObject = JSON.parse(flowData) - const nodes = parsedFlowData.nodes - const edges = parsedFlowData.edges + /*** Get Ending Node with Directed Graph ***/ + const { graph, nodeDependencies } = constructGraphs(nodes, edges) + const directedGraph = graph + const endingNodeId = getEndingNode(nodeDependencies, directedGraph) + if (!endingNodeId) return res.status(500).send(`Ending node ${endingNodeId} not found`) - if (isFlowReusable()) { - nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData - isStreamValid = isFlowValidForStream(nodes, nodeToExecuteData) - logger.debug( - `[server]: Reuse existing chatflow ${chatflowid} with ending node ${nodeToExecuteData.label} (${nodeToExecuteData.id})` - ) - } else { - /*** Get Ending Node with Directed Graph ***/ - const { graph, nodeDependencies } = constructGraphs(nodes, edges) - const directedGraph = graph - const endingNodeId = getEndingNode(nodeDependencies, directedGraph) - if (!endingNodeId) return res.status(500).send(`Ending node ${endingNodeId} not found`) + const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data + if (!endingNodeData) return res.status(500).send(`Ending node ${endingNodeId} data not found`) - const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data - if (!endingNodeData) return res.status(500).send(`Ending node ${endingNodeId} data not found`) - - if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { - return res.status(500).send(`Ending node must be either a Chain or Agent`) - } - - if ( - endingNodeData.outputs && - Object.keys(endingNodeData.outputs).length && - !Object.values(endingNodeData.outputs).includes(endingNodeData.name) - ) { - return res - .status(500) - .send( - `Output of ${endingNodeData.label} (${endingNodeData.id}) must be ${endingNodeData.label}, can't be an Output Prediction` - ) - } - - isStreamValid = isFlowValidForStream(nodes, endingNodeData) - - /*** Get Starting Nodes with Non-Directed Graph ***/ - const constructedObj = constructGraphs(nodes, edges, true) - const nonDirectedGraph = constructedObj.graph - const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) - - logger.debug(`[server]: Start building chatflow ${chatflowid}`) - /*** BFS to traverse from Starting Nodes to Ending Node ***/ - const reactFlowNodes = await buildLangchain( - startingNodeIds, - nodes, - graph, - depthQueue, - this.nodesPool.componentNodes, - incomingInput.question, - chatId, - this.AppDataSource, - incomingInput?.overrideConfig - ) - - const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId) - if (!nodeToExecute) return res.status(404).send(`Node ${endingNodeId} not found`) - - if (incomingInput.overrideConfig) - nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig) - const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question) - nodeToExecuteData = reactFlowNodeData - - const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id)) - this.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, incomingInput?.overrideConfig) + if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { + return res.status(500).send(`Ending node must be either a Chain or Agent`) } - const nodeInstanceFilePath = this.nodesPool.componentNodes[nodeToExecuteData.name].filePath as string - const nodeModule = await import(nodeInstanceFilePath) - const nodeInstance = new nodeModule.nodeClass() + if ( + endingNodeData.outputs && + Object.keys(endingNodeData.outputs).length && + !Object.values(endingNodeData.outputs).includes(endingNodeData.name) + ) { + return res + .status(500) + .send( + `Output of ${endingNodeData.label} (${endingNodeData.id}) must be ${endingNodeData.label}, can't be an Output Prediction` + ) + } - isStreamValid = isStreamValid && !isVectorStoreFaiss(nodeToExecuteData) - logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) + isStreamValid = isFlowValidForStream(nodes, endingNodeData) - if (nodeToExecuteData.instance) checkMemorySessionId(nodeToExecuteData.instance, chatId) + /*** Get Starting Nodes with Non-Directed Graph ***/ + const constructedObj = constructGraphs(nodes, edges, true) + const nonDirectedGraph = constructedObj.graph + const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) - const result = isStreamValid - ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { - chatHistory: incomingInput.history, - socketIO, - socketIOClientId: incomingInput.socketIOClientId, - logger, - appDataSource: this.AppDataSource, - databaseEntities - }) - : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { - chatHistory: incomingInput.history, - logger, - appDataSource: this.AppDataSource, - databaseEntities - }) + logger.debug(`[server]: Start building chatflow ${chatflowid}`) + /*** BFS to traverse from Starting Nodes to Ending Node ***/ + const reactFlowNodes = await buildLangchain( + startingNodeIds, + nodes, + graph, + depthQueue, + this.nodesPool.componentNodes, + incomingInput.question, + chatId, + this.AppDataSource, + incomingInput?.overrideConfig + ) - logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) - return res.json(result) + const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId) + if (!nodeToExecute) return res.status(404).send(`Node ${endingNodeId} not found`) + + if (incomingInput.overrideConfig) + nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig) + const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question) + nodeToExecuteData = reactFlowNodeData + + const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id)) + this.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, incomingInput?.overrideConfig) } + + const nodeInstanceFilePath = this.nodesPool.componentNodes[nodeToExecuteData.name].filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const nodeInstance = new nodeModule.nodeClass() + + isStreamValid = isStreamValid && !isVectorStoreFaiss(nodeToExecuteData) + logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) + + if (nodeToExecuteData.instance) checkMemorySessionId(nodeToExecuteData.instance, chatId) + + const result = isStreamValid + ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { + chatHistory: incomingInput.history, + socketIO, + socketIOClientId: incomingInput.socketIOClientId, + logger, + appDataSource: this.AppDataSource, + databaseEntities + }) + : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { + chatHistory: incomingInput.history, + logger, + appDataSource: this.AppDataSource, + databaseEntities + }) + + logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) + return res.json(result) } catch (e: any) { logger.error('[server]: Error:', e) return res.status(500).send(e.message) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 12dd6545..cf4a9c3f 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -791,7 +791,7 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod isValidChainOrAgent = whitelistAgents.includes(endingNodeData.name) } - return isChatOrLLMsExist && isValidChainOrAgent && !isVectorStoreFaiss(endingNodeData) && process.env.EXECUTION_MODE !== 'child' + return isChatOrLLMsExist && isValidChainOrAgent && !isVectorStoreFaiss(endingNodeData) } /** From 7543cc886cfc988adc3018789d8a11797d06057c Mon Sep 17 00:00:00 2001 From: Seif Date: Fri, 11 Aug 2023 22:30:28 -0700 Subject: [PATCH 374/398] Add Vectara Template --- .../chatflows/Vectara LLM Chain.json | 446 ++++++++++++++++++ 1 file changed, 446 insertions(+) create mode 100644 packages/server/marketplaces/chatflows/Vectara LLM Chain.json diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain.json new file mode 100644 index 00000000..451ca901 --- /dev/null +++ b/packages/server/marketplaces/chatflows/Vectara LLM Chain.json @@ -0,0 +1,446 @@ +{ + "description": "A simple LLM chain that uses Vectara to enable conversations with documents", + "nodes": [ + { + "width": 300, + "height": 408, + "id": "vectaraExisting_0", + "position": { "x": 438, "y": 214 }, + "type": "customNode", + "data": { + "id": "vectaraExisting_0", + "label": "Vectara Upsert Document", + "version": 1, + "name": "vectaraExisting", + "type": "Vectara", + "baseClasses": ["Vectara", "VectorStoreRetriever", "BaseRetriever"], + "category": "Vector Stores", + "description": "Upsert documents to Vectara", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["vectaraApi"], + "id": "vectaraExisting_0-input-credential-credential" + }, + { + "label": "Filter", + "name": "filter", + "type": "json", + "additionalParams": true, + "optional": true, + "id": "vectaraExisting_0-input-filter-json" + }, + { + "label": "Lambda", + "name": "lambda", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "vectaraExisting_0-input-lambda-number" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Defaults to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "vectaraExisting_0-input-topK-number" + } + ], + "inputAnchors": [ + { + "label": "Document", + "name": "document", + "type": "Document", + "list": true, + "id": "vectaraExisting_0-input-document-Document" + } + ], + "inputs": { + "document": ["{{pdfFile_0.data.instance}}"], + "filter": "", + "lambda": "", + "topK": "" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "vectaraExisting_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", + "name": "retriever", + "label": "Vectara Retriever", + "type": "Vectara | VectorStoreRetriever | BaseRetriever" + }, + { + "id": "vectaraExisting_0-output-vectorStore-Vectara|VectorStore", + "name": "vectorStore", + "label": "Vectara Vector Store", + "type": "Vectara | VectorStore" + } + ], + "default": "retriever" + } + ], + "outputs": { "output": "retriever" }, + "selected": false + }, + "selected": false, + "dragging": false, + "positionAbsolute": { "x": 438, "y": 214 } + }, + { + "width": 300, + "height": 509, + "id": "pdfFile_0", + "position": { "x": 68.3013317598369, "y": 199.60454731299677 }, + "type": "customNode", + "data": { + "id": "pdfFile_0", + "label": "Pdf File", + "version": 1, + "name": "pdfFile", + "type": "Document", + "baseClasses": ["Document"], + "category": "Document Loaders", + "description": "Load data from PDF files", + "inputParams": [ + { + "label": "Pdf File", + "name": "pdfFile", + "type": "file", + "fileType": ".pdf", + "id": "pdfFile_0-input-pdfFile-file" + }, + { + "label": "Usage", + "name": "usage", + "type": "options", + "options": [ + { "label": "One document per page", "name": "perPage" }, + { "label": "One document per file", "name": "perFile" } + ], + "default": "perPage", + "id": "pdfFile_0-input-usage-options" + }, + { + "label": "Use Legacy Build", + "name": "legacyBuild", + "type": "boolean", + "optional": true, + "additionalParams": true, + "id": "pdfFile_0-input-legacyBuild-boolean" + }, + { + "label": "Metadata", + "name": "metadata", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "pdfFile_0-input-metadata-json" + } + ], + "inputAnchors": [ + { + "label": "Text Splitter", + "name": "textSplitter", + "type": "TextSplitter", + "optional": true, + "id": "pdfFile_0-input-textSplitter-TextSplitter" + } + ], + "inputs": { + "textSplitter": "", + "usage": "perPage", + "legacyBuild": "", + "metadata": "" + }, + "outputAnchors": [ + { + "id": "pdfFile_0-output-pdfFile-Document", + "name": "pdfFile", + "label": "Document", + "type": "Document" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { "x": 68.3013317598369, "y": 199.60454731299677 }, + "dragging": false + }, + { + "width": 300, + "height": 525, + "id": "chatOpenAI_0", + "position": { "x": 804.3889791707068, "y": 195.11620799951592 }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "version": 1, + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["openAIApi"], + "id": "chatOpenAI_0-input-credential-credential" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { "label": "gpt-4", "name": "gpt-4" }, + { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { "label": "gpt-4-32k", "name": "gpt-4-32k" }, + { "label": "gpt-4-32k-0613", "name": "gpt-4-32k-0613" }, + { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, + { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" }, + { "label": "gpt-3.5-turbo-16k", "name": "gpt-3.5-turbo-16k" }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_0-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "step": 0.1, + "default": 0.9, + "optional": true, + "id": "chatOpenAI_0-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "gpt-3.5-turbo", + "temperature": "0.2", + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { "x": 804.3889791707068, "y": 195.11620799951592 }, + "dragging": false + }, + { + "width": 300, + "height": 481, + "id": "conversationalRetrievalQAChain_0", + "position": { "x": 1160.4877473512795, "y": 259.2799138505109 }, + "type": "customNode", + "data": { + "id": "conversationalRetrievalQAChain_0", + "label": "Conversational Retrieval QA Chain", + "version": 1, + "name": "conversationalRetrievalQAChain", + "type": "ConversationalRetrievalQAChain", + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], + "category": "Chains", + "description": "Document QA - built on RetrievalQAChain to provide a chat history component", + "inputParams": [ + { + "label": "Return Source Documents", + "name": "returnSourceDocuments", + "type": "boolean", + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" + }, + { + "label": "System Message", + "name": "systemMessagePrompt", + "type": "string", + "rows": 4, + "additionalParams": true, + "optional": true, + "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", + "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + }, + { + "label": "Chain Option", + "name": "chainOption", + "type": "options", + "options": [ + { + "label": "MapReduceDocumentsChain", + "name": "map_reduce", + "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" + }, + { + "label": "RefineDocumentsChain", + "name": "refine", + "description": "Suitable for QA tasks over a large number of documents." + }, + { + "label": "StuffDocumentsChain", + "name": "stuff", + "description": "Suitable for QA tasks over a small number of documents." + } + ], + "additionalParams": true, + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + }, + { + "label": "Vector Store Retriever", + "name": "vectorStoreRetriever", + "type": "BaseRetriever", + "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + }, + { + "label": "Memory", + "name": "memory", + "type": "BaseMemory", + "optional": true, + "description": "If left empty, a default BufferMemory will be used", + "id": "conversationalRetrievalQAChain_0-input-memory-BaseMemory" + } + ], + "inputs": { + "model": "{{chatOpenAI_0.data.instance}}", + "vectorStoreRetriever": "{{vectaraExisting_0.data.instance}}", + "memory": "", + "returnSourceDocuments": "", + "systemMessagePrompt": "", + "chainOption": "" + }, + "outputAnchors": [ + { + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", + "name": "conversationalRetrievalQAChain", + "label": "ConversationalRetrievalQAChain", + "type": "ConversationalRetrievalQAChain | BaseChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { "x": 1160.4877473512795, "y": 259.2799138505109 }, + "dragging": false + } + ], + "edges": [ + { + "source": "pdfFile_0", + "sourceHandle": "pdfFile_0-output-pdfFile-Document", + "target": "vectaraExisting_0", + "targetHandle": "vectaraExisting_0-input-document-Document", + "type": "buttonedge", + "id": "pdfFile_0-pdfFile_0-output-pdfFile-Document-vectaraExisting_0-vectaraExisting_0-input-document-Document", + "data": { "label": "" } + }, + { + "source": "vectaraExisting_0", + "sourceHandle": "vectaraExisting_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", + "target": "conversationalRetrievalQAChain_0", + "targetHandle": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", + "type": "buttonedge", + "id": "vectaraExisting_0-vectaraExisting_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", + "data": { "label": "" } + }, + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "target": "conversationalRetrievalQAChain_0", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "data": { "label": "" } + } + ] +} From 47989cfa33152605e424a0f659a5f765b8d209bf Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 12 Aug 2023 13:58:37 +0100 Subject: [PATCH 375/398] update webpageqna template --- .../marketplaces/chatflows/WebPage QnA.json | 632 +++++++++--------- 1 file changed, 316 insertions(+), 316 deletions(-) diff --git a/packages/server/marketplaces/chatflows/WebPage QnA.json b/packages/server/marketplaces/chatflows/WebPage QnA.json index b04ad5e2..8197c20a 100644 --- a/packages/server/marketplaces/chatflows/WebPage QnA.json +++ b/packages/server/marketplaces/chatflows/WebPage QnA.json @@ -3,18 +3,18 @@ "nodes": [ { "width": 300, - "height": 522, + "height": 523, "id": "chatOpenAI_0", "position": { - "x": 1509.7110310286191, - "y": -171.0099374102956 + "x": 1542.965468159417, + "y": -200.10756989974368 }, "type": "customNode", "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", - "name": "chatOpenAI", "version": 1, + "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -128,8 +128,8 @@ ], "inputAnchors": [], "inputs": { - "modelName": "gpt-3.5-turbo", - "temperature": "0", + "modelName": "gpt-3.5-turbo-16k", + "temperature": "0.9", "maxTokens": "", "topP": "", "frequencyPenalty": "", @@ -149,8 +149,8 @@ "selected": false }, "positionAbsolute": { - "x": 1509.7110310286191, - "y": -171.0099374102956 + "x": 1542.965468159417, + "y": -200.10756989974368 }, "selected": false, "dragging": false @@ -167,8 +167,8 @@ "data": { "id": "openAIEmbeddings_0", "label": "OpenAI Embeddings", - "name": "openAIEmbeddings", "version": 1, + "name": "openAIEmbeddings", "type": "OpenAIEmbeddings", "baseClasses": ["OpenAIEmbeddings", "Embeddings"], "category": "Embeddings", @@ -241,18 +241,291 @@ }, { "width": 300, - "height": 554, + "height": 376, + "id": "htmlToMarkdownTextSplitter_0", + "position": { + "x": 465.86869036784685, + "y": -17.41141011530891 + }, + "type": "customNode", + "data": { + "id": "htmlToMarkdownTextSplitter_0", + "label": "HtmlToMarkdown Text Splitter", + "version": 1, + "name": "htmlToMarkdownTextSplitter", + "type": "HtmlToMarkdownTextSplitter", + "baseClasses": [ + "HtmlToMarkdownTextSplitter", + "MarkdownTextSplitter", + "RecursiveCharacterTextSplitter", + "TextSplitter", + "BaseDocumentTransformer" + ], + "category": "Text Splitters", + "description": "Converts Html to Markdown and then split your content into documents based on the Markdown headers", + "inputParams": [ + { + "label": "Chunk Size", + "name": "chunkSize", + "type": "number", + "default": 1000, + "optional": true, + "id": "htmlToMarkdownTextSplitter_0-input-chunkSize-number" + }, + { + "label": "Chunk Overlap", + "name": "chunkOverlap", + "type": "number", + "optional": true, + "id": "htmlToMarkdownTextSplitter_0-input-chunkOverlap-number" + } + ], + "inputAnchors": [], + "inputs": { + "chunkSize": "4000", + "chunkOverlap": "" + }, + "outputAnchors": [ + { + "id": "htmlToMarkdownTextSplitter_0-output-htmlToMarkdownTextSplitter-HtmlToMarkdownTextSplitter|MarkdownTextSplitter|RecursiveCharacterTextSplitter|TextSplitter|BaseDocumentTransformer", + "name": "htmlToMarkdownTextSplitter", + "label": "HtmlToMarkdownTextSplitter", + "type": "HtmlToMarkdownTextSplitter | MarkdownTextSplitter | RecursiveCharacterTextSplitter | TextSplitter | BaseDocumentTransformer" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 465.86869036784685, + "y": -17.41141011530891 + }, + "dragging": false + }, + { + "width": 300, + "height": 479, + "id": "conversationalRetrievalQAChain_0", + "position": { + "x": 1882.5543981868987, + "y": 305.08959224761225 + }, + "type": "customNode", + "data": { + "id": "conversationalRetrievalQAChain_0", + "label": "Conversational Retrieval QA Chain", + "version": 1, + "name": "conversationalRetrievalQAChain", + "type": "ConversationalRetrievalQAChain", + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], + "category": "Chains", + "description": "Document QA - built on RetrievalQAChain to provide a chat history component", + "inputParams": [ + { + "label": "Return Source Documents", + "name": "returnSourceDocuments", + "type": "boolean", + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" + }, + { + "label": "System Message", + "name": "systemMessagePrompt", + "type": "string", + "rows": 4, + "additionalParams": true, + "optional": true, + "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", + "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + }, + { + "label": "Chain Option", + "name": "chainOption", + "type": "options", + "options": [ + { + "label": "MapReduceDocumentsChain", + "name": "map_reduce", + "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" + }, + { + "label": "RefineDocumentsChain", + "name": "refine", + "description": "Suitable for QA tasks over a large number of documents." + }, + { + "label": "StuffDocumentsChain", + "name": "stuff", + "description": "Suitable for QA tasks over a small number of documents." + } + ], + "additionalParams": true, + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + }, + { + "label": "Vector Store Retriever", + "name": "vectorStoreRetriever", + "type": "BaseRetriever", + "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + }, + { + "label": "Memory", + "name": "memory", + "type": "BaseMemory", + "optional": true, + "description": "If left empty, a default BufferMemory will be used", + "id": "conversationalRetrievalQAChain_0-input-memory-BaseMemory" + } + ], + "inputs": { + "model": "{{chatOpenAI_0.data.instance}}", + "vectorStoreRetriever": "{{pineconeUpsert_0.data.instance}}", + "memory": "{{motorheadMemory_0.data.instance}}", + "returnSourceDocuments": true, + "systemMessagePrompt": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given context. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Do not make up any information that is not in the context. Refuse to answer any question not about the info. Never break character.", + "chainOption": "" + }, + "outputAnchors": [ + { + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", + "name": "conversationalRetrievalQAChain", + "label": "ConversationalRetrievalQAChain", + "type": "ConversationalRetrievalQAChain | BaseChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1882.5543981868987, + "y": 305.08959224761225 + }, + "dragging": false + }, + { + "width": 300, + "height": 380, + "id": "cheerioWebScraper_0", + "position": { + "x": 831.9867292136466, + "y": -181.92350323746112 + }, + "type": "customNode", + "data": { + "id": "cheerioWebScraper_0", + "label": "Cheerio Web Scraper", + "version": 1, + "name": "cheerioWebScraper", + "type": "Document", + "baseClasses": ["Document"], + "category": "Document Loaders", + "description": "Load data from webpages", + "inputParams": [ + { + "label": "URL", + "name": "url", + "type": "string", + "id": "cheerioWebScraper_0-input-url-string" + }, + { + "label": "Get Relative Links Method", + "name": "relativeLinksMethod", + "type": "options", + "description": "Select a method to retrieve relative links", + "options": [ + { + "label": "Web Crawl", + "name": "webCrawl", + "description": "Crawl relative links from HTML URL" + }, + { + "label": "Scrape XML Sitemap", + "name": "scrapeXMLSitemap", + "description": "Scrape relative links from XML sitemap URL" + } + ], + "optional": true, + "additionalParams": true, + "id": "cheerioWebScraper_0-input-relativeLinksMethod-options" + }, + { + "label": "Get Relative Links Limit", + "name": "limit", + "type": "number", + "optional": true, + "additionalParams": true, + "description": "Only used when \"Get Relative Links Method\" is selected. Set 0 to retrieve all relative links, default limit is 10.", + "warning": "Retreiving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)", + "id": "cheerioWebScraper_0-input-limit-number" + }, + { + "label": "Metadata", + "name": "metadata", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "cheerioWebScraper_0-input-metadata-json" + } + ], + "inputAnchors": [ + { + "label": "Text Splitter", + "name": "textSplitter", + "type": "TextSplitter", + "optional": true, + "id": "cheerioWebScraper_0-input-textSplitter-TextSplitter" + } + ], + "inputs": { + "url": "https://flowiseai.com/", + "textSplitter": "{{htmlToMarkdownTextSplitter_0.data.instance}}", + "relativeLinksMethod": "", + "limit": "", + "metadata": "" + }, + "outputAnchors": [ + { + "id": "cheerioWebScraper_0-output-cheerioWebScraper-Document", + "name": "cheerioWebScraper", + "label": "Document", + "type": "Document" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 831.9867292136466, + "y": -181.92350323746112 + }, + "dragging": false + }, + { + "width": 300, + "height": 555, "id": "pineconeUpsert_0", "position": { - "x": 1178.0855412625938, - "y": -1.6626550640073674 + "x": 1179.6228496246993, + "y": -167.023255532671 }, "type": "customNode", "data": { "id": "pineconeUpsert_0", "label": "Pinecone Upsert Document", - "name": "pineconeUpsert", "version": 1, + "name": "pineconeUpsert", "type": "Pinecone", "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "category": "Vector Stores", @@ -342,298 +615,25 @@ }, "selected": false, "positionAbsolute": { - "x": 1178.0855412625938, - "y": -1.6626550640073674 + "x": 1179.6228496246993, + "y": -167.023255532671 }, "dragging": false }, { "width": 300, - "height": 379, - "id": "cheerioWebScraper_0", - "position": { - "x": 829.4409518246235, - "y": -168.78678247276423 - }, - "type": "customNode", - "data": { - "id": "cheerioWebScraper_0", - "label": "Cheerio Web Scraper", - "name": "cheerioWebScraper", - "version": 1, - "type": "Document", - "baseClasses": ["Document"], - "category": "Document Loaders", - "description": "Load data from webpages", - "inputParams": [ - { - "label": "URL", - "name": "url", - "type": "string", - "id": "cheerioWebScraper_0-input-url-string" - }, - { - "label": "Get Relative Links Method", - "name": "relativeLinksMethod", - "type": "options", - "description": "Select a method to retrieve relative links", - "options": [ - { - "label": "Web Crawl", - "name": "webCrawl", - "description": "Crawl relative links from HTML URL" - }, - { - "label": "Scrape XML Sitemap", - "name": "scrapeXMLSitemap", - "description": "Scrape relative links from XML sitemap URL" - } - ], - "optional": true, - "additionalParams": true, - "id": "cheerioWebScraper_0-input-relativeLinksMethod-options" - }, - { - "label": "Get Relative Links Limit", - "name": "limit", - "type": "number", - "optional": true, - "additionalParams": true, - "description": "Only used when \"Get Relative Links Method\" is selected. Set 0 to retrieve all relative links, default limit is 10.", - "warning": "Retreiving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)", - "id": "cheerioWebScraper_0-input-limit-number" - }, - { - "label": "Metadata", - "name": "metadata", - "type": "json", - "optional": true, - "additionalParams": true, - "id": "cheerioWebScraper_0-input-metadata-json" - } - ], - "inputAnchors": [ - { - "label": "Text Splitter", - "name": "textSplitter", - "type": "TextSplitter", - "optional": true, - "id": "cheerioWebScraper_0-input-textSplitter-TextSplitter" - } - ], - "inputs": { - "url": "https://www.itsjane.com", - "textSplitter": "{{htmlToMarkdownTextSplitter_0.data.instance}}", - "relativeLinksMethod": "", - "limit": "", - "metadata": "" - }, - "outputAnchors": [ - { - "id": "cheerioWebScraper_0-output-cheerioWebScraper-Document", - "name": "cheerioWebScraper", - "label": "Document", - "type": "Document" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 829.4409518246235, - "y": -168.78678247276423 - }, - "dragging": false - }, - { - "width": 300, - "height": 376, - "id": "htmlToMarkdownTextSplitter_0", - "position": { - "x": 443.00626484042334, - "y": 1.2942107707648631 - }, - "type": "customNode", - "data": { - "id": "htmlToMarkdownTextSplitter_0", - "label": "HtmlToMarkdown Text Splitter", - "name": "htmlToMarkdownTextSplitter", - "version": 1, - "type": "HtmlToMarkdownTextSplitter", - "baseClasses": [ - "HtmlToMarkdownTextSplitter", - "MarkdownTextSplitter", - "RecursiveCharacterTextSplitter", - "TextSplitter", - "BaseDocumentTransformer" - ], - "category": "Text Splitters", - "description": "Converts Html to Markdown and then split your content into documents based on the Markdown headers", - "inputParams": [ - { - "label": "Chunk Size", - "name": "chunkSize", - "type": "number", - "default": 1000, - "optional": true, - "id": "htmlToMarkdownTextSplitter_0-input-chunkSize-number" - }, - { - "label": "Chunk Overlap", - "name": "chunkOverlap", - "type": "number", - "optional": true, - "id": "htmlToMarkdownTextSplitter_0-input-chunkOverlap-number" - } - ], - "inputAnchors": [], - "inputs": { - "chunkSize": 1000, - "chunkOverlap": "" - }, - "outputAnchors": [ - { - "id": "htmlToMarkdownTextSplitter_0-output-htmlToMarkdownTextSplitter-HtmlToMarkdownTextSplitter|MarkdownTextSplitter|RecursiveCharacterTextSplitter|TextSplitter|BaseDocumentTransformer", - "name": "htmlToMarkdownTextSplitter", - "label": "HtmlToMarkdownTextSplitter", - "type": "HtmlToMarkdownTextSplitter | MarkdownTextSplitter | RecursiveCharacterTextSplitter | TextSplitter | BaseDocumentTransformer" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 443.00626484042334, - "y": 1.2942107707648631 - }, - "dragging": false - }, - { - "width": 300, - "height": 479, - "id": "conversationalRetrievalQAChain_0", - "position": { - "x": 1882.5543981868987, - "y": 305.08959224761225 - }, - "type": "customNode", - "data": { - "id": "conversationalRetrievalQAChain_0", - "label": "Conversational Retrieval QA Chain", - "name": "conversationalRetrievalQAChain", - "version": 1, - "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], - "category": "Chains", - "description": "Document QA - built on RetrievalQAChain to provide a chat history component", - "inputParams": [ - { - "label": "Return Source Documents", - "name": "returnSourceDocuments", - "type": "boolean", - "optional": true, - "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" - }, - { - "label": "System Message", - "name": "systemMessagePrompt", - "type": "string", - "rows": 4, - "additionalParams": true, - "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" - }, - { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], - "additionalParams": true, - "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" - } - ], - "inputAnchors": [ - { - "label": "Language Model", - "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" - }, - { - "label": "Vector Store Retriever", - "name": "vectorStoreRetriever", - "type": "BaseRetriever", - "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" - }, - { - "label": "Memory", - "name": "memory", - "type": "BaseMemory", - "optional": true, - "description": "If left empty, a default BufferMemory will be used", - "id": "conversationalRetrievalQAChain_0-input-memory-BaseMemory" - } - ], - "inputs": { - "model": "{{chatOpenAI_0.data.instance}}", - "vectorStoreRetriever": "{{pineconeUpsert_0.data.instance}}", - "memory": "{{motorheadMemory_0.data.instance}}", - "returnSourceDocuments": true, - "systemMessagePrompt": "", - "chainOption": "" - }, - "outputAnchors": [ - { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", - "name": "conversationalRetrievalQAChain", - "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 1882.5543981868987, - "y": 305.08959224761225 - }, - "dragging": false - }, - { - "width": 300, - "height": 426, + "height": 427, "id": "motorheadMemory_0", "position": { - "x": 1515.4202055109095, - "y": 539.7912360964175 + "x": 1202.1545938923578, + "y": 425.69055061366237 }, "type": "customNode", "data": { "id": "motorheadMemory_0", "label": "Motorhead Memory", - "name": "motorheadMemory", "version": 1, + "name": "motorheadMemory", "type": "MotorheadMemory", "baseClasses": ["MotorheadMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", @@ -660,7 +660,7 @@ "label": "Session Id", "name": "sessionId", "type": "string", - "description": "if empty, chatId will be used automatically", + "description": "If not specified, the first CHAT_MESSAGE_ID will be used as sessionId", "default": "", "additionalParams": true, "optional": true, @@ -694,31 +694,20 @@ }, "selected": false, "positionAbsolute": { - "x": 1515.4202055109095, - "y": 539.7912360964175 + "x": 1202.1545938923578, + "y": 425.69055061366237 }, "dragging": false } ], "edges": [ { - "source": "openAIEmbeddings_0", - "sourceHandle": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", - "target": "pineconeUpsert_0", - "targetHandle": "pineconeUpsert_0-input-embeddings-Embeddings", + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "target": "conversationalRetrievalQAChain_0", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "type": "buttonedge", - "id": "openAIEmbeddings_0-openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings-pineconeUpsert_0-pineconeUpsert_0-input-embeddings-Embeddings", - "data": { - "label": "" - } - }, - { - "source": "cheerioWebScraper_0", - "sourceHandle": "cheerioWebScraper_0-output-cheerioWebScraper-Document", - "target": "pineconeUpsert_0", - "targetHandle": "pineconeUpsert_0-input-document-Document", - "type": "buttonedge", - "id": "cheerioWebScraper_0-cheerioWebScraper_0-output-cheerioWebScraper-Document-pineconeUpsert_0-pineconeUpsert_0-input-document-Document", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", "data": { "label": "" } @@ -735,12 +724,23 @@ } }, { - "source": "chatOpenAI_0", - "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", - "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "source": "cheerioWebScraper_0", + "sourceHandle": "cheerioWebScraper_0-output-cheerioWebScraper-Document", + "target": "pineconeUpsert_0", + "targetHandle": "pineconeUpsert_0-input-document-Document", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "id": "cheerioWebScraper_0-cheerioWebScraper_0-output-cheerioWebScraper-Document-pineconeUpsert_0-pineconeUpsert_0-input-document-Document", + "data": { + "label": "" + } + }, + { + "source": "openAIEmbeddings_0", + "sourceHandle": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "target": "pineconeUpsert_0", + "targetHandle": "pineconeUpsert_0-input-embeddings-Embeddings", + "type": "buttonedge", + "id": "openAIEmbeddings_0-openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings-pineconeUpsert_0-pineconeUpsert_0-input-embeddings-Embeddings", "data": { "label": "" } From 137226e84cb0b74ed8658d0dcb4bd8747c53e9a1 Mon Sep 17 00:00:00 2001 From: Seif Date: Sat, 12 Aug 2023 10:38:40 -0700 Subject: [PATCH 376/398] Upload existing templates --- .../chatflows/Vectara LLM Chain Existing.json | 342 ++++++++++++++++++ ...ain.json => Vectara LLM Chain Upload.json} | 2 +- 2 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 packages/server/marketplaces/chatflows/Vectara LLM Chain Existing.json rename packages/server/marketplaces/chatflows/{Vectara LLM Chain.json => Vectara LLM Chain Upload.json} (99%) diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain Existing.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain Existing.json new file mode 100644 index 00000000..5ed1c4ba --- /dev/null +++ b/packages/server/marketplaces/chatflows/Vectara LLM Chain Existing.json @@ -0,0 +1,342 @@ +{ + "description": "A simple LLM chain that uses Vectara to enable conversations with existing documents", + "nodes": [ + { + "width": 300, + "height": 357, + "id": "vectaraExistingIndex_0", + "position": { "x": 465.7428816308626, "y": 241.80234144183515 }, + "type": "customNode", + "data": { + "id": "vectaraExistingIndex_0", + "label": "Vectara Load Existing Index", + "version": 1, + "name": "vectaraExistingIndex", + "type": "Vectara", + "baseClasses": ["Vectara", "VectorStoreRetriever", "BaseRetriever"], + "category": "Vector Stores", + "description": "Load existing index from Vectara (i.e: Document has been upserted)", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["vectaraApi"], + "id": "vectaraExistingIndex_0-input-credential-credential" + }, + { + "label": "Vectara Metadata Filter", + "name": "filter", + "type": "json", + "additionalParams": true, + "optional": true, + "id": "vectaraExistingIndex_0-input-filter-json" + }, + { + "label": "Lambda", + "name": "lambda", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "vectaraExistingIndex_0-input-lambda-number" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Defaults to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "vectaraExistingIndex_0-input-topK-number" + } + ], + "inputAnchors": [], + "inputs": { "filter": "", "lambda": "", "topK": "" }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "vectaraExistingIndex_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", + "name": "retriever", + "label": "Vectara Retriever", + "type": "Vectara | VectorStoreRetriever | BaseRetriever" + }, + { + "id": "vectaraExistingIndex_0-output-vectorStore-Vectara|VectorStore", + "name": "vectorStore", + "label": "Vectara Vector Store", + "type": "Vectara | VectorStore" + } + ], + "default": "retriever" + } + ], + "outputs": { "output": "retriever" }, + "selected": false + }, + "selected": false, + "dragging": false, + "positionAbsolute": { "x": 465.7428816308626, "y": 241.80234144183515 } + }, + { + "width": 300, + "height": 525, + "id": "chatOpenAI_0", + "position": { "x": 899.9419397673153, "y": 202.43889985803958 }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "version": 1, + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["openAIApi"], + "id": "chatOpenAI_0-input-credential-credential" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { "label": "gpt-4", "name": "gpt-4" }, + { "label": "gpt-4-0613", "name": "gpt-4-0613" }, + { "label": "gpt-4-32k", "name": "gpt-4-32k" }, + { "label": "gpt-4-32k-0613", "name": "gpt-4-32k-0613" }, + { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, + { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" }, + { "label": "gpt-3.5-turbo-16k", "name": "gpt-3.5-turbo-16k" }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_0-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "step": 0.1, + "default": 0.9, + "optional": true, + "id": "chatOpenAI_0-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "gpt-3.5-turbo", + "temperature": 0.9, + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { "x": 899.9419397673153, "y": 202.43889985803958 }, + "dragging": false + }, + { + "width": 300, + "height": 481, + "id": "conversationalRetrievalQAChain_0", + "position": { "x": 1290.3534883720931, "y": 376.49186046511625 }, + "type": "customNode", + "data": { + "id": "conversationalRetrievalQAChain_0", + "label": "Conversational Retrieval QA Chain", + "version": 1, + "name": "conversationalRetrievalQAChain", + "type": "ConversationalRetrievalQAChain", + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], + "category": "Chains", + "description": "Document QA - built on RetrievalQAChain to provide a chat history component", + "inputParams": [ + { + "label": "Return Source Documents", + "name": "returnSourceDocuments", + "type": "boolean", + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" + }, + { + "label": "System Message", + "name": "systemMessagePrompt", + "type": "string", + "rows": 4, + "additionalParams": true, + "optional": true, + "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", + "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" + }, + { + "label": "Chain Option", + "name": "chainOption", + "type": "options", + "options": [ + { + "label": "MapReduceDocumentsChain", + "name": "map_reduce", + "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" + }, + { + "label": "RefineDocumentsChain", + "name": "refine", + "description": "Suitable for QA tasks over a large number of documents." + }, + { + "label": "StuffDocumentsChain", + "name": "stuff", + "description": "Suitable for QA tasks over a small number of documents." + } + ], + "additionalParams": true, + "optional": true, + "id": "conversationalRetrievalQAChain_0-input-chainOption-options" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" + }, + { + "label": "Vector Store Retriever", + "name": "vectorStoreRetriever", + "type": "BaseRetriever", + "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + }, + { + "label": "Memory", + "name": "memory", + "type": "BaseMemory", + "optional": true, + "description": "If left empty, a default BufferMemory will be used", + "id": "conversationalRetrievalQAChain_0-input-memory-BaseMemory" + } + ], + "inputs": { + "model": "{{chatOpenAI_0.data.instance}}", + "vectorStoreRetriever": "{{vectaraExistingIndex_0.data.instance}}", + "memory": "", + "returnSourceDocuments": "", + "systemMessagePrompt": "", + "chainOption": "" + }, + "outputAnchors": [ + { + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", + "name": "conversationalRetrievalQAChain", + "label": "ConversationalRetrievalQAChain", + "type": "ConversationalRetrievalQAChain | BaseChain" + } + ], + "outputs": {}, + "selected": false + }, + "positionAbsolute": { "x": 1290.3534883720931, "y": 376.49186046511625 }, + "selected": false + } + ], + "edges": [ + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "target": "conversationalRetrievalQAChain_0", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "data": { "label": "" } + }, + { + "source": "vectaraExistingIndex_0", + "sourceHandle": "vectaraExistingIndex_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", + "target": "conversationalRetrievalQAChain_0", + "targetHandle": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", + "type": "buttonedge", + "id": "vectaraExistingIndex_0-vectaraExistingIndex_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", + "data": { "label": "" } + } + ] +} diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json similarity index 99% rename from packages/server/marketplaces/chatflows/Vectara LLM Chain.json rename to packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json index 451ca901..2146aa12 100644 --- a/packages/server/marketplaces/chatflows/Vectara LLM Chain.json +++ b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json @@ -1,5 +1,5 @@ { - "description": "A simple LLM chain that uses Vectara to enable conversations with documents", + "description": "A simple LLM chain that uses Vectara to enable conversations with uploaded documents", "nodes": [ { "width": 300, From b93f0d37f18de3fd2cc3fb5c8e43ce3b00b1b6d2 Mon Sep 17 00:00:00 2001 From: Seif Date: Sun, 13 Aug 2023 10:00:54 -0700 Subject: [PATCH 377/398] Delete extra template --- .../chatflows/Vectara LLM Chain Existing.json | 342 ------------------ 1 file changed, 342 deletions(-) delete mode 100644 packages/server/marketplaces/chatflows/Vectara LLM Chain Existing.json diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain Existing.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain Existing.json deleted file mode 100644 index 5ed1c4ba..00000000 --- a/packages/server/marketplaces/chatflows/Vectara LLM Chain Existing.json +++ /dev/null @@ -1,342 +0,0 @@ -{ - "description": "A simple LLM chain that uses Vectara to enable conversations with existing documents", - "nodes": [ - { - "width": 300, - "height": 357, - "id": "vectaraExistingIndex_0", - "position": { "x": 465.7428816308626, "y": 241.80234144183515 }, - "type": "customNode", - "data": { - "id": "vectaraExistingIndex_0", - "label": "Vectara Load Existing Index", - "version": 1, - "name": "vectaraExistingIndex", - "type": "Vectara", - "baseClasses": ["Vectara", "VectorStoreRetriever", "BaseRetriever"], - "category": "Vector Stores", - "description": "Load existing index from Vectara (i.e: Document has been upserted)", - "inputParams": [ - { - "label": "Connect Credential", - "name": "credential", - "type": "credential", - "credentialNames": ["vectaraApi"], - "id": "vectaraExistingIndex_0-input-credential-credential" - }, - { - "label": "Vectara Metadata Filter", - "name": "filter", - "type": "json", - "additionalParams": true, - "optional": true, - "id": "vectaraExistingIndex_0-input-filter-json" - }, - { - "label": "Lambda", - "name": "lambda", - "type": "number", - "additionalParams": true, - "optional": true, - "id": "vectaraExistingIndex_0-input-lambda-number" - }, - { - "label": "Top K", - "name": "topK", - "description": "Number of top results to fetch. Defaults to 4", - "placeholder": "4", - "type": "number", - "additionalParams": true, - "optional": true, - "id": "vectaraExistingIndex_0-input-topK-number" - } - ], - "inputAnchors": [], - "inputs": { "filter": "", "lambda": "", "topK": "" }, - "outputAnchors": [ - { - "name": "output", - "label": "Output", - "type": "options", - "options": [ - { - "id": "vectaraExistingIndex_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", - "name": "retriever", - "label": "Vectara Retriever", - "type": "Vectara | VectorStoreRetriever | BaseRetriever" - }, - { - "id": "vectaraExistingIndex_0-output-vectorStore-Vectara|VectorStore", - "name": "vectorStore", - "label": "Vectara Vector Store", - "type": "Vectara | VectorStore" - } - ], - "default": "retriever" - } - ], - "outputs": { "output": "retriever" }, - "selected": false - }, - "selected": false, - "dragging": false, - "positionAbsolute": { "x": 465.7428816308626, "y": 241.80234144183515 } - }, - { - "width": 300, - "height": 525, - "id": "chatOpenAI_0", - "position": { "x": 899.9419397673153, "y": 202.43889985803958 }, - "type": "customNode", - "data": { - "id": "chatOpenAI_0", - "label": "ChatOpenAI", - "version": 1, - "name": "chatOpenAI", - "type": "ChatOpenAI", - "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], - "category": "Chat Models", - "description": "Wrapper around OpenAI large language models that use the Chat endpoint", - "inputParams": [ - { - "label": "Connect Credential", - "name": "credential", - "type": "credential", - "credentialNames": ["openAIApi"], - "id": "chatOpenAI_0-input-credential-credential" - }, - { - "label": "Model Name", - "name": "modelName", - "type": "options", - "options": [ - { "label": "gpt-4", "name": "gpt-4" }, - { "label": "gpt-4-0613", "name": "gpt-4-0613" }, - { "label": "gpt-4-32k", "name": "gpt-4-32k" }, - { "label": "gpt-4-32k-0613", "name": "gpt-4-32k-0613" }, - { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, - { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" }, - { "label": "gpt-3.5-turbo-16k", "name": "gpt-3.5-turbo-16k" }, - { - "label": "gpt-3.5-turbo-16k-0613", - "name": "gpt-3.5-turbo-16k-0613" - } - ], - "default": "gpt-3.5-turbo", - "optional": true, - "id": "chatOpenAI_0-input-modelName-options" - }, - { - "label": "Temperature", - "name": "temperature", - "type": "number", - "step": 0.1, - "default": 0.9, - "optional": true, - "id": "chatOpenAI_0-input-temperature-number" - }, - { - "label": "Max Tokens", - "name": "maxTokens", - "type": "number", - "step": 1, - "optional": true, - "additionalParams": true, - "id": "chatOpenAI_0-input-maxTokens-number" - }, - { - "label": "Top Probability", - "name": "topP", - "type": "number", - "step": 0.1, - "optional": true, - "additionalParams": true, - "id": "chatOpenAI_0-input-topP-number" - }, - { - "label": "Frequency Penalty", - "name": "frequencyPenalty", - "type": "number", - "step": 0.1, - "optional": true, - "additionalParams": true, - "id": "chatOpenAI_0-input-frequencyPenalty-number" - }, - { - "label": "Presence Penalty", - "name": "presencePenalty", - "type": "number", - "step": 0.1, - "optional": true, - "additionalParams": true, - "id": "chatOpenAI_0-input-presencePenalty-number" - }, - { - "label": "Timeout", - "name": "timeout", - "type": "number", - "step": 1, - "optional": true, - "additionalParams": true, - "id": "chatOpenAI_0-input-timeout-number" - }, - { - "label": "BasePath", - "name": "basepath", - "type": "string", - "optional": true, - "additionalParams": true, - "id": "chatOpenAI_0-input-basepath-string" - } - ], - "inputAnchors": [], - "inputs": { - "modelName": "gpt-3.5-turbo", - "temperature": 0.9, - "maxTokens": "", - "topP": "", - "frequencyPenalty": "", - "presencePenalty": "", - "timeout": "", - "basepath": "" - }, - "outputAnchors": [ - { - "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", - "name": "chatOpenAI", - "label": "ChatOpenAI", - "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { "x": 899.9419397673153, "y": 202.43889985803958 }, - "dragging": false - }, - { - "width": 300, - "height": 481, - "id": "conversationalRetrievalQAChain_0", - "position": { "x": 1290.3534883720931, "y": 376.49186046511625 }, - "type": "customNode", - "data": { - "id": "conversationalRetrievalQAChain_0", - "label": "Conversational Retrieval QA Chain", - "version": 1, - "name": "conversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], - "category": "Chains", - "description": "Document QA - built on RetrievalQAChain to provide a chat history component", - "inputParams": [ - { - "label": "Return Source Documents", - "name": "returnSourceDocuments", - "type": "boolean", - "optional": true, - "id": "conversationalRetrievalQAChain_0-input-returnSourceDocuments-boolean" - }, - { - "label": "System Message", - "name": "systemMessagePrompt", - "type": "string", - "rows": 4, - "additionalParams": true, - "optional": true, - "placeholder": "I want you to act as a document that I am having a conversation with. Your name is \"AI Assistant\". You will provide me with answers from the given info. If the answer is not included, say exactly \"Hmm, I am not sure.\" and stop after that. Refuse to answer any question not about the info. Never break character.", - "id": "conversationalRetrievalQAChain_0-input-systemMessagePrompt-string" - }, - { - "label": "Chain Option", - "name": "chainOption", - "type": "options", - "options": [ - { - "label": "MapReduceDocumentsChain", - "name": "map_reduce", - "description": "Suitable for QA tasks over larger documents and can run the preprocessing step in parallel, reducing the running time" - }, - { - "label": "RefineDocumentsChain", - "name": "refine", - "description": "Suitable for QA tasks over a large number of documents." - }, - { - "label": "StuffDocumentsChain", - "name": "stuff", - "description": "Suitable for QA tasks over a small number of documents." - } - ], - "additionalParams": true, - "optional": true, - "id": "conversationalRetrievalQAChain_0-input-chainOption-options" - } - ], - "inputAnchors": [ - { - "label": "Language Model", - "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel" - }, - { - "label": "Vector Store Retriever", - "name": "vectorStoreRetriever", - "type": "BaseRetriever", - "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" - }, - { - "label": "Memory", - "name": "memory", - "type": "BaseMemory", - "optional": true, - "description": "If left empty, a default BufferMemory will be used", - "id": "conversationalRetrievalQAChain_0-input-memory-BaseMemory" - } - ], - "inputs": { - "model": "{{chatOpenAI_0.data.instance}}", - "vectorStoreRetriever": "{{vectaraExistingIndex_0.data.instance}}", - "memory": "", - "returnSourceDocuments": "", - "systemMessagePrompt": "", - "chainOption": "" - }, - "outputAnchors": [ - { - "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain", - "name": "conversationalRetrievalQAChain", - "label": "ConversationalRetrievalQAChain", - "type": "ConversationalRetrievalQAChain | BaseChain" - } - ], - "outputs": {}, - "selected": false - }, - "positionAbsolute": { "x": 1290.3534883720931, "y": 376.49186046511625 }, - "selected": false - } - ], - "edges": [ - { - "source": "chatOpenAI_0", - "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", - "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", - "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", - "data": { "label": "" } - }, - { - "source": "vectaraExistingIndex_0", - "sourceHandle": "vectaraExistingIndex_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", - "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", - "type": "buttonedge", - "id": "vectaraExistingIndex_0-vectaraExistingIndex_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", - "data": { "label": "" } - } - ] -} From ff7ee41758dd4fc24893cadb8c7551706f111529 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Mon, 14 Aug 2023 00:31:07 +0200 Subject: [PATCH 378/398] Added postgres, cockroachdb, mssql, mysql, mariadb, mongodb and oracle to SqlDatabaseChain_Chains --- .../SqlDatabaseChain/SqlDatabaseChain.ts | 68 +++++++++++++++---- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts index 9416371b..6bfc2f8a 100644 --- a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts +++ b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts @@ -5,6 +5,9 @@ import { DataSource } from 'typeorm' import { SqlDatabase } from 'langchain/sql_db' import { BaseLanguageModel } from 'langchain/base_language' import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' +import { DataSourceOptions } from 'typeorm/data-source' + +type DatabaseType = 'sqlite' | 'postgres' | 'cockroachdb' | 'mssql' | 'mysql' | 'mariadb' | 'mongodb' | 'oracle' class SqlDatabaseChain_Chains implements INode { label: string @@ -38,36 +41,64 @@ class SqlDatabaseChain_Chains implements INode { type: 'options', options: [ { - label: 'SQlite', + label: 'SQLite', name: 'sqlite' + }, + { + label: 'PostgreSQL', + name: 'postgres' + }, + { + label: 'CockroachDB', + name: 'cockroachdb' + }, + { + label: 'MSSQL', + name: 'mssql' + }, + { + label: 'MySQL', + name: 'mysql' + }, + { + label: 'MariaDB', + name: 'mariadb' + }, + { + label: 'MongoDB', + name: 'mongodb' + }, + { + label: 'Oracle', + name: 'oracle' } ], default: 'sqlite' }, { - label: 'Database File Path', - name: 'dbFilePath', + label: 'Connection string or file path (sqlite only)', + name: 'url', type: 'string', - placeholder: 'C:/Users/chinook.db' + placeholder: '1270.0.0.1:5432/chinook' } ] } async init(nodeData: INodeData): Promise { - const databaseType = nodeData.inputs?.database as 'sqlite' + const databaseType = nodeData.inputs?.database as DatabaseType const model = nodeData.inputs?.model as BaseLanguageModel - const dbFilePath = nodeData.inputs?.dbFilePath + const url = nodeData.inputs?.url - const chain = await getSQLDBChain(databaseType, dbFilePath, model) + const chain = await getSQLDBChain(databaseType, url, model) return chain } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const databaseType = nodeData.inputs?.database as 'sqlite' + const databaseType = nodeData.inputs?.database as DatabaseType const model = nodeData.inputs?.model as BaseLanguageModel - const dbFilePath = nodeData.inputs?.dbFilePath + const url = nodeData.inputs?.url - const chain = await getSQLDBChain(databaseType, dbFilePath, model) + const chain = await getSQLDBChain(databaseType, url, model) const loggerHandler = new ConsoleCallbackHandler(options.logger) if (options.socketIO && options.socketIOClientId) { @@ -81,11 +112,18 @@ class SqlDatabaseChain_Chains implements INode { } } -const getSQLDBChain = async (databaseType: 'sqlite', dbFilePath: string, llm: BaseLanguageModel) => { - const datasource = new DataSource({ - type: databaseType, - database: dbFilePath - }) +const getSQLDBChain = async (databaseType: DatabaseType, url: string, llm: BaseLanguageModel) => { + const datasource = new DataSource( + databaseType === 'sqlite' + ? { + type: databaseType, + database: url + } + : ({ + type: databaseType, + url: url + } as DataSourceOptions) + ) const db = await SqlDatabase.fromDataSourceParams({ appDataSource: datasource From 9989f6f70acf469270d156196759adbdfc89c011 Mon Sep 17 00:00:00 2001 From: Seif Date: Sun, 13 Aug 2023 19:40:33 -0700 Subject: [PATCH 379/398] Fix filters --- .../Vectara_Existing/Vectara_Existing.ts | 11 +++-------- .../vectorstores/Vectara_Upsert/Vectara_Upsert.ts | 13 ++++--------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts b/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts index 725a9e32..96fed4ef 100644 --- a/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts +++ b/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts @@ -34,7 +34,7 @@ class VectaraExisting_VectorStores implements INode { { label: 'Vectara Metadata Filter', name: 'filter', - type: 'json', + type: 'string', additionalParams: true, optional: true }, @@ -74,7 +74,7 @@ class VectaraExisting_VectorStores implements INode { const customerId = getCredentialParam('customerID', credentialData, nodeData) const corpusId = getCredentialParam('corpusID', credentialData, nodeData) - const vectaraMetadatafilter = nodeData.inputs?.filter as VectaraFilter + const vectaraMetadataFilter = nodeData.inputs?.filter as string const lambda = nodeData.inputs?.lambda as number const output = nodeData.outputs?.output as string const topK = nodeData.inputs?.topK as string @@ -87,12 +87,7 @@ class VectaraExisting_VectorStores implements INode { } const vectaraFilter: VectaraFilter = {} - - if (vectaraMetadatafilter) { - const metadatafilter = typeof vectaraMetadatafilter === 'object' ? vectaraMetadatafilter : JSON.parse(vectaraMetadatafilter) - vectaraFilter.filter = metadatafilter - } - + if (vectaraMetadataFilter) vectaraFilter.filter = vectaraMetadataFilter if (lambda) vectaraFilter.lambda = lambda const vectorStore = new VectaraStore(vectaraArgs) diff --git a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts index bc236060..ecbe5569 100644 --- a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts +++ b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts @@ -41,9 +41,9 @@ class VectaraExisting_VectorStores implements INode { list: true }, { - label: 'Filter', + label: 'Vectara Metadata Filter', name: 'filter', - type: 'json', + type: 'string', additionalParams: true, optional: true }, @@ -85,7 +85,7 @@ class VectaraExisting_VectorStores implements INode { const docs = nodeData.inputs?.document as Document[] const embeddings = {} as Embeddings - const vectaraMetadatafilter = nodeData.inputs?.filter as VectaraFilter + const vectaraMetadataFilter = nodeData.inputs?.filter as string const lambda = nodeData.inputs?.lambda as number const output = nodeData.outputs?.output as string const topK = nodeData.inputs?.topK as string @@ -98,12 +98,7 @@ class VectaraExisting_VectorStores implements INode { } const vectaraFilter: VectaraFilter = {} - - if (vectaraMetadatafilter) { - const metadatafilter = typeof vectaraMetadatafilter === 'object' ? vectaraMetadatafilter : JSON.parse(vectaraMetadatafilter) - vectaraFilter.filter = metadatafilter - } - + if (vectaraMetadataFilter) vectaraFilter.filter = vectaraMetadataFilter if (lambda) vectaraFilter.lambda = lambda const flattenDocs = docs && docs.length ? flatten(docs) : [] From 350a265cace0c49336ebfe810187b612be24beaf Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Mon, 14 Aug 2023 17:07:56 +0800 Subject: [PATCH 380/398] fix vectara upsert naming --- .../nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts index bc236060..c92c62a2 100644 --- a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts +++ b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts @@ -5,7 +5,7 @@ import { VectaraStore, VectaraLibArgs, VectaraFilter } from 'langchain/vectorsto import { Document } from 'langchain/document' import { flatten } from 'lodash' -class VectaraExisting_VectorStores implements INode { +class VectaraUpsert_VectorStores implements INode { label: string name: string version: number @@ -20,7 +20,7 @@ class VectaraExisting_VectorStores implements INode { constructor() { this.label = 'Vectara Upsert Document' - this.name = 'vectaraExisting' + this.name = 'vectaraUpsert' this.version = 1.0 this.type = 'Vectara' this.icon = 'vectara.png' @@ -125,4 +125,4 @@ class VectaraExisting_VectorStores implements INode { } } -module.exports = { nodeClass: VectaraExisting_VectorStores } +module.exports = { nodeClass: VectaraUpsert_VectorStores } From 1e90db79890b53bb1b81747acb3021d87c917765 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Mon, 14 Aug 2023 16:18:10 +0200 Subject: [PATCH 381/398] Remove unsupported databases --- .../SqlDatabaseChain/SqlDatabaseChain.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts index 6bfc2f8a..2a0c71cf 100644 --- a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts +++ b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts @@ -7,7 +7,7 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' import { DataSourceOptions } from 'typeorm/data-source' -type DatabaseType = 'sqlite' | 'postgres' | 'cockroachdb' | 'mssql' | 'mysql' | 'mariadb' | 'mongodb' | 'oracle' +type DatabaseType = 'sqlite' | 'postgres' | 'mssql' | 'mysql' class SqlDatabaseChain_Chains implements INode { label: string @@ -48,10 +48,6 @@ class SqlDatabaseChain_Chains implements INode { label: 'PostgreSQL', name: 'postgres' }, - { - label: 'CockroachDB', - name: 'cockroachdb' - }, { label: 'MSSQL', name: 'mssql' @@ -59,18 +55,6 @@ class SqlDatabaseChain_Chains implements INode { { label: 'MySQL', name: 'mysql' - }, - { - label: 'MariaDB', - name: 'mariadb' - }, - { - label: 'MongoDB', - name: 'mongodb' - }, - { - label: 'Oracle', - name: 'oracle' } ], default: 'sqlite' From cb5fbb90400146610888ed8bd6ee584f301d79fe Mon Sep 17 00:00:00 2001 From: Seif Date: Mon, 14 Aug 2023 11:41:51 -0700 Subject: [PATCH 382/398] Add description for Vectara filter --- .../nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts | 2 ++ .../nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts b/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts index 96fed4ef..f344338a 100644 --- a/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts +++ b/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts @@ -34,6 +34,8 @@ class VectaraExisting_VectorStores implements INode { { label: 'Vectara Metadata Filter', name: 'filter', + description: + 'Filter to apply to Vectara metadata. Refer to the documentation on how to use Vectara filters with Flowise.', type: 'string', additionalParams: true, optional: true diff --git a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts index ecbe5569..d80cad87 100644 --- a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts +++ b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts @@ -43,6 +43,8 @@ class VectaraExisting_VectorStores implements INode { { label: 'Vectara Metadata Filter', name: 'filter', + description: + 'Filter to apply to Vectara metadata. Refer to the documentation on how to use Vectara filters with Flowise.', type: 'string', additionalParams: true, optional: true From f6933b592d934fc2530ddd621066c0a7a30fea3c Mon Sep 17 00:00:00 2001 From: rkeshwani Date: Tue, 15 Aug 2023 00:13:38 +0000 Subject: [PATCH 383/398] Added additional file extensions and removed abstracted inputs. --- .../nodes/documentloaders/Folder/Folder.ts | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/packages/components/nodes/documentloaders/Folder/Folder.ts b/packages/components/nodes/documentloaders/Folder/Folder.ts index 4ffdce91..7b90d9ed 100644 --- a/packages/components/nodes/documentloaders/Folder/Folder.ts +++ b/packages/components/nodes/documentloaders/Folder/Folder.ts @@ -47,13 +47,6 @@ class Folder_DocumentLoaders implements INode { optional: true, additionalParams: true }, - { - label: 'Additional File Loaders', - name: 'additionalLoaders', - type: 'json', - optional: true, - additionalParams: true - } ] } @@ -61,8 +54,6 @@ class Folder_DocumentLoaders implements INode { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const folderPath = nodeData.inputs?.folderPath as string const metadata = nodeData.inputs?.metadata - const additionalLoaders = nodeData.inputs?.additionalLoaders - const parsedLoaders = additionalLoaders ? ( typeof metadata === 'object' ? additionalLoaders: JSON.parse( additionalLoaders ) ) : [] const loader = new DirectoryLoader(folderPath, { '.json': (path) => new JSONLoader(path), @@ -71,7 +62,39 @@ class Folder_DocumentLoaders implements INode { '.docx': (path) => new DocxLoader(path), // @ts-ignore '.pdf': (path) => new PDFLoader(path, { pdfjs: () => import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js') }), - ...parsedLoaders + '.aspx': (path) => new TextLoader(path), + '.asp': (path) => new TextLoader(path), + '.cpp': (path) => new TextLoader(path), // C++ + '.c': (path) => new TextLoader(path), + '.cs': (path) => new TextLoader(path), + '.css': (path) => new TextLoader(path), + '.go': (path) => new TextLoader(path), // Go + '.h': (path) => new TextLoader(path), // C++ Header files + '.java': (path) => new TextLoader(path), // Java + '.js': (path) => new TextLoader(path), // JavaScript + '.less': (path) => new TextLoader(path), // Less files + '.ts': (path) => new TextLoader(path), // TypeScript + '.php': (path) => new TextLoader(path), // PHP + '.proto': (path) => new TextLoader(path), // Protocol Buffers + '.python': (path) => new TextLoader(path), // Python + '.py': (path) => new TextLoader(path), // Python + '.rst': (path) => new TextLoader(path), // reStructuredText + '.ruby': (path) => new TextLoader(path), // Ruby + '.rb': (path) => new TextLoader(path), // Ruby + '.rs': (path) => new TextLoader(path), // Rust + '.scala': (path) => new TextLoader(path), // Scala + '.sc': (path) => new TextLoader(path), // Scala + '.scss': (path) => new TextLoader(path),// Sass + '.sol': (path) => new TextLoader(path), // Solidity + '.sql': (path) => new TextLoader(path),//SQL + '.swift': (path) => new TextLoader(path), // Swift + '.markdown': (path) => new TextLoader(path), // Markdown + '.md': (path) => new TextLoader(path), // Markdown + '.tex': (path) => new TextLoader(path), // LaTeX + '.ltx': (path) => new TextLoader(path), // LaTeX + '.html': (path) => new TextLoader(path), // HTML + '.vb': (path) => new TextLoader(path), // Visual Basic + '.xml': (path) => new TextLoader(path)// XML }) let docs = [] From 76e132bd9f39806aeaef51868b37ea5f0123e05f Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 15 Aug 2023 13:15:44 +0100 Subject: [PATCH 384/398] fix replicate api key --- .../nodes/llms/Replicate/Replicate.ts | 2 +- .../marketplaces/chatflows/Replicate LLM.json | 41 +++++++++++-------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/components/nodes/llms/Replicate/Replicate.ts b/packages/components/nodes/llms/Replicate/Replicate.ts index ca30cd97..22c6e93a 100644 --- a/packages/components/nodes/llms/Replicate/Replicate.ts +++ b/packages/components/nodes/llms/Replicate/Replicate.ts @@ -97,7 +97,7 @@ class Replicate_LLMs implements INode { const additionalInputs = nodeData.inputs?.additionalInputs as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) - const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + const apiKey = getCredentialParam('replicateApiKey', credentialData, nodeData) const version = modelName.split(':').pop() const name = modelName.split(':')[0].split('/').pop() diff --git a/packages/server/marketplaces/chatflows/Replicate LLM.json b/packages/server/marketplaces/chatflows/Replicate LLM.json index 5a57b2e7..c5a0ac8f 100644 --- a/packages/server/marketplaces/chatflows/Replicate LLM.json +++ b/packages/server/marketplaces/chatflows/Replicate LLM.json @@ -13,8 +13,8 @@ "data": { "id": "llmChain_1", "label": "LLM Chain", - "name": "llmChain", "version": 1, + "name": "llmChain", "type": "LLMChain", "baseClasses": ["LLMChain", "BaseChain", "BaseLangChain"], "category": "Chains", @@ -84,7 +84,7 @@ }, { "width": 300, - "height": 475, + "height": 474, "id": "promptTemplate_0", "position": { "x": 269.2203229225663, @@ -94,8 +94,8 @@ "data": { "id": "promptTemplate_0", "label": "Prompt Template", - "name": "promptTemplate", "version": 1, + "name": "promptTemplate", "type": "PromptTemplate", "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], "category": "Prompts", @@ -144,28 +144,29 @@ }, { "width": 300, - "height": 527, + "height": 526, "id": "replicate_0", "position": { - "x": 607.4915400488668, - "y": -60.643337207007804 + "x": 623.313978186024, + "y": -72.92788335022428 }, "type": "customNode", "data": { "id": "replicate_0", "label": "Replicate", - "name": "replicate", "version": 1, + "name": "replicate", "type": "Replicate", - "baseClasses": ["Replicate", "LLM", "BaseLLM", "BaseLanguageModel"], + "baseClasses": ["Replicate", "BaseChatModel", "LLM", "BaseLLM", "BaseLanguageModel", "Runnable"], "category": "LLMs", "description": "Use Replicate to run open source models on cloud", "inputParams": [ { - "label": "Replicate Api Key", - "name": "replicateApiKey", - "type": "password", - "id": "replicate_0-input-replicateApiKey-password" + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["replicateApi"], + "id": "replicate_0-input-credential-credential" }, { "label": "Model", @@ -179,6 +180,7 @@ "label": "Temperature", "name": "temperature", "type": "number", + "step": 0.1, "description": "Adjusts randomness of outputs, greater than 1 is random and 0 is deterministic, 0.75 is a good starting value.", "default": 0.7, "optional": true, @@ -188,6 +190,7 @@ "label": "Max Tokens", "name": "maxTokens", "type": "number", + "step": 1, "description": "Maximum number of tokens to generate. A word is generally 2-3 tokens", "optional": true, "additionalParams": true, @@ -197,6 +200,7 @@ "label": "Top Probability", "name": "topP", "type": "number", + "step": 0.1, "description": "When decoding text, samples from the top p percentage of most likely tokens; lower to ignore less likely tokens", "optional": true, "additionalParams": true, @@ -206,6 +210,7 @@ "label": "Repetition Penalty", "name": "repetitionPenalty", "type": "number", + "step": 0.1, "description": "Penalty for repeated words in generated text; 1 is no penalty, values greater than 1 discourage repetition, less than 1 encourage it. (minimum: 0.01; maximum: 5)", "optional": true, "additionalParams": true, @@ -232,10 +237,10 @@ }, "outputAnchors": [ { - "id": "replicate_0-output-replicate-Replicate|LLM|BaseLLM|BaseLanguageModel", + "id": "replicate_0-output-replicate-Replicate|BaseChatModel|LLM|BaseLLM|BaseLanguageModel|Runnable", "name": "replicate", "label": "Replicate", - "type": "Replicate | LLM | BaseLLM | BaseLanguageModel" + "type": "Replicate | BaseChatModel | LLM | BaseLLM | BaseLanguageModel | Runnable" } ], "outputs": {}, @@ -243,8 +248,8 @@ }, "selected": false, "positionAbsolute": { - "x": 607.4915400488668, - "y": -60.643337207007804 + "x": 623.313978186024, + "y": -72.92788335022428 }, "dragging": false } @@ -263,11 +268,11 @@ }, { "source": "replicate_0", - "sourceHandle": "replicate_0-output-replicate-Replicate|LLM|BaseLLM|BaseLanguageModel", + "sourceHandle": "replicate_0-output-replicate-Replicate|BaseChatModel|LLM|BaseLLM|BaseLanguageModel|Runnable", "target": "llmChain_1", "targetHandle": "llmChain_1-input-model-BaseLanguageModel", "type": "buttonedge", - "id": "replicate_0-replicate_0-output-replicate-Replicate|LLM|BaseLLM|BaseLanguageModel-llmChain_1-llmChain_1-input-model-BaseLanguageModel", + "id": "replicate_0-replicate_0-output-replicate-Replicate|BaseChatModel|LLM|BaseLLM|BaseLanguageModel|Runnable-llmChain_1-llmChain_1-input-model-BaseLanguageModel", "data": { "label": "" } From d37bc6acf6a5936a892a5810db6093c726fcbb7a Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 15 Aug 2023 17:20:53 +0100 Subject: [PATCH 385/398] fix conversation retrieval qa agent original prompt --- .../ConversationalRetrievalAgent.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts index ed39fbc8..c0cef052 100644 --- a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts +++ b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts @@ -5,6 +5,8 @@ import { flatten } from 'lodash' import { BaseChatMemory } from 'langchain/memory' import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' +const defaultMessage = `Do your best to answer the questions. Feel free to use any tools available to look up relevant information, only if necessary.` + class ConversationalRetrievalAgent_Agents implements INode { label: string name: string @@ -46,6 +48,7 @@ class ConversationalRetrievalAgent_Agents implements INode { label: 'System Message', name: 'systemMessage', type: 'string', + default: defaultMessage, rows: 4, optional: true, additionalParams: true @@ -65,7 +68,7 @@ class ConversationalRetrievalAgent_Agents implements INode { agentType: 'openai-functions', verbose: process.env.DEBUG === 'true' ? true : false, agentArgs: { - prefix: systemMessage ?? `You are a helpful AI assistant.` + prefix: systemMessage ?? defaultMessage }, returnIntermediateSteps: true }) From f80547af60f9d5a54b2e916b83888abf4e016004 Mon Sep 17 00:00:00 2001 From: Seif Date: Tue, 15 Aug 2023 11:45:31 -0700 Subject: [PATCH 386/398] Add sentence config to Flowise --- .../Vectara_Existing/Vectara_Existing.ts | 23 ++++++++++++++++++- .../Vectara_Upsert/Vectara_Upsert.ts | 23 ++++++++++++++++++- packages/components/package.json | 2 +- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts b/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts index f344338a..80fd0639 100644 --- a/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts +++ b/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts @@ -1,6 +1,6 @@ import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' -import { VectaraStore, VectaraLibArgs, VectaraFilter } from 'langchain/vectorstores/vectara' +import { VectaraStore, VectaraLibArgs, VectaraFilter, VectaraContextConfig } from 'langchain/vectorstores/vectara' class VectaraExisting_VectorStores implements INode { label: string @@ -40,6 +40,20 @@ class VectaraExisting_VectorStores implements INode { additionalParams: true, optional: true }, + { + label: 'Sentences Before', + name: 'sentencesBefore', + type: 'number', + additionalParams: true, + optional: true + }, + { + label: 'Sentences After', + name: 'sentencesAfter', + type: 'number', + additionalParams: true, + optional: true + }, { label: 'Lambda', name: 'lambda', @@ -77,6 +91,8 @@ class VectaraExisting_VectorStores implements INode { const corpusId = getCredentialParam('corpusID', credentialData, nodeData) const vectaraMetadataFilter = nodeData.inputs?.filter as string + const sentencesBefore = nodeData.inputs?.sentencesBefore as number + const sentencesAfter = nodeData.inputs?.sentencesAfter as number const lambda = nodeData.inputs?.lambda as number const output = nodeData.outputs?.output as string const topK = nodeData.inputs?.topK as string @@ -92,6 +108,11 @@ class VectaraExisting_VectorStores implements INode { if (vectaraMetadataFilter) vectaraFilter.filter = vectaraMetadataFilter if (lambda) vectaraFilter.lambda = lambda + const vectaraContextConfig: VectaraContextConfig = {} + if (sentencesBefore) vectaraContextConfig.sentencesBefore = sentencesBefore + if (sentencesAfter) vectaraContextConfig.sentencesAfter = sentencesAfter + vectaraFilter.contextConfig = vectaraContextConfig + const vectorStore = new VectaraStore(vectaraArgs) if (output === 'retriever') { diff --git a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts index b2ee79e7..cda03240 100644 --- a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts +++ b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts @@ -1,7 +1,7 @@ import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { Embeddings } from 'langchain/embeddings/base' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' -import { VectaraStore, VectaraLibArgs, VectaraFilter } from 'langchain/vectorstores/vectara' +import { VectaraStore, VectaraLibArgs, VectaraFilter, VectaraContextConfig } from 'langchain/vectorstores/vectara' import { Document } from 'langchain/document' import { flatten } from 'lodash' @@ -49,6 +49,20 @@ class VectaraUpsert_VectorStores implements INode { additionalParams: true, optional: true }, + { + label: 'Sentences Before', + name: 'sentencesBefore', + type: 'number', + additionalParams: true, + optional: true + }, + { + label: 'Sentences After', + name: 'sentencesAfter', + type: 'number', + additionalParams: true, + optional: true + }, { label: 'Lambda', name: 'lambda', @@ -88,6 +102,8 @@ class VectaraUpsert_VectorStores implements INode { const docs = nodeData.inputs?.document as Document[] const embeddings = {} as Embeddings const vectaraMetadataFilter = nodeData.inputs?.filter as string + const sentencesBefore = nodeData.inputs?.sentencesBefore as number + const sentencesAfter = nodeData.inputs?.sentencesAfter as number const lambda = nodeData.inputs?.lambda as number const output = nodeData.outputs?.output as string const topK = nodeData.inputs?.topK as string @@ -103,6 +119,11 @@ class VectaraUpsert_VectorStores implements INode { if (vectaraMetadataFilter) vectaraFilter.filter = vectaraMetadataFilter if (lambda) vectaraFilter.lambda = lambda + const vectaraContextConfig: VectaraContextConfig = {} + if (sentencesBefore) vectaraContextConfig.sentencesBefore = sentencesBefore + if (sentencesAfter) vectaraContextConfig.sentencesAfter = sentencesAfter + vectaraFilter.contextConfig = vectaraContextConfig + const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { diff --git a/packages/components/package.json b/packages/components/package.json index bad9fb74..da4d0971 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -40,7 +40,7 @@ "google-auth-library": "^9.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "^0.0.122", + "langchain": "^0.0.126", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", From 2e8bbaeab1fb1e8bedab6afe77272eac126a1d94 Mon Sep 17 00:00:00 2001 From: Seif Date: Tue, 15 Aug 2023 11:50:37 -0700 Subject: [PATCH 387/398] Add descriptions --- .../nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts | 4 ++++ .../nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts b/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts index 80fd0639..3ef04f07 100644 --- a/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts +++ b/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts @@ -43,6 +43,7 @@ class VectaraExisting_VectorStores implements INode { { label: 'Sentences Before', name: 'sentencesBefore', + description: 'Number of sentences to fetch before the matched sentence. Defaults to 2.', type: 'number', additionalParams: true, optional: true @@ -50,6 +51,7 @@ class VectaraExisting_VectorStores implements INode { { label: 'Sentences After', name: 'sentencesAfter', + description: 'Number of sentences to fetch after the matched sentence. Defaults to 2.', type: 'number', additionalParams: true, optional: true @@ -57,6 +59,8 @@ class VectaraExisting_VectorStores implements INode { { label: 'Lambda', name: 'lambda', + description: + 'Improves retrieval accuracy by adjusting the balance (from 0 to 1) between neural search and keyword-based search factors.', type: 'number', additionalParams: true, optional: true diff --git a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts index cda03240..51fb67ed 100644 --- a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts +++ b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts @@ -52,6 +52,7 @@ class VectaraUpsert_VectorStores implements INode { { label: 'Sentences Before', name: 'sentencesBefore', + description: 'Number of sentences to fetch before the matched sentence. Defaults to 2.', type: 'number', additionalParams: true, optional: true @@ -59,6 +60,7 @@ class VectaraUpsert_VectorStores implements INode { { label: 'Sentences After', name: 'sentencesAfter', + description: 'Number of sentences to fetch after the matched sentence. Defaults to 2.', type: 'number', additionalParams: true, optional: true @@ -66,6 +68,8 @@ class VectaraUpsert_VectorStores implements INode { { label: 'Lambda', name: 'lambda', + description: + 'Improves retrieval accuracy by adjusting the balance (from 0 to 1) between neural search and keyword-based search factors.', type: 'number', additionalParams: true, optional: true From d10f3800e6a66cf201f63f993865bcb5fb3f96fa Mon Sep 17 00:00:00 2001 From: rkeshwani Date: Tue, 15 Aug 2023 23:36:50 +0000 Subject: [PATCH 388/398] Fixed spaces and comma issue. --- .../components/nodes/documentloaders/Folder/Folder.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/documentloaders/Folder/Folder.ts b/packages/components/nodes/documentloaders/Folder/Folder.ts index 7b90d9ed..f5d0c640 100644 --- a/packages/components/nodes/documentloaders/Folder/Folder.ts +++ b/packages/components/nodes/documentloaders/Folder/Folder.ts @@ -46,7 +46,7 @@ class Folder_DocumentLoaders implements INode { type: 'json', optional: true, additionalParams: true - }, + } ] } @@ -84,9 +84,9 @@ class Folder_DocumentLoaders implements INode { '.rs': (path) => new TextLoader(path), // Rust '.scala': (path) => new TextLoader(path), // Scala '.sc': (path) => new TextLoader(path), // Scala - '.scss': (path) => new TextLoader(path),// Sass + '.scss': (path) => new TextLoader(path), // Sass '.sol': (path) => new TextLoader(path), // Solidity - '.sql': (path) => new TextLoader(path),//SQL + '.sql': (path) => new TextLoader(path), //SQL '.swift': (path) => new TextLoader(path), // Swift '.markdown': (path) => new TextLoader(path), // Markdown '.md': (path) => new TextLoader(path), // Markdown @@ -94,7 +94,7 @@ class Folder_DocumentLoaders implements INode { '.ltx': (path) => new TextLoader(path), // LaTeX '.html': (path) => new TextLoader(path), // HTML '.vb': (path) => new TextLoader(path), // Visual Basic - '.xml': (path) => new TextLoader(path)// XML + '.xml': (path) => new TextLoader(path) // XML }) let docs = [] From 94461025dc82ceae746f4f91aaad1608f84907d1 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 16 Aug 2023 01:43:11 +0100 Subject: [PATCH 389/398] add vector to prompt --- .../ConversationChain/ConversationChain.ts | 3 +- .../VectorStoreToDocument.ts | 87 ++ .../VectorStoreToDocument/vectorretriever.svg | 7 + packages/components/src/utils.ts | 19 + .../Prompt Chaining with VectorStore.json | 966 ++++++++++++++++++ .../chatflows/Vectara LLM Chain Upload.json | 34 +- packages/server/src/index.ts | 24 +- packages/server/src/utils/index.ts | 42 +- packages/ui/src/assets/images/chathistory.png | Bin 0 -> 12068 bytes .../src/ui-component/dialog/NodeInfoDialog.js | 2 +- .../src/ui-component/json/SelectVariable.js | 50 +- 11 files changed, 1194 insertions(+), 40 deletions(-) create mode 100644 packages/components/nodes/documentloaders/VectorStoreToDocument/VectorStoreToDocument.ts create mode 100644 packages/components/nodes/documentloaders/VectorStoreToDocument/vectorretriever.svg create mode 100644 packages/server/marketplaces/chatflows/Prompt Chaining with VectorStore.json create mode 100644 packages/ui/src/assets/images/chathistory.png diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index cd42a670..08663395 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -45,7 +45,8 @@ class ConversationChain_Chains implements INode { label: 'Document', name: 'document', type: 'Document', - description: 'Include whole document into the context window', + description: + 'Include whole document into the context window, if you get maximum context length error, please use model with higher context window like Claude 100k, or gpt4 32k', optional: true, list: true }, diff --git a/packages/components/nodes/documentloaders/VectorStoreToDocument/VectorStoreToDocument.ts b/packages/components/nodes/documentloaders/VectorStoreToDocument/VectorStoreToDocument.ts new file mode 100644 index 00000000..b3f320ce --- /dev/null +++ b/packages/components/nodes/documentloaders/VectorStoreToDocument/VectorStoreToDocument.ts @@ -0,0 +1,87 @@ +import { VectorStore } from 'langchain/vectorstores/base' +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { handleEscapeCharacters } from '../../../src/utils' + +class VectorStoreToDocument_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 = 'VectorStore To Document' + this.name = 'vectorStoreToDocument' + this.version = 1.0 + this.type = 'Document' + this.icon = 'vectorretriever.svg' + this.category = 'Document Loaders' + this.description = 'Search documents with scores from vector store' + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Vector Store', + name: 'vectorStore', + type: 'VectorStore' + }, + { + label: 'Minimum Score (%)', + name: 'minScore', + type: 'number', + optional: true, + placeholder: '75', + step: 1, + description: 'Minumum score for embeddings documents to be included' + } + ] + this.outputs = [ + { + label: 'Document', + name: 'document', + baseClasses: this.baseClasses + }, + { + label: 'Text', + name: 'text', + baseClasses: ['string', 'json'] + } + ] + } + + async init(nodeData: INodeData, input: string): Promise { + const vectorStore = nodeData.inputs?.vectorStore as VectorStore + const minScore = nodeData.inputs?.minScore as number + const output = nodeData.outputs?.output as string + + const topK = (vectorStore as any)?.k ?? 4 + + const docs = await vectorStore.similaritySearchWithScore(input, topK) + // eslint-disable-next-line no-console + console.log('\x1b[94m\x1b[1m\n*****VectorStore Documents*****\n\x1b[0m\x1b[0m') + // eslint-disable-next-line no-console + console.log(docs) + + if (output === 'document') { + let finaldocs = [] + for (const doc of docs) { + if (minScore && doc[1] < minScore / 100) continue + finaldocs.push(doc[0]) + } + return finaldocs + } else { + let finaltext = '' + for (const doc of docs) { + if (minScore && doc[1] < minScore / 100) continue + finaltext += `${doc[0].pageContent}\n` + } + return handleEscapeCharacters(finaltext, false) + } + } +} + +module.exports = { nodeClass: VectorStoreToDocument_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/VectorStoreToDocument/vectorretriever.svg b/packages/components/nodes/documentloaders/VectorStoreToDocument/vectorretriever.svg new file mode 100644 index 00000000..208a59f1 --- /dev/null +++ b/packages/components/nodes/documentloaders/VectorStoreToDocument/vectorretriever.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index bcca834a..8d06a650 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -520,3 +520,22 @@ export const mapChatHistory = (options: ICommonObject): ChatMessageHistory => { } return new ChatMessageHistory(chatHistory) } + +/** + * Convert incoming chat history to string + * @param {IMessage[]} chatHistory + * @returns {string} + */ +export const convertChatHistoryToText = (chatHistory: IMessage[]): string => { + return chatHistory + .map((chatMessage) => { + if (chatMessage.type === 'apiMessage') { + return `Assistant: ${chatMessage.message}` + } else if (chatMessage.type === 'userMessage') { + return `Human: ${chatMessage.message}` + } else { + return `${chatMessage.message}` + } + }) + .join('\n') +} diff --git a/packages/server/marketplaces/chatflows/Prompt Chaining with VectorStore.json b/packages/server/marketplaces/chatflows/Prompt Chaining with VectorStore.json new file mode 100644 index 00000000..9d6838eb --- /dev/null +++ b/packages/server/marketplaces/chatflows/Prompt Chaining with VectorStore.json @@ -0,0 +1,966 @@ +{ + "description": "Use chat history to rephrase user question, and answer the rephrased question using retrieved docs from vector store", + "nodes": [ + { + "width": 300, + "height": 503, + "id": "pineconeExistingIndex_0", + "position": { + "x": 1062.7418678410986, + "y": -109.27680365777141 + }, + "type": "customNode", + "data": { + "id": "pineconeExistingIndex_0", + "label": "Pinecone Load Existing Index", + "version": 1, + "name": "pineconeExistingIndex", + "type": "Pinecone", + "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], + "category": "Vector Stores", + "description": "Load existing index from Pinecone (i.e: Document has been upserted)", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["pineconeApi"], + "id": "pineconeExistingIndex_0-input-credential-credential" + }, + { + "label": "Pinecone Index", + "name": "pineconeIndex", + "type": "string", + "id": "pineconeExistingIndex_0-input-pineconeIndex-string" + }, + { + "label": "Pinecone Namespace", + "name": "pineconeNamespace", + "type": "string", + "placeholder": "my-first-namespace", + "additionalParams": true, + "optional": true, + "id": "pineconeExistingIndex_0-input-pineconeNamespace-string" + }, + { + "label": "Pinecone Metadata Filter", + "name": "pineconeMetadataFilter", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "pineconeExistingIndex_0-input-pineconeMetadataFilter-json" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pineconeExistingIndex_0-input-topK-number" + } + ], + "inputAnchors": [ + { + "label": "Embeddings", + "name": "embeddings", + "type": "Embeddings", + "id": "pineconeExistingIndex_0-input-embeddings-Embeddings" + } + ], + "inputs": { + "embeddings": "{{openAIEmbeddings_0.data.instance}}", + "pineconeIndex": "newindex", + "pineconeNamespace": "", + "pineconeMetadataFilter": "{}", + "topK": "" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "pineconeExistingIndex_0-output-retriever-Pinecone|VectorStoreRetriever|BaseRetriever", + "name": "retriever", + "label": "Pinecone Retriever", + "type": "Pinecone | VectorStoreRetriever | BaseRetriever" + }, + { + "id": "pineconeExistingIndex_0-output-vectorStore-Pinecone|VectorStore", + "name": "vectorStore", + "label": "Pinecone Vector Store", + "type": "Pinecone | VectorStore" + } + ], + "default": "retriever" + } + ], + "outputs": { + "output": "vectorStore" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1062.7418678410986, + "y": -109.27680365777141 + }, + "dragging": false + }, + { + "width": 300, + "height": 327, + "id": "openAIEmbeddings_0", + "position": { + "x": 711.3971966563331, + "y": 7.7184225021727 + }, + "type": "customNode", + "data": { + "id": "openAIEmbeddings_0", + "label": "OpenAI Embeddings", + "version": 1, + "name": "openAIEmbeddings", + "type": "OpenAIEmbeddings", + "baseClasses": ["OpenAIEmbeddings", "Embeddings"], + "category": "Embeddings", + "description": "OpenAI API to generate embeddings for a given text", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["openAIApi"], + "id": "openAIEmbeddings_0-input-credential-credential" + }, + { + "label": "Strip New Lines", + "name": "stripNewLines", + "type": "boolean", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-stripNewLines-boolean" + }, + { + "label": "Batch Size", + "name": "batchSize", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-batchSize-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "openAIEmbeddings_0-input-basepath-string" + } + ], + "inputAnchors": [], + "inputs": { + "stripNewLines": "", + "batchSize": "", + "timeout": "", + "basepath": "" + }, + "outputAnchors": [ + { + "id": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "name": "openAIEmbeddings", + "label": "OpenAIEmbeddings", + "type": "OpenAIEmbeddings | Embeddings" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 711.3971966563331, + "y": 7.7184225021727 + }, + "dragging": false + }, + { + "width": 300, + "height": 473, + "id": "promptTemplate_0", + "position": { + "x": 348.2881107399286, + "y": -97.74510214137423 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_0", + "label": "Prompt Template", + "version": 1, + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate", "Runnable"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.\n\nChat History:\n{chat_history}\nFollow Up Input: {question}\nStandalone question:", + "promptValues": "{\"question\":\"{{question}}\",\"chat_history\":\"{{chat_history}}\"}" + }, + "outputAnchors": [ + { + "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 348.2881107399286, + "y": -97.74510214137423 + }, + "dragging": false + }, + { + "width": 300, + "height": 522, + "id": "chatOpenAI_0", + "position": { + "x": 335.7621848973805, + "y": -651.7411273245009 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "version": 1, + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "Runnable"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["openAIApi"], + "id": "chatOpenAI_0-input-credential-credential" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_0-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "step": 0.1, + "default": 0.9, + "optional": true, + "id": "chatOpenAI_0-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "gpt-3.5-turbo-16k", + "temperature": 0.9, + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "dragging": false, + "positionAbsolute": { + "x": 335.7621848973805, + "y": -651.7411273245009 + } + }, + { + "width": 300, + "height": 522, + "id": "chatOpenAI_1", + "position": { + "x": 1765.2801848172305, + "y": -667.9261054149061 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_1", + "label": "ChatOpenAI", + "version": 1, + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "Runnable"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["openAIApi"], + "id": "chatOpenAI_1-input-credential-credential" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_1-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "step": 0.1, + "default": 0.9, + "optional": true, + "id": "chatOpenAI_1-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-basepath-string" + } + ], + "inputAnchors": [], + "inputs": { + "modelName": "gpt-3.5-turbo-16k", + "temperature": 0.9, + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "dragging": false, + "positionAbsolute": { + "x": 1765.2801848172305, + "y": -667.9261054149061 + } + }, + { + "width": 300, + "height": 473, + "id": "promptTemplate_1", + "position": { + "x": 1773.720934090435, + "y": -116.71323227575395 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_1", + "label": "Prompt Template", + "version": 1, + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate", "Runnable"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_1-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_1-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "Use the following pieces of context to answer the question at the end.\n\n{context}\n\nQuestion: {question}\nHelpful Answer:", + "promptValues": "{\"context\":\"{{vectorStoreToDocument_0.data.instance}}\",\"question\":\"{{llmChain_0.data.instance}}\"}" + }, + "outputAnchors": [ + { + "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1773.720934090435, + "y": -116.71323227575395 + }, + "dragging": false + }, + { + "width": 300, + "height": 404, + "id": "llmChain_0", + "position": { + "x": 756.1670091985342, + "y": -592.5151355056942 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", + "label": "LLM Chain", + "version": 1, + "name": "llmChain", + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain", "Runnable"], + "category": "Chains", + "description": "Chain to run queries against LLMs", + "inputParams": [ + { + "label": "Chain Name", + "name": "chainName", + "type": "string", + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_0-input-chainName-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "llmChain_0-input-model-BaseLanguageModel" + }, + { + "label": "Prompt", + "name": "prompt", + "type": "BasePromptTemplate", + "id": "llmChain_0-input-prompt-BasePromptTemplate" + } + ], + "inputs": { + "model": "{{chatOpenAI_0.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", + "chainName": "QuestionChain" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_0-output-llmChain-LLMChain|BaseChain|Runnable", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain | Runnable" + }, + { + "id": "llmChain_0-output-outputPrediction-string|json", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string | json" + } + ], + "default": "llmChain" + } + ], + "outputs": { + "output": "outputPrediction" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 756.1670091985342, + "y": -592.5151355056942 + }, + "dragging": false + }, + { + "width": 300, + "height": 404, + "id": "llmChain_1", + "position": { + "x": 2200.1274896215496, + "y": -144.29167974642334 + }, + "type": "customNode", + "data": { + "id": "llmChain_1", + "label": "LLM Chain", + "version": 1, + "name": "llmChain", + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain", "Runnable"], + "category": "Chains", + "description": "Chain to run queries against LLMs", + "inputParams": [ + { + "label": "Chain Name", + "name": "chainName", + "type": "string", + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_1-input-chainName-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "llmChain_1-input-model-BaseLanguageModel" + }, + { + "label": "Prompt", + "name": "prompt", + "type": "BasePromptTemplate", + "id": "llmChain_1-input-prompt-BasePromptTemplate" + } + ], + "inputs": { + "model": "{{chatOpenAI_1.data.instance}}", + "prompt": "{{promptTemplate_1.data.instance}}", + "chainName": "" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_1-output-llmChain-LLMChain|BaseChain|Runnable", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain | Runnable" + }, + { + "id": "llmChain_1-output-outputPrediction-string|json", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string | json" + } + ], + "default": "llmChain" + } + ], + "outputs": { + "output": "llmChain" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 2200.1274896215496, + "y": -144.29167974642334 + }, + "dragging": false + }, + { + "width": 300, + "height": 353, + "id": "vectorStoreToDocument_0", + "position": { + "x": 1407.7038120189868, + "y": -26.16468811205081 + }, + "type": "customNode", + "data": { + "id": "vectorStoreToDocument_0", + "label": "VectorStore To Document", + "version": 1, + "name": "vectorStoreToDocument", + "type": "Document", + "baseClasses": ["Document"], + "category": "Document Loaders", + "description": "Search documents with scores from vector store", + "inputParams": [ + { + "label": "Minimum Score (%)", + "name": "minScore", + "type": "number", + "optional": true, + "placeholder": "75", + "step": 1, + "description": "Minumum score for embeddings documents to be included", + "id": "vectorStoreToDocument_0-input-minScore-number" + } + ], + "inputAnchors": [ + { + "label": "Vector Store", + "name": "vectorStore", + "type": "VectorStore", + "id": "vectorStoreToDocument_0-input-vectorStore-VectorStore" + } + ], + "inputs": { + "vectorStore": "{{pineconeExistingIndex_0.data.instance}}", + "minScore": "" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "vectorStoreToDocument_0-output-document-Document", + "name": "document", + "label": "Document", + "type": "Document" + }, + { + "id": "vectorStoreToDocument_0-output-text-string|json", + "name": "text", + "label": "Text", + "type": "string | json" + } + ], + "default": "document" + } + ], + "outputs": { + "output": "text" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1407.7038120189868, + "y": -26.16468811205081 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "openAIEmbeddings_0", + "sourceHandle": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "target": "pineconeExistingIndex_0", + "targetHandle": "pineconeExistingIndex_0-input-embeddings-Embeddings", + "type": "buttonedge", + "id": "openAIEmbeddings_0-openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings-pineconeExistingIndex_0-pineconeExistingIndex_0-input-embeddings-Embeddings", + "data": { + "label": "" + } + }, + { + "source": "pineconeExistingIndex_0", + "sourceHandle": "pineconeExistingIndex_0-output-vectorStore-Pinecone|VectorStore", + "target": "vectorStoreToDocument_0", + "targetHandle": "vectorStoreToDocument_0-input-vectorStore-VectorStore", + "type": "buttonedge", + "id": "pineconeExistingIndex_0-pineconeExistingIndex_0-output-vectorStore-Pinecone|VectorStore-vectorStoreToDocument_0-vectorStoreToDocument_0-input-vectorStore-VectorStore", + "data": { + "label": "" + } + }, + { + "source": "vectorStoreToDocument_0", + "sourceHandle": "vectorStoreToDocument_0-output-text-string|json", + "target": "promptTemplate_1", + "targetHandle": "promptTemplate_1-input-promptValues-json", + "type": "buttonedge", + "id": "vectorStoreToDocument_0-vectorStoreToDocument_0-output-text-string|json-promptTemplate_1-promptTemplate_1-input-promptValues-json", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-llmChain_0-llmChain_0-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } + }, + { + "source": "llmChain_0", + "sourceHandle": "llmChain_0-output-outputPrediction-string|json", + "target": "promptTemplate_1", + "targetHandle": "promptTemplate_1-input-promptValues-json", + "type": "buttonedge", + "id": "llmChain_0-llmChain_0-output-outputPrediction-string|json-promptTemplate_1-promptTemplate_1-input-promptValues-json", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_1", + "sourceHandle": "chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_1-chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-llmChain_1-llmChain_1-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "promptTemplate_1", + "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_1-promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } + } + ] +} diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json index 2146aa12..0758ec9a 100644 --- a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json +++ b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json @@ -4,14 +4,14 @@ { "width": 300, "height": 408, - "id": "vectaraExisting_0", + "id": "vectaraUpsert_0", "position": { "x": 438, "y": 214 }, "type": "customNode", "data": { - "id": "vectaraExisting_0", + "id": "vectaraUpsert_0", "label": "Vectara Upsert Document", "version": 1, - "name": "vectaraExisting", + "name": "vectaraUpsert", "type": "Vectara", "baseClasses": ["Vectara", "VectorStoreRetriever", "BaseRetriever"], "category": "Vector Stores", @@ -22,7 +22,7 @@ "name": "credential", "type": "credential", "credentialNames": ["vectaraApi"], - "id": "vectaraExisting_0-input-credential-credential" + "id": "vectaraUpsert_0-input-credential-credential" }, { "label": "Filter", @@ -30,7 +30,7 @@ "type": "json", "additionalParams": true, "optional": true, - "id": "vectaraExisting_0-input-filter-json" + "id": "vectaraUpsert_0-input-filter-json" }, { "label": "Lambda", @@ -38,7 +38,7 @@ "type": "number", "additionalParams": true, "optional": true, - "id": "vectaraExisting_0-input-lambda-number" + "id": "vectaraUpsert_0-input-lambda-number" }, { "label": "Top K", @@ -48,7 +48,7 @@ "type": "number", "additionalParams": true, "optional": true, - "id": "vectaraExisting_0-input-topK-number" + "id": "vectaraUpsert_0-input-topK-number" } ], "inputAnchors": [ @@ -57,7 +57,7 @@ "name": "document", "type": "Document", "list": true, - "id": "vectaraExisting_0-input-document-Document" + "id": "vectaraUpsert_0-input-document-Document" } ], "inputs": { @@ -73,13 +73,13 @@ "type": "options", "options": [ { - "id": "vectaraExisting_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", + "id": "vectaraUpsert_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", "name": "retriever", "label": "Vectara Retriever", "type": "Vectara | VectorStoreRetriever | BaseRetriever" }, { - "id": "vectaraExisting_0-output-vectorStore-Vectara|VectorStore", + "id": "vectaraUpsert_0-output-vectorStore-Vectara|VectorStore", "name": "vectorStore", "label": "Vectara Vector Store", "type": "Vectara | VectorStore" @@ -392,7 +392,7 @@ ], "inputs": { "model": "{{chatOpenAI_0.data.instance}}", - "vectorStoreRetriever": "{{vectaraExisting_0.data.instance}}", + "vectorStoreRetriever": "{{vectaraUpsert_0.data.instance}}", "memory": "", "returnSourceDocuments": "", "systemMessagePrompt": "", @@ -418,19 +418,19 @@ { "source": "pdfFile_0", "sourceHandle": "pdfFile_0-output-pdfFile-Document", - "target": "vectaraExisting_0", - "targetHandle": "vectaraExisting_0-input-document-Document", + "target": "vectaraUpsert_0", + "targetHandle": "vectaraUpsert_0-input-document-Document", "type": "buttonedge", - "id": "pdfFile_0-pdfFile_0-output-pdfFile-Document-vectaraExisting_0-vectaraExisting_0-input-document-Document", + "id": "pdfFile_0-pdfFile_0-output-pdfFile-Document-vectaraUpsert_0-vectaraUpsert_0-input-document-Document", "data": { "label": "" } }, { - "source": "vectaraExisting_0", - "sourceHandle": "vectaraExisting_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", + "source": "vectaraUpsert_0", + "sourceHandle": "vectaraUpsert_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", "target": "conversationalRetrievalQAChain_0", "targetHandle": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", "type": "buttonedge", - "id": "vectaraExisting_0-vectaraExisting_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", + "id": "vectaraUpsert_0-vectaraUpsert_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", "data": { "label": "" } }, { diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index b7289149..fbb61b00 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -811,11 +811,17 @@ export class App { } } + /*** Get chatflows and prepare data ***/ + const flowData = chatflow.flowData + const parsedFlowData: IReactFlowObject = JSON.parse(flowData) + const nodes = parsedFlowData.nodes + const edges = parsedFlowData.edges + /* Reuse the flow without having to rebuild (to avoid duplicated upsert, recomputation) when all these conditions met: * - Node Data already exists in pool * - Still in sync (i.e the flow has not been modified since) * - Existing overrideConfig and new overrideConfig are the same - * - Flow doesn't start with nodes that depend on incomingInput.question + * - Flow doesn't start with/contain nodes that depend on incomingInput.question ***/ const isFlowReusable = () => { return ( @@ -826,16 +832,10 @@ export class App { this.chatflowPool.activeChatflows[chatflowid].overrideConfig, incomingInput.overrideConfig ) && - !isStartNodeDependOnInput(this.chatflowPool.activeChatflows[chatflowid].startingNodes) + !isStartNodeDependOnInput(this.chatflowPool.activeChatflows[chatflowid].startingNodes, nodes) ) } - /*** Get chatflows and prepare data ***/ - const flowData = chatflow.flowData - const parsedFlowData: IReactFlowObject = JSON.parse(flowData) - const nodes = parsedFlowData.nodes - const edges = parsedFlowData.edges - if (isFlowReusable()) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData isStreamValid = isFlowValidForStream(nodes, nodeToExecuteData) @@ -884,6 +884,7 @@ export class App { depthQueue, this.nodesPool.componentNodes, incomingInput.question, + incomingInput.history, chatId, this.AppDataSource, incomingInput?.overrideConfig @@ -894,7 +895,12 @@ export class App { if (incomingInput.overrideConfig) nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig) - const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question) + const reactFlowNodeData: INodeData = resolveVariables( + nodeToExecute.data, + reactFlowNodes, + incomingInput.question, + incomingInput.history + ) nodeToExecuteData = reactFlowNodeData const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id)) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index cf4a9c3f..788b7c0e 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -19,7 +19,14 @@ import { ICredentialReqBody } from '../Interface' import { cloneDeep, get, omit, merge, isEqual } from 'lodash' -import { ICommonObject, getInputVariables, IDatabaseEntity, handleEscapeCharacters } from 'flowise-components' +import { + ICommonObject, + getInputVariables, + IDatabaseEntity, + handleEscapeCharacters, + IMessage, + convertChatHistoryToText +} from 'flowise-components' import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' import { lib, PBKDF2, AES, enc } from 'crypto-js' @@ -30,6 +37,7 @@ import { Tool } from '../entity/Tool' import { DataSource } from 'typeorm' const QUESTION_VAR_PREFIX = 'question' +const CHAT_HISTORY_VAR_PREFIX = 'chat_history' const REDACTED_CREDENTIAL_VALUE = '_FLOWISE_BLANK_07167752-1a71-43b1-bf8f-4f32252165db' export const databaseEntities: IDatabaseEntity = { ChatFlow: ChatFlow, ChatMessage: ChatMessage, Tool: Tool, Credential: Credential } @@ -199,6 +207,7 @@ export const buildLangchain = async ( depthQueue: IDepthQueue, componentNodes: IComponentNodes, question: string, + chatHistory: IMessage[], chatId: string, appDataSource: DataSource, overrideConfig?: ICommonObject @@ -231,7 +240,7 @@ export const buildLangchain = async ( let flowNodeData = cloneDeep(reactFlowNode.data) if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig) - const reactFlowNodeData: INodeData = resolveVariables(flowNodeData, flowNodes, question) + const reactFlowNodeData: INodeData = resolveVariables(flowNodeData, flowNodes, question, chatHistory) logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, { @@ -315,7 +324,13 @@ export const clearSessionMemory = async ( * @param {boolean} isAcceptVariable * @returns {string} */ -export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowNode[], question: string, isAcceptVariable = false) => { +export const getVariableValue = ( + paramValue: string, + reactFlowNodes: IReactFlowNode[], + question: string, + chatHistory: IMessage[], + isAcceptVariable = false +) => { let returnVal = paramValue const variableStack = [] const variableDict = {} as IVariableDict @@ -345,6 +360,10 @@ export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowN variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(question, false) } + if (isAcceptVariable && variableFullPath === CHAT_HISTORY_VAR_PREFIX) { + variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(convertChatHistoryToText(chatHistory), false) + } + // Split by first occurrence of '.' to get just nodeId const [variableNodeId, _] = variableFullPath.split('.') const executedNode = reactFlowNodes.find((nd) => nd.id === variableNodeId) @@ -400,7 +419,12 @@ export const isVectorStoreFaiss = (flowNodeData: INodeData) => { * @param {string} question * @returns {INodeData} */ -export const resolveVariables = (reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[], question: string): INodeData => { +export const resolveVariables = ( + reactFlowNodeData: INodeData, + reactFlowNodes: IReactFlowNode[], + question: string, + chatHistory: IMessage[] +): INodeData => { let flowNodeData = cloneDeep(reactFlowNodeData) if (reactFlowNodeData.instance && isVectorStoreFaiss(reactFlowNodeData)) { // omit and merge because cloneDeep of instance gives "Illegal invocation" Exception @@ -415,13 +439,13 @@ export const resolveVariables = (reactFlowNodeData: INodeData, reactFlowNodes: I if (Array.isArray(paramValue)) { const resolvedInstances = [] for (const param of paramValue) { - const resolvedInstance = getVariableValue(param, reactFlowNodes, question) + const resolvedInstance = getVariableValue(param, reactFlowNodes, question, chatHistory) resolvedInstances.push(resolvedInstance) } paramsObj[key] = resolvedInstances } else { const isAcceptVariable = reactFlowNodeData.inputParams.find((param) => param.name === key)?.acceptVariable ?? false - const resolvedInstance = getVariableValue(paramValue, reactFlowNodes, question, isAcceptVariable) + const resolvedInstance = getVariableValue(paramValue, reactFlowNodes, question, chatHistory, isAcceptVariable) paramsObj[key] = resolvedInstance } } @@ -474,13 +498,17 @@ export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig: * @param {IReactFlowNode[]} startingNodes * @returns {boolean} */ -export const isStartNodeDependOnInput = (startingNodes: IReactFlowNode[]): boolean => { +export const isStartNodeDependOnInput = (startingNodes: IReactFlowNode[], nodes: IReactFlowNode[]): boolean => { for (const node of startingNodes) { for (const inputName in node.data.inputs) { const inputVariables = getInputVariables(node.data.inputs[inputName]) if (inputVariables.length > 0) return true } } + const whitelistNodeNames = ['vectorStoreToDocument'] + for (const node of nodes) { + if (whitelistNodeNames.includes(node.data.name)) return true + } return false } diff --git a/packages/ui/src/assets/images/chathistory.png b/packages/ui/src/assets/images/chathistory.png new file mode 100644 index 0000000000000000000000000000000000000000..52f496a89bafe01b39986a9ff63d19fd006972fe GIT binary patch literal 12068 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 {
)} {getNodeConfigApi.data && getNodeConfigApi.data.length > 0 && ( - + )}
diff --git a/packages/ui/src/ui-component/json/SelectVariable.js b/packages/ui/src/ui-component/json/SelectVariable.js index 1b891ed1..7a482bae 100644 --- a/packages/ui/src/ui-component/json/SelectVariable.js +++ b/packages/ui/src/ui-component/json/SelectVariable.js @@ -2,14 +2,15 @@ import { useSelector } from 'react-redux' import PropTypes from 'prop-types' import { Box, List, ListItemButton, ListItem, ListItemAvatar, ListItemText, Typography, Stack } from '@mui/material' import PerfectScrollbar from 'react-perfect-scrollbar' - +import robotPNG from 'assets/images/robot.png' +import chatPNG from 'assets/images/chathistory.png' import { baseURL } from 'store/constant' const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectAndReturnVal }) => { const customization = useSelector((state) => state.customization) - const onSelectOutputResponseClick = (node, isUserQuestion = false) => { - let variablePath = isUserQuestion ? `question` : `${node.id}.data.instance` + const onSelectOutputResponseClick = (node, prefix) => { + let variablePath = node ? `${node.id}.data.instance` : prefix const newInput = `{{${variablePath}}}` onSelectAndReturnVal(newInput) } @@ -32,7 +33,7 @@ const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectA mb: 1 }} disabled={disabled} - onClick={() => onSelectOutputResponseClick(null, true)} + onClick={() => onSelectOutputResponseClick(null, 'question')} > @@ -52,13 +53,52 @@ const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectA objectFit: 'contain' }} alt='AI' - src='https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/parroticon.png' + src={robotPNG} />
+ onSelectOutputResponseClick(null, 'chat_history')} + > + + +
+ chatHistory +
+
+ +
+
{availableNodesForVariable && availableNodesForVariable.length > 0 && availableNodesForVariable.map((node, index) => { From 8034076361d82dd12dae8b36f36f7ad27702e88d Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 16 Aug 2023 02:02:49 +0100 Subject: [PATCH 390/398] update Vectara template --- .../chatflows/Vectara LLM Chain Upload.json | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json index 2146aa12..0758ec9a 100644 --- a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json +++ b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json @@ -4,14 +4,14 @@ { "width": 300, "height": 408, - "id": "vectaraExisting_0", + "id": "vectaraUpsert_0", "position": { "x": 438, "y": 214 }, "type": "customNode", "data": { - "id": "vectaraExisting_0", + "id": "vectaraUpsert_0", "label": "Vectara Upsert Document", "version": 1, - "name": "vectaraExisting", + "name": "vectaraUpsert", "type": "Vectara", "baseClasses": ["Vectara", "VectorStoreRetriever", "BaseRetriever"], "category": "Vector Stores", @@ -22,7 +22,7 @@ "name": "credential", "type": "credential", "credentialNames": ["vectaraApi"], - "id": "vectaraExisting_0-input-credential-credential" + "id": "vectaraUpsert_0-input-credential-credential" }, { "label": "Filter", @@ -30,7 +30,7 @@ "type": "json", "additionalParams": true, "optional": true, - "id": "vectaraExisting_0-input-filter-json" + "id": "vectaraUpsert_0-input-filter-json" }, { "label": "Lambda", @@ -38,7 +38,7 @@ "type": "number", "additionalParams": true, "optional": true, - "id": "vectaraExisting_0-input-lambda-number" + "id": "vectaraUpsert_0-input-lambda-number" }, { "label": "Top K", @@ -48,7 +48,7 @@ "type": "number", "additionalParams": true, "optional": true, - "id": "vectaraExisting_0-input-topK-number" + "id": "vectaraUpsert_0-input-topK-number" } ], "inputAnchors": [ @@ -57,7 +57,7 @@ "name": "document", "type": "Document", "list": true, - "id": "vectaraExisting_0-input-document-Document" + "id": "vectaraUpsert_0-input-document-Document" } ], "inputs": { @@ -73,13 +73,13 @@ "type": "options", "options": [ { - "id": "vectaraExisting_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", + "id": "vectaraUpsert_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", "name": "retriever", "label": "Vectara Retriever", "type": "Vectara | VectorStoreRetriever | BaseRetriever" }, { - "id": "vectaraExisting_0-output-vectorStore-Vectara|VectorStore", + "id": "vectaraUpsert_0-output-vectorStore-Vectara|VectorStore", "name": "vectorStore", "label": "Vectara Vector Store", "type": "Vectara | VectorStore" @@ -392,7 +392,7 @@ ], "inputs": { "model": "{{chatOpenAI_0.data.instance}}", - "vectorStoreRetriever": "{{vectaraExisting_0.data.instance}}", + "vectorStoreRetriever": "{{vectaraUpsert_0.data.instance}}", "memory": "", "returnSourceDocuments": "", "systemMessagePrompt": "", @@ -418,19 +418,19 @@ { "source": "pdfFile_0", "sourceHandle": "pdfFile_0-output-pdfFile-Document", - "target": "vectaraExisting_0", - "targetHandle": "vectaraExisting_0-input-document-Document", + "target": "vectaraUpsert_0", + "targetHandle": "vectaraUpsert_0-input-document-Document", "type": "buttonedge", - "id": "pdfFile_0-pdfFile_0-output-pdfFile-Document-vectaraExisting_0-vectaraExisting_0-input-document-Document", + "id": "pdfFile_0-pdfFile_0-output-pdfFile-Document-vectaraUpsert_0-vectaraUpsert_0-input-document-Document", "data": { "label": "" } }, { - "source": "vectaraExisting_0", - "sourceHandle": "vectaraExisting_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", + "source": "vectaraUpsert_0", + "sourceHandle": "vectaraUpsert_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", "target": "conversationalRetrievalQAChain_0", "targetHandle": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", "type": "buttonedge", - "id": "vectaraExisting_0-vectaraExisting_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", + "id": "vectaraUpsert_0-vectaraUpsert_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", "data": { "label": "" } }, { From f0f5585cac26ff7a8252b4761a5da6dc923de2d1 Mon Sep 17 00:00:00 2001 From: Seif Date: Wed, 16 Aug 2023 09:56:14 -0700 Subject: [PATCH 391/398] Update template --- .../chatflows/Vectara LLM Chain Upload.json | 388 +++++++++--------- 1 file changed, 195 insertions(+), 193 deletions(-) diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json index 0758ec9a..784ad240 100644 --- a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json +++ b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json @@ -1,186 +1,11 @@ { "description": "A simple LLM chain that uses Vectara to enable conversations with uploaded documents", "nodes": [ - { - "width": 300, - "height": 408, - "id": "vectaraUpsert_0", - "position": { "x": 438, "y": 214 }, - "type": "customNode", - "data": { - "id": "vectaraUpsert_0", - "label": "Vectara Upsert Document", - "version": 1, - "name": "vectaraUpsert", - "type": "Vectara", - "baseClasses": ["Vectara", "VectorStoreRetriever", "BaseRetriever"], - "category": "Vector Stores", - "description": "Upsert documents to Vectara", - "inputParams": [ - { - "label": "Connect Credential", - "name": "credential", - "type": "credential", - "credentialNames": ["vectaraApi"], - "id": "vectaraUpsert_0-input-credential-credential" - }, - { - "label": "Filter", - "name": "filter", - "type": "json", - "additionalParams": true, - "optional": true, - "id": "vectaraUpsert_0-input-filter-json" - }, - { - "label": "Lambda", - "name": "lambda", - "type": "number", - "additionalParams": true, - "optional": true, - "id": "vectaraUpsert_0-input-lambda-number" - }, - { - "label": "Top K", - "name": "topK", - "description": "Number of top results to fetch. Defaults to 4", - "placeholder": "4", - "type": "number", - "additionalParams": true, - "optional": true, - "id": "vectaraUpsert_0-input-topK-number" - } - ], - "inputAnchors": [ - { - "label": "Document", - "name": "document", - "type": "Document", - "list": true, - "id": "vectaraUpsert_0-input-document-Document" - } - ], - "inputs": { - "document": ["{{pdfFile_0.data.instance}}"], - "filter": "", - "lambda": "", - "topK": "" - }, - "outputAnchors": [ - { - "name": "output", - "label": "Output", - "type": "options", - "options": [ - { - "id": "vectaraUpsert_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", - "name": "retriever", - "label": "Vectara Retriever", - "type": "Vectara | VectorStoreRetriever | BaseRetriever" - }, - { - "id": "vectaraUpsert_0-output-vectorStore-Vectara|VectorStore", - "name": "vectorStore", - "label": "Vectara Vector Store", - "type": "Vectara | VectorStore" - } - ], - "default": "retriever" - } - ], - "outputs": { "output": "retriever" }, - "selected": false - }, - "selected": false, - "dragging": false, - "positionAbsolute": { "x": 438, "y": 214 } - }, - { - "width": 300, - "height": 509, - "id": "pdfFile_0", - "position": { "x": 68.3013317598369, "y": 199.60454731299677 }, - "type": "customNode", - "data": { - "id": "pdfFile_0", - "label": "Pdf File", - "version": 1, - "name": "pdfFile", - "type": "Document", - "baseClasses": ["Document"], - "category": "Document Loaders", - "description": "Load data from PDF files", - "inputParams": [ - { - "label": "Pdf File", - "name": "pdfFile", - "type": "file", - "fileType": ".pdf", - "id": "pdfFile_0-input-pdfFile-file" - }, - { - "label": "Usage", - "name": "usage", - "type": "options", - "options": [ - { "label": "One document per page", "name": "perPage" }, - { "label": "One document per file", "name": "perFile" } - ], - "default": "perPage", - "id": "pdfFile_0-input-usage-options" - }, - { - "label": "Use Legacy Build", - "name": "legacyBuild", - "type": "boolean", - "optional": true, - "additionalParams": true, - "id": "pdfFile_0-input-legacyBuild-boolean" - }, - { - "label": "Metadata", - "name": "metadata", - "type": "json", - "optional": true, - "additionalParams": true, - "id": "pdfFile_0-input-metadata-json" - } - ], - "inputAnchors": [ - { - "label": "Text Splitter", - "name": "textSplitter", - "type": "TextSplitter", - "optional": true, - "id": "pdfFile_0-input-textSplitter-TextSplitter" - } - ], - "inputs": { - "textSplitter": "", - "usage": "perPage", - "legacyBuild": "", - "metadata": "" - }, - "outputAnchors": [ - { - "id": "pdfFile_0-output-pdfFile-Document", - "name": "pdfFile", - "label": "Document", - "type": "Document" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { "x": 68.3013317598369, "y": 199.60454731299677 }, - "dragging": false - }, { "width": 300, "height": 525, "id": "chatOpenAI_0", - "position": { "x": 804.3889791707068, "y": 195.11620799951592 }, + "position": { "x": 514.1088940275924, "y": 199.574479681537 }, "type": "customNode", "data": { "id": "chatOpenAI_0", @@ -211,10 +36,7 @@ { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" }, { "label": "gpt-3.5-turbo-16k", "name": "gpt-3.5-turbo-16k" }, - { - "label": "gpt-3.5-turbo-16k-0613", - "name": "gpt-3.5-turbo-16k-0613" - } + { "label": "gpt-3.5-turbo-16k-0613", "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", "optional": true, @@ -286,7 +108,7 @@ "inputAnchors": [], "inputs": { "modelName": "gpt-3.5-turbo", - "temperature": "0.2", + "temperature": "0.5", "maxTokens": "", "topP": "", "frequencyPenalty": "", @@ -306,14 +128,14 @@ "selected": false }, "selected": false, - "positionAbsolute": { "x": 804.3889791707068, "y": 195.11620799951592 }, + "positionAbsolute": { "x": 514.1088940275924, "y": 199.574479681537 }, "dragging": false }, { "width": 300, "height": 481, "id": "conversationalRetrievalQAChain_0", - "position": { "x": 1160.4877473512795, "y": 259.2799138505109 }, + "position": { "x": 900.4793407261002, "y": 205.9476004518217 }, "type": "customNode", "data": { "id": "conversationalRetrievalQAChain_0", @@ -410,11 +232,200 @@ "selected": false }, "selected": false, - "positionAbsolute": { "x": 1160.4877473512795, "y": 259.2799138505109 }, + "positionAbsolute": { "x": 900.4793407261002, "y": 205.9476004518217 }, "dragging": false + }, + { + "width": 300, + "height": 509, + "id": "pdfFile_0", + "position": { "x": -210.44158723479913, "y": 236.6627524951051 }, + "type": "customNode", + "data": { + "id": "pdfFile_0", + "label": "Pdf File", + "version": 1, + "name": "pdfFile", + "type": "Document", + "baseClasses": ["Document"], + "category": "Document Loaders", + "description": "Load data from PDF files", + "inputParams": [ + { "label": "Pdf File", "name": "pdfFile", "type": "file", "fileType": ".pdf", "id": "pdfFile_0-input-pdfFile-file" }, + { + "label": "Usage", + "name": "usage", + "type": "options", + "options": [ + { "label": "One document per page", "name": "perPage" }, + { "label": "One document per file", "name": "perFile" } + ], + "default": "perPage", + "id": "pdfFile_0-input-usage-options" + }, + { + "label": "Use Legacy Build", + "name": "legacyBuild", + "type": "boolean", + "optional": true, + "additionalParams": true, + "id": "pdfFile_0-input-legacyBuild-boolean" + }, + { + "label": "Metadata", + "name": "metadata", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "pdfFile_0-input-metadata-json" + } + ], + "inputAnchors": [ + { + "label": "Text Splitter", + "name": "textSplitter", + "type": "TextSplitter", + "optional": true, + "id": "pdfFile_0-input-textSplitter-TextSplitter" + } + ], + "inputs": { "textSplitter": "", "usage": "perPage", "legacyBuild": "", "metadata": "" }, + "outputAnchors": [ + { "id": "pdfFile_0-output-pdfFile-Document", "name": "pdfFile", "label": "Document", "type": "Document" } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { "x": -210.44158723479913, "y": 236.6627524951051 }, + "dragging": false + }, + { + "width": 300, + "height": 408, + "id": "vectaraUpsert_0", + "position": { "x": 172.06946164914868, "y": 373.11406233089934 }, + "type": "customNode", + "data": { + "id": "vectaraUpsert_0", + "label": "Vectara Upsert Document", + "version": 1, + "name": "vectaraUpsert", + "type": "Vectara", + "baseClasses": ["Vectara", "VectorStoreRetriever", "BaseRetriever"], + "category": "Vector Stores", + "description": "Upsert documents to Vectara", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["vectaraApi"], + "id": "vectaraUpsert_0-input-credential-credential" + }, + { + "label": "Vectara Metadata Filter", + "name": "filter", + "description": "Filter to apply to Vectara metadata. Refer to the documentation on how to use Vectara filters with Flowise.", + "type": "string", + "additionalParams": true, + "optional": true, + "id": "vectaraUpsert_0-input-filter-string" + }, + { + "label": "Sentences Before", + "name": "sentencesBefore", + "description": "Number of sentences to fetch before the matched sentence. Defaults to 2.", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "vectaraUpsert_0-input-sentencesBefore-number" + }, + { + "label": "Sentences After", + "name": "sentencesAfter", + "description": "Number of sentences to fetch after the matched sentence. Defaults to 2.", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "vectaraUpsert_0-input-sentencesAfter-number" + }, + { + "label": "Lambda", + "name": "lambda", + "description": "Improves retrieval accuracy by adjusting the balance (from 0 to 1) between neural search and keyword-based search factors.", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "vectaraUpsert_0-input-lambda-number" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Defaults to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "vectaraUpsert_0-input-topK-number" + } + ], + "inputAnchors": [ + { + "label": "Document", + "name": "document", + "type": "Document", + "list": true, + "id": "vectaraUpsert_0-input-document-Document" + } + ], + "inputs": { + "document": ["{{pdfFile_0.data.instance}}"], + "filter": "", + "sentencesBefore": "", + "sentencesAfter": "", + "lambda": "", + "topK": "" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "vectaraUpsert_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever", + "name": "retriever", + "label": "Vectara Retriever", + "type": "Vectara | VectorStoreRetriever | BaseRetriever" + }, + { + "id": "vectaraUpsert_0-output-vectorStore-Vectara|VectorStore", + "name": "vectorStore", + "label": "Vectara Vector Store", + "type": "Vectara | VectorStore" + } + ], + "default": "retriever" + } + ], + "outputs": { "output": "retriever" }, + "selected": false + }, + "positionAbsolute": { "x": 172.06946164914868, "y": 373.11406233089934 }, + "selected": false } ], "edges": [ + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "target": "conversationalRetrievalQAChain_0", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "data": { "label": "" } + }, { "source": "pdfFile_0", "sourceHandle": "pdfFile_0-output-pdfFile-Document", @@ -432,15 +443,6 @@ "type": "buttonedge", "id": "vectaraUpsert_0-vectaraUpsert_0-output-retriever-Vectara|VectorStoreRetriever|BaseRetriever-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", "data": { "label": "" } - }, - { - "source": "chatOpenAI_0", - "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", - "target": "conversationalRetrievalQAChain_0", - "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", - "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", - "data": { "label": "" } } ] } From c83d0ab3205739dc5fa9e289ed609c2241041a36 Mon Sep 17 00:00:00 2001 From: Atish Amte Date: Thu, 17 Aug 2023 00:33:01 +0530 Subject: [PATCH 392/398] added puppeteer options --- .../documentloaders/Puppeteer/Puppeteer.ts | 64 +++++++++++++++++-- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index ea6280db..036e4053 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -1,8 +1,9 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' -import { PuppeteerWebBaseLoader } from 'langchain/document_loaders/web/puppeteer' +import { Browser, Page, PuppeteerWebBaseLoader, PuppeteerWebBaseLoaderOptions } from 'langchain/document_loaders/web/puppeteer' import { test } from 'linkifyjs' import { webCrawl, xmlScrape } from '../../../src' +import { PuppeteerLifeCycleEvent } from 'puppeteer' class Puppeteer_DocumentLoaders implements INode { label: string @@ -62,10 +63,47 @@ class Puppeteer_DocumentLoaders implements INode { type: 'number', optional: true, additionalParams: true, - description: - 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', + description: 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', warning: `Retreiving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` }, + { + label: 'Wait Until', + name: 'waitUntilGoToOption', + type: 'options', + description: 'Select a go to wait until option', + options: [ + { + label: 'Load', + name: 'load', + description: `When the initial HTML document\'s DOM has been loaded and parsed` + }, + { + label: 'DOM Content Loaded', + name: 'domcontentloaded', + description: `When the complete HTML document\'s DOM has been loaded and parsed` + }, + { + label: 'Network Idle 0', + name: 'networkidle0', + description: 'Navigation is finished when there are no more than 0 network connections for at least 500 ms' + }, + { + label: 'Network Idle 2', + name: 'networkidle2', + description: 'Navigation is finished when there are no more than 2 network connections for at least 500 ms' + } + ], + optional: true, + additionalParams: true + }, + { + label: 'Wait for selector to load', + name: 'waitForSelector', + type: 'string', + optional: true, + additionalParams: true, + description: 'CSS selectors like .div or #div', + }, { label: 'Metadata', name: 'metadata', @@ -81,6 +119,8 @@ class Puppeteer_DocumentLoaders implements INode { const metadata = nodeData.inputs?.metadata const relativeLinksMethod = nodeData.inputs?.relativeLinksMethod as string let limit = nodeData.inputs?.limit as string + let waitUntilGoToOption = nodeData.inputs?.waitUntilGoToOption as PuppeteerLifeCycleEvent + let waitForSelector = nodeData.inputs?.waitForSelector as string let url = nodeData.inputs?.url as string url = url.trim() @@ -91,12 +131,26 @@ class Puppeteer_DocumentLoaders implements INode { async function puppeteerLoader(url: string): Promise { try { let docs = [] - const loader = new PuppeteerWebBaseLoader(url, { + const config: PuppeteerWebBaseLoaderOptions = { launchOptions: { args: ['--no-sandbox'], headless: 'new' } - }) + }; + if (waitUntilGoToOption) { + config['gotoOptions'] = { + waitUntil: waitUntilGoToOption + } + } + if (waitForSelector) { + config['evaluate'] = async (page: Page, browser: Browser): Promise => { + await page.waitForSelector(waitForSelector) + + const result = await page.evaluate(() => document.body.innerHTML) + return result + } + } + const loader = new PuppeteerWebBaseLoader(url, config) if (textSplitter) { docs = await loader.loadAndSplit(textSplitter) } else { From 8414f347def05fe405744562c28615e5bc9952d8 Mon Sep 17 00:00:00 2001 From: Atish Amte Date: Thu, 17 Aug 2023 00:36:03 +0530 Subject: [PATCH 393/398] spelling correction --- .../nodes/documentloaders/Cheerio/Cheerio.ts | 2 +- .../documentloaders/Playwright/Playwright.ts | 2 +- .../documentloaders/Puppeteer/Puppeteer.ts | 2 +- .../marketplaces/chatflows/WebPage QnA.json | 56 ++++++++++++++----- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts index 310aa9e6..1c21c1ea 100644 --- a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts +++ b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts @@ -64,7 +64,7 @@ class Cheerio_DocumentLoaders implements INode { additionalParams: true, description: 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', - warning: `Retreiving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` + warning: `Retrieving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` }, { label: 'Metadata', diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts index 3399574d..2ddd6a8d 100644 --- a/packages/components/nodes/documentloaders/Playwright/Playwright.ts +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -64,7 +64,7 @@ class Playwright_DocumentLoaders implements INode { additionalParams: true, description: 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', - warning: `Retreiving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` + warning: `Retrieving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` }, { label: 'Metadata', diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index 036e4053..c3b61a2b 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -64,7 +64,7 @@ class Puppeteer_DocumentLoaders implements INode { optional: true, additionalParams: true, description: 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', - warning: `Retreiving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` + warning: `Retrieving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` }, { label: 'Wait Until', diff --git a/packages/server/marketplaces/chatflows/WebPage QnA.json b/packages/server/marketplaces/chatflows/WebPage QnA.json index 8197c20a..09246150 100644 --- a/packages/server/marketplaces/chatflows/WebPage QnA.json +++ b/packages/server/marketplaces/chatflows/WebPage QnA.json @@ -16,7 +16,11 @@ "version": 1, "name": "chatOpenAI", "type": "ChatOpenAI", - "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], + "baseClasses": [ + "ChatOpenAI", + "BaseChatModel", + "BaseLanguageModel" + ], "category": "Chat Models", "description": "Wrapper around OpenAI large language models that use the Chat endpoint", "inputParams": [ @@ -24,7 +28,9 @@ "label": "Connect Credential", "name": "credential", "type": "credential", - "credentialNames": ["openAIApi"], + "credentialNames": [ + "openAIApi" + ], "id": "chatOpenAI_0-input-credential-credential" }, { @@ -170,7 +176,10 @@ "version": 1, "name": "openAIEmbeddings", "type": "OpenAIEmbeddings", - "baseClasses": ["OpenAIEmbeddings", "Embeddings"], + "baseClasses": [ + "OpenAIEmbeddings", + "Embeddings" + ], "category": "Embeddings", "description": "OpenAI API to generate embeddings for a given text", "inputParams": [ @@ -178,7 +187,9 @@ "label": "Connect Credential", "name": "credential", "type": "credential", - "credentialNames": ["openAIApi"], + "credentialNames": [ + "openAIApi" + ], "id": "openAIEmbeddings_0-input-credential-credential" }, { @@ -318,7 +329,10 @@ "version": 1, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", - "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], + "baseClasses": [ + "ConversationalRetrievalQAChain", + "BaseChain" + ], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -428,7 +442,9 @@ "version": 1, "name": "cheerioWebScraper", "type": "Document", - "baseClasses": ["Document"], + "baseClasses": [ + "Document" + ], "category": "Document Loaders", "description": "Load data from webpages", "inputParams": [ @@ -466,7 +482,7 @@ "optional": true, "additionalParams": true, "description": "Only used when \"Get Relative Links Method\" is selected. Set 0 to retrieve all relative links, default limit is 10.", - "warning": "Retreiving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)", + "warning": "Retrieving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)", "id": "cheerioWebScraper_0-input-limit-number" }, { @@ -527,7 +543,11 @@ "version": 1, "name": "pineconeUpsert", "type": "Pinecone", - "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], + "baseClasses": [ + "Pinecone", + "VectorStoreRetriever", + "BaseRetriever" + ], "category": "Vector Stores", "description": "Upsert documents to Pinecone", "inputParams": [ @@ -535,7 +555,9 @@ "label": "Connect Credential", "name": "credential", "type": "credential", - "credentialNames": ["pineconeApi"], + "credentialNames": [ + "pineconeApi" + ], "id": "pineconeUpsert_0-input-credential-credential" }, { @@ -580,7 +602,9 @@ } ], "inputs": { - "document": ["{{cheerioWebScraper_0.data.instance}}"], + "document": [ + "{{cheerioWebScraper_0.data.instance}}" + ], "embeddings": "{{openAIEmbeddings_0.data.instance}}", "pineconeIndex": "", "pineconeNamespace": "", @@ -635,7 +659,11 @@ "version": 1, "name": "motorheadMemory", "type": "MotorheadMemory", - "baseClasses": ["MotorheadMemory", "BaseChatMemory", "BaseMemory"], + "baseClasses": [ + "MotorheadMemory", + "BaseChatMemory", + "BaseMemory" + ], "category": "Memory", "description": "Use Motorhead Memory to store chat conversations", "inputParams": [ @@ -645,7 +673,9 @@ "type": "credential", "optional": true, "description": "Only needed when using hosted solution - https://getmetal.io", - "credentialNames": ["motorheadMemoryApi"], + "credentialNames": [ + "motorheadMemoryApi" + ], "id": "motorheadMemory_0-input-credential-credential" }, { @@ -768,4 +798,4 @@ } } ] -} +} \ No newline at end of file From 338082f0aa6e7bfc7d61077d03b0ff10253c3d9b Mon Sep 17 00:00:00 2001 From: Atish Amte Date: Thu, 17 Aug 2023 00:52:35 +0530 Subject: [PATCH 394/398] playwright config --- .../documentloaders/Playwright/Playwright.ts | 63 ++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts index 2ddd6a8d..b376c05b 100644 --- a/packages/components/nodes/documentloaders/Playwright/Playwright.ts +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -1,6 +1,6 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' -import { PlaywrightWebBaseLoader } from 'langchain/document_loaders/web/playwright' +import { Browser, Page, PlaywrightWebBaseLoader, PlaywrightWebBaseLoaderOptions } from 'langchain/document_loaders/web/playwright' import { test } from 'linkifyjs' import { webCrawl, xmlScrape } from '../../../src' @@ -66,6 +66,44 @@ class Playwright_DocumentLoaders implements INode { 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', warning: `Retrieving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` }, + { + label: 'Wait Until', + name: 'waitUntilGoToOption', + type: 'options', + description: 'Select a go to wait until option', + options: [ + { + label: 'Load', + name: 'load', + description: 'Consider operation to be finished when the load event is fired.' + }, + { + label: 'DOM Content Loaded', + name: 'domcontentloaded', + description: 'Consider operation to be finished when the DOMContentLoaded event is fired.' + }, + { + label: 'Network Idle', + name: 'networkidle', + description: 'Navigation is finished when there are no more connections for at least 500 ms.' + }, + { + label: 'Commit', + name: 'commit', + description: 'Consider operation to be finished when network response is received and the document started loading.' + } + ], + optional: true, + additionalParams: true + }, + { + label: 'Wait for selector to load', + name: 'waitForSelector', + type: 'string', + optional: true, + additionalParams: true, + description: 'CSS selectors like .div or #div', + }, { label: 'Metadata', name: 'metadata', @@ -81,6 +119,8 @@ class Playwright_DocumentLoaders implements INode { const metadata = nodeData.inputs?.metadata const relativeLinksMethod = nodeData.inputs?.relativeLinksMethod as string let limit = nodeData.inputs?.limit as string + let waitUntilGoToOption = nodeData.inputs?.waitUntilGoToOption as "load" | "domcontentloaded" | "networkidle" | "commit" | undefined + let waitForSelector = nodeData.inputs?.waitForSelector as string let url = nodeData.inputs?.url as string url = url.trim() @@ -91,7 +131,26 @@ class Playwright_DocumentLoaders implements INode { async function playwrightLoader(url: string): Promise { try { let docs = [] - const loader = new PlaywrightWebBaseLoader(url) + const config: PlaywrightWebBaseLoaderOptions = { + launchOptions: { + args: ['--no-sandbox'], + headless: true + } + }; + if (waitUntilGoToOption) { + config['gotoOptions'] = { + waitUntil: waitUntilGoToOption + } + } + if (waitForSelector) { + config['evaluate'] = async (page: Page, browser: Browser): Promise => { + await page.waitForSelector(waitForSelector) + + const result = await page.evaluate(() => document.body.innerHTML) + return result + } + } + const loader = new PlaywrightWebBaseLoader(url, config) if (textSplitter) { docs = await loader.loadAndSplit(textSplitter) } else { From 888fa356b93d2d0d2ff3b11addd11c839c5b225f Mon Sep 17 00:00:00 2001 From: Atish Amte Date: Thu, 17 Aug 2023 01:11:31 +0530 Subject: [PATCH 395/398] lint fixes --- .../documentloaders/Playwright/Playwright.ts | 8 +-- .../documentloaders/Puppeteer/Puppeteer.ts | 13 ++--- .../marketplaces/chatflows/WebPage QnA.json | 54 +++++-------------- 3 files changed, 23 insertions(+), 52 deletions(-) diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts index b376c05b..eb246045 100644 --- a/packages/components/nodes/documentloaders/Playwright/Playwright.ts +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -102,7 +102,7 @@ class Playwright_DocumentLoaders implements INode { type: 'string', optional: true, additionalParams: true, - description: 'CSS selectors like .div or #div', + description: 'CSS selectors like .div or #div' }, { label: 'Metadata', @@ -119,7 +119,7 @@ class Playwright_DocumentLoaders implements INode { const metadata = nodeData.inputs?.metadata const relativeLinksMethod = nodeData.inputs?.relativeLinksMethod as string let limit = nodeData.inputs?.limit as string - let waitUntilGoToOption = nodeData.inputs?.waitUntilGoToOption as "load" | "domcontentloaded" | "networkidle" | "commit" | undefined + let waitUntilGoToOption = nodeData.inputs?.waitUntilGoToOption as 'load' | 'domcontentloaded' | 'networkidle' | 'commit' | undefined let waitForSelector = nodeData.inputs?.waitForSelector as string let url = nodeData.inputs?.url as string @@ -136,14 +136,14 @@ class Playwright_DocumentLoaders implements INode { args: ['--no-sandbox'], headless: true } - }; + } if (waitUntilGoToOption) { config['gotoOptions'] = { waitUntil: waitUntilGoToOption } } if (waitForSelector) { - config['evaluate'] = async (page: Page, browser: Browser): Promise => { + config['evaluate'] = async (page: Page, _: Browser): Promise => { await page.waitForSelector(waitForSelector) const result = await page.evaluate(() => document.body.innerHTML) diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index c3b61a2b..4691eb94 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -63,7 +63,8 @@ class Puppeteer_DocumentLoaders implements INode { type: 'number', optional: true, additionalParams: true, - description: 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', + description: + 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', warning: `Retrieving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` }, { @@ -75,12 +76,12 @@ class Puppeteer_DocumentLoaders implements INode { { label: 'Load', name: 'load', - description: `When the initial HTML document\'s DOM has been loaded and parsed` + description: `When the initial HTML document's DOM has been loaded and parsed` }, { label: 'DOM Content Loaded', name: 'domcontentloaded', - description: `When the complete HTML document\'s DOM has been loaded and parsed` + description: `When the complete HTML document's DOM has been loaded and parsed` }, { label: 'Network Idle 0', @@ -102,7 +103,7 @@ class Puppeteer_DocumentLoaders implements INode { type: 'string', optional: true, additionalParams: true, - description: 'CSS selectors like .div or #div', + description: 'CSS selectors like .div or #div' }, { label: 'Metadata', @@ -136,14 +137,14 @@ class Puppeteer_DocumentLoaders implements INode { args: ['--no-sandbox'], headless: 'new' } - }; + } if (waitUntilGoToOption) { config['gotoOptions'] = { waitUntil: waitUntilGoToOption } } if (waitForSelector) { - config['evaluate'] = async (page: Page, browser: Browser): Promise => { + config['evaluate'] = async (page: Page, _: Browser): Promise => { await page.waitForSelector(waitForSelector) const result = await page.evaluate(() => document.body.innerHTML) diff --git a/packages/server/marketplaces/chatflows/WebPage QnA.json b/packages/server/marketplaces/chatflows/WebPage QnA.json index 09246150..812f0bd5 100644 --- a/packages/server/marketplaces/chatflows/WebPage QnA.json +++ b/packages/server/marketplaces/chatflows/WebPage QnA.json @@ -16,11 +16,7 @@ "version": 1, "name": "chatOpenAI", "type": "ChatOpenAI", - "baseClasses": [ - "ChatOpenAI", - "BaseChatModel", - "BaseLanguageModel" - ], + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", "description": "Wrapper around OpenAI large language models that use the Chat endpoint", "inputParams": [ @@ -28,9 +24,7 @@ "label": "Connect Credential", "name": "credential", "type": "credential", - "credentialNames": [ - "openAIApi" - ], + "credentialNames": ["openAIApi"], "id": "chatOpenAI_0-input-credential-credential" }, { @@ -176,10 +170,7 @@ "version": 1, "name": "openAIEmbeddings", "type": "OpenAIEmbeddings", - "baseClasses": [ - "OpenAIEmbeddings", - "Embeddings" - ], + "baseClasses": ["OpenAIEmbeddings", "Embeddings"], "category": "Embeddings", "description": "OpenAI API to generate embeddings for a given text", "inputParams": [ @@ -187,9 +178,7 @@ "label": "Connect Credential", "name": "credential", "type": "credential", - "credentialNames": [ - "openAIApi" - ], + "credentialNames": ["openAIApi"], "id": "openAIEmbeddings_0-input-credential-credential" }, { @@ -329,10 +318,7 @@ "version": 1, "name": "conversationalRetrievalQAChain", "type": "ConversationalRetrievalQAChain", - "baseClasses": [ - "ConversationalRetrievalQAChain", - "BaseChain" - ], + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain"], "category": "Chains", "description": "Document QA - built on RetrievalQAChain to provide a chat history component", "inputParams": [ @@ -442,9 +428,7 @@ "version": 1, "name": "cheerioWebScraper", "type": "Document", - "baseClasses": [ - "Document" - ], + "baseClasses": ["Document"], "category": "Document Loaders", "description": "Load data from webpages", "inputParams": [ @@ -543,11 +527,7 @@ "version": 1, "name": "pineconeUpsert", "type": "Pinecone", - "baseClasses": [ - "Pinecone", - "VectorStoreRetriever", - "BaseRetriever" - ], + "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], "category": "Vector Stores", "description": "Upsert documents to Pinecone", "inputParams": [ @@ -555,9 +535,7 @@ "label": "Connect Credential", "name": "credential", "type": "credential", - "credentialNames": [ - "pineconeApi" - ], + "credentialNames": ["pineconeApi"], "id": "pineconeUpsert_0-input-credential-credential" }, { @@ -602,9 +580,7 @@ } ], "inputs": { - "document": [ - "{{cheerioWebScraper_0.data.instance}}" - ], + "document": ["{{cheerioWebScraper_0.data.instance}}"], "embeddings": "{{openAIEmbeddings_0.data.instance}}", "pineconeIndex": "", "pineconeNamespace": "", @@ -659,11 +635,7 @@ "version": 1, "name": "motorheadMemory", "type": "MotorheadMemory", - "baseClasses": [ - "MotorheadMemory", - "BaseChatMemory", - "BaseMemory" - ], + "baseClasses": ["MotorheadMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", "description": "Use Motorhead Memory to store chat conversations", "inputParams": [ @@ -673,9 +645,7 @@ "type": "credential", "optional": true, "description": "Only needed when using hosted solution - https://getmetal.io", - "credentialNames": [ - "motorheadMemoryApi" - ], + "credentialNames": ["motorheadMemoryApi"], "id": "motorheadMemory_0-input-credential-credential" }, { @@ -798,4 +768,4 @@ } } ] -} \ No newline at end of file +} From 913b3439562e8ad7bbeae6ffcfe530736c593de6 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 16 Aug 2023 23:26:18 +0100 Subject: [PATCH 396/398] add Zep VS and update Zep Memory --- .../nodes/memory/ZepMemory/ZepMemory.ts | 20 +- .../components/nodes/memory/ZepMemory/zep.png | Bin 17169 -> 25127 bytes .../nodes/vectorstores/Zep/Zep_Existing.ts | 235 ++++++++++++++++++ .../nodes/vectorstores/Zep/Zep_Upsert.ts | 133 ++++++++++ .../components/nodes/vectorstores/Zep/zep.png | Bin 0 -> 25127 bytes packages/components/package.json | 4 +- 6 files changed, 372 insertions(+), 20 deletions(-) create mode 100644 packages/components/nodes/vectorstores/Zep/Zep_Existing.ts create mode 100644 packages/components/nodes/vectorstores/Zep/Zep_Upsert.ts create mode 100644 packages/components/nodes/vectorstores/Zep/zep.png diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index f2a47852..0c05563a 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -120,7 +120,8 @@ class ZepMemory_Memory implements INode { zep.loadMemoryVariables = async (values) => { let data = await tmpFunc.bind(zep, values)() if (autoSummary && zep.returnMessages && data[zep.memoryKey] && data[zep.memoryKey].length) { - const memory = await zep.zepClient.getMemory(zep.sessionId, parseInt(k, 10) ?? 10) + const zepClient = await zep.zepClientPromise + const memory = await zepClient.memory.getMemory(zep.sessionId, parseInt(k, 10) ?? 10) if (memory?.summary) { let summary = autoSummaryTemplate.replace(/{summary}/g, memory.summary.content) // eslint-disable-next-line no-console @@ -190,23 +191,6 @@ class ZepMemoryExtended extends ZepMemory { super(fields) this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId } - - async clear(): Promise { - // Only clear when sessionId is using chatId - // If sessionId is specified, clearing and inserting again will error because the sessionId has been soft deleted - // If using chatId, it will not be a problem because the sessionId will always be the new chatId - if (this.isSessionIdUsingChatMessageId) { - try { - await this.zepClient.deleteMemory(this.sessionId) - } catch (error) { - console.error('Error deleting session: ', error) - } - - // Clear the superclass's chat history - await super.clear() - } - await this.chatHistory.clear() - } } module.exports = { nodeClass: ZepMemory_Memory } diff --git a/packages/components/nodes/memory/ZepMemory/zep.png b/packages/components/nodes/memory/ZepMemory/zep.png index 293be6f6de9a238631d52059b16c90aa8e395fdb..2fdb238271c40ec0de48c196b92f14115c944dbf 100644 GIT binary patch literal 25127 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 Y|Gacjls{jx3;+NC07*qoM6N<$f`z|uM*si- diff --git a/packages/components/nodes/vectorstores/Zep/Zep_Existing.ts b/packages/components/nodes/vectorstores/Zep/Zep_Existing.ts new file mode 100644 index 00000000..a2c2261f --- /dev/null +++ b/packages/components/nodes/vectorstores/Zep/Zep_Existing.ts @@ -0,0 +1,235 @@ +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ZepVectorStore, IZepConfig } from 'langchain/vectorstores/zep' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { IDocument, ZepClient } from '@getzep/zep-js' + +class Zep_Existing_VectorStores implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Zep Load Existing Index' + this.name = 'zepExistingIndex' + this.version = 1.0 + this.type = 'Zep' + this.icon = 'zep.png' + this.category = 'Vector Stores' + this.description = 'Load existing index from Zep (i.e: Document has been upserted)' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: true, + description: 'Configure JWT authentication on your Zep instance (Optional)', + credentialNames: ['zepMemoryApi'] + } + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Base URL', + name: 'baseURL', + type: 'string', + default: 'http://127.0.0.1:8000' + }, + { + label: 'Zep Collection', + name: 'zepCollection', + type: 'string', + placeholder: 'my-first-collection' + }, + { + label: 'Zep Metadata Filter', + name: 'zepMetadataFilter', + type: 'json', + optional: true, + additionalParams: true + }, + { + label: 'Embedding Dimension', + name: 'dimension', + type: 'number', + default: 1536, + additionalParams: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'Pinecone Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Pinecone Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(ZepVectorStore)] + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const baseURL = nodeData.inputs?.baseURL as string + const zepCollection = nodeData.inputs?.zepCollection as string + const zepMetadataFilter = nodeData.inputs?.zepMetadataFilter + const dimension = nodeData.inputs?.dimension as number + const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseFloat(topK) : 4 + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + + const zepConfig: IZepConfig & Partial = { + apiUrl: baseURL, + collectionName: zepCollection, + embeddingDimensions: dimension, + isAutoEmbedded: false + } + if (apiKey) zepConfig.apiKey = apiKey + if (zepMetadataFilter) { + const metadatafilter = typeof zepMetadataFilter === 'object' ? zepMetadataFilter : JSON.parse(zepMetadataFilter) + zepConfig.filter = metadatafilter + } + + const vectorStore = await ZepExistingVS.fromExistingIndex(embeddings, zepConfig) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +interface ZepFilter { + filter: Record +} + +function zepDocsToDocumentsAndScore(results: IDocument[]): [Document, number][] { + return results.map((d) => [ + new Document({ + pageContent: d.content, + metadata: d.metadata + }), + d.score ? d.score : 0 + ]) +} + +function assignMetadata(value: string | Record | object | undefined): Record | undefined { + if (typeof value === 'object' && value !== null) { + return value as Record + } + if (value !== undefined) { + console.warn('Metadata filters must be an object, Record, or undefined.') + } + return undefined +} + +class ZepExistingVS extends ZepVectorStore { + filter?: Record + args?: IZepConfig & Partial + + constructor(embeddings: Embeddings, args: IZepConfig & Partial) { + super(embeddings, args) + this.filter = args.filter + this.args = args + } + + async initalizeCollection(args: IZepConfig & Partial) { + this.client = await ZepClient.init(args.apiUrl, args.apiKey) + try { + this.collection = await this.client.document.getCollection(args.collectionName) + } catch (err) { + if (err instanceof Error) { + if (err.name === 'NotFoundError') { + await this.createNewCollection(args) + } else { + throw err + } + } + } + } + + async createNewCollection(args: IZepConfig & Partial) { + if (!args.embeddingDimensions) { + throw new Error( + `Collection ${args.collectionName} not found. You can create a new Collection by providing embeddingDimensions.` + ) + } + + this.collection = await this.client.document.addCollection({ + name: args.collectionName, + description: args.description, + metadata: args.metadata, + embeddingDimensions: args.embeddingDimensions, + isAutoEmbedded: false + }) + } + + async similaritySearchVectorWithScore( + query: number[], + k: number, + filter?: Record | undefined + ): Promise<[Document, number][]> { + if (filter && this.filter) { + throw new Error('cannot provide both `filter` and `this.filter`') + } + const _filters = filter ?? this.filter + const ANDFilters = [] + for (const filterKey in _filters) { + let filterVal = _filters[filterKey] + if (typeof filterVal === 'string') filterVal = `"${filterVal}"` + ANDFilters.push({ jsonpath: `$[*] ? (@.${filterKey} == ${filterVal})` }) + } + const newfilter = { + where: { and: ANDFilters } + } + await this.initalizeCollection(this.args!).catch((err) => { + console.error('Error initializing collection:', err) + throw err + }) + const results = await this.collection.search( + { + embedding: new Float32Array(query), + metadata: assignMetadata(newfilter) + }, + k + ) + return zepDocsToDocumentsAndScore(results) + } + + static async fromExistingIndex(embeddings: Embeddings, dbConfig: IZepConfig & Partial): Promise { + const instance = new this(embeddings, dbConfig) + return instance + } +} + +module.exports = { nodeClass: Zep_Existing_VectorStores } diff --git a/packages/components/nodes/vectorstores/Zep/Zep_Upsert.ts b/packages/components/nodes/vectorstores/Zep/Zep_Upsert.ts new file mode 100644 index 00000000..0f976d2b --- /dev/null +++ b/packages/components/nodes/vectorstores/Zep/Zep_Upsert.ts @@ -0,0 +1,133 @@ +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ZepVectorStore, IZepConfig } from 'langchain/vectorstores/zep' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { flatten } from 'lodash' + +class Zep_Upsert_VectorStores implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Zep Upsert Document' + this.name = 'zepUpsert' + this.version = 1.0 + this.type = 'Zep' + this.icon = 'zep.png' + this.category = 'Vector Stores' + this.description = 'Upsert documents to Zep' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: true, + description: 'Configure JWT authentication on your Zep instance (Optional)', + credentialNames: ['zepMemoryApi'] + } + this.inputs = [ + { + label: 'Document', + name: 'document', + type: 'Document', + list: true + }, + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Base URL', + name: 'baseURL', + type: 'string', + default: 'http://127.0.0.1:8000' + }, + { + label: 'Zep Collection', + name: 'zepCollection', + type: 'string', + placeholder: 'my-first-collection' + }, + { + label: 'Embedding Dimension', + name: 'dimension', + type: 'number', + default: 1536, + additionalParams: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'Zep Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Zep Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(ZepVectorStore)] + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const baseURL = nodeData.inputs?.baseURL as string + const zepCollection = nodeData.inputs?.zepCollection as string + const dimension = (nodeData.inputs?.dimension as number) ?? 1536 + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const topK = nodeData.inputs?.topK as string + const k = topK ? parseFloat(topK) : 4 + const output = nodeData.outputs?.output as string + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + finalDocs.push(new Document(flattenDocs[i])) + } + + const zepConfig: IZepConfig = { + apiUrl: baseURL, + collectionName: zepCollection, + embeddingDimensions: dimension, + isAutoEmbedded: false + } + if (apiKey) zepConfig.apiKey = apiKey + + const vectorStore = await ZepVectorStore.fromDocuments(finalDocs, embeddings, zepConfig) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: Zep_Upsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/Zep/zep.png b/packages/components/nodes/vectorstores/Zep/zep.png new file mode 100644 index 0000000000000000000000000000000000000000..2fdb238271c40ec0de48c196b92f14115c944dbf GIT binary patch literal 25127 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 av;QB=>6btFOcTZc0000 Date: Wed, 16 Aug 2023 20:07:22 -0300 Subject: [PATCH 397/398] Added BaseOptions to ChatOpenAI --- .../nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts index 9512da66..ca081ff4 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts @@ -125,6 +125,13 @@ class ChatOpenAI_ChatModels implements INode { type: 'string', optional: true, additionalParams: true + }, + { + label: 'BaseOptions', + name: 'baseOptions', + type: 'json', + optional: true, + additionalParams: true } ] } @@ -139,6 +146,7 @@ class ChatOpenAI_ChatModels implements INode { const timeout = nodeData.inputs?.timeout as string const streaming = nodeData.inputs?.streaming as boolean const basePath = nodeData.inputs?.basepath as string + const baseOptions = nodeData.inputs?.baseOptions const credentialData = await getCredentialData(nodeData.credential ?? '', options) const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) @@ -156,8 +164,18 @@ class ChatOpenAI_ChatModels implements INode { if (presencePenalty) obj.presencePenalty = parseFloat(presencePenalty) if (timeout) obj.timeout = parseInt(timeout, 10) + let parsedBaseOptions: any | undefined = undefined + + if (baseOptions) { + try { + parsedBaseOptions = typeof baseOptions === 'object' ? baseOptions : JSON.parse(baseOptions) + } catch (exception) { + throw new Error("Invalid JSON in the ChatOpenAI's BaseOptions: " + exception) + } + } const model = new ChatOpenAI(obj, { - basePath + basePath, + baseOptions: parsedBaseOptions }) return model } From c33a4a78d664839c947799e318443dcff42edd14 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 17 Aug 2023 16:15:00 +0100 Subject: [PATCH 398/398] fix source link return host name --- packages/ui/src/views/chatmessage/ChatMessage.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 98eef72a..506f02da 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -346,7 +346,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { key={index} label={ URL - ? `${URL.pathname.substring(0, 15)}...` + ? URL.pathname.substring(0, 15) === '/' + ? URL.host + : `${URL.pathname.substring(0, 15)}...` : `${source.pageContent.substring(0, 15)}...` } component='a'