diff --git a/package.json b/package.json index 13db19d1..e894a437 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.8", + "version": "1.3.9", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/components/credentials/RedisCacheUrlApi.credential.ts b/packages/components/credentials/RedisCacheUrlApi.credential.ts new file mode 100644 index 00000000..fc2e2eb2 --- /dev/null +++ b/packages/components/credentials/RedisCacheUrlApi.credential.ts @@ -0,0 +1,25 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class RedisCacheUrlApi implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Redis Cache URL' + this.name = 'redisCacheUrlApi' + this.version = 1.0 + this.inputs = [ + { + label: 'Redis URL', + name: 'redisUrl', + type: 'string', + default: '127.0.0.1' + } + ] + } +} + +module.exports = { credClass: RedisCacheUrlApi } diff --git a/packages/components/credentials/SearchApi.credential.ts b/packages/components/credentials/SearchApi.credential.ts new file mode 100644 index 00000000..f23577a1 --- /dev/null +++ b/packages/components/credentials/SearchApi.credential.ts @@ -0,0 +1,26 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class SearchApi implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Search API' + this.name = 'searchApi' + this.version = 1.0 + this.description = + 'Sign in to SearchApi to obtain a free API key from the dashboard.' + this.inputs = [ + { + label: 'SearchApi API Key', + name: 'searchApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: SearchApi } diff --git a/packages/components/nodes/agents/CSVAgent/CSVAgent.ts b/packages/components/nodes/agents/CSVAgent/CSVAgent.ts index 905bef02..5baad2ec 100644 --- a/packages/components/nodes/agents/CSVAgent/CSVAgent.ts +++ b/packages/components/nodes/agents/CSVAgent/CSVAgent.ts @@ -131,7 +131,7 @@ json.dumps(my_dict)` const code = `import pandas as pd\n${pythonCode}` finalResult = await pyodide.runPythonAsync(code) } catch (error) { - throw new Error(`Sorry, I'm unable to find answer for question: "${input}" using follwoing code: "${pythonCode}"`) + throw new Error(`Sorry, I'm unable to find answer for question: "${input}" using following code: "${pythonCode}"`) } } diff --git a/packages/components/nodes/cache/RedisCache/RedisCache.ts b/packages/components/nodes/cache/RedisCache/RedisCache.ts index 38998a46..3b68cf12 100644 --- a/packages/components/nodes/cache/RedisCache/RedisCache.ts +++ b/packages/components/nodes/cache/RedisCache/RedisCache.ts @@ -30,7 +30,7 @@ class RedisCache implements INode { name: 'credential', type: 'credential', optional: true, - credentialNames: ['redisCacheApi'] + credentialNames: ['redisCacheApi', 'redisCacheUrlApi'] } this.inputs = [ { @@ -48,17 +48,24 @@ class RedisCache implements INode { const ttl = nodeData.inputs?.ttl as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) - const username = getCredentialParam('redisCacheUser', credentialData, nodeData) - const password = getCredentialParam('redisCachePwd', credentialData, nodeData) - const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) - const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + const redisUrl = getCredentialParam('redisUrl', credentialData, nodeData) - const client = new Redis({ - port: portStr ? parseInt(portStr) : 6379, - host, - username, - password - }) + let client: Redis + if (!redisUrl || redisUrl === '') { + const username = getCredentialParam('redisCacheUser', credentialData, nodeData) + const password = getCredentialParam('redisCachePwd', credentialData, nodeData) + const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) + const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + + client = new Redis({ + port: portStr ? parseInt(portStr) : 6379, + host, + username, + password + }) + } else { + client = new Redis(redisUrl) + } const redisClient = new LangchainRedisCache(client) diff --git a/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts index 4eecb1f5..f15869d7 100644 --- a/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts +++ b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts @@ -30,7 +30,7 @@ class RedisEmbeddingsCache implements INode { name: 'credential', type: 'credential', optional: true, - credentialNames: ['redisCacheApi'] + credentialNames: ['redisCacheApi', 'redisCacheUrlApi'] } this.inputs = [ { @@ -63,17 +63,25 @@ class RedisEmbeddingsCache implements INode { const underlyingEmbeddings = nodeData.inputs?.embeddings as Embeddings const credentialData = await getCredentialData(nodeData.credential ?? '', options) - const username = getCredentialParam('redisCacheUser', credentialData, nodeData) - const password = getCredentialParam('redisCachePwd', credentialData, nodeData) - const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) - const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + const redisUrl = getCredentialParam('redisUrl', credentialData, nodeData) + + let client: Redis + if (!redisUrl || redisUrl === '') { + const username = getCredentialParam('redisCacheUser', credentialData, nodeData) + const password = getCredentialParam('redisCachePwd', credentialData, nodeData) + const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) + const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + + client = new Redis({ + port: portStr ? parseInt(portStr) : 6379, + host, + username, + password + }) + } else { + client = new Redis(redisUrl) + } - const client = new Redis({ - port: portStr ? parseInt(portStr) : 6379, - host, - username, - password - }) ttl ??= '3600' let ttlNumber = parseInt(ttl, 10) const redisStore = new RedisByteStore({ diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index f12cd30b..92a0b5ea 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -71,7 +71,9 @@ class ConversationChain_Chains implements INode { const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } let finalText = '' diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index 63994b13..0d555884 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -3,6 +3,10 @@ import { getBaseClasses, handleEscapeCharacters } from '../../../src/utils' import { LLMChain } from 'langchain/chains' import { BaseLanguageModel } from 'langchain/base_language' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' +import { BaseOutputParser } from 'langchain/schema/output_parser' +import { formatResponse, injectOutputParser } from '../../outputparsers/OutputParserHelpers' +import { BaseLLMOutputParser } from 'langchain/schema/output_parser' +import { OutputFixingParser } from 'langchain/output_parsers' class LLMChain_Chains implements INode { label: string @@ -15,11 +19,12 @@ class LLMChain_Chains implements INode { description: string inputs: INodeParams[] outputs: INodeOutputsValue[] + outputParser: BaseOutputParser constructor() { this.label = 'LLM Chain' this.name = 'llmChain' - this.version = 1.0 + this.version = 3.0 this.type = 'LLMChain' this.icon = 'chain.svg' this.category = 'Chains' @@ -36,6 +41,12 @@ class LLMChain_Chains implements INode { name: 'prompt', type: 'BasePromptTemplate' }, + { + label: 'Output Parser', + name: 'outputParser', + type: 'BaseLLMOutputParser', + optional: true + }, { label: 'Chain Name', name: 'chainName', @@ -63,12 +74,29 @@ class LLMChain_Chains implements INode { const prompt = nodeData.inputs?.prompt const output = nodeData.outputs?.output as string const promptValues = prompt.promptValues as ICommonObject - + const llmOutputParser = nodeData.inputs?.outputParser as BaseOutputParser + this.outputParser = llmOutputParser + if (llmOutputParser) { + let autoFix = (llmOutputParser as any).autoFix + if (autoFix === true) { + this.outputParser = OutputFixingParser.fromLLM(model, llmOutputParser) + } + } if (output === this.name) { - const chain = new LLMChain({ llm: model, prompt, verbose: process.env.DEBUG === 'true' ? true : false }) + const chain = new LLMChain({ + llm: model, + outputParser: this.outputParser as BaseLLMOutputParser, + prompt, + verbose: process.env.DEBUG === 'true' + }) return chain } else if (output === 'outputPrediction') { - const chain = new LLMChain({ llm: model, prompt, verbose: process.env.DEBUG === 'true' ? true : false }) + const chain = new LLMChain({ + llm: model, + outputParser: this.outputParser as BaseLLMOutputParser, + prompt, + verbose: process.env.DEBUG === 'true' + }) const inputVariables = chain.prompt.inputVariables as string[] // ["product"] const res = await runPrediction(inputVariables, chain, input, promptValues, options, nodeData) // eslint-disable-next-line no-console @@ -84,10 +112,15 @@ class LLMChain_Chains implements INode { } } - async run(nodeData: INodeData, input: string, options: ICommonObject): 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 + let promptValues: ICommonObject | undefined = nodeData.inputs?.prompt.promptValues as ICommonObject + const outputParser = nodeData.inputs?.outputParser as BaseOutputParser + if (!this.outputParser && outputParser) { + this.outputParser = outputParser + } + promptValues = injectOutputParser(this.outputParser, chain, promptValues) const res = await runPrediction(inputVariables, chain, input, promptValues, options, nodeData) // eslint-disable-next-line no-console console.log('\x1b[93m\x1b[1m\n*****FINAL RESULT*****\n\x1b[0m\x1b[0m') @@ -99,9 +132,9 @@ class LLMChain_Chains implements INode { const runPrediction = async ( inputVariables: string[], - chain: LLMChain, + chain: LLMChain, input: string, - promptValuesRaw: ICommonObject, + promptValuesRaw: ICommonObject | undefined, options: ICommonObject, nodeData: INodeData ) => { @@ -135,10 +168,10 @@ const runPrediction = async ( if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) const res = await chain.call(options, [loggerHandler, handler, ...callbacks]) - return res?.text + return formatResponse(res?.text) } else { const res = await chain.call(options, [loggerHandler, ...callbacks]) - return res?.text + return formatResponse(res?.text) } } else if (seen.length === 1) { // If one inputVariable is not specify, use input (user's question) as value @@ -151,10 +184,10 @@ const runPrediction = async ( if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) const res = await chain.call(options, [loggerHandler, handler, ...callbacks]) - return res?.text + return formatResponse(res?.text) } else { const res = await chain.call(options, [loggerHandler, ...callbacks]) - return res?.text + return formatResponse(res?.text) } } else { throw new Error(`Please provide Prompt Values for: ${seen.join(', ')}`) @@ -163,10 +196,10 @@ const runPrediction = async ( if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) const res = await chain.run(input, [loggerHandler, handler, ...callbacks]) - return res + return formatResponse(res) } else { const res = await chain.run(input, [loggerHandler, ...callbacks]) - return res + return formatResponse(res) } } } diff --git a/packages/components/nodes/documentloaders/SearchApi/SearchAPI.ts b/packages/components/nodes/documentloaders/SearchApi/SearchAPI.ts new file mode 100644 index 00000000..b7f0b0b5 --- /dev/null +++ b/packages/components/nodes/documentloaders/SearchApi/SearchAPI.ts @@ -0,0 +1,109 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { TextSplitter } from 'langchain/text_splitter' +import { SearchApiLoader } from 'langchain/document_loaders/web/searchapi' +import { getCredentialData, getCredentialParam } from '../../../src' + +// Provides access to multiple search engines using the SearchApi. +// For available parameters & engines, refer to: https://www.searchapi.io/docs/google +class SearchAPI_DocumentLoaders 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 = 'SearchApi For Web Search' + this.name = 'searchApi' + this.version = 1.0 + this.type = 'Document' + this.icon = 'searchapi.svg' + this.category = 'Document Loaders' + this.description = 'Load data from real-time search results' + this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: false, + credentialNames: ['searchApi'] + } + this.inputs = [ + { + label: 'Query', + name: 'query', + type: 'string', + optional: true + }, + { + label: 'Custom Parameters', + name: 'customParameters', + type: 'json', + optional: true, + additionalParams: true + }, + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + 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 query = nodeData.inputs?.query as string + const customParameters = nodeData.inputs?.customParameters + const metadata = nodeData.inputs?.metadata + + // Fetch the API credentials for this node + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const searchApiKey = getCredentialParam('searchApiKey', credentialData, nodeData) + + // Check and parse custom parameters (should be JSON or object) + const parsedParameters = typeof customParameters === 'object' ? customParameters : JSON.parse(customParameters || '{}') + + // Prepare the configuration for the SearchApiLoader + const loaderConfig = { + q: query, + apiKey: searchApiKey, + ...parsedParameters + } + + // Initialize the loader with the given configuration + const loader = new SearchApiLoader(loaderConfig) + + // Fetch documents, split if a text splitter is provided + 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: SearchAPI_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/SearchApi/searchapi.svg b/packages/components/nodes/documentloaders/SearchApi/searchapi.svg new file mode 100644 index 00000000..c44c29c4 --- /dev/null +++ b/packages/components/nodes/documentloaders/SearchApi/searchapi.svg @@ -0,0 +1 @@ +0479_octopus_verti diff --git a/packages/components/nodes/documentloaders/Text/Text.ts b/packages/components/nodes/documentloaders/Text/Text.ts index c3e3b61e..3f12e490 100644 --- a/packages/components/nodes/documentloaders/Text/Text.ts +++ b/packages/components/nodes/documentloaders/Text/Text.ts @@ -19,7 +19,7 @@ class Text_DocumentLoaders implements INode { constructor() { this.label = 'Text File' this.name = 'textFile' - this.version = 2.0 + this.version = 3.0 this.type = 'Document' this.icon = 'textFile.svg' this.category = 'Document Loaders' @@ -30,7 +30,8 @@ class Text_DocumentLoaders implements INode { label: 'Txt File', name: 'txtFile', type: 'file', - fileType: '.txt' + fileType: + '.txt, .html, .aspx, .asp, .cpp, .c, .cs, .css, .go, .h, .java, .js, .less, .ts, .php, .proto, .python, .py, .rst, .ruby, .rb, .rs, .scala, .sc, .scss, .sol, .sql, .swift, .markdown, .md, .tex, .ltx, .vb, .xml' }, { label: 'Text Splitter', diff --git a/packages/components/nodes/documentloaders/VectorStoreToDocument/VectorStoreToDocument.ts b/packages/components/nodes/documentloaders/VectorStoreToDocument/VectorStoreToDocument.ts index b3f320ce..becd0ac6 100644 --- a/packages/components/nodes/documentloaders/VectorStoreToDocument/VectorStoreToDocument.ts +++ b/packages/components/nodes/documentloaders/VectorStoreToDocument/VectorStoreToDocument.ts @@ -17,7 +17,7 @@ class VectorStoreToDocument_DocumentLoaders implements INode { constructor() { this.label = 'VectorStore To Document' this.name = 'vectorStoreToDocument' - this.version = 1.0 + this.version = 2.0 this.type = 'Document' this.icon = 'vectorretriever.svg' this.category = 'Document Loaders' @@ -29,6 +29,14 @@ class VectorStoreToDocument_DocumentLoaders implements INode { name: 'vectorStore', type: 'VectorStore' }, + { + label: 'Query', + name: 'query', + type: 'string', + description: 'Query to retrieve documents from vector database. If not specified, user question will be used', + optional: true, + acceptVariable: true + }, { label: 'Minimum Score (%)', name: 'minScore', @@ -56,11 +64,12 @@ class VectorStoreToDocument_DocumentLoaders implements INode { async init(nodeData: INodeData, input: string): Promise { const vectorStore = nodeData.inputs?.vectorStore as VectorStore const minScore = nodeData.inputs?.minScore as number + const query = nodeData.inputs?.query as string const output = nodeData.outputs?.output as string const topK = (vectorStore as any)?.k ?? 4 - const docs = await vectorStore.similaritySearchWithScore(input, topK) + const docs = await vectorStore.similaritySearchWithScore(query ?? 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 diff --git a/packages/components/nodes/embeddings/OpenAIEmbeddingCustom/OpenAIEmbeddingCustom.ts b/packages/components/nodes/embeddings/OpenAIEmbeddingCustom/OpenAIEmbeddingCustom.ts new file mode 100644 index 00000000..185236b1 --- /dev/null +++ b/packages/components/nodes/embeddings/OpenAIEmbeddingCustom/OpenAIEmbeddingCustom.ts @@ -0,0 +1,94 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { OpenAIEmbeddings, OpenAIEmbeddingsParams } from 'langchain/embeddings/openai' + +class OpenAIEmbeddingCustom_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 = 'OpenAI Embeddings Custom' + this.name = 'openAIEmbeddingsCustom' + this.version = 1.0 + this.type = 'OpenAIEmbeddingsCustom' + this.icon = 'openai.png' + 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: 'Strip New Lines', + name: 'stripNewLines', + type: 'boolean', + optional: true, + additionalParams: true + }, + { + label: 'Batch Size', + name: 'batchSize', + type: 'number', + optional: true, + additionalParams: true + }, + { + label: 'Timeout', + name: 'timeout', + type: 'number', + optional: true, + additionalParams: true + }, + { + label: 'BasePath', + name: 'basepath', + type: 'string', + optional: true, + additionalParams: true + }, + { + label: 'Model Name', + name: 'modelName', + type: 'string', + optional: true + } + ] + } + + 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 modelName = nodeData.inputs?.modelName as string + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) + + const obj: Partial & { openAIApiKey?: string } = { + openAIApiKey + } + + if (stripNewLines) obj.stripNewLines = stripNewLines + if (batchSize) obj.batchSize = parseInt(batchSize, 10) + if (timeout) obj.timeout = parseInt(timeout, 10) + if (modelName) obj.modelName = modelName + + const model = new OpenAIEmbeddings(obj, { basePath }) + return model + } +} + +module.exports = { nodeClass: OpenAIEmbeddingCustom_Embeddings } diff --git a/packages/components/nodes/embeddings/OpenAIEmbeddingCustom/openai.png b/packages/components/nodes/embeddings/OpenAIEmbeddingCustom/openai.png new file mode 100644 index 00000000..de08a05b Binary files /dev/null and b/packages/components/nodes/embeddings/OpenAIEmbeddingCustom/openai.png differ diff --git a/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts index cf8e7f1d..84e607e5 100644 --- a/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts +++ b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts @@ -20,7 +20,7 @@ class BufferWindowMemory_Memory implements INode { 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.description = 'Uses a window of size k to surface the last k back-and-forth to use as memory' this.baseClasses = [this.type, ...getBaseClasses(BufferWindowMemory)] this.inputs = [ { @@ -40,7 +40,7 @@ class BufferWindowMemory_Memory implements INode { name: 'k', type: 'number', default: '4', - description: 'Window of size k to surface the last k back-and-forths to use as memory.' + description: 'Window of size k to surface the last k back-and-forth to use as memory.' } ] } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index f10f25ce..c65d729b 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -1,9 +1,9 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' -import { ICommonObject } from '../../../src' +import { INode, INodeData, INodeParams, ICommonObject } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' -import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/redis' -import { createClient } from 'redis' +import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/ioredis' +import { mapStoredMessageToChatMessage, BaseMessage } from 'langchain/schema' +import { Redis } from 'ioredis' class RedisBackedChatMemory_Memory implements INode { label: string @@ -15,23 +15,25 @@ class RedisBackedChatMemory_Memory implements INode { category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams constructor() { this.label = 'Redis-Backed Chat Memory' this.name = 'RedisBackedChatMemory' - this.version = 1.0 + this.version = 2.0 this.type = 'RedisBackedChatMemory' 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)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: true, + credentialNames: ['redisCacheApi', 'redisCacheUrlApi'] + } this.inputs = [ - { - label: 'Base URL', - name: 'baseURL', - type: 'string', - default: 'redis://localhost:6379' - }, { label: 'Session Id', name: 'sessionId', @@ -60,11 +62,11 @@ class RedisBackedChatMemory_Memory implements INode { } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - return initalizeRedis(nodeData, options) + return await initalizeRedis(nodeData, options) } async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const redis = initalizeRedis(nodeData, options) + const redis = await initalizeRedis(nodeData, options) const sessionId = nodeData.inputs?.sessionId as string const chatId = options?.chatId as string options.logger.info(`Clearing Redis memory session ${sessionId ? sessionId : chatId}`) @@ -73,8 +75,7 @@ class RedisBackedChatMemory_Memory implements INode { } } -const initalizeRedis = (nodeData: INodeData, options: ICommonObject): BufferMemory => { - const baseURL = nodeData.inputs?.baseURL as string +const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Promise => { const sessionId = nodeData.inputs?.sessionId as string const sessionTTL = nodeData.inputs?.sessionTTL as number const memoryKey = nodeData.inputs?.memoryKey as string @@ -83,10 +84,29 @@ const initalizeRedis = (nodeData: INodeData, options: ICommonObject): BufferMemo let isSessionIdUsingChatMessageId = false if (!sessionId && chatId) isSessionIdUsingChatMessageId = true - const redisClient = createClient({ url: baseURL }) + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const redisUrl = getCredentialParam('redisUrl', credentialData, nodeData) + + let client: Redis + if (!redisUrl || redisUrl === '') { + const username = getCredentialParam('redisCacheUser', credentialData, nodeData) + const password = getCredentialParam('redisCachePwd', credentialData, nodeData) + const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) + const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + + client = new Redis({ + port: portStr ? parseInt(portStr) : 6379, + host, + username, + password + }) + } else { + client = new Redis(redisUrl) + } + let obj: RedisChatMessageHistoryInput = { sessionId: sessionId ? sessionId : chatId, - client: redisClient + client } if (sessionTTL) { @@ -98,10 +118,27 @@ const initalizeRedis = (nodeData: INodeData, options: ICommonObject): BufferMemo const redisChatMessageHistory = new RedisChatMessageHistory(obj) + redisChatMessageHistory.getMessages = async (): Promise => { + const rawStoredMessages = await client.lrange((redisChatMessageHistory as any).sessionId, 0, -1) + const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message)) + return orderedMessages.map(mapStoredMessageToChatMessage) + } + + redisChatMessageHistory.addMessage = async (message: BaseMessage): Promise => { + const messageToAdd = [message].map((msg) => msg.toDict()) + await client.lpush((redisChatMessageHistory as any).sessionId, JSON.stringify(messageToAdd[0])) + if (sessionTTL) { + await client.expire((redisChatMessageHistory as any).sessionId, sessionTTL) + } + } + + redisChatMessageHistory.clear = async (): Promise => { + await client.del((redisChatMessageHistory as any).sessionId) + } + const memory = new BufferMemoryExtended({ memoryKey, chatHistory: redisChatMessageHistory, - returnMessages: true, isSessionIdUsingChatMessageId }) return memory diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 0c05563a..c4498644 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -60,7 +60,7 @@ class ZepMemory_Memory implements INode { name: 'k', type: 'number', default: '10', - description: 'Window of size k to surface the last k back-and-forths to use as memory.' + description: 'Window of size k to surface the last k back-and-forth to use as memory.' }, { label: 'Auto Summary Template', diff --git a/packages/components/nodes/outputparsers/CSVListOutputParser/CSVListOutputParser.ts b/packages/components/nodes/outputparsers/CSVListOutputParser/CSVListOutputParser.ts new file mode 100644 index 00000000..8758d4f7 --- /dev/null +++ b/packages/components/nodes/outputparsers/CSVListOutputParser/CSVListOutputParser.ts @@ -0,0 +1,52 @@ +import { getBaseClasses, INode, INodeData, INodeParams } from '../../../src' +import { BaseOutputParser } from 'langchain/schema/output_parser' +import { CommaSeparatedListOutputParser } from 'langchain/output_parsers' +import { CATEGORY } from '../OutputParserHelpers' + +class CSVListOutputParser implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'CSV Output Parser' + this.name = 'csvOutputParser' + this.version = 1.0 + this.type = 'CSVListOutputParser' + this.description = 'Parse the output of an LLM call as a comma-separated list of values' + this.icon = 'csv.png' + this.category = CATEGORY + this.baseClasses = [this.type, ...getBaseClasses(BaseOutputParser)] + this.inputs = [ + { + label: 'Autofix', + name: 'autofixParser', + type: 'boolean', + optional: true, + description: 'In the event that the first call fails, will make another call to the model to fix any errors.' + } + ] + } + + async init(nodeData: INodeData): Promise { + const autoFix = nodeData.inputs?.autofixParser as boolean + + const commaSeparatedListOutputParser = new CommaSeparatedListOutputParser() + Object.defineProperty(commaSeparatedListOutputParser, 'autoFix', { + enumerable: true, + configurable: true, + writable: true, + value: autoFix + }) + return commaSeparatedListOutputParser + } +} + +module.exports = { nodeClass: CSVListOutputParser } diff --git a/packages/components/nodes/outputparsers/CSVListOutputParser/csv.png b/packages/components/nodes/outputparsers/CSVListOutputParser/csv.png new file mode 100644 index 00000000..41b84e16 Binary files /dev/null and b/packages/components/nodes/outputparsers/CSVListOutputParser/csv.png differ diff --git a/packages/components/nodes/outputparsers/CustomListOutputParser/CustomListOutputParser.ts b/packages/components/nodes/outputparsers/CustomListOutputParser/CustomListOutputParser.ts new file mode 100644 index 00000000..06523afb --- /dev/null +++ b/packages/components/nodes/outputparsers/CustomListOutputParser/CustomListOutputParser.ts @@ -0,0 +1,71 @@ +import { getBaseClasses, INode, INodeData, INodeParams } from '../../../src' +import { BaseOutputParser } from 'langchain/schema/output_parser' +import { CustomListOutputParser as LangchainCustomListOutputParser } from 'langchain/output_parsers' +import { CATEGORY } from '../OutputParserHelpers' + +class CustomListOutputParser implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'Custom List Output Parser' + this.name = 'customListOutputParser' + this.version = 1.0 + this.type = 'CustomListOutputParser' + this.description = 'Parse the output of an LLM call as a list of values.' + this.icon = 'list.png' + this.category = CATEGORY + this.baseClasses = [this.type, ...getBaseClasses(BaseOutputParser)] + this.inputs = [ + { + label: 'Length', + name: 'length', + type: 'number', + default: 5, + step: 1, + description: 'Number of values to return' + }, + { + label: 'Separator', + name: 'separator', + type: 'string', + description: 'Separator between values', + default: ',' + }, + { + label: 'Autofix', + name: 'autofixParser', + type: 'boolean', + optional: true, + description: 'In the event that the first call fails, will make another call to the model to fix any errors.' + } + ] + } + + async init(nodeData: INodeData): Promise { + const separator = nodeData.inputs?.separator as string + const lengthStr = nodeData.inputs?.length as string + const autoFix = nodeData.inputs?.autofixParser as boolean + let length = 5 + if (lengthStr) length = parseInt(lengthStr, 10) + + const parser = new LangchainCustomListOutputParser({ length: length, separator: separator }) + Object.defineProperty(parser, 'autoFix', { + enumerable: true, + configurable: true, + writable: true, + value: autoFix + }) + return parser + } +} + +module.exports = { nodeClass: CustomListOutputParser } diff --git a/packages/components/nodes/outputparsers/CustomListOutputParser/list.png b/packages/components/nodes/outputparsers/CustomListOutputParser/list.png new file mode 100644 index 00000000..acb4e5d6 Binary files /dev/null and b/packages/components/nodes/outputparsers/CustomListOutputParser/list.png differ diff --git a/packages/components/nodes/outputparsers/OutputParserHelpers.ts b/packages/components/nodes/outputparsers/OutputParserHelpers.ts new file mode 100644 index 00000000..a94edddd --- /dev/null +++ b/packages/components/nodes/outputparsers/OutputParserHelpers.ts @@ -0,0 +1,46 @@ +import { BaseOutputParser } from 'langchain/schema/output_parser' +import { LLMChain } from 'langchain/chains' +import { BaseLanguageModel } from 'langchain/base_language' +import { ICommonObject } from '../../src' +import { ChatPromptTemplate, FewShotPromptTemplate, PromptTemplate, SystemMessagePromptTemplate } from 'langchain/prompts' + +export const CATEGORY = 'Output Parsers' + +export const formatResponse = (response: string | object): string | object => { + if (typeof response === 'object') { + return { json: response } + } + return response +} + +export const injectOutputParser = ( + outputParser: BaseOutputParser, + chain: LLMChain, + promptValues: ICommonObject | undefined = undefined +) => { + if (outputParser && chain.prompt) { + const formatInstructions = outputParser.getFormatInstructions() + if (chain.prompt instanceof PromptTemplate) { + let pt = chain.prompt + pt.template = pt.template + '\n{format_instructions}' + chain.prompt.partialVariables = { format_instructions: formatInstructions } + } else if (chain.prompt instanceof ChatPromptTemplate) { + let pt = chain.prompt + pt.promptMessages.forEach((msg) => { + if (msg instanceof SystemMessagePromptTemplate) { + ;(msg.prompt as any).partialVariables = { format_instructions: outputParser.getFormatInstructions() } + ;(msg.prompt as any).template = ((msg.prompt as any).template + '\n{format_instructions}') as string + } + }) + } else if (chain.prompt instanceof FewShotPromptTemplate) { + chain.prompt.examplePrompt.partialVariables = { format_instructions: formatInstructions } + chain.prompt.examplePrompt.template = chain.prompt.examplePrompt.template + '\n{format_instructions}' + } + + chain.prompt.inputVariables.push('format_instructions') + if (promptValues) { + promptValues = { ...promptValues, format_instructions: outputParser.getFormatInstructions() } + } + } + return promptValues +} diff --git a/packages/components/nodes/outputparsers/StructuredOutputParser/StructuredOutputParser.ts b/packages/components/nodes/outputparsers/StructuredOutputParser/StructuredOutputParser.ts new file mode 100644 index 00000000..10a5f0bb --- /dev/null +++ b/packages/components/nodes/outputparsers/StructuredOutputParser/StructuredOutputParser.ts @@ -0,0 +1,90 @@ +import { convertSchemaToZod, getBaseClasses, INode, INodeData, INodeParams } from '../../../src' +import { BaseOutputParser } from 'langchain/schema/output_parser' +import { StructuredOutputParser as LangchainStructuredOutputParser } from 'langchain/output_parsers' +import { CATEGORY } from '../OutputParserHelpers' +import { z } from 'zod' + +class StructuredOutputParser implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'Structured Output Parser' + this.name = 'structuredOutputParser' + this.version = 1.0 + this.type = 'StructuredOutputParser' + this.description = 'Parse the output of an LLM call into a given (JSON) structure.' + this.icon = 'structure.png' + this.category = CATEGORY + this.baseClasses = [this.type, ...getBaseClasses(BaseOutputParser)] + this.inputs = [ + { + label: 'Autofix', + name: 'autofixParser', + type: 'boolean', + optional: true, + description: 'In the event that the first call fails, will make another call to the model to fix any errors.' + }, + { + label: 'JSON Structure', + name: 'jsonStructure', + type: 'datagrid', + description: 'JSON structure for LLM to return', + datagrid: [ + { field: 'property', headerName: 'Property', editable: true }, + { + field: 'type', + headerName: 'Type', + type: 'singleSelect', + valueOptions: ['string', 'number', 'boolean'], + editable: true + }, + { field: 'description', headerName: 'Description', editable: true, flex: 1 } + ], + default: [ + { + property: 'answer', + type: 'string', + description: `answer to the user's question` + }, + { + property: 'source', + type: 'string', + description: `sources used to answer the question, should be websites` + } + ], + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const jsonStructure = nodeData.inputs?.jsonStructure as string + const autoFix = nodeData.inputs?.autofixParser as boolean + + try { + const structuredOutputParser = LangchainStructuredOutputParser.fromZodSchema(z.object(convertSchemaToZod(jsonStructure))) + + // NOTE: When we change Flowise to return a json response, the following has to be changed to: JsonStructuredOutputParser + Object.defineProperty(structuredOutputParser, 'autoFix', { + enumerable: true, + configurable: true, + writable: true, + value: autoFix + }) + return structuredOutputParser + } catch (exception) { + throw new Error('Invalid JSON in StructuredOutputParser: ' + exception) + } + } +} + +module.exports = { nodeClass: StructuredOutputParser } diff --git a/packages/components/nodes/outputparsers/StructuredOutputParser/structure.png b/packages/components/nodes/outputparsers/StructuredOutputParser/structure.png new file mode 100644 index 00000000..c56b2dd7 Binary files /dev/null and b/packages/components/nodes/outputparsers/StructuredOutputParser/structure.png differ diff --git a/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts b/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts index 83e1a857..de86413c 100644 --- a/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts +++ b/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts @@ -51,7 +51,7 @@ class ChatPromptTemplate_Prompts implements INode { async init(nodeData: INodeData): Promise { const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string const humanMessagePrompt = nodeData.inputs?.humanMessagePrompt as string - const promptValuesStr = nodeData.inputs?.promptValues as string + const promptValuesStr = nodeData.inputs?.promptValues const prompt = ChatPromptTemplate.fromMessages([ SystemMessagePromptTemplate.fromTemplate(systemMessagePrompt), @@ -60,7 +60,11 @@ class ChatPromptTemplate_Prompts implements INode { let promptValues: ICommonObject = {} if (promptValuesStr) { - promptValues = JSON.parse(promptValuesStr) + try { + promptValues = typeof promptValuesStr === 'object' ? promptValuesStr : JSON.parse(promptValuesStr) + } catch (exception) { + throw new Error("Invalid JSON in the ChatPromptTemplate's promptValues: " + exception) + } } // @ts-ignore prompt.promptValues = promptValues diff --git a/packages/components/nodes/prompts/FewShotPromptTemplate/FewShotPromptTemplate.ts b/packages/components/nodes/prompts/FewShotPromptTemplate/FewShotPromptTemplate.ts index ed1d3cb2..a1a2afc2 100644 --- a/packages/components/nodes/prompts/FewShotPromptTemplate/FewShotPromptTemplate.ts +++ b/packages/components/nodes/prompts/FewShotPromptTemplate/FewShotPromptTemplate.ts @@ -55,7 +55,7 @@ class FewShotPromptTemplate_Prompts implements INode { placeholder: `Word: {input}\nAntonym:` }, { - label: 'Example Seperator', + label: 'Example Separator', name: 'exampleSeparator', type: 'string', placeholder: `\n\n` @@ -80,7 +80,7 @@ class FewShotPromptTemplate_Prompts implements INode { } async init(nodeData: INodeData): Promise { - const examplesStr = nodeData.inputs?.examples as string + const examplesStr = nodeData.inputs?.examples const prefix = nodeData.inputs?.prefix as string const suffix = nodeData.inputs?.suffix as string const exampleSeparator = nodeData.inputs?.exampleSeparator as string @@ -88,7 +88,15 @@ class FewShotPromptTemplate_Prompts implements INode { const examplePrompt = nodeData.inputs?.examplePrompt as PromptTemplate const inputVariables = getInputVariables(suffix) - const examples: Example[] = JSON.parse(examplesStr) + + let examples: Example[] = [] + if (examplesStr) { + try { + examples = typeof examplesStr === 'object' ? examplesStr : JSON.parse(examplesStr) + } catch (exception) { + throw new Error("Invalid JSON in the FewShotPromptTemplate's examples: " + exception) + } + } try { const obj: FewShotPromptTemplateInput = { diff --git a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts index a401e282..f28d6976 100644 --- a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts +++ b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts @@ -43,11 +43,15 @@ class PromptTemplate_Prompts implements INode { async init(nodeData: INodeData): Promise { const template = nodeData.inputs?.template as string - const promptValuesStr = nodeData.inputs?.promptValues as string + const promptValuesStr = nodeData.inputs?.promptValues let promptValues: ICommonObject = {} if (promptValuesStr) { - promptValues = JSON.parse(promptValuesStr) + try { + promptValues = typeof promptValuesStr === 'object' ? promptValuesStr : JSON.parse(promptValuesStr) + } catch (exception) { + throw new Error("Invalid JSON in the PromptTemplate's promptValues: " + exception) + } } const inputVariables = getInputVariables(template) diff --git a/packages/components/nodes/textsplitters/CharacterTextSplitter/CharacterTextSplitter.ts b/packages/components/nodes/textsplitters/CharacterTextSplitter/CharacterTextSplitter.ts index ed972ad6..76c5cfb2 100644 --- a/packages/components/nodes/textsplitters/CharacterTextSplitter/CharacterTextSplitter.ts +++ b/packages/components/nodes/textsplitters/CharacterTextSplitter/CharacterTextSplitter.ts @@ -41,7 +41,7 @@ class CharacterTextSplitter_TextSplitters implements INode { name: 'separator', type: 'string', placeholder: `" "`, - description: 'Seperator to determine when to split the text, will override the default separator', + description: 'Separator to determine when to split the text, will override the default separator', optional: true } ] diff --git a/packages/components/nodes/textsplitters/RecursiveCharacterTextSplitter/RecursiveCharacterTextSplitter.ts b/packages/components/nodes/textsplitters/RecursiveCharacterTextSplitter/RecursiveCharacterTextSplitter.ts index ec9095b6..13386bd9 100644 --- a/packages/components/nodes/textsplitters/RecursiveCharacterTextSplitter/RecursiveCharacterTextSplitter.ts +++ b/packages/components/nodes/textsplitters/RecursiveCharacterTextSplitter/RecursiveCharacterTextSplitter.ts @@ -41,8 +41,9 @@ class RecursiveCharacterTextSplitter_TextSplitters implements INode { name: 'separators', type: 'string', rows: 4, - description: 'Array of custom seperators to determine when to split the text, will override the default separators', + description: 'Array of custom separators to determine when to split the text, will override the default separators', placeholder: `["|", "##", ">", "-"]`, + additionalParams: true, optional: true } ] @@ -51,7 +52,7 @@ class RecursiveCharacterTextSplitter_TextSplitters implements INode { async init(nodeData: INodeData): Promise { const chunkSize = nodeData.inputs?.chunkSize as string const chunkOverlap = nodeData.inputs?.chunkOverlap as string - const separators = nodeData.inputs?.separators as string + const separators = nodeData.inputs?.separators const obj = {} as RecursiveCharacterTextSplitterParams @@ -59,7 +60,7 @@ class RecursiveCharacterTextSplitter_TextSplitters implements INode { if (chunkOverlap) obj.chunkOverlap = parseInt(chunkOverlap, 10) if (separators) { try { - obj.separators = JSON.parse(separators) + obj.separators = typeof separators === 'object' ? separators : JSON.parse(separators) } catch (e) { throw new Error(e) } diff --git a/packages/components/nodes/tools/CustomTool/CustomTool.ts b/packages/components/nodes/tools/CustomTool/CustomTool.ts index c070df31..541edcf0 100644 --- a/packages/components/nodes/tools/CustomTool/CustomTool.ts +++ b/packages/components/nodes/tools/CustomTool/CustomTool.ts @@ -1,5 +1,5 @@ import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { convertSchemaToZod, getBaseClasses } from '../../../src/utils' import { DynamicStructuredTool } from './core' import { z } from 'zod' import { DataSource } from 'typeorm' @@ -87,26 +87,4 @@ class CustomTool_Tools implements INode { } } -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/SearchApi/SearchAPI.ts b/packages/components/nodes/tools/SearchApi/SearchAPI.ts new file mode 100644 index 00000000..3f760087 --- /dev/null +++ b/packages/components/nodes/tools/SearchApi/SearchAPI.ts @@ -0,0 +1,42 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { SearchApi } from 'langchain/tools' + +class SearchAPI_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 = 'SearchApi' + this.name = 'searchAPI' + this.version = 1.0 + this.type = 'SearchAPI' + this.icon = 'searchapi.svg' + this.category = 'Tools' + this.description = 'Real-time API for accessing Google Search data' + this.inputs = [] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['searchApi'] + } + this.baseClasses = [this.type, ...getBaseClasses(SearchApi)] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const searchApiKey = getCredentialParam('searchApiKey', credentialData, nodeData) + return new SearchApi(searchApiKey) + } +} + +module.exports = { nodeClass: SearchAPI_Tools } diff --git a/packages/components/nodes/tools/SearchApi/searchapi.svg b/packages/components/nodes/tools/SearchApi/searchapi.svg new file mode 100644 index 00000000..c44c29c4 --- /dev/null +++ b/packages/components/nodes/tools/SearchApi/searchapi.svg @@ -0,0 +1 @@ +0479_octopus_verti diff --git a/packages/components/nodes/vectorstores/Chroma/Chroma_Upsert.ts b/packages/components/nodes/vectorstores/Chroma/Chroma_Upsert.ts index 0527b729..951338ba 100644 --- a/packages/components/nodes/vectorstores/Chroma/Chroma_Upsert.ts +++ b/packages/components/nodes/vectorstores/Chroma/Chroma_Upsert.ts @@ -98,7 +98,9 @@ class ChromaUpsert_VectorStores implements INode { const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } const obj: { diff --git a/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch_Upsert.ts b/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch_Upsert.ts index d4b79a5d..d3965786 100644 --- a/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch_Upsert.ts +++ b/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch_Upsert.ts @@ -38,7 +38,9 @@ class ElasicsearchUpsert_VectorStores extends ElasticSearchBase implements INode const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } // The following code is a workaround for a bug (Langchain Issue #1589) in the underlying library. diff --git a/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts b/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts index c234a4f5..a84b9da4 100644 --- a/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts +++ b/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts @@ -80,7 +80,9 @@ class FaissUpsert_VectorStores implements INode { const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } const vectorStore = await FaissStore.fromDocuments(finalDocs, embeddings) diff --git a/packages/components/nodes/vectorstores/InMemory/InMemoryVectorStore.ts b/packages/components/nodes/vectorstores/InMemory/InMemoryVectorStore.ts index 55a01e2b..a827e3ee 100644 --- a/packages/components/nodes/vectorstores/InMemory/InMemoryVectorStore.ts +++ b/packages/components/nodes/vectorstores/InMemory/InMemoryVectorStore.ts @@ -71,7 +71,9 @@ class InMemoryVectorStore_VectorStores implements INode { const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } const vectorStore = await MemoryVectorStore.fromDocuments(finalDocs, embeddings) diff --git a/packages/components/nodes/vectorstores/Milvus/Milvus_Upsert.ts b/packages/components/nodes/vectorstores/Milvus/Milvus_Upsert.ts index ca69cb39..371fd16f 100644 --- a/packages/components/nodes/vectorstores/Milvus/Milvus_Upsert.ts +++ b/packages/components/nodes/vectorstores/Milvus/Milvus_Upsert.ts @@ -110,7 +110,9 @@ class Milvus_Upsert_VectorStores implements INode { const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } const vectorStore = await MilvusUpsert.fromDocuments(finalDocs, embeddings, milVusArgs) @@ -252,7 +254,7 @@ class MilvusUpsert extends Milvus { collection_name: this.collectionName }) - if (descIndexResp.status.error_code === ErrorCode.INDEX_NOT_EXIST) { + if (descIndexResp.status.error_code === ErrorCode.IndexNotExist) { const resp = await this.client.createIndex({ collection_name: this.collectionName, field_name: this.vectorField, diff --git a/packages/components/nodes/vectorstores/OpenSearch_Upsert/OpenSearch_Upsert.ts b/packages/components/nodes/vectorstores/OpenSearch_Upsert/OpenSearch_Upsert.ts index c11d8b11..da123581 100644 --- a/packages/components/nodes/vectorstores/OpenSearch_Upsert/OpenSearch_Upsert.ts +++ b/packages/components/nodes/vectorstores/OpenSearch_Upsert/OpenSearch_Upsert.ts @@ -86,7 +86,9 @@ class OpenSearchUpsert_VectorStores implements INode { const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } const client = new Client({ diff --git a/packages/components/nodes/vectorstores/Pinecone/Pinecone_Upsert.ts b/packages/components/nodes/vectorstores/Pinecone/Pinecone_Upsert.ts index 4a12f27b..0851fa3e 100644 --- a/packages/components/nodes/vectorstores/Pinecone/Pinecone_Upsert.ts +++ b/packages/components/nodes/vectorstores/Pinecone/Pinecone_Upsert.ts @@ -106,7 +106,9 @@ class PineconeUpsert_VectorStores implements INode { const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } const obj: PineconeLibArgs = { diff --git a/packages/components/nodes/vectorstores/Postgres_Upsert/Postgres_Upsert.ts b/packages/components/nodes/vectorstores/Postgres_Upsert/Postgres_Upsert.ts index bc16a052..8ea3501d 100644 --- a/packages/components/nodes/vectorstores/Postgres_Upsert/Postgres_Upsert.ts +++ b/packages/components/nodes/vectorstores/Postgres_Upsert/Postgres_Upsert.ts @@ -143,7 +143,9 @@ class PostgresUpsert_VectorStores implements INode { const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } const vectorStore = await TypeORMVectorStore.fromDocuments(finalDocs, embeddings, args) diff --git a/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts b/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts index 407a8d22..183271f7 100644 --- a/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts +++ b/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts @@ -147,7 +147,9 @@ class QdrantUpsert_VectorStores implements INode { const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } const dbConfig: QdrantLibArgs = { diff --git a/packages/components/nodes/vectorstores/Redis/RedisSearchBase.ts b/packages/components/nodes/vectorstores/Redis/RedisSearchBase.ts new file mode 100644 index 00000000..9d1c2ea1 --- /dev/null +++ b/packages/components/nodes/vectorstores/Redis/RedisSearchBase.ts @@ -0,0 +1,215 @@ +import { + getBaseClasses, + getCredentialData, + getCredentialParam, + ICommonObject, + INodeData, + INodeOutputsValue, + INodeParams +} from '../../../src' + +import { Embeddings } from 'langchain/embeddings/base' +import { VectorStore } from 'langchain/vectorstores/base' +import { Document } from 'langchain/document' +import { createClient, SearchOptions } from 'redis' +import { RedisVectorStore } from 'langchain/vectorstores/redis' +import { escapeSpecialChars, unEscapeSpecialChars } from './utils' + +export abstract class RedisSearchBase { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + outputs: INodeOutputsValue[] + redisClient: ReturnType + + protected constructor() { + this.type = 'Redis' + this.icon = 'redis.svg' + this.category = 'Vector Stores' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['redisCacheUrlApi', 'redisCacheApi'] + } + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Index Name', + name: 'indexName', + placeholder: '', + type: 'string' + }, + { + label: 'Replace Index?', + name: 'replaceIndex', + description: 'Selecting this option will delete the existing index and recreate a new one', + default: false, + type: 'boolean' + }, + { + label: 'Content Field', + name: 'contentKey', + description: 'Name of the field (column) that contains the actual content', + type: 'string', + default: 'content', + additionalParams: true, + optional: true + }, + { + label: 'Metadata Field', + name: 'metadataKey', + description: 'Name of the field (column) that contains the metadata of the document', + type: 'string', + default: 'metadata', + additionalParams: true, + optional: true + }, + { + label: 'Vector Field', + name: 'vectorKey', + description: 'Name of the field (column) that contains the vector', + type: 'string', + default: 'content_vector', + 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 + } + ] + this.outputs = [ + { + label: 'Redis Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Redis Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(RedisVectorStore)] + } + ] + } + + abstract constructVectorStore( + embeddings: Embeddings, + indexName: string, + replaceIndex: boolean, + docs: Document>[] | undefined + ): Promise + + async init(nodeData: INodeData, _: string, options: ICommonObject, docs: Document>[] | undefined): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const indexName = nodeData.inputs?.indexName as string + let contentKey = nodeData.inputs?.contentKey as string + let metadataKey = nodeData.inputs?.metadataKey as string + let vectorKey = nodeData.inputs?.vectorKey as string + const embeddings = nodeData.inputs?.embeddings as Embeddings + const topK = nodeData.inputs?.topK as string + const replaceIndex = nodeData.inputs?.replaceIndex as boolean + const k = topK ? parseFloat(topK) : 4 + const output = nodeData.outputs?.output as string + + let redisUrl = getCredentialParam('redisUrl', credentialData, nodeData) + if (!redisUrl || redisUrl === '') { + const username = getCredentialParam('redisCacheUser', credentialData, nodeData) + const password = getCredentialParam('redisCachePwd', credentialData, nodeData) + const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) + const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + + redisUrl = 'redis://' + username + ':' + password + '@' + host + ':' + portStr + } + + this.redisClient = createClient({ url: redisUrl }) + await this.redisClient.connect() + + const vectorStore = await this.constructVectorStore(embeddings, indexName, replaceIndex, docs) + if (!contentKey || contentKey === '') contentKey = 'content' + if (!metadataKey || metadataKey === '') metadataKey = 'metadata' + if (!vectorKey || vectorKey === '') vectorKey = 'content_vector' + + const buildQuery = (query: number[], k: number, filter?: string[]): [string, SearchOptions] => { + const vectorScoreField = 'vector_score' + + let hybridFields = '*' + // if a filter is set, modify the hybrid query + if (filter && filter.length) { + // `filter` is a list of strings, then it's applied using the OR operator in the metadata key + hybridFields = `@${metadataKey}:(${filter.map(escapeSpecialChars).join('|')})` + } + + const baseQuery = `${hybridFields} => [KNN ${k} @${vectorKey} $vector AS ${vectorScoreField}]` + const returnFields = [metadataKey, contentKey, vectorScoreField] + + const options: SearchOptions = { + PARAMS: { + vector: Buffer.from(new Float32Array(query).buffer) + }, + RETURN: returnFields, + SORTBY: vectorScoreField, + DIALECT: 2, + LIMIT: { + from: 0, + size: k + } + } + + return [baseQuery, options] + } + + vectorStore.similaritySearchVectorWithScore = async ( + query: number[], + k: number, + filter?: string[] + ): Promise<[Document, number][]> => { + const results = await this.redisClient.ft.search(indexName, ...buildQuery(query, k, filter)) + const result: [Document, number][] = [] + + if (results.total) { + for (const res of results.documents) { + if (res.value) { + const document = res.value + if (document.vector_score) { + const metadataString = unEscapeSpecialChars(document[metadataKey] as string) + result.push([ + new Document({ + pageContent: document[contentKey] as string, + metadata: JSON.parse(metadataString) + }), + Number(document.vector_score) + ]) + } + } + } + } + return result + } + + if (output === 'retriever') { + return vectorStore.asRetriever(k) + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} diff --git a/packages/components/nodes/vectorstores/Redis/Redis_Existing.ts b/packages/components/nodes/vectorstores/Redis/Redis_Existing.ts new file mode 100644 index 00000000..9ad472a8 --- /dev/null +++ b/packages/components/nodes/vectorstores/Redis/Redis_Existing.ts @@ -0,0 +1,42 @@ +import { ICommonObject, INode, INodeData } from '../../../src/Interface' +import { Embeddings } from 'langchain/embeddings/base' +import { VectorStore } from 'langchain/vectorstores/base' +import { RedisVectorStore, RedisVectorStoreConfig } from 'langchain/vectorstores/redis' +import { Document } from 'langchain/document' + +import { RedisSearchBase } from './RedisSearchBase' + +class RedisExisting_VectorStores extends RedisSearchBase implements INode { + constructor() { + super() + this.label = 'Redis Load Existing Index' + this.name = 'RedisIndex' + this.version = 1.0 + this.description = 'Load existing index from Redis (i.e: Document has been upserted)' + + // Remove deleteIndex from inputs as it is not applicable while fetching data from Redis + let input = this.inputs.find((i) => i.name === 'deleteIndex') + if (input) this.inputs.splice(this.inputs.indexOf(input), 1) + } + + async constructVectorStore( + embeddings: Embeddings, + indexName: string, + // eslint-disable-next-line unused-imports/no-unused-vars + replaceIndex: boolean, + _: Document>[] + ): Promise { + const storeConfig: RedisVectorStoreConfig = { + redisClient: this.redisClient, + indexName: indexName + } + + return new RedisVectorStore(embeddings, storeConfig) + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + return super.init(nodeData, _, options, undefined) + } +} + +module.exports = { nodeClass: RedisExisting_VectorStores } diff --git a/packages/components/nodes/vectorstores/Redis/Redis_Upsert.ts b/packages/components/nodes/vectorstores/Redis/Redis_Upsert.ts new file mode 100644 index 00000000..9d1a4f45 --- /dev/null +++ b/packages/components/nodes/vectorstores/Redis/Redis_Upsert.ts @@ -0,0 +1,63 @@ +import { ICommonObject, INode, INodeData } from '../../../src/Interface' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' + +import { flatten } from 'lodash' +import { RedisSearchBase } from './RedisSearchBase' +import { VectorStore } from 'langchain/vectorstores/base' +import { RedisVectorStore, RedisVectorStoreConfig } from 'langchain/vectorstores/redis' +import { escapeAllStrings } from './utils' + +class RedisUpsert_VectorStores extends RedisSearchBase implements INode { + constructor() { + super() + this.label = 'Redis Upsert Document' + this.name = 'RedisUpsert' + this.version = 1.0 + this.description = 'Upsert documents to Redis' + this.inputs.unshift({ + label: 'Document', + name: 'document', + type: 'Document', + list: true + }) + } + + async constructVectorStore( + embeddings: Embeddings, + indexName: string, + replaceIndex: boolean, + docs: Document>[] + ): Promise { + const storeConfig: RedisVectorStoreConfig = { + redisClient: this.redisClient, + indexName: indexName + } + if (replaceIndex) { + let response = await this.redisClient.ft.dropIndex(indexName) + if (process.env.DEBUG === 'true') { + // eslint-disable-next-line no-console + console.log(`Redis Vector Store :: Dropping index [${indexName}], Received Response [${response}]`) + } + } + return await RedisVectorStore.fromDocuments(docs, embeddings, storeConfig) + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + 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) { + if (flattenDocs[i] && flattenDocs[i].pageContent) { + const document = new Document(flattenDocs[i]) + escapeAllStrings(document.metadata) + finalDocs.push(document) + } + } + + return super.init(nodeData, _, options, flattenDocs) + } +} + +module.exports = { nodeClass: RedisUpsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/Redis/redis.svg b/packages/components/nodes/vectorstores/Redis/redis.svg new file mode 100644 index 00000000..90359069 --- /dev/null +++ b/packages/components/nodes/vectorstores/Redis/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/vectorstores/Redis/utils.ts b/packages/components/nodes/vectorstores/Redis/utils.ts new file mode 100644 index 00000000..acef03ef --- /dev/null +++ b/packages/components/nodes/vectorstores/Redis/utils.ts @@ -0,0 +1,25 @@ +/* + * Escapes all '-' characters. + * Redis Search considers '-' as a negative operator, hence we need + * to escape it + */ +export const escapeSpecialChars = (str: string) => { + return str.replaceAll('-', '\\-') +} + +export const escapeAllStrings = (obj: object) => { + Object.keys(obj).forEach((key: string) => { + // @ts-ignore + let item = obj[key] + if (typeof item === 'object') { + escapeAllStrings(item) + } else if (typeof item === 'string') { + // @ts-ignore + obj[key] = escapeSpecialChars(item) + } + }) +} + +export const unEscapeSpecialChars = (str: string) => { + return str.replaceAll('\\-', '-') +} diff --git a/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts b/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts index 9889a154..d8edc8d4 100644 --- a/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts +++ b/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts @@ -140,7 +140,9 @@ class SingleStoreUpsert_VectorStores implements INode { const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } let vectorStore: SingleStoreVectorStore diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara_Upsert.ts b/packages/components/nodes/vectorstores/Vectara/Vectara_Upsert.ts index 3e035a51..376668de 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara_Upsert.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara_Upsert.ts @@ -132,7 +132,9 @@ class VectaraUpsert_VectorStores implements INode { const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } const vectorStore = await VectaraStore.fromDocuments(finalDocs, embeddings, vectaraArgs) diff --git a/packages/components/nodes/vectorstores/Weaviate_Upsert/Weaviate_Upsert.ts b/packages/components/nodes/vectorstores/Weaviate_Upsert/Weaviate_Upsert.ts index a2f82831..0b7f2393 100644 --- a/packages/components/nodes/vectorstores/Weaviate_Upsert/Weaviate_Upsert.ts +++ b/packages/components/nodes/vectorstores/Weaviate_Upsert/Weaviate_Upsert.ts @@ -143,7 +143,9 @@ class WeaviateUpsert_VectorStores implements INode { const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } const obj: WeaviateLibArgs = { diff --git a/packages/components/nodes/vectorstores/Zep/Zep_Upsert.ts b/packages/components/nodes/vectorstores/Zep/Zep_Upsert.ts index 0f976d2b..915513b9 100644 --- a/packages/components/nodes/vectorstores/Zep/Zep_Upsert.ts +++ b/packages/components/nodes/vectorstores/Zep/Zep_Upsert.ts @@ -106,7 +106,9 @@ class Zep_Upsert_VectorStores implements INode { const flattenDocs = docs && docs.length ? flatten(docs) : [] const finalDocs = [] for (let i = 0; i < flattenDocs.length; i += 1) { - finalDocs.push(new Document(flattenDocs[i])) + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } } const zepConfig: IZepConfig = { diff --git a/packages/components/package.json b/packages/components/package.json index 262a49db..051eec2f 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.3.10", + "version": "1.3.11", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index d0694d6f..5008813b 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -6,6 +6,7 @@ export type NodeParamsType = | 'asyncOptions' | 'options' | 'multiOptions' + | 'datagrid' | 'string' | 'number' | 'boolean' @@ -60,6 +61,7 @@ export interface INodeParams { description?: string warning?: string options?: Array + datagrid?: Array credentialNames?: Array optional?: boolean | INodeDisplay step?: number diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 8f33683c..69f8b268 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -3,6 +3,7 @@ import { load } from 'cheerio' import * as fs from 'fs' import * as path from 'path' import { JSDOM } from 'jsdom' +import { z } from 'zod' import { DataSource } from 'typeorm' import { ICommonObject, IDatabaseEntity, IMessage, INodeData } from './Interface' import { AES, enc } from 'crypto-js' @@ -132,6 +133,7 @@ export const getNodeModulesPackagePath = (packageName: string): string => { * @returns {boolean} */ export const getInputVariables = (paramValue: string): string[] => { + if (typeof paramValue !== 'string') return [] let returnVal = paramValue const variableStack = [] const inputVariables = [] @@ -301,7 +303,7 @@ async function crawl(baseURL: string, currentURL: string, pages: string[], limit } /** - * Prep URL before passing into recursive carwl function + * Prep URL before passing into recursive crawl function * @param {string} stringURL * @param {number} limit * @returns {Promise} @@ -445,7 +447,7 @@ export const getCredentialData = async (selectedCredentialId: string, options: I if (!credential) return {} - // Decrpyt credentialData + // Decrypt credentialData const decryptedCredentialData = await decryptCredentialData(credential.encryptedData) return decryptedCredentialData @@ -511,7 +513,7 @@ export const getUserHome = (): string => { /** * Map incoming chat history to ChatMessageHistory - * @param {options} ICommonObject + * @param {ICommonObject} options * @returns {ChatMessageHistory} */ export const mapChatHistory = (options: ICommonObject): ChatMessageHistory => { @@ -546,3 +548,30 @@ export const convertChatHistoryToText = (chatHistory: IMessage[] = []): string = }) .join('\n') } + +/** + * Convert schema to zod schema + * @param {string | object} schema + * @returns {ICommonObject} + */ +export const convertSchemaToZod = (schema: string | object): ICommonObject => { + try { + const parsedSchema = typeof schema === 'string' ? JSON.parse(schema) : schema + const zodObj: ICommonObject = {} + 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) + } +} diff --git a/packages/server/marketplaces/chatflows/Antonym.json b/packages/server/marketplaces/chatflows/Antonym.json index 5f8ff7a8..059ab6c1 100644 --- a/packages/server/marketplaces/chatflows/Antonym.json +++ b/packages/server/marketplaces/chatflows/Antonym.json @@ -13,8 +13,8 @@ "data": { "id": "fewShotPromptTemplate_1", "label": "Few Shot Prompt Template", - "name": "fewShotPromptTemplate", "version": 1, + "name": "fewShotPromptTemplate", "type": "FewShotPromptTemplate", "baseClasses": ["FewShotPromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], "category": "Prompts", @@ -45,7 +45,7 @@ "id": "fewShotPromptTemplate_1-input-suffix-string" }, { - "label": "Example Seperator", + "label": "Example Separator", "name": "exampleSeparator", "type": "string", "placeholder": "\n\n", @@ -115,8 +115,8 @@ "data": { "id": "promptTemplate_0", "label": "Prompt Template", - "name": "promptTemplate", "version": 1, + "name": "promptTemplate", "type": "PromptTemplate", "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], "category": "Prompts", @@ -165,7 +165,7 @@ }, { "width": 300, - "height": 523, + "height": 574, "id": "chatOpenAI_0", "position": { "x": 1226.7977900193628, @@ -175,8 +175,8 @@ "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", - "name": "chatOpenAI", "version": 2, + "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -327,20 +327,20 @@ }, { "width": 300, - "height": 405, + "height": 456, "id": "llmChain_0", "position": { - "x": 1573.7490072386481, - "y": 429.1905949837192 + "x": 1609.3428158423485, + "y": 409.3763727612179 }, "type": "customNode", "data": { "id": "llmChain_0", "label": "LLM Chain", + "version": 3, "name": "llmChain", - "version": 1, "type": "LLMChain", - "baseClasses": ["LLMChain", "BaseChain"], + "baseClasses": ["LLMChain", "BaseChain", "Runnable"], "category": "Chains", "description": "Chain to run queries against LLMs", "inputParams": [ @@ -365,11 +365,19 @@ "name": "prompt", "type": "BasePromptTemplate", "id": "llmChain_0-input-prompt-BasePromptTemplate" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_0-input-outputParser-BaseLLMOutputParser" } ], "inputs": { "model": "{{chatOpenAI_0.data.instance}}", "prompt": "{{fewShotPromptTemplate_1.data.instance}}", + "outputParser": "", "chainName": "" }, "outputAnchors": [ @@ -379,10 +387,10 @@ "type": "options", "options": [ { - "id": "llmChain_0-output-llmChain-LLMChain|BaseChain", + "id": "llmChain_0-output-llmChain-LLMChain|BaseChain|Runnable", "name": "llmChain", "label": "LLM Chain", - "type": "LLMChain | BaseChain" + "type": "LLMChain | BaseChain | Runnable" }, { "id": "llmChain_0-output-outputPrediction-string|json", @@ -401,8 +409,8 @@ }, "selected": false, "positionAbsolute": { - "x": 1573.7490072386481, - "y": 429.1905949837192 + "x": 1609.3428158423485, + "y": 409.3763727612179 }, "dragging": false } diff --git a/packages/server/marketplaces/chatflows/Chat with a Podcast.json b/packages/server/marketplaces/chatflows/Chat with a Podcast.json new file mode 100644 index 00000000..2a9e05b5 --- /dev/null +++ b/packages/server/marketplaces/chatflows/Chat with a Podcast.json @@ -0,0 +1,669 @@ +{ + "description": "Engage with data sources such as YouTube Transcripts, Google, and more through intelligent Q&A interactions", + "nodes": [ + { + "width": 300, + "height": 483, + "id": "conversationalRetrievalQAChain_0", + "position": { + "x": 1499.2693059023254, + "y": 430.03911199833317 + }, + "type": "customNode", + "data": { + "id": "conversationalRetrievalQAChain_0", + "label": "Conversational Retrieval QA Chain", + "version": 1, + "name": "conversationalRetrievalQAChain", + "type": "ConversationalRetrievalQAChain", + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], + "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": "{{memoryVectorStore_0.data.instance}}", + "memory": "", + "returnSourceDocuments": "", + "systemMessagePrompt": "", + "chainOption": "" + }, + "outputAnchors": [ + { + "id": "conversationalRetrievalQAChain_0-output-conversationalRetrievalQAChain-ConversationalRetrievalQAChain|BaseChain|Runnable", + "name": "conversationalRetrievalQAChain", + "label": "ConversationalRetrievalQAChain", + "type": "ConversationalRetrievalQAChain | BaseChain | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1499.2693059023254, + "y": 430.03911199833317 + }, + "dragging": false + }, + { + "width": 300, + "height": 408, + "id": "memoryVectorStore_0", + "position": { + "x": 1082.0280622332507, + "y": 589.9990964387842 + }, + "type": "customNode", + "data": { + "id": "memoryVectorStore_0", + "label": "In-Memory Vector Store", + "version": 1, + "name": "memoryVectorStore", + "type": "Memory", + "baseClasses": ["Memory", "VectorStoreRetriever", "BaseRetriever"], + "category": "Vector Stores", + "description": "In-memory vectorstore that stores embeddings and does an exact, linear search for the most similar embeddings.", + "inputParams": [ + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "optional": true, + "id": "memoryVectorStore_0-input-topK-number" + } + ], + "inputAnchors": [ + { + "label": "Document", + "name": "document", + "type": "Document", + "list": true, + "id": "memoryVectorStore_0-input-document-Document" + }, + { + "label": "Embeddings", + "name": "embeddings", + "type": "Embeddings", + "id": "memoryVectorStore_0-input-embeddings-Embeddings" + } + ], + "inputs": { + "document": ["{{searchApi_0.data.instance}}", "{{searchApi_0.data.instance}}", "{{searchApi_0.data.instance}}"], + "embeddings": "{{openAIEmbeddings_0.data.instance}}", + "topK": "" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "memoryVectorStore_0-output-retriever-Memory|VectorStoreRetriever|BaseRetriever", + "name": "retriever", + "label": "Memory Retriever", + "type": "Memory | VectorStoreRetriever | BaseRetriever" + }, + { + "id": "memoryVectorStore_0-output-vectorStore-Memory|VectorStore", + "name": "vectorStore", + "label": "Memory Vector Store", + "type": "Memory | VectorStore" + } + ], + "default": "retriever" + } + ], + "outputs": { + "output": "retriever" + }, + "selected": false + }, + "positionAbsolute": { + "x": 1082.0280622332507, + "y": 589.9990964387842 + }, + "selected": false, + "dragging": false + }, + { + "width": 300, + "height": 577, + "id": "chatOpenAI_0", + "position": { + "x": 1056.2788608917747, + "y": -60.59149112477064 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "version": 2, + "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" + }, + { + "label": "BaseOptions", + "name": "baseOptions", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-baseOptions-json" + } + ], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], + "inputs": { + "cache": "", + "modelName": "gpt-3.5-turbo", + "temperature": "0.5", + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "", + "baseOptions": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1056.2788608917747, + "y": -60.59149112477064 + }, + "dragging": false + }, + { + "width": 300, + "height": 478, + "id": "characterTextSplitter_0", + "position": { + "x": 260.5475803279806, + "y": -65.1647664861618 + }, + "type": "customNode", + "data": { + "id": "characterTextSplitter_0", + "label": "Character Text Splitter", + "version": 1, + "name": "characterTextSplitter", + "type": "CharacterTextSplitter", + "baseClasses": ["CharacterTextSplitter", "TextSplitter", "BaseDocumentTransformer", "Runnable"], + "category": "Text Splitters", + "description": "splits only on one type of character (defaults to \"\\n\\n\").", + "inputParams": [ + { + "label": "Chunk Size", + "name": "chunkSize", + "type": "number", + "default": 1000, + "optional": true, + "id": "characterTextSplitter_0-input-chunkSize-number" + }, + { + "label": "Chunk Overlap", + "name": "chunkOverlap", + "type": "number", + "optional": true, + "id": "characterTextSplitter_0-input-chunkOverlap-number" + }, + { + "label": "Custom Separator", + "name": "separator", + "type": "string", + "placeholder": "\" \"", + "description": "Seperator to determine when to split the text, will override the default separator", + "optional": true, + "id": "characterTextSplitter_0-input-separator-string" + } + ], + "inputAnchors": [], + "inputs": { + "chunkSize": "2000", + "chunkOverlap": "200", + "separator": "" + }, + "outputAnchors": [ + { + "id": "characterTextSplitter_0-output-characterTextSplitter-CharacterTextSplitter|TextSplitter|BaseDocumentTransformer|Runnable", + "name": "characterTextSplitter", + "label": "CharacterTextSplitter", + "type": "CharacterTextSplitter | TextSplitter | BaseDocumentTransformer | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 260.5475803279806, + "y": -65.1647664861618 + }, + "dragging": false + }, + { + "width": 300, + "height": 332, + "id": "openAIEmbeddings_0", + "position": { + "x": 666.3950526535211, + "y": 777.4191705193945 + }, + "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, + "dragging": false, + "positionAbsolute": { + "x": 666.3950526535211, + "y": 777.4191705193945 + } + }, + { + "width": 300, + "height": 482, + "id": "searchApi_0", + "position": { + "x": 680.1258121447145, + "y": 144.9905217023999 + }, + "type": "customNode", + "data": { + "id": "searchApi_0", + "label": "SearchApi", + "version": 1, + "name": "searchApi", + "type": "Document", + "baseClasses": ["Document"], + "category": "Document Loaders", + "description": "Load data from real-time search results", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "optional": false, + "credentialNames": ["searchApi"], + "id": "searchApi_0-input-credential-credential" + }, + { + "label": "Query", + "name": "query", + "type": "string", + "optional": true, + "id": "searchApi_0-input-query-string" + }, + { + "label": "Custom Parameters", + "name": "customParameters", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "searchApi_0-input-customParameters-json" + }, + { + "label": "Metadata", + "name": "metadata", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "searchApi_0-input-metadata-json" + } + ], + "inputAnchors": [ + { + "label": "Text Splitter", + "name": "textSplitter", + "type": "TextSplitter", + "optional": true, + "id": "searchApi_0-input-textSplitter-TextSplitter" + } + ], + "inputs": { + "query": "", + "customParameters": "{\"engine\":\"youtube_transcripts\",\"video_id\":\"0e3GPea1Tyg\"}", + "textSplitter": "{{characterTextSplitter_0.data.instance}}", + "metadata": "" + }, + "outputAnchors": [ + { + "id": "searchApi_0-output-searchApi-Document", + "name": "searchApi", + "label": "Document", + "type": "Document" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 680.1258121447145, + "y": 144.9905217023999 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "memoryVectorStore_0", + "sourceHandle": "memoryVectorStore_0-output-retriever-Memory|VectorStoreRetriever|BaseRetriever", + "target": "conversationalRetrievalQAChain_0", + "targetHandle": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", + "type": "buttonedge", + "id": "memoryVectorStore_0-memoryVectorStore_0-output-retriever-Memory|VectorStoreRetriever|BaseRetriever-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "target": "conversationalRetrievalQAChain_0", + "targetHandle": "conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "openAIEmbeddings_0", + "sourceHandle": "openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "target": "memoryVectorStore_0", + "targetHandle": "memoryVectorStore_0-input-embeddings-Embeddings", + "type": "buttonedge", + "id": "openAIEmbeddings_0-openAIEmbeddings_0-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings-memoryVectorStore_0-memoryVectorStore_0-input-embeddings-Embeddings", + "data": { + "label": "" + } + }, + { + "source": "characterTextSplitter_0", + "sourceHandle": "characterTextSplitter_0-output-characterTextSplitter-CharacterTextSplitter|TextSplitter|BaseDocumentTransformer|Runnable", + "target": "searchApi_0", + "targetHandle": "searchApi_0-input-textSplitter-TextSplitter", + "type": "buttonedge", + "id": "characterTextSplitter_0-characterTextSplitter_0-output-characterTextSplitter-CharacterTextSplitter|TextSplitter|BaseDocumentTransformer|Runnable-searchApi_0-searchApi_0-input-textSplitter-TextSplitter", + "data": { + "label": "" + } + }, + { + "source": "searchApi_0", + "sourceHandle": "searchApi_0-output-searchApi-Document", + "target": "memoryVectorStore_0", + "targetHandle": "memoryVectorStore_0-input-document-Document", + "type": "buttonedge", + "id": "searchApi_0-searchApi_0-output-searchApi-Document-memoryVectorStore_0-memoryVectorStore_0-input-document-Document", + "data": { + "label": "" + } + } + ] +} diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json index 8bbb904e..38e780cc 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json @@ -368,7 +368,7 @@ "id": "recursiveCharacterTextSplitter_0", "label": "Recursive Character Text Splitter", "name": "recursiveCharacterTextSplitter", - "version": 1, + "version": 2, "type": "RecursiveCharacterTextSplitter", "baseClasses": ["RecursiveCharacterTextSplitter", "TextSplitter"], "category": "Text Splitters", @@ -388,6 +388,17 @@ "type": "number", "optional": true, "id": "recursiveCharacterTextSplitter_0-input-chunkOverlap-number" + }, + { + "label": "Custom Separators", + "name": "separators", + "type": "string", + "rows": 4, + "description": "Array of custom separators to determine when to split the text, will override the default separators", + "placeholder": "[\"|\", \"##\", \">\", \"-\"]", + "additionalParams": true, + "optional": true, + "id": "recursiveCharacterTextSplitter_0-input-separators-string" } ], "inputAnchors": [], @@ -426,7 +437,7 @@ "id": "textFile_0", "label": "Text File", "name": "textFile", - "version": 2, + "version": 3, "type": "Document", "baseClasses": ["Document"], "category": "Document Loaders", @@ -436,7 +447,7 @@ "label": "Txt File", "name": "txtFile", "type": "file", - "fileType": ".txt", + "fileType": ".txt, .html, .aspx, .asp, .cpp, .c, .cs, .css, .go, .h, .java, .js, .less, .ts, .php, .proto, .python, .py, .rst, .ruby, .rb, .rs, .scala, .sc, .scss, .sol, .sql, .swift, .markdown, .md, .tex, .ltx, .vb, .xml", "id": "textFile_0-input-txtFile-file" }, { diff --git a/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json b/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json index e51e1ee0..5e33b63a 100644 --- a/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json +++ b/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json @@ -1,87 +1,6 @@ { "description": "Simple LLM Chain using HuggingFace Inference API on falcon-7b-instruct model", "nodes": [ - { - "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", - "version": 1, - "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_0.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|json", - "name": "outputPrediction", - "label": "Output Prediction", - "type": "string | json" - } - ], - "default": "llmChain" - } - ], - "outputs": { - "output": "llmChain" - }, - "selected": false - }, - "positionAbsolute": { - "x": 970.9254258940236, - "y": 320.56761595884564 - }, - "selected": false, - "dragging": false - }, { "width": 300, "height": 475, @@ -94,8 +13,8 @@ "data": { "id": "promptTemplate_0", "label": "Prompt Template", - "name": "promptTemplate", "version": 1, + "name": "promptTemplate", "type": "PromptTemplate", "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], "category": "Prompts", @@ -144,7 +63,7 @@ }, { "width": 300, - "height": 526, + "height": 577, "id": "huggingFaceInference_LLMs_0", "position": { "x": 498.8594464193537, @@ -154,8 +73,8 @@ "data": { "id": "huggingFaceInference_LLMs_0", "label": "HuggingFace Inference", - "name": "huggingFaceInference_LLMs", "version": 2, + "name": "huggingFaceInference_LLMs", "type": "HuggingFaceInference", "baseClasses": ["HuggingFaceInference", "LLM", "BaseLLM", "BaseLanguageModel"], "category": "LLMs", @@ -267,27 +186,116 @@ "y": -94.91050256311678 }, "dragging": false + }, + { + "width": 300, + "height": 456, + "id": "llmChain_0", + "position": { + "x": 909.6249320819859, + "y": 338.9520801783737 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", + "label": "LLM Chain", + "version": 3, + "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" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_0-input-outputParser-BaseLLMOutputParser" + } + ], + "inputs": { + "model": "{{huggingFaceInference_LLMs_0.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", + "outputParser": "", + "chainName": "" + }, + "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": "llmChain" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 909.6249320819859, + "y": 338.9520801783737 + }, + "dragging": false } ], "edges": [ { - "source": "promptTemplate_0", - "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "target": "llmChain_1", - "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", + "source": "huggingFaceInference_LLMs_0", + "sourceHandle": "huggingFaceInference_LLMs_0-output-huggingFaceInference_LLMs-HuggingFaceInference|LLM|BaseLLM|BaseLanguageModel", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-model-BaseLanguageModel", "type": "buttonedge", - "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "id": "huggingFaceInference_LLMs_0-huggingFaceInference_LLMs_0-output-huggingFaceInference_LLMs-HuggingFaceInference|LLM|BaseLLM|BaseLanguageModel-llmChain_0-llmChain_0-input-model-BaseLanguageModel", "data": { "label": "" } }, { - "source": "huggingFaceInference_LLMs_0", - "sourceHandle": "huggingFaceInference_LLMs_0-output-huggingFaceInference_LLMs-HuggingFaceInference|LLM|BaseLLM|BaseLanguageModel", - "target": "llmChain_1", - "targetHandle": "llmChain_1-input-model-BaseLanguageModel", + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", "type": "buttonedge", - "id": "huggingFaceInference_LLMs_0-huggingFaceInference_LLMs_0-output-huggingFaceInference_LLMs-HuggingFaceInference|LLM|BaseLLM|BaseLanguageModel-llmChain_1-llmChain_1-input-model-BaseLanguageModel", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/List Output Parser.json b/packages/server/marketplaces/chatflows/List Output Parser.json new file mode 100644 index 00000000..c96dd530 --- /dev/null +++ b/packages/server/marketplaces/chatflows/List Output Parser.json @@ -0,0 +1,417 @@ +{ + "description": "Return response as a list (array) instead of a string/text", + "nodes": [ + { + "width": 300, + "height": 456, + "id": "llmChain_0", + "position": { + "x": 1490.4252662385359, + "y": 229.91198307750102 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", + "label": "LLM Chain", + "version": 3, + "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" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_0-input-outputParser-BaseLLMOutputParser" + } + ], + "inputs": { + "model": "{{chatOpenAI_0.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", + "outputParser": "{{csvOutputParser_0.data.instance}}", + "chainName": "" + }, + "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": "llmChain" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1490.4252662385359, + "y": 229.91198307750102 + }, + "dragging": false + }, + { + "width": 300, + "height": 276, + "id": "csvOutputParser_0", + "position": { + "x": 476.70884184429417, + "y": 346.38506209058426 + }, + "type": "customNode", + "data": { + "id": "csvOutputParser_0", + "label": "CSV Output Parser", + "version": 1, + "name": "csvOutputParser", + "type": "CSVListOutputParser", + "baseClasses": ["CSVListOutputParser", "BaseLLMOutputParser", "Runnable"], + "category": "Output Parsers", + "description": "Parse the output of an LLM call as a comma-separated list of values", + "inputParams": [ + { + "label": "Autofix", + "name": "autofixParser", + "type": "boolean", + "optional": true, + "description": "In the event that the first call fails, will make another call to the model to fix any errors.", + "id": "csvOutputParser_0-input-autofixParser-boolean" + } + ], + "inputAnchors": [], + "inputs": { + "autofixParser": true + }, + "outputAnchors": [ + { + "id": "csvOutputParser_0-output-csvOutputParser-CSVListOutputParser|BaseLLMOutputParser|Runnable", + "name": "csvOutputParser", + "label": "CSVListOutputParser", + "type": "CSVListOutputParser | BaseLLMOutputParser | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 476.70884184429417, + "y": 346.38506209058426 + }, + "dragging": false + }, + { + "width": 300, + "height": 475, + "id": "promptTemplate_0", + "position": { + "x": 804.3731431892371, + "y": 10.888147964487587 + }, + "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": "Answer user's question as best you can: {question}", + "promptValues": "" + }, + "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": 804.3731431892371, + "y": 10.888147964487587 + }, + "dragging": false + }, + { + "width": 300, + "height": 574, + "id": "chatOpenAI_0", + "position": { + "x": 1137.2591863882824, + "y": -204.50870351724768 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "version": 2, + "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" + }, + { + "label": "BaseOptions", + "name": "baseOptions", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-baseOptions-json" + } + ], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], + "inputs": { + "cache": "", + "modelName": "gpt-3.5-turbo-16k", + "temperature": "0", + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "", + "baseOptions": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1137.2591863882824, + "y": -204.50870351724768 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "csvOutputParser_0", + "sourceHandle": "csvOutputParser_0-output-csvOutputParser-CSVListOutputParser|BaseLLMOutputParser|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-outputParser-BaseLLMOutputParser", + "type": "buttonedge", + "id": "csvOutputParser_0-csvOutputParser_0-output-csvOutputParser-CSVListOutputParser|BaseLLMOutputParser|Runnable-llmChain_0-llmChain_0-input-outputParser-BaseLLMOutputParser", + "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": "" + } + } + ] +} diff --git a/packages/server/marketplaces/chatflows/Local QnA.json b/packages/server/marketplaces/chatflows/Local QnA.json index fb13ba21..78ce16f6 100644 --- a/packages/server/marketplaces/chatflows/Local QnA.json +++ b/packages/server/marketplaces/chatflows/Local QnA.json @@ -14,7 +14,7 @@ "id": "recursiveCharacterTextSplitter_1", "label": "Recursive Character Text Splitter", "name": "recursiveCharacterTextSplitter", - "version": 1, + "version": 2, "type": "RecursiveCharacterTextSplitter", "baseClasses": ["RecursiveCharacterTextSplitter", "TextSplitter"], "category": "Text Splitters", @@ -34,6 +34,17 @@ "type": "number", "optional": true, "id": "recursiveCharacterTextSplitter_1-input-chunkOverlap-number" + }, + { + "label": "Custom Separators", + "name": "separators", + "type": "string", + "rows": 4, + "description": "Array of custom separators to determine when to split the text, will override the default separators", + "placeholder": "[\"|\", \"##\", \">\", \"-\"]", + "additionalParams": true, + "optional": true, + "id": "recursiveCharacterTextSplitter_1-input-separators-string" } ], "inputAnchors": [], @@ -373,7 +384,7 @@ "id": "textFile_0", "label": "Text File", "name": "textFile", - "version": 1, + "version": 3, "type": "Document", "baseClasses": ["Document"], "category": "Document Loaders", @@ -383,7 +394,7 @@ "label": "Txt File", "name": "txtFile", "type": "file", - "fileType": ".txt", + "fileType": ".txt, .html, .aspx, .asp, .cpp, .c, .cs, .css, .go, .h, .java, .js, .less, .ts, .php, .proto, .python, .py, .rst, .ruby, .rb, .rs, .scala, .sc, .scss, .sol, .sql, .swift, .markdown, .md, .tex, .ltx, .vb, .xml", "id": "textFile_0-input-txtFile-file" }, { diff --git a/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json b/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json index 6e79301e..38cec6dd 100644 --- a/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json +++ b/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json @@ -14,7 +14,7 @@ "id": "recursiveCharacterTextSplitter_1", "label": "Recursive Character Text Splitter", "name": "recursiveCharacterTextSplitter", - "version": 1, + "version": 2, "type": "RecursiveCharacterTextSplitter", "baseClasses": ["RecursiveCharacterTextSplitter", "TextSplitter"], "category": "Text Splitters", @@ -34,6 +34,17 @@ "type": "number", "optional": true, "id": "recursiveCharacterTextSplitter_1-input-chunkOverlap-number" + }, + { + "label": "Custom Separators", + "name": "separators", + "type": "string", + "rows": 4, + "description": "Array of custom separators to determine when to split the text, will override the default separators", + "placeholder": "[\"|\", \"##\", \">\", \"-\"]", + "additionalParams": true, + "optional": true, + "id": "recursiveCharacterTextSplitter_1-input-separators-string" } ], "inputAnchors": [], @@ -72,7 +83,7 @@ "id": "textFile_0", "label": "Text File", "name": "textFile", - "version": 1, + "version": 3, "type": "Document", "baseClasses": ["Document"], "category": "Document Loaders", @@ -82,7 +93,7 @@ "label": "Txt File", "name": "txtFile", "type": "file", - "fileType": ".txt", + "fileType": ".txt, .html, .aspx, .asp, .cpp, .c, .cs, .css, .go, .h, .java, .js, .less, .ts, .php, .proto, .python, .py, .rst, .ruby, .rb, .rs, .scala, .sc, .scss, .sol, .sql, .swift, .markdown, .md, .tex, .ltx, .vb, .xml", "id": "textFile_0-input-txtFile-file" }, { diff --git a/packages/server/marketplaces/chatflows/Prompt Chaining with VectorStore.json b/packages/server/marketplaces/chatflows/Prompt Chaining with VectorStore.json index 2af61190..bca8db04 100644 --- a/packages/server/marketplaces/chatflows/Prompt Chaining with VectorStore.json +++ b/packages/server/marketplaces/chatflows/Prompt Chaining with VectorStore.json @@ -3,120 +3,11 @@ "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, + "height": 329, "id": "openAIEmbeddings_0", "position": { - "x": 711.3971966563331, - "y": 7.7184225021727 + "x": 1198.6643452533754, + "y": -584.4233173804803 }, "type": "customNode", "data": { @@ -189,18 +80,18 @@ }, "selected": false, "positionAbsolute": { - "x": 711.3971966563331, - "y": 7.7184225021727 + "x": 1198.6643452533754, + "y": -584.4233173804803 }, "dragging": false }, { "width": 300, - "height": 473, + "height": 475, "id": "promptTemplate_0", "position": { - "x": 348.2881107399286, - "y": -97.74510214137423 + "x": 354.2706973608643, + "y": -122.34815000085804 }, "type": "customNode", "data": { @@ -249,18 +140,18 @@ }, "selected": false, "positionAbsolute": { - "x": 348.2881107399286, - "y": -97.74510214137423 + "x": 354.2706973608643, + "y": -122.34815000085804 }, "dragging": false }, { "width": 300, - "height": 522, + "height": 574, "id": "chatOpenAI_0", "position": { - "x": 335.7621848973805, - "y": -721.7411273245009 + "x": 353.5672832154869, + "y": -730.6436764835541 }, "type": "customNode", "data": { @@ -396,7 +287,7 @@ ], "inputs": { "modelName": "gpt-3.5-turbo-16k", - "temperature": 0.9, + "temperature": "0", "maxTokens": "", "topP": "", "frequencyPenalty": "", @@ -418,17 +309,17 @@ "selected": false, "dragging": false, "positionAbsolute": { - "x": 335.7621848973805, - "y": -721.7411273245009 + "x": 353.5672832154869, + "y": -730.6436764835541 } }, { "width": 300, - "height": 522, + "height": 574, "id": "chatOpenAI_1", "position": { - "x": 1765.2801848172305, - "y": -737.9261054149061 + "x": 2281.9246645710673, + "y": -778.8379360672121 }, "type": "customNode", "data": { @@ -564,7 +455,7 @@ ], "inputs": { "modelName": "gpt-3.5-turbo-16k", - "temperature": 0.9, + "temperature": "0", "maxTokens": "", "topP": "", "frequencyPenalty": "", @@ -586,36 +477,153 @@ "selected": false, "dragging": false, "positionAbsolute": { - "x": 1765.2801848172305, - "y": -737.9261054149061 + "x": 2281.9246645710673, + "y": -778.8379360672121 } }, { "width": 300, - "height": 473, - "id": "promptTemplate_1", + "height": 505, + "id": "pineconeExistingIndex_0", "position": { - "x": 1773.720934090435, - "y": -116.71323227575395 + "x": 1544.4998097474581, + "y": -628.8477510577202 }, "type": "customNode", "data": { - "id": "promptTemplate_1", - "label": "Prompt Template", + "id": "pineconeExistingIndex_0", + "label": "Pinecone Load Existing Index", "version": 1, - "name": "promptTemplate", - "type": "PromptTemplate", - "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate", "Runnable"], - "category": "Prompts", - "description": "Schema to represent a basic prompt for an LLM", + "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": "Template", - "name": "template", + "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": "", + "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": 1544.4998097474581, + "y": -628.8477510577202 + }, + "dragging": false + }, + { + "width": 300, + "height": 652, + "id": "chatPromptTemplate_0", + "position": { + "x": 2290.8365353040026, + "y": -168.49082887954518 + }, + "type": "customNode", + "data": { + "id": "chatPromptTemplate_0", + "label": "Chat Prompt Template", + "version": 1, + "name": "chatPromptTemplate", + "type": "ChatPromptTemplate", + "baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate", "Runnable"], + "category": "Prompts", + "description": "Schema to represent a chat prompt", + "inputParams": [ + { + "label": "System Message", + "name": "systemMessagePrompt", "type": "string", "rows": 4, - "placeholder": "What is a good name for a company that makes {product}?", - "id": "promptTemplate_1-input-template-string" + "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", @@ -624,20 +632,21 @@ "optional": true, "acceptVariable": true, "list": true, - "id": "promptTemplate_1-input-promptValues-json" + "id": "chatPromptTemplate_0-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}}\"}" + "systemMessagePrompt": "Using the provided context, answer the user's question to the best of your ability using the resources provided. If there is nothing in the context relevant to the question at hand, just say \"Hmm, I'm not sure.\" Don't try to make up an answer.\n\nAnything between the following \\`context\\` html blocks is retrieved from a knowledge bank, not part of the conversation with the user.\n\n\n {context}\n\n\nREMEMBER: If there is no relevant information within the context, just say \"Hmm, I'm not sure.\" Don't try to make up an answer. Anything between the preceding 'context' html blocks is retrieved from a knowledge bank, not part of the conversation with the user.", + "humanMessagePrompt": "{text}", + "promptValues": "{\"context\":\"{{vectorStoreToDocument_0.data.instance}}\",\"text\":\"{{question}}\"}" }, "outputAnchors": [ { - "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", - "name": "promptTemplate", - "label": "PromptTemplate", - "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate | Runnable" + "id": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable", + "name": "chatPromptTemplate", + "label": "ChatPromptTemplate", + "type": "ChatPromptTemplate | BaseChatPromptTemplate | BasePromptTemplate | Runnable" } ], "outputs": {}, @@ -645,192 +654,39 @@ }, "selected": false, "positionAbsolute": { - "x": 1773.720934090435, - "y": -116.71323227575395 + "x": 2290.8365353040026, + "y": -168.49082887954518 }, "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, + "height": 454, "id": "vectorStoreToDocument_0", "position": { - "x": 1407.7038120189868, - "y": -26.16468811205081 + "x": 1906.6871314089658, + "y": -157.0046189166955 }, "type": "customNode", "data": { "id": "vectorStoreToDocument_0", "label": "VectorStore To Document", - "version": 1, + "version": 2, "name": "vectorStoreToDocument", "type": "Document", "baseClasses": ["Document"], "category": "Document Loaders", "description": "Search documents with scores from vector store", "inputParams": [ + { + "label": "Query", + "name": "query", + "type": "string", + "description": "Query to retrieve documents from vector database. If not specified, user question will be used", + "optional": true, + "acceptVariable": true, + "id": "vectorStoreToDocument_0-input-query-string" + }, { "label": "Minimum Score (%)", "name": "minScore", @@ -852,6 +708,7 @@ ], "inputs": { "vectorStore": "{{pineconeExistingIndex_0.data.instance}}", + "query": "{{llmChain_2.data.instance}}", "minScore": "" }, "outputAnchors": [ @@ -883,8 +740,186 @@ }, "selected": false, "positionAbsolute": { - "x": 1407.7038120189868, - "y": -26.16468811205081 + "x": 1906.6871314089658, + "y": -157.0046189166955 + }, + "dragging": false + }, + { + "width": 300, + "height": 456, + "id": "llmChain_2", + "position": { + "x": 756.2678342825631, + "y": -244.07972550448233 + }, + "type": "customNode", + "data": { + "id": "llmChain_2", + "label": "LLM Chain", + "version": 3, + "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_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" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_2-input-outputParser-BaseLLMOutputParser" + } + ], + "inputs": { + "model": "{{chatOpenAI_0.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", + "outputParser": "", + "chainName": "RephraseQuestion" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_2-output-llmChain-LLMChain|BaseChain|Runnable", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain | Runnable" + }, + { + "id": "llmChain_2-output-outputPrediction-string|json", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string | json" + } + ], + "default": "llmChain" + } + ], + "outputs": { + "output": "outputPrediction" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 756.2678342825631, + "y": -244.07972550448233 + }, + "dragging": false + }, + { + "width": 300, + "height": 456, + "id": "llmChain_1", + "position": { + "x": 2684.08901232628, + "y": -301.4742415779482 + }, + "type": "customNode", + "data": { + "id": "llmChain_1", + "label": "LLM Chain", + "version": 3, + "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" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_1-input-outputParser-BaseLLMOutputParser" + } + ], + "inputs": { + "model": "{{chatOpenAI_1.data.instance}}", + "prompt": "{{chatPromptTemplate_0.data.instance}}", + "outputParser": "", + "chainName": "FinalResponse" + }, + "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": 2684.08901232628, + "y": -301.4742415779482 }, "dragging": false } @@ -915,10 +950,10 @@ { "source": "vectorStoreToDocument_0", "sourceHandle": "vectorStoreToDocument_0-output-text-string|json", - "target": "promptTemplate_1", - "targetHandle": "promptTemplate_1-input-promptValues-json", + "target": "chatPromptTemplate_0", + "targetHandle": "chatPromptTemplate_0-input-promptValues-json", "type": "buttonedge", - "id": "vectorStoreToDocument_0-vectorStoreToDocument_0-output-text-string|json-promptTemplate_1-promptTemplate_1-input-promptValues-json", + "id": "vectorStoreToDocument_0-vectorStoreToDocument_0-output-text-string|json-chatPromptTemplate_0-chatPromptTemplate_0-input-promptValues-json", "data": { "label": "" } @@ -926,10 +961,10 @@ { "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", - "target": "llmChain_0", - "targetHandle": "llmChain_0-input-model-BaseLanguageModel", + "target": "llmChain_2", + "targetHandle": "llmChain_2-input-model-BaseLanguageModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-llmChain_0-llmChain_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-llmChain_2-llmChain_2-input-model-BaseLanguageModel", "data": { "label": "" } @@ -937,21 +972,21 @@ { "source": "promptTemplate_0", "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", - "target": "llmChain_0", - "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", + "target": "llmChain_2", + "targetHandle": "llmChain_2-input-prompt-BasePromptTemplate", "type": "buttonedge", - "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable-llmChain_2-llmChain_2-input-prompt-BasePromptTemplate", "data": { "label": "" } }, { - "source": "llmChain_0", - "sourceHandle": "llmChain_0-output-outputPrediction-string|json", - "target": "promptTemplate_1", - "targetHandle": "promptTemplate_1-input-promptValues-json", + "source": "llmChain_2", + "sourceHandle": "llmChain_2-output-outputPrediction-string|json", + "target": "vectorStoreToDocument_0", + "targetHandle": "vectorStoreToDocument_0-input-query-string", "type": "buttonedge", - "id": "llmChain_0-llmChain_0-output-outputPrediction-string|json-promptTemplate_1-promptTemplate_1-input-promptValues-json", + "id": "llmChain_2-llmChain_2-output-outputPrediction-string|json-vectorStoreToDocument_0-vectorStoreToDocument_0-input-query-string", "data": { "label": "" } @@ -968,12 +1003,12 @@ } }, { - "source": "promptTemplate_1", - "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", + "source": "chatPromptTemplate_0", + "sourceHandle": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|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", + "id": "chatPromptTemplate_0-chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Prompt Chaining.json b/packages/server/marketplaces/chatflows/Prompt Chaining.json index 77c238ad..3181bc47 100644 --- a/packages/server/marketplaces/chatflows/Prompt Chaining.json +++ b/packages/server/marketplaces/chatflows/Prompt Chaining.json @@ -13,8 +13,8 @@ "data": { "id": "promptTemplate_0", "label": "Prompt Template", - "name": "promptTemplate", "version": 1, + "name": "promptTemplate", "type": "PromptTemplate", "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], "category": "Prompts", @@ -73,8 +73,8 @@ "data": { "id": "promptTemplate_1", "label": "Prompt Template", - "name": "promptTemplate", "version": 1, + "name": "promptTemplate", "type": "PromptTemplate", "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], "category": "Prompts", @@ -123,169 +123,7 @@ }, { "width": 300, - "height": 405, - "id": "llmChain_0", - "position": { - "x": 1192.835706086358, - "y": 367.49653955405995 - }, - "type": "customNode", - "data": { - "id": "llmChain_0", - "label": "LLM Chain", - "name": "llmChain", - "version": 1, - "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_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": "{{openAI_1.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": 1192.835706086358, - "y": 367.49653955405995 - }, - "dragging": false - }, - { - "width": 300, - "height": 405, - "id": "llmChain_1", - "position": { - "x": 1956.8236771865425, - "y": 359.10696865911547 - }, - "type": "customNode", - "data": { - "id": "llmChain_1", - "label": "LLM Chain", - "name": "llmChain", - "version": 1, - "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_2.data.instance}}", - "prompt": "{{promptTemplate_1.data.instance}}", - "chainName": "LastChain" - }, - "outputAnchors": [ - { - "name": "output", - "label": "Output", - "type": "options", - "options": [ - { - "id": "llmChain_1-output-llmChain-LLMChain|BaseChain", - "name": "llmChain", - "label": "LLM Chain", - "type": "LLMChain | BaseChain" - }, - { - "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": 1956.8236771865425, - "y": 359.10696865911547 - }, - "dragging": false - }, - { - "width": 300, - "height": 523, + "height": 574, "id": "openAI_1", "position": { "x": 791.6102007244282, @@ -295,8 +133,8 @@ "data": { "id": "openAI_1", "label": "OpenAI", - "name": "openAI", "version": 3, + "name": "openAI", "type": "OpenAI", "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], "category": "LLMs", @@ -445,18 +283,18 @@ }, { "width": 300, - "height": 523, + "height": 574, "id": "openAI_2", "position": { "x": 1571.148617508543, - "y": -90.372437481171687 + "y": -90.37243748117169 }, "type": "customNode", "data": { "id": "openAI_2", "label": "OpenAI", - "name": "openAI", "version": 3, + "name": "openAI", "type": "OpenAI", "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], "category": "LLMs", @@ -573,7 +411,6 @@ "id": "openAI_2-input-cache-BaseCache" } ], - "default": "gpt-3.5-turbo-instruct", "inputs": { "modelName": "gpt-3.5-turbo-instruct", "temperature": 0.7, @@ -600,12 +437,201 @@ "selected": false, "positionAbsolute": { "x": 1571.148617508543, - "y": -90.372437481171687 + "y": -90.37243748117169 + }, + "dragging": false + }, + { + "width": 300, + "height": 456, + "id": "llmChain_0", + "position": { + "x": 1183.0899727188096, + "y": 385.0159960992951 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", + "label": "LLM Chain", + "version": 3, + "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" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_0-input-outputParser-BaseLLMOutputParser" + } + ], + "inputs": { + "model": "{{openAI_1.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", + "outputParser": "", + "chainName": "FirstChain" + }, + "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": 1183.0899727188096, + "y": 385.0159960992951 + }, + "dragging": false + }, + { + "width": 300, + "height": 456, + "id": "llmChain_1", + "position": { + "x": 1973.883197748518, + "y": 370.7937277714931 + }, + "type": "customNode", + "data": { + "id": "llmChain_1", + "label": "LLM Chain", + "version": 3, + "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" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_1-input-outputParser-BaseLLMOutputParser" + } + ], + "inputs": { + "model": "{{openAI_2.data.instance}}", + "prompt": "{{promptTemplate_1.data.instance}}", + "outputParser": "", + "chainName": "LastChain" + }, + "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": 1973.883197748518, + "y": 370.7937277714931 }, "dragging": false } ], "edges": [ + { + "source": "openAI_1", + "sourceHandle": "openAI_1-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "openAI_1-openAI_1-output-openAI-OpenAI|BaseLLM|BaseLanguageModel-llmChain_0-llmChain_0-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, { "source": "promptTemplate_0", "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", @@ -639,17 +665,6 @@ "label": "" } }, - { - "source": "openAI_1", - "sourceHandle": "openAI_1-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", - "target": "llmChain_0", - "targetHandle": "llmChain_0-input-model-BaseLanguageModel", - "type": "buttonedge", - "id": "openAI_1-openAI_1-output-openAI-OpenAI|BaseLLM|BaseLanguageModel-llmChain_0-llmChain_0-input-model-BaseLanguageModel", - "data": { - "label": "" - } - }, { "source": "openAI_2", "sourceHandle": "openAI_2-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", diff --git a/packages/server/marketplaces/chatflows/Replicate LLM.json b/packages/server/marketplaces/chatflows/Replicate LLM.json index 0049214c..bee565ce 100644 --- a/packages/server/marketplaces/chatflows/Replicate LLM.json +++ b/packages/server/marketplaces/chatflows/Replicate LLM.json @@ -3,88 +3,7 @@ "nodes": [ { "width": 300, - "height": 405, - "id": "llmChain_1", - "position": { - "x": 967.581544453458, - "y": 320.56761595884564 - }, - "type": "customNode", - "data": { - "id": "llmChain_1", - "label": "LLM Chain", - "version": 1, - "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": "{{replicate_0.data.instance}}", - "prompt": "{{promptTemplate_0.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|json", - "name": "outputPrediction", - "label": "Output Prediction", - "type": "string | json" - } - ], - "default": "llmChain" - } - ], - "outputs": { - "output": "llmChain" - }, - "selected": false - }, - "positionAbsolute": { - "x": 967.581544453458, - "y": 320.56761595884564 - }, - "selected": false, - "dragging": false - }, - { - "width": 300, - "height": 474, + "height": 475, "id": "promptTemplate_0", "position": { "x": 269.2203229225663, @@ -144,7 +63,7 @@ }, { "width": 300, - "height": 526, + "height": 577, "id": "replicate_0", "position": { "x": 623.313978186024, @@ -260,27 +179,116 @@ "y": -142.92788335022428 }, "dragging": false + }, + { + "width": 300, + "height": 456, + "id": "llmChain_0", + "position": { + "x": 1013.8484815418046, + "y": 298.7146179121001 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", + "label": "LLM Chain", + "version": 3, + "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" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_0-input-outputParser-BaseLLMOutputParser" + } + ], + "inputs": { + "model": "{{replicate_0.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", + "outputParser": "", + "chainName": "" + }, + "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": "llmChain" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1013.8484815418046, + "y": 298.7146179121001 + }, + "dragging": false } ], "edges": [ { - "source": "promptTemplate_0", - "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "target": "llmChain_1", - "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", + "source": "replicate_0", + "sourceHandle": "replicate_0-output-replicate-Replicate|BaseChatModel|LLM|BaseLLM|BaseLanguageModel|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-model-BaseLanguageModel", "type": "buttonedge", - "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "id": "replicate_0-replicate_0-output-replicate-Replicate|BaseChatModel|LLM|BaseLLM|BaseLanguageModel|Runnable-llmChain_0-llmChain_0-input-model-BaseLanguageModel", "data": { "label": "" } }, { - "source": "replicate_0", - "sourceHandle": "replicate_0-output-replicate-Replicate|BaseChatModel|LLM|BaseLLM|BaseLanguageModel|Runnable", - "target": "llmChain_1", - "targetHandle": "llmChain_1-input-model-BaseLanguageModel", + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", "type": "buttonedge", - "id": "replicate_0-replicate_0-output-replicate-Replicate|BaseChatModel|LLM|BaseLLM|BaseLanguageModel|Runnable-llmChain_1-llmChain_1-input-model-BaseLanguageModel", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Simple LLM Chain.json b/packages/server/marketplaces/chatflows/Simple LLM Chain.json index f3db04ef..b07124c0 100644 --- a/packages/server/marketplaces/chatflows/Simple LLM Chain.json +++ b/packages/server/marketplaces/chatflows/Simple LLM Chain.json @@ -1,87 +1,6 @@ { "description": "Basic example of stateless (no memory) LLM Chain with a Prompt Template and LLM Model", "nodes": [ - { - "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", - "version": 1, - "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": "{{openAI_0.data.instance}}", - "prompt": "{{promptTemplate_0.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|json", - "name": "outputPrediction", - "label": "Output Prediction", - "type": "string | json" - } - ], - "default": "llmChain" - } - ], - "outputs": { - "output": "llmChain" - }, - "selected": false - }, - "positionAbsolute": { - "x": 970.9254258940236, - "y": 320.56761595884564 - }, - "selected": false, - "dragging": false - }, { "width": 300, "height": 475, @@ -94,8 +13,8 @@ "data": { "id": "promptTemplate_0", "label": "Prompt Template", - "name": "promptTemplate", "version": 1, + "name": "promptTemplate", "type": "PromptTemplate", "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], "category": "Prompts", @@ -144,7 +63,7 @@ }, { "width": 300, - "height": 523, + "height": 574, "id": "openAI_0", "position": { "x": 513.3297923232442, @@ -154,8 +73,8 @@ "data": { "id": "openAI_0", "label": "OpenAI", - "name": "openAI", "version": 3, + "name": "openAI", "type": "OpenAI", "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], "category": "LLMs", @@ -301,27 +220,116 @@ "y": -112.67554802812833 }, "dragging": false + }, + { + "width": 300, + "height": 456, + "id": "llmChain_0", + "position": { + "x": 919.263534910828, + "y": 318.465734712124 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", + "label": "LLM Chain", + "version": 3, + "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" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_0-input-outputParser-BaseLLMOutputParser" + } + ], + "inputs": { + "model": "{{openAI_0.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", + "outputParser": "", + "chainName": "" + }, + "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": "llmChain" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 919.263534910828, + "y": 318.465734712124 + }, + "dragging": false } ], "edges": [ { - "source": "promptTemplate_0", - "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "target": "llmChain_1", - "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", + "source": "openAI_0", + "sourceHandle": "openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-model-BaseLanguageModel", "type": "buttonedge", - "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "id": "openAI_0-openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel-llmChain_0-llmChain_0-input-model-BaseLanguageModel", "data": { "label": "" } }, { - "source": "openAI_0", - "sourceHandle": "openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel", - "target": "llmChain_1", - "targetHandle": "llmChain_1-input-model-BaseLanguageModel", + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", "type": "buttonedge", - "id": "openAI_0-openAI_0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel-llmChain_1-llmChain_1-input-model-BaseLanguageModel", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Structured Output Parser.json b/packages/server/marketplaces/chatflows/Structured Output Parser.json new file mode 100644 index 00000000..38947f93 --- /dev/null +++ b/packages/server/marketplaces/chatflows/Structured Output Parser.json @@ -0,0 +1,477 @@ +{ + "description": "Return response as a specified JSON structure instead of a string/text", + "nodes": [ + { + "width": 300, + "height": 574, + "id": "chatOpenAI_0", + "position": { + "x": 845.3961479115309, + "y": -205.74401580699953 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "version": 2, + "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" + }, + { + "label": "BaseOptions", + "name": "baseOptions", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-baseOptions-json" + } + ], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], + "inputs": { + "cache": "", + "modelName": "gpt-3.5-turbo", + "temperature": "0", + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "", + "baseOptions": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 845.3961479115309, + "y": -205.74401580699953 + }, + "dragging": false + }, + { + "width": 300, + "height": 456, + "id": "llmChain_0", + "position": { + "x": 1229.1699649849293, + "y": 245.55173505632646 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", + "label": "LLM Chain", + "version": 3, + "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" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_0-input-outputParser-BaseLLMOutputParser" + } + ], + "inputs": { + "model": "{{chatOpenAI_0.data.instance}}", + "prompt": "{{chatPromptTemplate_0.data.instance}}", + "outputParser": "{{structuredOutputParser_0.data.instance}}", + "chainName": "" + }, + "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": "llmChain" + }, + "selected": false + }, + "positionAbsolute": { + "x": 1229.1699649849293, + "y": 245.55173505632646 + }, + "selected": false + }, + { + "width": 300, + "height": 652, + "id": "chatPromptTemplate_0", + "position": { + "x": 501.1597501123828, + "y": -154.43917602832562 + }, + "type": "customNode", + "data": { + "id": "chatPromptTemplate_0", + "label": "Chat Prompt Template", + "version": 1, + "name": "chatPromptTemplate", + "type": "ChatPromptTemplate", + "baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate", "Runnable"], + "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": "Answer user's question as best you can", + "humanMessagePrompt": "{text}", + "promptValues": "" + }, + "outputAnchors": [ + { + "id": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable", + "name": "chatPromptTemplate", + "label": "ChatPromptTemplate", + "type": "ChatPromptTemplate | BaseChatPromptTemplate | BasePromptTemplate | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 501.1597501123828, + "y": -154.43917602832562 + }, + "dragging": false + }, + { + "width": 300, + "height": 328, + "id": "structuredOutputParser_0", + "position": { + "x": 170.3869571939727, + "y": 343.9298288967859 + }, + "type": "customNode", + "data": { + "id": "structuredOutputParser_0", + "label": "Structured Output Parser", + "version": 1, + "name": "structuredOutputParser", + "type": "StructuredOutputParser", + "baseClasses": ["StructuredOutputParser", "BaseLLMOutputParser", "Runnable"], + "category": "Output Parsers", + "description": "Parse the output of an LLM call into a given (JSON) structure.", + "inputParams": [ + { + "label": "Autofix", + "name": "autofixParser", + "type": "boolean", + "optional": true, + "description": "In the event that the first call fails, will make another call to the model to fix any errors.", + "id": "structuredOutputParser_0-input-autofixParser-boolean" + }, + { + "label": "JSON Structure", + "name": "jsonStructure", + "type": "datagrid", + "description": "JSON structure for LLM to return", + "datagrid": [ + { + "field": "property", + "headerName": "Property", + "editable": true + }, + { + "field": "type", + "headerName": "Type", + "type": "singleSelect", + "valueOptions": ["string", "number", "boolean"], + "editable": true + }, + { + "field": "description", + "headerName": "Description", + "editable": true, + "flex": 1 + } + ], + "default": [ + { + "property": "answer", + "type": "string", + "description": "answer to the user's question" + }, + { + "property": "source", + "type": "string", + "description": "sources used to answer the question, should be websites" + } + ], + "additionalParams": true, + "id": "structuredOutputParser_0-input-jsonStructure-datagrid" + } + ], + "inputAnchors": [], + "inputs": { + "autofixParser": true, + "jsonStructure": [ + { + "property": "answer", + "type": "string", + "description": "answer to the user's question" + }, + { + "property": "source", + "type": "string", + "description": "sources used to answer the question, should be websites" + } + ] + }, + "outputAnchors": [ + { + "id": "structuredOutputParser_0-output-structuredOutputParser-StructuredOutputParser|BaseLLMOutputParser|Runnable", + "name": "structuredOutputParser", + "label": "StructuredOutputParser", + "type": "StructuredOutputParser | BaseLLMOutputParser | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 170.3869571939727, + "y": 343.9298288967859 + }, + "dragging": false + } + ], + "edges": [ + { + "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": "chatPromptTemplate_0", + "sourceHandle": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "chatPromptTemplate_0-chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } + }, + { + "source": "structuredOutputParser_0", + "sourceHandle": "structuredOutputParser_0-output-structuredOutputParser-StructuredOutputParser|BaseLLMOutputParser|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-outputParser-BaseLLMOutputParser", + "type": "buttonedge", + "id": "structuredOutputParser_0-structuredOutputParser_0-output-structuredOutputParser-StructuredOutputParser|BaseLLMOutputParser|Runnable-llmChain_0-llmChain_0-input-outputParser-BaseLLMOutputParser", + "data": { + "label": "" + } + } + ] +} diff --git a/packages/server/marketplaces/chatflows/Translator.json b/packages/server/marketplaces/chatflows/Translator.json index b552aceb..7cbe5ac7 100644 --- a/packages/server/marketplaces/chatflows/Translator.json +++ b/packages/server/marketplaces/chatflows/Translator.json @@ -1,87 +1,6 @@ { "description": "Language translation using LLM Chain with a Chat Prompt Template and Chat Model", "nodes": [ - { - "width": 300, - "height": 405, - "id": "llmChain_1", - "position": { - "x": 865.7775572410412, - "y": 543.9211372857111 - }, - "type": "customNode", - "data": { - "id": "llmChain_1", - "label": "LLM Chain", - "name": "llmChain", - "version": 1, - "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": "{{chatOpenAI_0.data.instance}}", - "prompt": "{{chatPromptTemplate_0.data.instance}}", - "chainName": "Language Translation" - }, - "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|json", - "name": "outputPrediction", - "label": "Output Prediction", - "type": "string | json" - } - ], - "default": "llmChain" - } - ], - "outputs": { - "output": "llmChain" - }, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 865.7775572410412, - "y": 543.9211372857111 - }, - "dragging": false - }, { "width": 300, "height": 652, @@ -94,8 +13,8 @@ "data": { "id": "chatPromptTemplate_0", "label": "Chat Prompt Template", - "name": "chatPromptTemplate", "version": 1, + "name": "chatPromptTemplate", "type": "ChatPromptTemplate", "baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate"], "category": "Prompts", @@ -153,7 +72,7 @@ }, { "width": 300, - "height": 523, + "height": 574, "id": "chatOpenAI_0", "position": { "x": 436.97058562345904, @@ -163,8 +82,8 @@ "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", - "name": "chatOpenAI", "version": 2, + "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -312,27 +231,116 @@ "y": 29.96180150605153 }, "dragging": false + }, + { + "width": 300, + "height": 456, + "id": "llmChain_0", + "position": { + "x": 836.089121144244, + "y": 510.07109938359963 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", + "label": "LLM Chain", + "version": 3, + "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" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_0-input-outputParser-BaseLLMOutputParser" + } + ], + "inputs": { + "model": "{{chatOpenAI_0.data.instance}}", + "prompt": "{{chatPromptTemplate_0.data.instance}}", + "outputParser": "", + "chainName": "Language Translation" + }, + "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": "llmChain" + }, + "selected": false + }, + "selected": false, + "dragging": false, + "positionAbsolute": { + "x": 836.089121144244, + "y": 510.07109938359963 + } } ], "edges": [ { - "source": "chatPromptTemplate_0", - "sourceHandle": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate", - "target": "llmChain_1", - "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-model-BaseLanguageModel", "type": "buttonedge", - "id": "chatPromptTemplate_0-chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-llmChain_0-llmChain_0-input-model-BaseLanguageModel", "data": { "label": "" } }, { - "source": "chatOpenAI_0", - "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", - "target": "llmChain_1", - "targetHandle": "llmChain_1-input-model-BaseLanguageModel", + "source": "chatPromptTemplate_0", + "sourceHandle": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-llmChain_1-llmChain_1-input-model-BaseLanguageModel", + "id": "chatPromptTemplate_0-chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", "data": { "label": "" } diff --git a/packages/server/package.json b/packages/server/package.json index eadedea0..0a722683 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.8", + "version": "1.3.9", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", @@ -64,6 +64,7 @@ "socket.io": "^4.6.1", "sqlite3": "^5.1.6", "typeorm": "^0.3.6", + "uuid": "^9.0.1", "winston": "^3.9.0" }, "devDependencies": { diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index b3c3a392..d6d24596 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -2,6 +2,10 @@ import { ICommonObject, INode, INodeData as INodeDataFromComponent, INodeParams export type MessageType = 'apiMessage' | 'userMessage' +export enum chatType { + INTERNAL = 'INTERNAL', + EXTERNAL = 'EXTERNAL' +} /** * Databases */ @@ -24,8 +28,12 @@ export interface IChatMessage { role: MessageType content: string chatflowid: string - createdDate: Date sourceDocuments?: string + chatType: string + chatId: string + memoryType?: string + sessionId?: string + createdDate: Date } export interface ITool { @@ -146,6 +154,7 @@ export interface IncomingInput { history: IMessage[] overrideConfig?: ICommonObject socketIOClientId?: string + chatId?: string } export interface IActiveChatflows { diff --git a/packages/server/src/database/entities/ChatMessage.ts b/packages/server/src/database/entities/ChatMessage.ts index 4b5306ee..3efd7fbb 100644 --- a/packages/server/src/database/entities/ChatMessage.ts +++ b/packages/server/src/database/entities/ChatMessage.ts @@ -20,6 +20,18 @@ export class ChatMessage implements IChatMessage { @Column({ nullable: true, type: 'text' }) sourceDocuments?: string + @Column() + chatType: string + + @Column() + chatId: string + + @Column({ nullable: true }) + memoryType?: string + + @Column({ nullable: true }) + sessionId?: string + @CreateDateColumn() createdDate: Date } diff --git a/packages/server/src/database/migrations/mysql/1694099200729-AddApiConfig.ts b/packages/server/src/database/migrations/mysql/1694099200729-AddApiConfig.ts index c82b36ea..4509c5bb 100644 --- a/packages/server/src/database/migrations/mysql/1694099200729-AddApiConfig.ts +++ b/packages/server/src/database/migrations/mysql/1694099200729-AddApiConfig.ts @@ -2,7 +2,8 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class AddApiConfig1694099200729 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`apiConfig\` TEXT;`) + const columnExists = await queryRunner.hasColumn('chat_flow', 'apiConfig') + if (!columnExists) queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`apiConfig\` TEXT;`) } public async down(queryRunner: QueryRunner): Promise { diff --git a/packages/server/src/database/migrations/mysql/1694432361423-AddAnalytic.ts b/packages/server/src/database/migrations/mysql/1694432361423-AddAnalytic.ts index a5e088fa..5fed5753 100644 --- a/packages/server/src/database/migrations/mysql/1694432361423-AddAnalytic.ts +++ b/packages/server/src/database/migrations/mysql/1694432361423-AddAnalytic.ts @@ -2,7 +2,8 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class AddAnalytic1694432361423 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`analytic\` TEXT;`) + const columnExists = await queryRunner.hasColumn('chat_flow', 'analytic') + if (!columnExists) queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`analytic\` TEXT;`) } public async down(queryRunner: QueryRunner): Promise { diff --git a/packages/server/src/database/migrations/mysql/1694658767766-AddChatHistory.ts b/packages/server/src/database/migrations/mysql/1694658767766-AddChatHistory.ts new file mode 100644 index 00000000..317d6188 --- /dev/null +++ b/packages/server/src/database/migrations/mysql/1694658767766-AddChatHistory.ts @@ -0,0 +1,41 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddChatHistory1694658767766 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const chatTypeColumnExists = await queryRunner.hasColumn('chat_message', 'chatType') + if (!chatTypeColumnExists) + await queryRunner.query(`ALTER TABLE \`chat_message\` ADD COLUMN \`chatType\` VARCHAR(255) NOT NULL DEFAULT 'INTERNAL';`) + + const chatIdColumnExists = await queryRunner.hasColumn('chat_message', 'chatId') + if (!chatIdColumnExists) await queryRunner.query(`ALTER TABLE \`chat_message\` ADD COLUMN \`chatId\` VARCHAR(255);`) + const results: { id: string; chatflowid: string }[] = await queryRunner.query(`WITH RankedMessages AS ( + SELECT + \`chatflowid\`, + \`id\`, + \`createdDate\`, + ROW_NUMBER() OVER (PARTITION BY \`chatflowid\` ORDER BY \`createdDate\`) AS row_num + FROM \`chat_message\` + ) + SELECT \`chatflowid\`, \`id\` + FROM RankedMessages + WHERE row_num = 1;`) + for (const chatMessage of results) { + await queryRunner.query( + `UPDATE \`chat_message\` SET \`chatId\` = '${chatMessage.id}' WHERE \`chatflowid\` = '${chatMessage.chatflowid}'` + ) + } + await queryRunner.query(`ALTER TABLE \`chat_message\` MODIFY \`chatId\` VARCHAR(255) NOT NULL;`) + + const memoryTypeColumnExists = await queryRunner.hasColumn('chat_message', 'memoryType') + if (!memoryTypeColumnExists) await queryRunner.query(`ALTER TABLE \`chat_message\` ADD COLUMN \`memoryType\` VARCHAR(255);`) + + const sessionIdColumnExists = await queryRunner.hasColumn('chat_message', 'sessionId') + if (!sessionIdColumnExists) await queryRunner.query(`ALTER TABLE \`chat_message\` ADD COLUMN \`sessionId\` VARCHAR(255);`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE \`chat_message\` DROP COLUMN \`chatType\`, DROP COLUMN \`chatId\`, DROP COLUMN \`memoryType\`, DROP COLUMN \`sessionId\`;` + ) + } +} diff --git a/packages/server/src/database/migrations/mysql/index.ts b/packages/server/src/database/migrations/mysql/index.ts index ea26789e..aa34fa55 100644 --- a/packages/server/src/database/migrations/mysql/index.ts +++ b/packages/server/src/database/migrations/mysql/index.ts @@ -5,6 +5,7 @@ import { ModifyCredential1693999261583 } from './1693999261583-ModifyCredential' import { ModifyTool1694001465232 } from './1694001465232-ModifyTool' import { AddApiConfig1694099200729 } from './1694099200729-AddApiConfig' import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' +import { AddChatHistory1694658767766 } from './1694658767766-AddChatHistory' export const mysqlMigrations = [ Init1693840429259, @@ -13,5 +14,6 @@ export const mysqlMigrations = [ ModifyCredential1693999261583, ModifyTool1694001465232, AddApiConfig1694099200729, - AddAnalytic1694432361423 + AddAnalytic1694432361423, + AddChatHistory1694658767766 ] diff --git a/packages/server/src/database/migrations/postgres/1694099183389-AddApiConfig.ts b/packages/server/src/database/migrations/postgres/1694099183389-AddApiConfig.ts index 832c2fa3..840bcc24 100644 --- a/packages/server/src/database/migrations/postgres/1694099183389-AddApiConfig.ts +++ b/packages/server/src/database/migrations/postgres/1694099183389-AddApiConfig.ts @@ -2,7 +2,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class AddApiConfig1694099183389 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN "apiConfig" TEXT;`) + await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN IF NOT EXISTS "apiConfig" TEXT;`) } public async down(queryRunner: QueryRunner): Promise { diff --git a/packages/server/src/database/migrations/postgres/1694432361423-AddAnalytic.ts b/packages/server/src/database/migrations/postgres/1694432361423-AddAnalytic.ts index ed83c833..e95bd68c 100644 --- a/packages/server/src/database/migrations/postgres/1694432361423-AddAnalytic.ts +++ b/packages/server/src/database/migrations/postgres/1694432361423-AddAnalytic.ts @@ -2,7 +2,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class AddAnalytic1694432361423 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN "analytic" TEXT;`) + await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN IF NOT EXISTS "analytic" TEXT;`) } public async down(queryRunner: QueryRunner): Promise { diff --git a/packages/server/src/database/migrations/postgres/1694658756136-AddChatHistory.ts b/packages/server/src/database/migrations/postgres/1694658756136-AddChatHistory.ts new file mode 100644 index 00000000..cba0e1e4 --- /dev/null +++ b/packages/server/src/database/migrations/postgres/1694658756136-AddChatHistory.ts @@ -0,0 +1,32 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddChatHistory1694658756136 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "chat_message" ADD COLUMN IF NOT EXISTS "chatType" VARCHAR NOT NULL DEFAULT 'INTERNAL', ADD COLUMN IF NOT EXISTS "chatId" VARCHAR, ADD COLUMN IF NOT EXISTS "memoryType" VARCHAR, ADD COLUMN IF NOT EXISTS "sessionId" VARCHAR;` + ) + const results: { id: string; chatflowid: string }[] = await queryRunner.query(`WITH RankedMessages AS ( + SELECT + "chatflowid", + "id", + "createdDate", + ROW_NUMBER() OVER (PARTITION BY "chatflowid" ORDER BY "createdDate") AS row_num + FROM "chat_message" + ) + SELECT "chatflowid", "id" + FROM RankedMessages + WHERE row_num = 1;`) + for (const chatMessage of results) { + await queryRunner.query( + `UPDATE "chat_message" SET "chatId" = '${chatMessage.id}' WHERE "chatflowid" = '${chatMessage.chatflowid}'` + ) + } + await queryRunner.query(`ALTER TABLE "chat_message" ALTER COLUMN "chatId" SET NOT NULL;`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "chat_message" DROP COLUMN "chatType", DROP COLUMN "chatId", DROP COLUMN "memoryType", DROP COLUMN "sessionId";` + ) + } +} diff --git a/packages/server/src/database/migrations/postgres/index.ts b/packages/server/src/database/migrations/postgres/index.ts index 4a78556b..e16d9107 100644 --- a/packages/server/src/database/migrations/postgres/index.ts +++ b/packages/server/src/database/migrations/postgres/index.ts @@ -5,6 +5,7 @@ import { ModifyCredential1693997070000 } from './1693997070000-ModifyCredential' import { ModifyTool1693997339912 } from './1693997339912-ModifyTool' import { AddApiConfig1694099183389 } from './1694099183389-AddApiConfig' import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' +import { AddChatHistory1694658756136 } from './1694658756136-AddChatHistory' export const postgresMigrations = [ Init1693891895163, @@ -13,5 +14,6 @@ export const postgresMigrations = [ ModifyCredential1693997070000, ModifyTool1693997339912, AddApiConfig1694099183389, - AddAnalytic1694432361423 + AddAnalytic1694432361423, + AddChatHistory1694658756136 ] diff --git a/packages/server/src/database/migrations/sqlite/1694657778173-AddChatHistory.ts b/packages/server/src/database/migrations/sqlite/1694657778173-AddChatHistory.ts new file mode 100644 index 00000000..67703ae0 --- /dev/null +++ b/packages/server/src/database/migrations/sqlite/1694657778173-AddChatHistory.ts @@ -0,0 +1,40 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddChatHistory1694657778173 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_message" ADD COLUMN "chatId" VARCHAR;`) + const results: { id: string; chatflowid: string }[] = await queryRunner.query(`WITH RankedMessages AS ( + SELECT + "chatflowid", + "id", + "createdDate", + ROW_NUMBER() OVER (PARTITION BY "chatflowid" ORDER BY "createdDate") AS row_num + FROM "chat_message" + ) + SELECT "chatflowid", "id" + FROM RankedMessages + WHERE row_num = 1;`) + for (const chatMessage of results) { + await queryRunner.query( + `UPDATE "chat_message" SET "chatId" = '${chatMessage.id}' WHERE "chatflowid" = '${chatMessage.chatflowid}'` + ) + } + await queryRunner.query( + `CREATE TABLE "temp_chat_message" ("id" varchar PRIMARY KEY NOT NULL, "role" varchar NOT NULL, "chatflowid" varchar NOT NULL, "content" text NOT NULL, "sourceDocuments" text, "createdDate" datetime NOT NULL DEFAULT (datetime('now')), "chatType" VARCHAR NOT NULL DEFAULT 'INTERNAL', "chatId" VARCHAR NOT NULL, "memoryType" VARCHAR, "sessionId" VARCHAR);` + ) + await queryRunner.query( + `INSERT INTO "temp_chat_message" ("id", "role", "chatflowid", "content", "sourceDocuments", "createdDate", "chatId") SELECT "id", "role", "chatflowid", "content", "sourceDocuments", "createdDate", "chatId" FROM "chat_message";` + ) + await queryRunner.query(`DROP TABLE "chat_message";`) + await queryRunner.query(`ALTER TABLE "temp_chat_message" RENAME TO "chat_message";`) + await queryRunner.query(`CREATE INDEX "IDX_e574527322272fd838f4f0f3d3" ON "chat_message" ("chatflowid") ;`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE IF EXISTS "temp_chat_message";`) + await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "chatType";`) + await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "chatId";`) + await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "memoryType";`) + await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "sessionId";`) + } +} diff --git a/packages/server/src/database/migrations/sqlite/index.ts b/packages/server/src/database/migrations/sqlite/index.ts index bff926b0..680f762b 100644 --- a/packages/server/src/database/migrations/sqlite/index.ts +++ b/packages/server/src/database/migrations/sqlite/index.ts @@ -5,6 +5,7 @@ import { ModifyCredential1693923551694 } from './1693923551694-ModifyCredential' import { ModifyTool1693924207475 } from './1693924207475-ModifyTool' import { AddApiConfig1694090982460 } from './1694090982460-AddApiConfig' import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' +import { AddChatHistory1694657778173 } from './1694657778173-AddChatHistory' export const sqliteMigrations = [ Init1693835579790, @@ -13,5 +14,6 @@ export const sqliteMigrations = [ ModifyCredential1693923551694, ModifyTool1693924207475, AddApiConfig1694090982460, - AddAnalytic1694432361423 + AddAnalytic1694432361423, + AddChatHistory1694657778173 ] diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 89b36ad1..eade3d98 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -8,7 +8,8 @@ import basicAuth from 'express-basic-auth' import { Server } from 'socket.io' import logger from './utils/logger' import { expressRequestLogger } from './utils/logger' - +import { v4 as uuidv4 } from 'uuid' +import { Between, IsNull, FindOptionsWhere } from 'typeorm' import { IChatFlow, IncomingInput, @@ -16,7 +17,10 @@ import { IReactFlowObject, INodeData, IDatabaseExport, - ICredentialReturnResponse + ICredentialReturnResponse, + chatType, + IChatMessage, + IReactFlowEdge } from './Interface' import { getNodeModulesPackagePath, @@ -40,10 +44,11 @@ import { getApiKey, transformToCredentialEntity, decryptCredentialData, - clearSessionMemory, + clearAllSessionMemory, replaceInputsWithConfig, getEncryptionKey, - checkMemorySessionId + checkMemorySessionId, + clearSessionMemoryFromViewMessageDialog } from './utils' import { cloneDeep, omit } from 'lodash' import { getDataSource } from './DataSource' @@ -395,45 +400,92 @@ 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).find({ - where: { - chatflowid: req.params.id - }, - order: { - createdDate: 'ASC' + const sortOrder = req.query?.order as string | undefined + const chatId = req.query?.chatId as string | undefined + const memoryType = req.query?.memoryType as string | undefined + const sessionId = req.query?.sessionId as string | undefined + const startDate = req.query?.startDate as string | undefined + const endDate = req.query?.endDate as string | undefined + let chatTypeFilter = req.query?.chatType as chatType | undefined + + if (chatTypeFilter) { + try { + const chatTypeFilterArray = JSON.parse(chatTypeFilter) + if (chatTypeFilterArray.includes(chatType.EXTERNAL) && chatTypeFilterArray.includes(chatType.INTERNAL)) { + chatTypeFilter = undefined + } else if (chatTypeFilterArray.includes(chatType.EXTERNAL)) { + chatTypeFilter = chatType.EXTERNAL + } else if (chatTypeFilterArray.includes(chatType.INTERNAL)) { + chatTypeFilter = chatType.INTERNAL + } + } catch (e) { + return res.status(500).send(e) } - }) + } + + const chatmessages = await this.getChatMessage( + req.params.id, + chatTypeFilter, + sortOrder, + chatId, + memoryType, + sessionId, + startDate, + endDate + ) + return res.json(chatmessages) + }) + + // Get internal chatmessages from chatflowid + this.app.get('/api/v1/internal-chatmessage/:id', async (req: Request, res: Response) => { + const chatmessages = await this.getChatMessage(req.params.id, chatType.INTERNAL) return res.json(chatmessages) }) // Add chatmessages for chatflowid this.app.post('/api/v1/chatmessage/:id', async (req: Request, res: Response) => { const body = req.body - const newChatMessage = new ChatMessage() - Object.assign(newChatMessage, body) - - const chatmessage = this.AppDataSource.getRepository(ChatMessage).create(newChatMessage) - const results = await this.AppDataSource.getRepository(ChatMessage).save(chatmessage) - + const results = await this.addChatMessage(body) return res.json(results) }) - // Delete all chatmessages from chatflowid + // Delete all chatmessages from chatId this.app.delete('/api/v1/chatmessage/:id', async (req: Request, res: Response) => { + const chatflowid = req.params.id const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ - id: req.params.id + id: chatflowid }) if (!chatflow) { - res.status(404).send(`Chatflow ${req.params.id} not found`) + res.status(404).send(`Chatflow ${chatflowid} not found`) return } + const chatId = (req.query?.chatId as string) ?? (await getChatId(chatflowid)) + const memoryType = req.query?.memoryType as string | undefined + const sessionId = req.query?.sessionId as string | undefined + const chatType = req.query?.chatType as string | undefined + const isClearFromViewMessageDialog = req.query?.isClearFromViewMessageDialog as string | undefined + 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, this.AppDataSource, req.query.sessionId as string) - const results = await this.AppDataSource.getRepository(ChatMessage).delete({ chatflowid: req.params.id }) + + if (isClearFromViewMessageDialog) + clearSessionMemoryFromViewMessageDialog( + nodes, + this.nodesPool.componentNodes, + chatId, + this.AppDataSource, + sessionId, + memoryType + ) + else clearAllSessionMemory(nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, sessionId) + + const deleteOptions: FindOptionsWhere = { chatflowid, chatId } + if (memoryType) deleteOptions.memoryType = memoryType + if (sessionId) deleteOptions.sessionId = sessionId + if (chatType) deleteOptions.chatType = chatType + + const results = await this.AppDataSource.getRepository(ChatMessage).delete(deleteOptions) return res.json(results) }) @@ -826,6 +878,80 @@ export class App { return false } + /** + * Method that get chat messages. + * @param {string} chatflowid + * @param {chatType} chatType + * @param {string} sortOrder + * @param {string} chatId + * @param {string} memoryType + * @param {string} sessionId + * @param {string} startDate + * @param {string} endDate + */ + async getChatMessage( + chatflowid: string, + chatType: chatType | undefined, + sortOrder: string = 'ASC', + chatId?: string, + memoryType?: string, + sessionId?: string, + startDate?: string, + endDate?: string + ): Promise { + let fromDate + if (startDate) fromDate = new Date(startDate) + + let toDate + if (endDate) toDate = new Date(endDate) + + return await this.AppDataSource.getRepository(ChatMessage).find({ + where: { + chatflowid, + chatType, + chatId, + memoryType: memoryType ?? (chatId ? IsNull() : undefined), + sessionId: sessionId ?? (chatId ? IsNull() : undefined), + createdDate: toDate && fromDate ? Between(fromDate, toDate) : undefined + }, + order: { + createdDate: sortOrder === 'DESC' ? 'DESC' : 'ASC' + } + }) + } + + /** + * Method that add chat messages. + * @param {Partial} chatMessage + */ + async addChatMessage(chatMessage: Partial): Promise { + const newChatMessage = new ChatMessage() + Object.assign(newChatMessage, chatMessage) + + const chatmessage = this.AppDataSource.getRepository(ChatMessage).create(newChatMessage) + return await this.AppDataSource.getRepository(ChatMessage).save(chatmessage) + } + + /** + * Method that find memory label that is connected within chatflow + * In a chatflow, there should only be 1 memory node + * @param {IReactFlowNode[]} nodes + * @param {IReactFlowEdge[]} edges + * @returns {string | undefined} + */ + findMemoryLabel(nodes: IReactFlowNode[], edges: IReactFlowEdge[]): string | undefined { + const memoryNodes = nodes.filter((node) => node.data.category === 'Memory') + const memoryNodeIds = memoryNodes.map((mem) => mem.data.id) + + for (const edge of edges) { + if (memoryNodeIds.includes(edge.source)) { + const memoryNode = nodes.find((node) => node.data.id === edge.source) + return memoryNode ? memoryNode.data.label : undefined + } + } + return undefined + } + /** * Process Prediction * @param {Request} req @@ -833,7 +959,7 @@ export class App { * @param {Server} socketIO * @param {boolean} isInternal */ - async processPrediction(req: Request, res: Response, socketIO?: Server, isInternal = false) { + async processPrediction(req: Request, res: Response, socketIO?: Server, isInternal: boolean = false) { try { const chatflowid = req.params.id let incomingInput: IncomingInput = req.body @@ -845,8 +971,8 @@ export class App { }) if (!chatflow) return res.status(404).send(`Chatflow ${chatflowid} not found`) - let chatId = await getChatId(chatflow.id) - if (!chatId) chatId = chatflowid + const chatId = incomingInput.chatId ?? incomingInput.overrideConfig?.sessionId ?? uuidv4() + const userMessageDateTime = new Date() if (!isInternal) { const isKeyValidated = await this.validateKey(req, chatflow) @@ -982,9 +1108,12 @@ export class App { logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) - if (nodeToExecuteData.instance) checkMemorySessionId(nodeToExecuteData.instance, chatId) + let sessionId = undefined + if (nodeToExecuteData.instance) sessionId = checkMemorySessionId(nodeToExecuteData.instance, chatId) - const result = isStreamValid + const memoryType = this.findMemoryLabel(nodes, edges) + + let result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, socketIO, @@ -1002,7 +1131,42 @@ export class App { analytic: chatflow.analytic }) + result = typeof result === 'string' ? { text: result } : result + + const userMessage: Omit = { + role: 'userMessage', + content: incomingInput.question, + chatflowid, + chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL, + chatId, + memoryType, + sessionId, + createdDate: userMessageDateTime + } + await this.addChatMessage(userMessage) + + let resultText = '' + if (result.text) resultText = result.text + else if (result.json) resultText = '```json\n' + JSON.stringify(result.json, null, 2) + else resultText = JSON.stringify(result, null, 2) + + const apiMessage: Omit = { + role: 'apiMessage', + content: resultText, + chatflowid, + chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL, + chatId, + memoryType, + sessionId + } + if (result?.sourceDocuments) apiMessage.sourceDocuments = JSON.stringify(result.sourceDocuments) + await this.addChatMessage(apiMessage) + logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) + + // Only return ChatId when its Internal OR incoming input has ChatId, to avoid confusion when calling API + if (incomingInput.chatId || isInternal) result.chatId = chatId + return res.json(result) } catch (e: any) { logger.error('[server]: Error:', e) @@ -1025,7 +1189,7 @@ export class App { * @param {string} chatflowid * @returns {string} */ -export async function getChatId(chatflowid: string) { +export async function getChatId(chatflowid: string): Promise { // first chatmessage id as the unique chat id const firstChatMessage = await getDataSource() .getRepository(ChatMessage) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 317bcc0c..96086efd 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -298,14 +298,14 @@ export const buildLangchain = async ( } /** - * Clear memory + * Clear all session memories on the canvas * @param {IReactFlowNode[]} reactFlowNodes * @param {IComponentNodes} componentNodes * @param {string} chatId * @param {DataSource} appDataSource * @param {string} sessionId */ -export const clearSessionMemory = async ( +export const clearAllSessionMemory = async ( reactFlowNodes: IReactFlowNode[], componentNodes: IComponentNodes, chatId: string, @@ -317,9 +317,46 @@ export const clearSessionMemory = async ( 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) + + if (newNodeInstance.clearSessionMemory) { await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) + } + } +} + +/** + * Clear specific session memory from View Message Dialog UI + * @param {IReactFlowNode[]} reactFlowNodes + * @param {IComponentNodes} componentNodes + * @param {string} chatId + * @param {DataSource} appDataSource + * @param {string} sessionId + * @param {string} memoryType + */ +export const clearSessionMemoryFromViewMessageDialog = async ( + reactFlowNodes: IReactFlowNode[], + componentNodes: IComponentNodes, + chatId: string, + appDataSource: DataSource, + sessionId?: string, + memoryType?: string +) => { + if (!sessionId) return + for (const node of reactFlowNodes) { + if (node.data.category !== 'Memory') continue + if (node.data.label !== memoryType) 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, appDataSource, databaseEntities, logger }) + return + } } } @@ -804,7 +841,16 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod isValidChainOrAgent = whitelistAgents.includes(endingNodeData.name) } - return isChatOrLLMsExist && isValidChainOrAgent + // If no output parser, flow is available to stream + let isOutputParserExist = false + for (const flowNode of reactFlowNodes) { + const data = flowNode.data + if (data.category.includes('Output Parser')) { + isOutputParserExist = true + } + } + + return isChatOrLLMsExist && isValidChainOrAgent && !isOutputParserExist } /** @@ -928,8 +974,10 @@ export const redactCredentialWithPasswordType = ( * @param {any} instance * @param {string} chatId */ -export const checkMemorySessionId = (instance: any, chatId: string) => { +export const checkMemorySessionId = (instance: any, chatId: string): string => { if (instance.memory && instance.memory.isSessionIdUsingChatMessageId && chatId) { instance.memory.sessionId = chatId + instance.memory.chatHistory.sessionId = chatId } + return instance.memory ? instance.memory.sessionId ?? instance.memory.chatHistory.sessionId : undefined } diff --git a/packages/ui/package.json b/packages/ui/package.json index 41ad515f..f0a60f5b 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.3.6", + "version": "1.3.7", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { @@ -31,7 +31,7 @@ "react": "^18.2.0", "react-code-blocks": "^0.0.9-0", "react-color": "^2.19.3", - "react-datepicker": "^4.8.0", + "react-datepicker": "^4.21.0", "react-device-detect": "^1.17.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.6", diff --git a/packages/ui/src/api/chatmessage.js b/packages/ui/src/api/chatmessage.js index d93068e6..5f1a4bad 100644 --- a/packages/ui/src/api/chatmessage.js +++ b/packages/ui/src/api/chatmessage.js @@ -1,13 +1,13 @@ import client from './client' -const getChatmessageFromChatflow = (id) => client.get(`/chatmessage/${id}`) - -const createNewChatmessage = (id, body) => client.post(`/chatmessage/${id}`, body) - -const deleteChatmessage = (id) => client.delete(`/chatmessage/${id}`) +const getInternalChatmessageFromChatflow = (id) => client.get(`/internal-chatmessage/${id}`) +const getAllChatmessageFromChatflow = (id, params = {}) => client.get(`/chatmessage/${id}`, { params: { order: 'DESC', ...params } }) +const getChatmessageFromPK = (id, params = {}) => client.get(`/chatmessage/${id}`, { params: { order: 'ASC', ...params } }) +const deleteChatmessage = (id, params = {}) => client.delete(`/chatmessage/${id}`, { params: { ...params } }) export default { - getChatmessageFromChatflow, - createNewChatmessage, + getInternalChatmessageFromChatflow, + getAllChatmessageFromChatflow, + getChatmessageFromPK, deleteChatmessage } diff --git a/packages/ui/src/assets/images/message_empty.svg b/packages/ui/src/assets/images/message_empty.svg new file mode 100644 index 00000000..d7c9df25 --- /dev/null +++ b/packages/ui/src/assets/images/message_empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js index 41de3dd4..c10b3289 100644 --- a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js +++ b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js @@ -71,7 +71,7 @@ const ProfileSection = ({ username, handleLogout }) => { try { const response = await databaseApi.getExportDatabase() const exportItems = response.data - let dataStr = JSON.stringify(exportItems) + let dataStr = JSON.stringify(exportItems, null, 2) let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr) let exportFileDefaultName = `DB.json` diff --git a/packages/ui/src/menu-items/settings.js b/packages/ui/src/menu-items/settings.js index 038dc740..307bd0bd 100644 --- a/packages/ui/src/menu-items/settings.js +++ b/packages/ui/src/menu-items/settings.js @@ -1,8 +1,8 @@ // assets -import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch } from '@tabler/icons' +import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage } from '@tabler/icons' // constant -const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch } +const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage } // ==============================|| SETTINGS MENU ITEMS ||============================== // @@ -11,6 +11,13 @@ const settings = { title: '', type: 'group', children: [ + { + id: 'viewMessages', + title: 'View Messages', + type: 'item', + url: '', + icon: icons.IconMessage + }, { id: 'duplicateChatflow', title: 'Duplicate Chatflow', diff --git a/packages/ui/src/themes/palette.js b/packages/ui/src/themes/palette.js index 19a7df11..66ec3d01 100644 --- a/packages/ui/src/themes/palette.js +++ b/packages/ui/src/themes/palette.js @@ -80,6 +80,9 @@ export default function themePalette(theme) { asyncSelect: { main: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.grey50 }, + timeMessage: { + main: theme.customization.isDarkMode ? theme.colors?.darkLevel2 : theme.colors?.grey200 + }, canvasHeader: { deployLight: theme.colors?.primaryLight, deployDark: theme.colors?.primaryDark, diff --git a/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js b/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js new file mode 100644 index 00000000..6d147dee --- /dev/null +++ b/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js @@ -0,0 +1,695 @@ +import { createPortal } from 'react-dom' +import { useDispatch, useSelector } from 'react-redux' +import { useState, useEffect, forwardRef } from 'react' +import PropTypes from 'prop-types' +import moment from 'moment' +import rehypeMathjax from 'rehype-mathjax' +import remarkGfm from 'remark-gfm' +import remarkMath from 'remark-math' + +// material-ui +import { + Button, + Tooltip, + ListItemButton, + Box, + Stack, + Dialog, + DialogContent, + DialogTitle, + ListItem, + ListItemText, + Chip +} from '@mui/material' +import { useTheme } from '@mui/material/styles' +import DatePicker from 'react-datepicker' + +import robotPNG from 'assets/images/robot.png' +import userPNG from 'assets/images/account.png' +import msgEmptySVG from 'assets/images/message_empty.svg' +import { IconFileExport, IconEraser, IconX } from '@tabler/icons' + +// Project import +import { MemoizedReactMarkdown } from 'ui-component/markdown/MemoizedReactMarkdown' +import { CodeBlock } from 'ui-component/markdown/CodeBlock' +import SourceDocDialog from 'ui-component/dialog/SourceDocDialog' +import { MultiDropdown } from 'ui-component/dropdown/MultiDropdown' +import { StyledButton } from 'ui-component/button/StyledButton' + +// store +import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' + +// API +import chatmessageApi from 'api/chatmessage' +import useApi from 'hooks/useApi' +import useConfirm from 'hooks/useConfirm' + +// Utils +import { isValidURL, removeDuplicateURL } from 'utils/genericHelper' +import useNotifier from 'utils/useNotifier' + +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' + +import 'views/chatmessage/ChatMessage.css' +import 'react-datepicker/dist/react-datepicker.css' + +const DatePickerCustomInput = forwardRef(function DatePickerCustomInput({ value, onClick }, ref) { + return ( + + {value} + + ) +}) + +DatePickerCustomInput.propTypes = { + value: PropTypes.string, + onClick: PropTypes.func +} + +const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { + const portalElement = document.getElementById('portal') + const dispatch = useDispatch() + const theme = useTheme() + const customization = useSelector((state) => state.customization) + const { confirm } = useConfirm() + + useNotifier() + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [chatlogs, setChatLogs] = useState([]) + const [allChatlogs, setAllChatLogs] = useState([]) + const [chatMessages, setChatMessages] = useState([]) + const [selectedMessageIndex, setSelectedMessageIndex] = useState(0) + const [sourceDialogOpen, setSourceDialogOpen] = useState(false) + const [sourceDialogProps, setSourceDialogProps] = useState({}) + const [chatTypeFilter, setChatTypeFilter] = useState([]) + const [startDate, setStartDate] = useState(new Date().setMonth(new Date().getMonth() - 1)) + const [endDate, setEndDate] = useState(new Date()) + + const getChatmessageApi = useApi(chatmessageApi.getAllChatmessageFromChatflow) + const getChatmessageFromPKApi = useApi(chatmessageApi.getChatmessageFromPK) + + const onStartDateSelected = (date) => { + setStartDate(date) + getChatmessageApi.request(dialogProps.chatflow.id, { + startDate: date, + endDate: endDate, + chatType: chatTypeFilter.length ? chatTypeFilter : undefined + }) + } + + const onEndDateSelected = (date) => { + setEndDate(date) + getChatmessageApi.request(dialogProps.chatflow.id, { + endDate: date, + startDate: startDate, + chatType: chatTypeFilter.length ? chatTypeFilter : undefined + }) + } + + const onChatTypeSelected = (chatTypes) => { + setChatTypeFilter(chatTypes) + getChatmessageApi.request(dialogProps.chatflow.id, { + chatType: chatTypes.length ? chatTypes : undefined, + startDate: startDate, + endDate: endDate + }) + } + + const exportMessages = () => { + const obj = {} + for (let i = 0; i < allChatlogs.length; i += 1) { + const chatmsg = allChatlogs[i] + const chatPK = getChatPK(chatmsg) + const msg = { + content: chatmsg.content, + role: chatmsg.role === 'apiMessage' ? 'bot' : 'user', + time: chatmsg.createdDate + } + if (chatmsg.sourceDocuments) msg.sourceDocuments = JSON.parse(chatmsg.sourceDocuments) + + if (!Object.prototype.hasOwnProperty.call(obj, chatPK)) { + obj[chatPK] = { + id: chatmsg.chatId, + source: chatmsg.chatType === 'INTERNAL' ? 'UI' : 'API/Embed', + sessionId: chatmsg.sessionId ?? null, + memoryType: chatmsg.memoryType ?? null, + messages: [msg] + } + } else if (Object.prototype.hasOwnProperty.call(obj, chatPK)) { + obj[chatPK].messages = [...obj[chatPK].messages, msg] + } + } + + const exportMessages = [] + for (const key in obj) { + exportMessages.push({ + ...obj[key] + }) + } + + for (let i = 0; i < exportMessages.length; i += 1) { + exportMessages[i].messages = exportMessages[i].messages.reverse() + } + + const dataStr = JSON.stringify(exportMessages, null, 2) + const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr) + + const exportFileDefaultName = `${dialogProps.chatflow.id}-Message.json` + + let linkElement = document.createElement('a') + linkElement.setAttribute('href', dataUri) + linkElement.setAttribute('download', exportFileDefaultName) + linkElement.click() + } + + const clearChat = async (chatmsg) => { + const description = + chatmsg.sessionId && chatmsg.memoryType + ? `Are you sure you want to clear session id: ${chatmsg.sessionId} from ${chatmsg.memoryType}?` + : `Are you sure you want to clear messages?` + const confirmPayload = { + title: `Clear Session`, + description, + confirmButtonName: 'Clear', + cancelButtonName: 'Cancel' + } + const isConfirmed = await confirm(confirmPayload) + + const chatflowid = dialogProps.chatflow.id + if (isConfirmed) { + try { + const obj = { chatflowid, isClearFromViewMessageDialog: true } + if (chatmsg.chatId) obj.chatId = chatmsg.chatId + if (chatmsg.chatType) obj.chatType = chatmsg.chatType + if (chatmsg.memoryType) obj.memoryType = chatmsg.memoryType + if (chatmsg.sessionId) obj.sessionId = chatmsg.sessionId + + await chatmessageApi.deleteChatmessage(chatflowid, obj) + const description = + chatmsg.sessionId && chatmsg.memoryType + ? `Succesfully cleared session id: ${chatmsg.sessionId} from ${chatmsg.memoryType}` + : `Succesfully cleared messages` + enqueueSnackbar({ + message: description, + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + getChatmessageApi.request(chatflowid) + } 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 getChatMessages = (chatmessages) => { + let prevDate = '' + const loadedMessages = [] + for (let i = 0; i < chatmessages.length; i += 1) { + const chatmsg = chatmessages[i] + if (!prevDate) { + prevDate = chatmsg.createdDate.split('T')[0] + loadedMessages.push({ + message: chatmsg.createdDate, + type: 'timeMessage' + }) + } else { + const currentDate = chatmsg.createdDate.split('T')[0] + if (currentDate !== prevDate) { + prevDate = currentDate + loadedMessages.push({ + message: chatmsg.createdDate, + type: 'timeMessage' + }) + } + } + const obj = { + ...chatmsg, + message: chatmsg.content, + type: chatmsg.role + } + if (chatmsg.sourceDocuments) obj.sourceDocuments = JSON.parse(chatmsg.sourceDocuments) + loadedMessages.push(obj) + } + setChatMessages(loadedMessages) + } + + const getChatPK = (chatmsg) => { + const chatId = chatmsg.chatId + const memoryType = chatmsg.memoryType ?? 'null' + const sessionId = chatmsg.sessionId ?? 'null' + return `${chatId}_${memoryType}_${sessionId}` + } + + const transformChatPKToParams = (chatPK) => { + const chatId = chatPK.split('_')[0] + const memoryType = chatPK.split('_')[1] + const sessionId = chatPK.split('_')[2] + + const params = { chatId } + if (memoryType !== 'null') params.memoryType = memoryType + if (sessionId !== 'null') params.sessionId = sessionId + + return params + } + + const processChatLogs = (allChatMessages) => { + const seen = {} + const filteredChatLogs = [] + for (let i = 0; i < allChatMessages.length; i += 1) { + const PK = getChatPK(allChatMessages[i]) + + const item = allChatMessages[i] + if (!Object.prototype.hasOwnProperty.call(seen, PK)) { + seen[PK] = { + counter: 1, + item: allChatMessages[i] + } + } else if (Object.prototype.hasOwnProperty.call(seen, PK) && seen[PK].counter === 1) { + seen[PK] = { + counter: 2, + item: { + ...seen[PK].item, + apiContent: + seen[PK].item.role === 'apiMessage' ? `Bot: ${seen[PK].item.content}` : `User: ${seen[PK].item.content}`, + userContent: item.role === 'apiMessage' ? `Bot: ${item.content}` : `User: ${item.content}` + } + } + filteredChatLogs.push(seen[PK].item) + } + } + setChatLogs(filteredChatLogs) + if (filteredChatLogs.length) return getChatPK(filteredChatLogs[0]) + return undefined + } + + const handleItemClick = (idx, chatmsg) => { + setSelectedMessageIndex(idx) + getChatmessageFromPKApi.request(dialogProps.chatflow.id, transformChatPKToParams(getChatPK(chatmsg))) + } + + const onURLClick = (data) => { + window.open(data, '_blank') + } + + const onSourceDialogClick = (data) => { + setSourceDialogProps({ data }) + setSourceDialogOpen(true) + } + + useEffect(() => { + if (getChatmessageFromPKApi.data) { + getChatMessages(getChatmessageFromPKApi.data) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getChatmessageFromPKApi.data]) + + useEffect(() => { + if (getChatmessageApi.data) { + setAllChatLogs(getChatmessageApi.data) + const chatPK = processChatLogs(getChatmessageApi.data) + setSelectedMessageIndex(0) + if (chatPK) getChatmessageFromPKApi.request(dialogProps.chatflow.id, transformChatPKToParams(chatPK)) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getChatmessageApi.data]) + + useEffect(() => { + if (dialogProps.chatflow) { + getChatmessageApi.request(dialogProps.chatflow.id) + } + + return () => { + setChatLogs([]) + setAllChatLogs([]) + setChatMessages([]) + setChatTypeFilter([]) + setSelectedMessageIndex(0) + setStartDate(new Date().setMonth(new Date().getMonth() - 1)) + setEndDate(new Date()) + } + + // 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 component = show ? ( + + +
+ {dialogProps.title} +
+ +
+ + + <> +
+
+ From Date + onStartDateSelected(date)} + selectsStart + startDate={startDate} + endDate={endDate} + customInput={} + /> +
+
+ To Date + onEndDateSelected(date)} + selectsEnd + startDate={startDate} + endDate={endDate} + minDate={startDate} + maxDate={new Date()} + customInput={} + /> +
+
+ Source + onChatTypeSelected(newValue)} + value={chatTypeFilter} + formControlSx={{ mt: 0 }} + /> +
+
+
+
+ {chatlogs && chatlogs.length == 0 && ( + + + msgEmptySVG + +
No Messages
+
+ )} + {chatlogs && chatlogs.length > 0 && ( +
+ + {chatlogs.map((chatmsg, index) => ( + handleItemClick(index, chatmsg)} + > + + + {chatmsg?.userContent} +
+ {chatmsg?.apiContent} +
+
+ } + secondary={moment(chatmsg.createdDate).format('MMMM Do YYYY, h:mm:ss a')} + /> + + + ))} + +
+ )} + {chatlogs && chatlogs.length > 0 && ( +
+ {chatMessages && chatMessages.length > 1 && ( +
+
+ {chatMessages[1].sessionId && ( +
+ Session Id: {chatMessages[1].sessionId} +
+ )} + {chatMessages[1].chatType && ( +
+ Source: {chatMessages[1].chatType === 'INTERNAL' ? 'UI' : 'API/Embed'} +
+ )} + {chatMessages[1].memoryType && ( +
+ Memory: {chatMessages[1].memoryType} +
+ )} +
+
+ clearChat(chatMessages[1])} + startIcon={} + > + Clear + + {chatMessages[1].sessionId && ( + +
+ Why my session is not deleted? +
+
+ )} +
+
+ )} +
+
+ {chatMessages && + chatMessages.map((message, index) => { + if (message.type === 'apiMessage' || message.type === 'userMessage') { + return ( + + {/* 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 && ( +
+ {removeDuplicateURL(message).map((source, index) => { + const URL = isValidURL(source.metadata.source) + return ( + + URL + ? onURLClick(source.metadata.source) + : onSourceDialogClick(source) + } + /> + ) + })} +
+ )} +
+
+ ) + } else { + return ( + + {moment(message.message).format('MMMM Do YYYY, h:mm:ss a')} + + ) + } + })} +
+
+
+ )} +
+ setSourceDialogOpen(false)} /> + + +
+ ) : null + + return createPortal(component, portalElement) +} + +ViewMessagesDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func +} + +export default ViewMessagesDialog diff --git a/packages/ui/src/ui-component/dropdown/MultiDropdown.js b/packages/ui/src/ui-component/dropdown/MultiDropdown.js index ff07b89f..9b96e55c 100644 --- a/packages/ui/src/ui-component/dropdown/MultiDropdown.js +++ b/packages/ui/src/ui-component/dropdown/MultiDropdown.js @@ -18,7 +18,7 @@ const StyledPopper = styled(Popper)({ } }) -export const MultiDropdown = ({ name, value, options, onSelect, disabled = false, disableClearable = false }) => { +export const MultiDropdown = ({ name, value, options, onSelect, formControlSx = {}, disabled = false, disableClearable = false }) => { const customization = useSelector((state) => state.customization) const findMatchingOptions = (options = [], internalValue) => { let values = [] @@ -30,7 +30,7 @@ export const MultiDropdown = ({ name, value, options, onSelect, disabled = false let [internalValue, setInternalValue] = useState(value ?? []) return ( - + { + const [rowValues, setRowValues] = useState(formatDataGridRows(rows) ?? []) + + const deleteItem = useCallback( + (id) => () => { + let updatedRows = [] + setRowValues((prevRows) => { + let allRows = [...cloneDeep(prevRows)] + allRows = allRows.filter((row) => row.id !== id) + updatedRows = allRows + return allRows + }) + onChange(JSON.stringify(updatedRows)) + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ) + + const addCols = (columns) => { + return [ + ...columns, + { + field: 'actions', + type: 'actions', + width: 80, + getActions: (params) => [ + } label='Delete' onClick={deleteItem(params.id)} /> + ] + } + ] + } + + const colValues = addCols(columns) + + const handleProcessRowUpdate = (newRow) => { + let updatedRows = [] + setRowValues((prevRows) => { + let allRows = [...cloneDeep(prevRows)] + const indexToUpdate = allRows.findIndex((row) => row.id === newRow.id) + if (indexToUpdate >= 0) { + allRows[indexToUpdate] = { ...newRow } + } + updatedRows = allRows + return allRows + }) + onChange(JSON.stringify(updatedRows)) + return newRow + } + + const getEmptyJsonObj = () => { + const obj = {} + for (let i = 0; i < colValues.length; i += 1) { + obj[colValues[i]?.field] = '' + } + return obj + } + + const addNewRow = () => { + setRowValues((prevRows) => { + let allRows = [...cloneDeep(prevRows)] + const lastRowId = allRows.length ? allRows[allRows.length - 1].id + 1 : 1 + allRows.push({ + ...getEmptyJsonObj(), + id: lastRowId + }) + return allRows + }) + } + + return ( + <> + {rowValues && colValues && ( +
+ { + return !disabled + }} + hideFooter={hideFooter} + onProcessRowUpdateError={(error) => console.error(error)} + rows={rowValues} + columns={colValues} + /> +
+ )} + {!disabled && ( + + )} + + ) +} + +DataGrid.propTypes = { + rows: PropTypes.array, + columns: PropTypes.array, + style: PropTypes.any, + disabled: PropTypes.bool, + hideFooter: PropTypes.bool, + onChange: PropTypes.func +} diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index 95bf968d..6993847b 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -1,10 +1,39 @@ -import { useState } from 'react' +import { useState, useEffect, useRef } from 'react' import PropTypes from 'prop-types' -import { FormControl, OutlinedInput } from '@mui/material' +import { FormControl, OutlinedInput, Popover } from '@mui/material' import ExpandTextDialog from 'ui-component/dialog/ExpandTextDialog' +import SelectVariable from 'ui-component/json/SelectVariable' +import { getAvailableNodesForVariable } from 'utils/genericHelper' -export const Input = ({ inputParam, value, onChange, disabled = false, showDialog, dialogProps, onDialogCancel, onDialogConfirm }) => { +export const Input = ({ + inputParam, + value, + nodes, + edges, + nodeId, + onChange, + disabled = false, + showDialog, + dialogProps, + onDialogCancel, + onDialogConfirm +}) => { const [myValue, setMyValue] = useState(value ?? '') + const [anchorEl, setAnchorEl] = useState(null) + const [availableNodesForVariable, setAvailableNodesForVariable] = useState([]) + const ref = useRef(null) + + const openPopOver = Boolean(anchorEl) + + const handleClosePopOver = () => { + setAnchorEl(null) + } + + const setNewVal = (val) => { + const newVal = myValue + val.substring(2) + onChange(newVal) + setMyValue(newVal) + } const getInputType = (type) => { switch (type) { @@ -19,6 +48,19 @@ export const Input = ({ inputParam, value, onChange, disabled = false, showDialo } } + useEffect(() => { + if (!disabled && nodes && edges && nodeId && inputParam) { + const nodesForVariable = inputParam?.acceptVariable ? getAvailableNodesForVariable(nodes, edges, nodeId, inputParam.id) : [] + setAvailableNodesForVariable(nodesForVariable) + } + }, [disabled, inputParam, nodes, edges, nodeId]) + + useEffect(() => { + if (typeof myValue === 'string' && myValue && myValue.endsWith('{{')) { + setAnchorEl(ref.current) + } + }, [myValue]) + return ( <> @@ -55,6 +97,31 @@ export const Input = ({ inputParam, value, onChange, disabled = false, showDialo }} > )} +
+ {inputParam?.acceptVariable && ( + + { + setNewVal(val) + handleClosePopOver() + }} + /> + + )} ) } @@ -66,6 +133,9 @@ Input.propTypes = { disabled: PropTypes.bool, showDialog: PropTypes.bool, dialogProps: PropTypes.object, + nodes: PropTypes.array, + edges: PropTypes.array, + nodeId: PropTypes.string, onDialogCancel: PropTypes.func, onDialogConfirm: PropTypes.func } diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index af7f4353..32331b14 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -43,6 +43,7 @@ export const initNode = (nodeData, newNodeId) => { 'asyncOptions', 'options', 'multiOptions', + 'datagrid', 'string', 'number', 'boolean', @@ -415,6 +416,23 @@ export const getInputVariables = (paramValue) => { return inputVariables } +export const removeDuplicateURL = (message) => { + const visitedURLs = [] + const newSourceDocuments = [] + + if (!message.sourceDocuments) return 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 +} + export const isValidURL = (url) => { try { return new URL(url) @@ -422,3 +440,17 @@ export const isValidURL = (url) => { return undefined } } + +export const formatDataGridRows = (rows) => { + try { + const parsedRows = typeof rows === 'string' ? JSON.parse(rows) : rows + return parsedRows.map((sch, index) => { + return { + ...sch, + id: index + } + }) + } catch (e) { + return [] + } +} diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index a9d2f39e..56365ba8 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -15,6 +15,7 @@ import Settings from 'views/settings' import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog' import APICodeDialog from 'views/chatflows/APICodeDialog' import AnalyseFlowDialog from 'ui-component/dialog/AnalyseFlowDialog' +import ViewMessagesDialog from 'ui-component/dialog/ViewMessagesDialog' // API import chatflowsApi from 'api/chatflows' @@ -44,6 +45,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl const [apiDialogProps, setAPIDialogProps] = useState({}) const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false) const [analyseDialogProps, setAnalyseDialogProps] = useState({}) + const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false) + const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({}) const updateChatflowApi = useApi(chatflowsApi.updateChatflow) const canvas = useSelector((state) => state.canvas) @@ -59,6 +62,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl chatflow: chatflow }) setAnalyseDialogOpen(true) + } else if (setting === 'viewMessages') { + setViewMessagesDialogProps({ + title: 'View Messages', + chatflow: chatflow + }) + setViewMessagesDialogOpen(true) } else if (setting === 'duplicateChatflow') { try { localStorage.setItem('duplicatedFlowData', chatflow.flowData) @@ -69,7 +78,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl } else if (setting === 'exportChatflow') { try { const flowData = JSON.parse(chatflow.flowData) - let dataStr = JSON.stringify(generateExportFlowData(flowData)) + let dataStr = JSON.stringify(generateExportFlowData(flowData), null, 2) let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr) let exportFileDefaultName = `${chatflow.name} Chatflow.json` @@ -367,6 +376,11 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl /> setAPIDialogOpen(false)} /> setAnalyseDialogOpen(false)} /> + setViewMessagesDialogOpen(false)} + /> ) } diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 3a13a3b5..de865e31 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -14,6 +14,7 @@ import { Dropdown } from 'ui-component/dropdown/Dropdown' import { MultiDropdown } from 'ui-component/dropdown/MultiDropdown' import { AsyncDropdown } from 'ui-component/dropdown/AsyncDropdown' import { Input } from 'ui-component/input/Input' +import { DataGrid } from 'ui-component/grid/DataGrid' import { File } from 'ui-component/file/File' import { SwitchInput } from 'ui-component/switch/Switch' import { flowContext } from 'store/context/ReactFlowContext' @@ -258,6 +259,15 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA value={data.inputs[inputParam.name] ?? inputParam.default ?? false} /> )} + {inputParam.type === 'datagrid' && ( + (data.inputs[inputParam.name] = newValue)} + /> + )} {(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && ( (data.inputs[inputParam.name] = newValue)} value={data.inputs[inputParam.name] ?? inputParam.default ?? ''} + nodes={inputParam?.acceptVariable && reactFlowInstance ? reactFlowInstance.getNodes() : []} + edges={inputParam?.acceptVariable && reactFlowInstance ? reactFlowInstance.getEdges() : []} + nodeId={data.id} showDialog={showExpandDialog} dialogProps={expandDialogProps} onDialogCancel={() => setShowExpandDialog(false)} diff --git a/packages/ui/src/views/chatflows/ShareChatbot.js b/packages/ui/src/views/chatflows/ShareChatbot.js index 0f05c28a..dc6c0621 100644 --- a/packages/ui/src/views/chatflows/ShareChatbot.js +++ b/packages/ui/src/views/chatflows/ShareChatbot.js @@ -57,6 +57,9 @@ const ShareChatbot = ({ isSessionMemory }) => { const [isPublicChatflow, setChatflowIsPublic] = useState(chatflow.isPublic ?? false) const [generateNewSession, setGenerateNewSession] = useState(chatbotConfig?.generateNewSession ?? false) + const [title, setTitle] = useState(chatbotConfig?.title ?? '') + const [titleAvatarSrc, setTitleAvatarSrc] = useState(chatbotConfig?.titleAvatarSrc ?? '') + const [welcomeMessage, setWelcomeMessage] = useState(chatbotConfig?.welcomeMessage ?? '') const [backgroundColor, setBackgroundColor] = useState(chatbotConfig?.backgroundColor ?? defaultConfig.backgroundColor) const [fontSize, setFontSize] = useState(chatbotConfig?.fontSize ?? defaultConfig.fontSize) @@ -108,6 +111,8 @@ const ShareChatbot = ({ isSessionMemory }) => { textInput: {}, overrideConfig: {} } + if (title) obj.title = title + if (titleAvatarSrc) obj.titleAvatarSrc = titleAvatarSrc if (welcomeMessage) obj.welcomeMessage = welcomeMessage if (backgroundColor) obj.backgroundColor = backgroundColor if (fontSize) obj.fontSize = fontSize @@ -252,6 +257,12 @@ const ShareChatbot = ({ isSessionMemory }) => { const onTextChanged = (value, fieldName) => { switch (fieldName) { + case 'title': + setTitle(value) + break + case 'titleAvatarSrc': + setTitleAvatarSrc(value) + break case 'welcomeMessage': setWelcomeMessage(value) break @@ -395,6 +406,14 @@ const ShareChatbot = ({ isSessionMemory }) => { /> + {textField(title, 'title', 'Title', 'string', 'Flowise Assistant')} + {textField( + titleAvatarSrc, + 'titleAvatarSrc', + 'Title Avatar Link', + 'string', + `https://raw.githubusercontent.com/FlowiseAI/Flowise/main/assets/FloWiseAI_dark.png` + )} {textField(welcomeMessage, 'welcomeMessage', 'Welcome Message', 'string', 'Hello! This is custom welcome message')} {colorField(backgroundColor, 'backgroundColor', 'Background Color')} {textField(fontSize, 'fontSize', 'Font Size', 'number')} diff --git a/packages/ui/src/views/chatmessage/ChatMessage.css b/packages/ui/src/views/chatmessage/ChatMessage.css index 3b006c1d..2298fee6 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.css +++ b/packages/ui/src/views/chatmessage/ChatMessage.css @@ -134,3 +134,13 @@ justify-content: center; align-items: center; } + +.cloud-message { + width: 100%; + height: calc(100vh - 260px); + overflow-y: scroll; + 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 3e967541..6d99768a 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -30,7 +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' +import { isValidURL, removeDuplicateURL } from 'utils/genericHelper' export const ChatMessage = ({ open, chatflowid, isDialog }) => { const theme = useTheme() @@ -50,9 +50,10 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { const [isChatFlowAvailableToStream, setIsChatFlowAvailableToStream] = useState(false) const [sourceDialogOpen, setSourceDialogOpen] = useState(false) const [sourceDialogProps, setSourceDialogProps] = useState({}) + const [chatId, setChatId] = useState(undefined) const inputRef = useRef(null) - const getChatmessageApi = useApi(chatmessageApi.getChatmessageFromChatflow) + const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow) const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming) const onSourceDialogClick = (data) => { @@ -64,21 +65,6 @@ 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 }) @@ -87,20 +73,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { const onChange = useCallback((e) => setUserInput(e.target.value), [setUserInput]) - 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) - } - } - const updateLastMessage = (text) => { setMessages((prevMessages) => { let allMessages = [...cloneDeep(prevMessages)] @@ -123,7 +95,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { 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`, '') setMessages((prevMessages) => [...prevMessages, { message, type: 'apiMessage' }]) - addChatMessage(message, 'apiMessage') setLoading(false) setUserInput('') setTimeout(() => { @@ -141,36 +112,36 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { setLoading(true) setMessages((prevMessages) => [...prevMessages, { message: userInput, type: 'userMessage' }]) - // waiting for first chatmessage saved, the first chatmessage will be used in sendMessageAndGetPrediction - await addChatMessage(userInput, 'userMessage') // Send user question and history to API try { const params = { question: userInput, - history: messages.filter((msg) => msg.message !== 'Hi there! How can I help?') + history: messages.filter((msg) => msg.message !== 'Hi there! How can I help?'), + chatId } if (isChatFlowAvailableToStream) params.socketIOClientId = socketIOClientId const response = await predictionApi.sendMessageAndGetPrediction(chatflowid, params) if (response.data) { - let data = response.data - - 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') + const data = response.data + if (!chatId) { + setChatId(data.chatId) + localStorage.setItem(`${chatflowid}_INTERNAL`, data.chatId) } + if (!isChatFlowAvailableToStream) { + let text = '' + if (data.text) text = data.text + else if (data.json) text = '```json\n' + JSON.stringify(data.json, null, 2) + else text = JSON.stringify(data, null, 2) + + setMessages((prevMessages) => [ + ...prevMessages, + { message: text, sourceDocuments: data?.sourceDocuments, type: 'apiMessage' } + ]) + } + setLoading(false) setUserInput('') setTimeout(() => { @@ -200,16 +171,18 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { // Get chatmessages successful useEffect(() => { - if (getChatmessageApi.data) { - const loadedMessages = [] - for (const message of getChatmessageApi.data) { + if (getChatmessageApi.data?.length) { + const chatId = getChatmessageApi.data[0]?.chatId + setChatId(chatId) + localStorage.setItem(`${chatflowid}_INTERNAL`, chatId) + const loadedMessages = getChatmessageApi.data.map((message) => { const obj = { message: message.content, type: message.role } if (message.sourceDocuments) obj.sourceDocuments = JSON.parse(message.sourceDocuments) - loadedMessages.push(obj) - } + return obj + }) setMessages((prevMessages) => [...prevMessages, ...loadedMessages]) } diff --git a/packages/ui/src/views/chatmessage/ChatPopUp.js b/packages/ui/src/views/chatmessage/ChatPopUp.js index 93050c3a..1b87ac30 100644 --- a/packages/ui/src/views/chatmessage/ChatPopUp.js +++ b/packages/ui/src/views/chatmessage/ChatPopUp.js @@ -85,7 +85,9 @@ export const ChatPopUp = ({ chatflowid }) => { if (isConfirmed) { try { - await chatmessageApi.deleteChatmessage(chatflowid) + const chatId = localStorage.getItem(`${chatflowid}_INTERNAL`) + await chatmessageApi.deleteChatmessage(chatflowid, { chatId, chatType: 'INTERNAL' }) + localStorage.removeItem(`${chatflowid}_INTERNAL`) resetChatDialog() enqueueSnackbar({ message: 'Succesfully cleared all chat history', diff --git a/packages/ui/src/views/tools/ToolDialog.js b/packages/ui/src/views/tools/ToolDialog.js index 2b67f6d4..398e9eb8 100644 --- a/packages/ui/src/views/tools/ToolDialog.js +++ b/packages/ui/src/views/tools/ToolDialog.js @@ -28,7 +28,7 @@ import useApi from 'hooks/useApi' // utils import useNotifier from 'utils/useNotifier' -import { generateRandomGradient } from 'utils/genericHelper' +import { generateRandomGradient, formatDataGridRows } from 'utils/genericHelper' import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' const exampleAPIFunc = `/* @@ -142,20 +142,6 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) = [deleteItem] ) - const formatSchema = (schema) => { - try { - const parsedSchema = JSON.parse(schema) - return parsedSchema.map((sch, index) => { - return { - ...sch, - id: index - } - }) - } catch (e) { - return [] - } - } - useEffect(() => { if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) else dispatch({ type: HIDE_CANVAS_DIALOG }) @@ -167,7 +153,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) = setToolId(getSpecificToolApi.data.id) setToolName(getSpecificToolApi.data.name) setToolDesc(getSpecificToolApi.data.description) - setToolSchema(formatSchema(getSpecificToolApi.data.schema)) + setToolSchema(formatDataGridRows(getSpecificToolApi.data.schema)) if (getSpecificToolApi.data.func) setToolFunc(getSpecificToolApi.data.func) else setToolFunc('') } @@ -180,7 +166,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) = setToolName(dialogProps.data.name) setToolDesc(dialogProps.data.description) setToolIcon(dialogProps.data.iconSrc) - setToolSchema(formatSchema(dialogProps.data.schema)) + setToolSchema(formatDataGridRows(dialogProps.data.schema)) if (dialogProps.data.func) setToolFunc(dialogProps.data.func) else setToolFunc('') } else if (dialogProps.type === 'EDIT' && dialogProps.toolId) { @@ -191,7 +177,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) = setToolName(dialogProps.data.name) setToolDesc(dialogProps.data.description) setToolIcon(dialogProps.data.iconSrc) - setToolSchema(formatSchema(dialogProps.data.schema)) + setToolSchema(formatDataGridRows(dialogProps.data.schema)) if (dialogProps.data.func) setToolFunc(dialogProps.data.func) else setToolFunc('') } else if (dialogProps.type === 'TEMPLATE' && dialogProps.data) { @@ -199,7 +185,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) = setToolName(dialogProps.data.name) setToolDesc(dialogProps.data.description) setToolIcon(dialogProps.data.iconSrc) - setToolSchema(formatSchema(dialogProps.data.schema)) + setToolSchema(formatDataGridRows(dialogProps.data.schema)) if (dialogProps.data.func) setToolFunc(dialogProps.data.func) else setToolFunc('') } else if (dialogProps.type === 'ADD') { @@ -227,7 +213,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) = delete toolData.id delete toolData.createdDate delete toolData.updatedDate - let dataStr = JSON.stringify(toolData) + let dataStr = JSON.stringify(toolData, null, 2) let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr) let exportFileDefaultName = `${toolName}-CustomTool.json`