diff --git a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts index 3c9568333..643c6a658 100644 --- a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts +++ b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts @@ -21,7 +21,7 @@ class ConversationalRetrievalAgent_Agents implements INode { constructor() { this.label = 'Conversational Retrieval Agent' this.name = 'conversationalRetrievalAgent' - this.version = 2.0 + this.version = 3.0 this.type = 'AgentExecutor' this.category = 'Agents' this.icon = 'agent.svg' @@ -42,7 +42,7 @@ class ConversationalRetrievalAgent_Agents implements INode { { label: 'OpenAI/Azure Chat Model', name: 'model', - type: 'ChatOpenAI | AzureChatOpenAI' + type: 'BaseChatModel' }, { label: 'System Message', diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index 2dc5a95d9..7f2377bde 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -23,7 +23,7 @@ class OpenAIAssistant_Agents implements INode { constructor() { this.label = 'OpenAI Assistant' this.name = 'openAIAssistant' - this.version = 1.0 + this.version = 2.0 this.type = 'OpenAIAssistant' this.category = 'Agents' this.icon = 'openai.png' @@ -85,43 +85,46 @@ class OpenAIAssistant_Agents implements INode { return null } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const selectedAssistantId = nodeData.inputs?.selectedAssistant as string - const appDataSource = options.appDataSource as DataSource - const databaseEntities = options.databaseEntities as IDatabaseEntity - let sessionId = nodeData.inputs?.sessionId as string + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const selectedAssistantId = nodeData.inputs?.selectedAssistant as string + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + let sessionId = nodeData.inputs?.sessionId as string - const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({ - id: selectedAssistantId - }) - - if (!assistant) { - options.logger.error(`Assistant ${selectedAssistantId} not found`) - return - } - - if (!sessionId && options.chatId) { - const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({ - chatId: options.chatId + const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({ + id: selectedAssistantId }) - if (!chatmsg) { - options.logger.error(`Chat Message with Chat Id: ${options.chatId} not found`) + + if (!assistant) { + options.logger.error(`Assistant ${selectedAssistantId} not found`) return } - sessionId = chatmsg.sessionId - } - const credentialData = await getCredentialData(assistant.credential ?? '', options) - const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) - if (!openAIApiKey) { - options.logger.error(`OpenAI ApiKey not found`) - return - } + if (!sessionId && options.chatId) { + const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({ + chatId: options.chatId + }) + if (!chatmsg) { + options.logger.error(`Chat Message with Chat Id: ${options.chatId} not found`) + return + } + sessionId = chatmsg.sessionId + } - const openai = new OpenAI({ apiKey: openAIApiKey }) - options.logger.info(`Clearing OpenAI Thread ${sessionId}`) - if (sessionId) await openai.beta.threads.del(sessionId) - options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`) + const credentialData = await getCredentialData(assistant.credential ?? '', options) + const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) + if (!openAIApiKey) { + options.logger.error(`OpenAI ApiKey not found`) + return + } + + const openai = new OpenAI({ apiKey: openAIApiKey }) + options.logger.info(`Clearing OpenAI Thread ${sessionId}`) + if (sessionId) await openai.beta.threads.del(sessionId) + options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`) + } } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { @@ -359,7 +362,10 @@ class OpenAIAssistant_Agents implements INode { } // Replace the text with a footnote - message_content.value = message_content.value.replace(`${annotation.text}`, `${filePath}`) + message_content.value = message_content.value.replace( + `${annotation.text}`, + `${disableFileDownload ? '' : filePath}` + ) } returnVal += message_content.value diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index 0f5b9aec3..96ba7ea38 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -20,7 +20,7 @@ class OpenAIFunctionAgent_Agents implements INode { constructor() { this.label = 'OpenAI Function Agent' this.name = 'openAIFunctionAgent' - this.version = 2.0 + this.version = 3.0 this.type = 'AgentExecutor' this.category = 'Agents' this.icon = 'openai.png' @@ -41,7 +41,7 @@ class OpenAIFunctionAgent_Agents implements INode { { label: 'OpenAI/Azure Chat Model', name: 'model', - type: 'ChatOpenAI | AzureChatOpenAI' + type: 'BaseChatModel' }, { label: 'System Message', diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index ac4f76020..8ca6cf9e5 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -1,4 +1,13 @@ -import { ICommonObject, INode, INodeData, INodeParams, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src' +import { + ICommonObject, + INode, + INodeData, + INodeParams, + getBaseClasses, + getCredentialData, + getCredentialParam, + serializeChatHistory +} from '../../../src' import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' @@ -70,13 +79,23 @@ class DynamoDb_Memory implements INode { return initalizeDynamoDB(nodeData, options) } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const dynamodbMemory = await initalizeDynamoDB(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing DynamoDb memory session ${sessionId ? sessionId : chatId}`) - await dynamodbMemory.clear() - options.logger.info(`Successfully cleared DynamoDb memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const dynamodbMemory = await initalizeDynamoDB(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing DynamoDb memory session ${sessionId ? sessionId : chatId}`) + await dynamodbMemory.clear() + options.logger.info(`Successfully cleared DynamoDb memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const dynamodbMemory = await initalizeDynamoDB(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await dynamodbMemory.loadMemoryVariables({}) + return serializeChatHistory(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts index 6f800cdc4..76cb7e313 100644 --- a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts +++ b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts @@ -1,4 +1,13 @@ -import { getBaseClasses, getCredentialData, getCredentialParam, ICommonObject, INode, INodeData, INodeParams } from '../../../src' +import { + getBaseClasses, + getCredentialData, + getCredentialParam, + ICommonObject, + INode, + INodeData, + INodeParams, + serializeChatHistory +} from '../../../src' import { MongoDBChatMessageHistory } from 'langchain/stores/message/mongodb' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { BaseMessage, mapStoredMessageToChatMessage } from 'langchain/schema' @@ -67,13 +76,23 @@ class MongoDB_Memory implements INode { return initializeMongoDB(nodeData, options) } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const mongodbMemory = await initializeMongoDB(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing MongoDB memory session ${sessionId ? sessionId : chatId}`) - await mongodbMemory.clear() - options.logger.info(`Successfully cleared MongoDB memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const mongodbMemory = await initializeMongoDB(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing MongoDB memory session ${sessionId ? sessionId : chatId}`) + await mongodbMemory.clear() + options.logger.info(`Successfully cleared MongoDB memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const mongodbMemory = await initializeMongoDB(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await mongodbMemory.loadMemoryVariables({}) + return serializeChatHistory(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 0ec2f42ad..9cdbcd5cc 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -3,6 +3,7 @@ import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../ import { ICommonObject } from '../../../src' import { MotorheadMemory, MotorheadMemoryInput } from 'langchain/memory' import fetch from 'node-fetch' +import { getBufferString } from 'langchain/memory' class MotorMemory_Memory implements INode { label: string @@ -64,13 +65,23 @@ class MotorMemory_Memory implements INode { return initalizeMotorhead(nodeData, options) } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const motorhead = await initalizeMotorhead(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Motorhead memory session ${sessionId ? sessionId : chatId}`) - await motorhead.clear() - options.logger.info(`Successfully cleared Motorhead memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const motorhead = await initalizeMotorhead(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Motorhead memory session ${sessionId ? sessionId : chatId}`) + await motorhead.clear() + options.logger.info(`Successfully cleared Motorhead memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const motorhead = await initalizeMotorhead(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await motorhead.loadMemoryVariables({}) + return getBufferString(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index bdb62911c..7fe447ad5 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -1,5 +1,5 @@ import { INode, INodeData, INodeParams, ICommonObject } from '../../../src/Interface' -import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam, serializeChatHistory } from '../../../src/utils' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/ioredis' import { mapStoredMessageToChatMessage, BaseMessage } from 'langchain/schema' @@ -65,13 +65,23 @@ class RedisBackedChatMemory_Memory implements INode { return await initalizeRedis(nodeData, options) } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - 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}`) - await redis.clear() - options.logger.info(`Successfully cleared Redis memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + 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}`) + await redis.clear() + options.logger.info(`Successfully cleared Redis memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const redis = await initalizeRedis(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await redis.loadMemoryVariables({}) + return serializeChatHistory(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts index 2b8b46503..8bca04404 100644 --- a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts @@ -1,5 +1,5 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam, serializeChatHistory } from '../../../src/utils' import { ICommonObject } from '../../../src' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { UpstashRedisChatMessageHistory } from 'langchain/stores/message/upstash_redis' @@ -63,13 +63,22 @@ class UpstashRedisBackedChatMemory_Memory implements INode { return initalizeUpstashRedis(nodeData, options) } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const redis = await initalizeUpstashRedis(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Upstash Redis memory session ${sessionId ? sessionId : chatId}`) - await redis.clear() - options.logger.info(`Successfully cleared Upstash Redis memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const redis = await initalizeUpstashRedis(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Upstash Redis memory session ${sessionId ? sessionId : chatId}`) + await redis.clear() + options.logger.info(`Successfully cleared Upstash Redis memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const redis = await initalizeUpstashRedis(nodeData, options) + const key = 'chat_history' + const memoryResult = await redis.loadMemoryVariables({}) + return serializeChatHistory(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index c44986444..ced871a1e 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -3,6 +3,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' import { ICommonObject } from '../../../src' +import { getBufferString } from 'langchain/memory' class ZepMemory_Memory implements INode { label: string @@ -140,13 +141,25 @@ class ZepMemory_Memory implements INode { return zep } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const zep = await initalizeZep(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Zep memory session ${sessionId ? sessionId : chatId}`) - await zep.clear() - options.logger.info(`Successfully cleared Zep memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const zep = await initalizeZep(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Zep memory session ${sessionId ? sessionId : chatId}`) + await zep.clear() + options.logger.info(`Successfully cleared Zep memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const aiPrefix = nodeData.inputs?.aiPrefix as string + const humanPrefix = nodeData.inputs?.humanPrefix as string + const zep = await initalizeZep(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await zep.loadMemoryVariables({}) + return getBufferString(memoryResult[key], humanPrefix, aiPrefix) + } } } diff --git a/packages/components/nodes/tools/ChainTool/core.ts b/packages/components/nodes/tools/ChainTool/core.ts index 6c3dba554..5520d6df0 100644 --- a/packages/components/nodes/tools/ChainTool/core.ts +++ b/packages/components/nodes/tools/ChainTool/core.ts @@ -1,5 +1,6 @@ import { DynamicTool, DynamicToolInput } from 'langchain/tools' import { BaseChain } from 'langchain/chains' +import { handleEscapeCharacters } from '../../../src/utils' export interface ChainToolInput extends Omit { chain: BaseChain @@ -14,7 +15,8 @@ export class ChainTool extends DynamicTool { func: async (input, runManager) => { // To enable LLM Chain which has promptValues if ((chain as any).prompt && (chain as any).prompt.promptValues) { - const values = await chain.call((chain as any).prompt.promptValues, runManager?.getChild()) + const promptValues = handleEscapeCharacters((chain as any).prompt.promptValues, true) + const values = await chain.call(promptValues, runManager?.getChild()) return values?.text } return chain.run(input, runManager?.getChild()) diff --git a/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts b/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts deleted file mode 100644 index 31ac989b5..000000000 --- a/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ZapierNLAWrapper, ZapierNLAWrapperParams } from 'langchain/tools' -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { ZapierToolKit } from 'langchain/agents' -import { getCredentialData, getCredentialParam } from '../../../src' - -class ZapierNLA_Tools implements INode { - label: string - name: string - version: number - description: string - type: string - icon: string - category: string - badge: string - baseClasses: string[] - inputs: INodeParams[] - credential: INodeParams - - constructor() { - this.label = 'Zapier NLA' - this.name = 'zapierNLA' - this.version = 1.0 - this.type = 'ZapierNLA' - this.icon = 'zapier.svg' - this.category = 'Tools' - this.description = "Access to apps and actions on Zapier's platform through a natural language API interface" - this.badge = 'DEPRECATING' - this.inputs = [] - this.credential = { - label: 'Connect Credential', - name: 'credential', - type: 'credential', - credentialNames: ['zapierNLAApi'] - } - this.baseClasses = [this.type, 'Tool'] - } - - async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const credentialData = await getCredentialData(nodeData.credential ?? '', options) - const zapierNLAApiKey = getCredentialParam('zapierNLAApiKey', credentialData, nodeData) - - const obj: Partial = { - apiKey: zapierNLAApiKey - } - const zapier = new ZapierNLAWrapper(obj) - const toolkit = await ZapierToolKit.fromZapierNLAWrapper(zapier) - - return toolkit.tools - } -} - -module.exports = { nodeClass: ZapierNLA_Tools } diff --git a/packages/components/nodes/tools/ZapierNLA/zapier.svg b/packages/components/nodes/tools/ZapierNLA/zapier.svg deleted file mode 100644 index 6ed35f295..000000000 --- a/packages/components/nodes/tools/ZapierNLA/zapier.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index bb0743926..bea9a7a06 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.4.4", + "version": "1.4.5", "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 af3042720..6752f9440 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -107,9 +107,12 @@ export interface INode extends INodeProperties { search: (nodeData: INodeData, options?: ICommonObject) => Promise delete: (nodeData: INodeData, options?: ICommonObject) => Promise } + memoryMethods?: { + clearSessionMemory: (nodeData: INodeData, options?: ICommonObject) => Promise + getChatMessages: (nodeData: INodeData, options?: ICommonObject) => Promise + } init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise - clearSessionMemory?(nodeData: INodeData, options?: ICommonObject): Promise } export interface INodeData extends INodeProperties { diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 69f8b268e..404f7c75d 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -549,6 +549,18 @@ export const convertChatHistoryToText = (chatHistory: IMessage[] = []): string = .join('\n') } +/** + * Serialize array chat history to string + * @param {IMessage[]} chatHistory + * @returns {string} + */ +export const serializeChatHistory = (chatHistory: string | Array) => { + if (Array.isArray(chatHistory)) { + return chatHistory.join('\n') + } + return chatHistory +} + /** * Convert schema to zod schema * @param {string | object} schema diff --git a/packages/server/marketplaces/chatflows/API Agent OpenAI.json b/packages/server/marketplaces/chatflows/API Agent OpenAI.json index 0e9aa0917..002c08c18 100644 --- a/packages/server/marketplaces/chatflows/API Agent OpenAI.json +++ b/packages/server/marketplaces/chatflows/API Agent OpenAI.json @@ -333,8 +333,8 @@ "data": { "id": "openAIFunctionAgent_0", "label": "OpenAI Function Agent", - "version": 2, "name": "openAIFunctionAgent", + "version": 3, "type": "AgentExecutor", "baseClasses": ["AgentExecutor", "BaseChain"], "category": "Agents", @@ -367,8 +367,8 @@ { "label": "OpenAI/Azure Chat Model", "name": "model", - "type": "ChatOpenAI | AzureChatOpenAI", - "id": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI" + "type": "BaseChatModel", + "id": "openAIFunctionAgent_0-input-model-BaseChatModel" } ], "inputs": { @@ -672,9 +672,9 @@ "source": "chatOpenAI_2", "sourceHandle": "chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "target": "openAIFunctionAgent_0", - "targetHandle": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_2-chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "id": "chatOpenAI_2-chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json index 68618a638..0e9e41bdd 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json @@ -98,7 +98,7 @@ "data": { "id": "conversationalRetrievalAgent_0", "label": "Conversational Retrieval Agent", - "version": 2, + "version": 3, "name": "conversationalRetrievalAgent", "type": "AgentExecutor", "baseClasses": ["AgentExecutor", "BaseChain", "Runnable"], @@ -132,8 +132,8 @@ { "label": "OpenAI/Azure Chat Model", "name": "model", - "type": "ChatOpenAI | AzureChatOpenAI", - "id": "conversationalRetrievalAgent_0-input-model-ChatOpenAI | AzureChatOpenAI" + "type": "BaseChatModel", + "id": "conversationalRetrievalAgent_0-input-model-BaseChatModel" } ], "inputs": { @@ -642,9 +642,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalAgent_0", - "targetHandle": "conversationalRetrievalAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "targetHandle": "conversationalRetrievalAgent_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalAgent_0-conversationalRetrievalAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalAgent_0-conversationalRetrievalAgent_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/OpenAI Agent.json b/packages/server/marketplaces/chatflows/OpenAI Agent.json index d2c842c6b..17e59236d 100644 --- a/packages/server/marketplaces/chatflows/OpenAI Agent.json +++ b/packages/server/marketplaces/chatflows/OpenAI Agent.json @@ -205,8 +205,8 @@ "data": { "id": "openAIFunctionAgent_0", "label": "OpenAI Function Agent", - "version": 2, "name": "openAIFunctionAgent", + "version": 3, "type": "AgentExecutor", "baseClasses": ["AgentExecutor", "BaseChain"], "category": "Agents", @@ -239,8 +239,8 @@ { "label": "OpenAI/Azure Chat Model", "name": "model", - "type": "ChatOpenAI | AzureChatOpenAI", - "id": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI" + "type": "BaseChatModel", + "id": "openAIFunctionAgent_0-input-model-BaseChatModel" } ], "inputs": { @@ -488,9 +488,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "target": "openAIFunctionAgent_0", - "targetHandle": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/OpenAI Assistant.json b/packages/server/marketplaces/chatflows/OpenAI Assistant.json index 2f9a860cc..ba4c61343 100644 --- a/packages/server/marketplaces/chatflows/OpenAI Assistant.json +++ b/packages/server/marketplaces/chatflows/OpenAI Assistant.json @@ -14,7 +14,7 @@ "data": { "id": "openAIAssistant_0", "label": "OpenAI Assistant", - "version": 1, + "version": 2, "name": "openAIAssistant", "type": "OpenAIAssistant", "baseClasses": ["OpenAIAssistant"], @@ -27,6 +27,15 @@ "type": "asyncOptions", "loadMethod": "listAssistants", "id": "openAIAssistant_0-input-selectedAssistant-asyncOptions" + }, + { + "label": "Disable File Download", + "name": "disableFileDownload", + "type": "boolean", + "description": "Messages can contain text, images, or files. In some cases, you may want to prevent others from downloading the files. Learn more from OpenAI File Annotation docs", + "optional": true, + "additionalParams": true, + "id": "openAIAssistant_0-input-disableFileDownload-boolean" } ], "inputAnchors": [ diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index c562b4eec..d5890ab6c 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -190,12 +190,6 @@ export interface IOverrideConfig { type: string } -export interface IDatabaseExport { - chatmessages: IChatMessage[] - chatflows: IChatFlow[] - apikeys: ICommonObject[] -} - export type ICredentialDataDecrypted = ICommonObject // Plain credential object sent to server diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 35139bf0d..42fb326d8 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -17,7 +17,6 @@ import { IReactFlowNode, IReactFlowObject, INodeData, - IDatabaseExport, ICredentialReturnResponse, chatType, IChatMessage, @@ -31,18 +30,11 @@ import { constructGraphs, resolveVariables, isStartNodeDependOnInput, - getAPIKeys, - addAPIKey, - updateAPIKey, - deleteAPIKey, - compareKeys, mapMimeTypeToInputField, findAvailableConfigs, isSameOverrideConfig, - replaceAllAPIKeys, isFlowValidForStream, databaseEntities, - getApiKey, transformToCredentialEntity, decryptCredentialData, clearAllSessionMemory, @@ -50,7 +42,8 @@ import { getEncryptionKey, checkMemorySessionId, clearSessionMemoryFromViewMessageDialog, - getUserHome + getUserHome, + replaceChatHistory } from './utils' import { cloneDeep, omit, uniqWith, isEqual } from 'lodash' import { getDataSource } from './DataSource' @@ -62,8 +55,9 @@ import { Tool } from './database/entities/Tool' import { Assistant } from './database/entities/Assistant' import { ChatflowPool } from './ChatflowPool' import { CachePool } from './CachePool' -import { ICommonObject, INodeOptionsValue } from 'flowise-components' +import { ICommonObject, IMessage, INodeOptionsValue } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' +import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey' import axios from 'axios' import { Client } from 'langchainhub' import { parsePrompt } from './utils/hub' @@ -1028,57 +1022,6 @@ export class App { } }) - // ---------------------------------------- - // Export Load Chatflow & ChatMessage & Apikeys - // ---------------------------------------- - - this.app.get('/api/v1/database/export', async (req: Request, res: Response) => { - const chatmessages = await this.AppDataSource.getRepository(ChatMessage).find() - const chatflows = await this.AppDataSource.getRepository(ChatFlow).find() - const apikeys = await getAPIKeys() - const result: IDatabaseExport = { - chatmessages, - chatflows, - apikeys - } - return res.json(result) - }) - - this.app.post('/api/v1/database/load', async (req: Request, res: Response) => { - const databaseItems: IDatabaseExport = req.body - - await this.AppDataSource.getRepository(ChatFlow).delete({}) - await this.AppDataSource.getRepository(ChatMessage).delete({}) - - let error = '' - - // Get a new query runner instance - const queryRunner = this.AppDataSource.createQueryRunner() - - // Start a new transaction - await queryRunner.startTransaction() - - try { - const chatflows: ChatFlow[] = databaseItems.chatflows - const chatmessages: ChatMessage[] = databaseItems.chatmessages - - await queryRunner.manager.insert(ChatFlow, chatflows) - await queryRunner.manager.insert(ChatMessage, chatmessages) - - await queryRunner.commitTransaction() - } catch (err: any) { - error = err?.message ?? 'Error loading database' - await queryRunner.rollbackTransaction() - } finally { - await queryRunner.release() - } - - await replaceAllAPIKeys(databaseItems.apikeys) - - if (error) return res.status(500).send(error) - return res.status(201).send('OK') - }) - // ---------------------------------------- // Upsert // ---------------------------------------- @@ -1377,14 +1320,14 @@ export class App { * @param {IReactFlowEdge[]} edges * @returns {string | undefined} */ - findMemoryLabel(nodes: IReactFlowNode[], edges: IReactFlowEdge[]): string | undefined { + findMemoryLabel(nodes: IReactFlowNode[], edges: IReactFlowEdge[]): IReactFlowNode | 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 memoryNode } } return undefined @@ -1505,6 +1448,19 @@ export class App { isStreamValid = isFlowValidForStream(nodes, endingNodeData) + let chatHistory: IMessage[] | string = incomingInput.history + if ( + endingNodeData.inputs?.memory && + !incomingInput.history && + (incomingInput.chatId || incomingInput.overrideConfig?.sessionId) + ) { + const memoryNodeId = endingNodeData.inputs?.memory.split('.')[0].replace('{{', '') + const memoryNode = nodes.find((node) => node.data.id === memoryNodeId) + if (memoryNode) { + chatHistory = await replaceChatHistory(memoryNode, incomingInput, this.AppDataSource, databaseEntities, logger) + } + } + /*** Get Starting Nodes with Non-Directed Graph ***/ const constructedObj = constructGraphs(nodes, edges, true) const nonDirectedGraph = constructedObj.graph @@ -1519,7 +1475,7 @@ export class App { depthQueue, this.nodesPool.componentNodes, incomingInput.question, - incomingInput.history, + chatHistory, chatId, chatflowid, this.AppDataSource, @@ -1539,7 +1495,7 @@ export class App { nodeToExecute.data, reactFlowNodes, incomingInput.question, - incomingInput.history + chatHistory ) nodeToExecuteData = reactFlowNodeData @@ -1556,11 +1512,17 @@ export class App { let sessionId = undefined if (nodeToExecuteData.instance) sessionId = checkMemorySessionId(nodeToExecuteData.instance, chatId) - const memoryType = this.findMemoryLabel(nodes, edges) + const memoryNode = this.findMemoryLabel(nodes, edges) + const memoryType = memoryNode?.data.label + + let chatHistory: IMessage[] | string = incomingInput.history + if (memoryNode && !incomingInput.history && (incomingInput.chatId || incomingInput.overrideConfig?.sessionId)) { + chatHistory = await replaceChatHistory(memoryNode, incomingInput, this.AppDataSource, databaseEntities, logger) + } let result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { - chatHistory: incomingInput.history, + chatHistory, socketIO, socketIOClientId: incomingInput.socketIOClientId, logger, @@ -1570,7 +1532,7 @@ export class App { chatId }) : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { - chatHistory: incomingInput.history, + chatHistory, logger, appDataSource: this.AppDataSource, databaseEntities, diff --git a/packages/server/src/utils/apiKey.ts b/packages/server/src/utils/apiKey.ts new file mode 100644 index 000000000..08a9ecd37 --- /dev/null +++ b/packages/server/src/utils/apiKey.ts @@ -0,0 +1,147 @@ +import { randomBytes, scryptSync, timingSafeEqual } from 'crypto' +import { ICommonObject } from 'flowise-components' +import moment from 'moment' +import fs from 'fs' +import path from 'path' +import logger from './logger' + +/** + * Returns the api key path + * @returns {string} + */ +export const getAPIKeyPath = (): string => { + return process.env.APIKEY_PATH ? path.join(process.env.APIKEY_PATH, 'api.json') : path.join(__dirname, '..', '..', 'api.json') +} + +/** + * Generate the api key + * @returns {string} + */ +export const generateAPIKey = (): string => { + const buffer = randomBytes(32) + return buffer.toString('base64') +} + +/** + * Generate the secret key + * @param {string} apiKey + * @returns {string} + */ +export const generateSecretHash = (apiKey: string): string => { + const salt = randomBytes(8).toString('hex') + const buffer = scryptSync(apiKey, salt, 64) as Buffer + return `${buffer.toString('hex')}.${salt}` +} + +/** + * Verify valid keys + * @param {string} storedKey + * @param {string} suppliedKey + * @returns {boolean} + */ +export const compareKeys = (storedKey: string, suppliedKey: string): boolean => { + const [hashedPassword, salt] = storedKey.split('.') + const buffer = scryptSync(suppliedKey, salt, 64) as Buffer + return timingSafeEqual(Buffer.from(hashedPassword, 'hex'), buffer) +} + +/** + * Get API keys + * @returns {Promise} + */ +export const getAPIKeys = async (): Promise => { + try { + const content = await fs.promises.readFile(getAPIKeyPath(), 'utf8') + return JSON.parse(content) + } catch (error) { + const keyName = 'DefaultKey' + const apiKey = generateAPIKey() + const apiSecret = generateSecretHash(apiKey) + const content = [ + { + keyName, + apiKey, + apiSecret, + createdAt: moment().format('DD-MMM-YY'), + id: randomBytes(16).toString('hex') + } + ] + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') + return content + } +} + +/** + * Add new API key + * @param {string} keyName + * @returns {Promise} + */ +export const addAPIKey = async (keyName: string): Promise => { + const existingAPIKeys = await getAPIKeys() + const apiKey = generateAPIKey() + const apiSecret = generateSecretHash(apiKey) + const content = [ + ...existingAPIKeys, + { + keyName, + apiKey, + apiSecret, + createdAt: moment().format('DD-MMM-YY'), + id: randomBytes(16).toString('hex') + } + ] + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') + return content +} + +/** + * Get API Key details + * @param {string} apiKey + * @returns {Promise} + */ +export const getApiKey = async (apiKey: string) => { + const existingAPIKeys = await getAPIKeys() + const keyIndex = existingAPIKeys.findIndex((key) => key.apiKey === apiKey) + if (keyIndex < 0) return undefined + return existingAPIKeys[keyIndex] +} + +/** + * Update existing API key + * @param {string} keyIdToUpdate + * @param {string} newKeyName + * @returns {Promise} + */ +export const updateAPIKey = async (keyIdToUpdate: string, newKeyName: string): Promise => { + const existingAPIKeys = await getAPIKeys() + const keyIndex = existingAPIKeys.findIndex((key) => key.id === keyIdToUpdate) + if (keyIndex < 0) return [] + existingAPIKeys[keyIndex].keyName = newKeyName + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(existingAPIKeys), 'utf8') + return existingAPIKeys +} + +/** + * Delete API key + * @param {string} keyIdToDelete + * @returns {Promise} + */ +export const deleteAPIKey = async (keyIdToDelete: string): Promise => { + const existingAPIKeys = await getAPIKeys() + const result = existingAPIKeys.filter((key) => key.id !== keyIdToDelete) + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(result), 'utf8') + return result +} + +/** + * Replace all api keys + * @param {ICommonObject[]} content + * @returns {Promise} + */ +export const replaceAllAPIKeys = async (content: ICommonObject[]): Promise => { + try { + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') + } catch (error) { + logger.error(error) + } +} diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 9dbb695ee..0b1e62d25 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -1,33 +1,33 @@ import path from 'path' import fs from 'fs' -import moment from 'moment' import logger from './logger' import { + IComponentCredentials, IComponentNodes, + ICredentialDataDecrypted, + ICredentialReqBody, IDepthQueue, IExploredNode, + INodeData, INodeDependencies, INodeDirectedGraph, INodeQueue, + IOverrideConfig, IReactFlowEdge, IReactFlowNode, IVariableDict, - INodeData, - IOverrideConfig, - ICredentialDataDecrypted, - IComponentCredentials, - ICredentialReqBody + IncomingInput } from '../Interface' import { cloneDeep, get, isEqual } from 'lodash' import { - ICommonObject, + convertChatHistoryToText, getInputVariables, - IDatabaseEntity, handleEscapeCharacters, - IMessage, - convertChatHistoryToText + ICommonObject, + IDatabaseEntity, + IMessage } from 'flowise-components' -import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' +import { randomBytes } from 'crypto' import { AES, enc } from 'crypto-js' import { ChatFlow } from '../database/entities/ChatFlow' @@ -217,7 +217,7 @@ export const buildLangchain = async ( depthQueue: IDepthQueue, componentNodes: IComponentNodes, question: string, - chatHistory: IMessage[], + chatHistory: IMessage[] | string, chatId: string, chatflowid: string, appDataSource: DataSource, @@ -348,8 +348,8 @@ export const clearAllSessionMemory = async ( node.data.inputs.sessionId = sessionId } - if (newNodeInstance.clearSessionMemory) { - await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) + if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) { + await newNodeInstance.memoryMethods.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) } } } @@ -381,8 +381,8 @@ export const clearSessionMemoryFromViewMessageDialog = async ( if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId - if (newNodeInstance.clearSessionMemory) { - await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) + if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) { + await newNodeInstance.memoryMethods.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) return } } @@ -400,7 +400,7 @@ export const getVariableValue = ( paramValue: string, reactFlowNodes: IReactFlowNode[], question: string, - chatHistory: IMessage[], + chatHistory: IMessage[] | string, isAcceptVariable = false ) => { let returnVal = paramValue @@ -433,7 +433,10 @@ export const getVariableValue = ( } if (isAcceptVariable && variableFullPath === CHAT_HISTORY_VAR_PREFIX) { - variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(convertChatHistoryToText(chatHistory), false) + variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters( + typeof chatHistory === 'string' ? chatHistory : convertChatHistoryToText(chatHistory), + false + ) } // Split by first occurrence of '.' to get just nodeId @@ -476,7 +479,7 @@ export const resolveVariables = ( reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[], question: string, - chatHistory: IMessage[] + chatHistory: IMessage[] | string ): INodeData => { let flowNodeData = cloneDeep(reactFlowNodeData) const types = 'inputs' @@ -593,147 +596,6 @@ export const isSameOverrideConfig = ( return false } -/** - * Returns the api key path - * @returns {string} - */ -export const getAPIKeyPath = (): string => { - return process.env.APIKEY_PATH ? path.join(process.env.APIKEY_PATH, 'api.json') : path.join(__dirname, '..', '..', 'api.json') -} - -/** - * Generate the api key - * @returns {string} - */ -export const generateAPIKey = (): string => { - const buffer = randomBytes(32) - return buffer.toString('base64') -} - -/** - * Generate the secret key - * @param {string} apiKey - * @returns {string} - */ -export const generateSecretHash = (apiKey: string): string => { - const salt = randomBytes(8).toString('hex') - const buffer = scryptSync(apiKey, salt, 64) as Buffer - return `${buffer.toString('hex')}.${salt}` -} - -/** - * Verify valid keys - * @param {string} storedKey - * @param {string} suppliedKey - * @returns {boolean} - */ -export const compareKeys = (storedKey: string, suppliedKey: string): boolean => { - const [hashedPassword, salt] = storedKey.split('.') - const buffer = scryptSync(suppliedKey, salt, 64) as Buffer - return timingSafeEqual(Buffer.from(hashedPassword, 'hex'), buffer) -} - -/** - * Get API keys - * @returns {Promise} - */ -export const getAPIKeys = async (): Promise => { - try { - const content = await fs.promises.readFile(getAPIKeyPath(), 'utf8') - return JSON.parse(content) - } catch (error) { - const keyName = 'DefaultKey' - const apiKey = generateAPIKey() - const apiSecret = generateSecretHash(apiKey) - const content = [ - { - keyName, - apiKey, - apiSecret, - createdAt: moment().format('DD-MMM-YY'), - id: randomBytes(16).toString('hex') - } - ] - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') - return content - } -} - -/** - * Add new API key - * @param {string} keyName - * @returns {Promise} - */ -export const addAPIKey = async (keyName: string): Promise => { - const existingAPIKeys = await getAPIKeys() - const apiKey = generateAPIKey() - const apiSecret = generateSecretHash(apiKey) - const content = [ - ...existingAPIKeys, - { - keyName, - apiKey, - apiSecret, - createdAt: moment().format('DD-MMM-YY'), - id: randomBytes(16).toString('hex') - } - ] - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') - return content -} - -/** - * Get API Key details - * @param {string} apiKey - * @returns {Promise} - */ -export const getApiKey = async (apiKey: string) => { - const existingAPIKeys = await getAPIKeys() - const keyIndex = existingAPIKeys.findIndex((key) => key.apiKey === apiKey) - if (keyIndex < 0) return undefined - return existingAPIKeys[keyIndex] -} - -/** - * Update existing API key - * @param {string} keyIdToUpdate - * @param {string} newKeyName - * @returns {Promise} - */ -export const updateAPIKey = async (keyIdToUpdate: string, newKeyName: string): Promise => { - const existingAPIKeys = await getAPIKeys() - const keyIndex = existingAPIKeys.findIndex((key) => key.id === keyIdToUpdate) - if (keyIndex < 0) return [] - existingAPIKeys[keyIndex].keyName = newKeyName - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(existingAPIKeys), 'utf8') - return existingAPIKeys -} - -/** - * Delete API key - * @param {string} keyIdToDelete - * @returns {Promise} - */ -export const deleteAPIKey = async (keyIdToDelete: string): Promise => { - const existingAPIKeys = await getAPIKeys() - const result = existingAPIKeys.filter((key) => key.id !== keyIdToDelete) - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(result), 'utf8') - return result -} - -/** - * Replace all api keys - * @param {ICommonObject[]} content - * @returns {Promise} - */ -export const replaceAllAPIKeys = async (content: ICommonObject[]): Promise => { - try { - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') - } catch (error) { - logger.error(error) - } -} - /** * Map MimeType to InputField * @param {string} mimeType @@ -1015,3 +877,39 @@ export const checkMemorySessionId = (instance: any, chatId: string): string | un return instance.memory.chatHistory.sessionId return undefined } + +/** + * Replace chatHistory if incomingInput.history is empty and sessionId/chatId is provided + * @param {IReactFlowNode} memoryNode + * @param {IncomingInput} incomingInput + * @param {DataSource} appDataSource + * @param {IDatabaseEntity} databaseEntities + * @param {any} logger + * @returns {string} + */ +export const replaceChatHistory = async ( + memoryNode: IReactFlowNode, + incomingInput: IncomingInput, + appDataSource: DataSource, + databaseEntities: IDatabaseEntity, + logger: any +): Promise => { + const nodeInstanceFilePath = memoryNode.data.filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const newNodeInstance = new nodeModule.nodeClass() + + if (incomingInput.overrideConfig?.sessionId && memoryNode.data.inputs) { + memoryNode.data.inputs.sessionId = incomingInput.overrideConfig.sessionId + } + + if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.getChatMessages) { + return await newNodeInstance.memoryMethods.getChatMessages(memoryNode.data, { + chatId: incomingInput.chatId, + appDataSource, + databaseEntities, + logger + }) + } + + return '' +} diff --git a/packages/ui/src/api/database.js b/packages/ui/src/api/database.js deleted file mode 100644 index f36fb72c7..000000000 --- a/packages/ui/src/api/database.js +++ /dev/null @@ -1,9 +0,0 @@ -import client from './client' - -const getExportDatabase = () => client.get('/database/export') -const createLoadDatabase = (body) => client.post('/database/load', body) - -export default { - getExportDatabase, - createLoadDatabase -} diff --git a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js index c10b32894..ac114c6cc 100644 --- a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js +++ b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js @@ -1,7 +1,6 @@ import { useState, useRef, useEffect } from 'react' import PropTypes from 'prop-types' -import { useSelector, useDispatch } from 'react-redux' -import { useNavigate } from 'react-router-dom' +import { useSelector } from 'react-redux' // material-ui import { useTheme } from '@mui/material/styles' @@ -26,16 +25,10 @@ import PerfectScrollbar from 'react-perfect-scrollbar' // project imports import MainCard from 'ui-component/cards/MainCard' import Transitions from 'ui-component/extended/Transitions' -import { BackdropLoader } from 'ui-component/loading/BackdropLoader' import AboutDialog from 'ui-component/dialog/AboutDialog' // assets -import { IconLogout, IconSettings, IconFileExport, IconFileDownload, IconInfoCircle } from '@tabler/icons' - -// API -import databaseApi from 'api/database' - -import { SET_MENU } from 'store/actions' +import { IconLogout, IconSettings, IconInfoCircle } from '@tabler/icons' import './index.css' @@ -43,17 +36,13 @@ import './index.css' const ProfileSection = ({ username, handleLogout }) => { const theme = useTheme() - const dispatch = useDispatch() - const navigate = useNavigate() const customization = useSelector((state) => state.customization) const [open, setOpen] = useState(false) - const [loading, setLoading] = useState(false) const [aboutDialogOpen, setAboutDialogOpen] = useState(false) const anchorRef = useRef(null) - const uploadRef = useRef(null) const handleClose = (event) => { if (anchorRef.current && anchorRef.current.contains(event.target)) { @@ -66,56 +55,6 @@ const ProfileSection = ({ username, handleLogout }) => { setOpen((prevOpen) => !prevOpen) } - const handleExportDB = async () => { - setOpen(false) - try { - const response = await databaseApi.getExportDatabase() - const exportItems = response.data - let dataStr = JSON.stringify(exportItems, null, 2) - let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr) - - let exportFileDefaultName = `DB.json` - - let linkElement = document.createElement('a') - linkElement.setAttribute('href', dataUri) - linkElement.setAttribute('download', exportFileDefaultName) - linkElement.click() - } catch (e) { - console.error(e) - } - } - - const handleFileUpload = (e) => { - if (!e.target.files) return - - const file = e.target.files[0] - const reader = new FileReader() - reader.onload = async (evt) => { - if (!evt?.target?.result) { - return - } - const { result } = evt.target - - if (result.includes(`"chatmessages":[`) && result.includes(`"chatflows":[`) && result.includes(`"apikeys":[`)) { - dispatch({ type: SET_MENU, opened: false }) - setLoading(true) - - try { - await databaseApi.createLoadDatabase(JSON.parse(result)) - setLoading(false) - navigate('/', { replace: true }) - navigate(0) - } catch (e) { - console.error(e) - setLoading(false) - } - } else { - alert('Incorrect Flowise Database Format') - } - } - reader.readAsText(file) - } - const prevOpen = useRef(open) useEffect(() => { if (prevOpen.current === true && open === false) { @@ -196,27 +135,6 @@ const ProfileSection = ({ username, handleLogout }) => { } }} > - { - setOpen(false) - uploadRef.current.click() - }} - > - - - - Load Database} /> - - - - - - Export Database} /> - { @@ -249,8 +167,6 @@ const ProfileSection = ({ username, handleLogout }) => { )} - handleFileUpload(e)} /> - setAboutDialogOpen(false)} /> )