Merge branch 'main' into FEATURE/lshub-prompt

# Conflicts:
#	packages/server/src/index.ts
This commit is contained in:
vinodkiran 2023-12-01 10:36:11 +05:30
commit acac9ee68d
25 changed files with 452 additions and 491 deletions

View File

@ -21,7 +21,7 @@ class ConversationalRetrievalAgent_Agents implements INode {
constructor() { constructor() {
this.label = 'Conversational Retrieval Agent' this.label = 'Conversational Retrieval Agent'
this.name = 'conversationalRetrievalAgent' this.name = 'conversationalRetrievalAgent'
this.version = 2.0 this.version = 3.0
this.type = 'AgentExecutor' this.type = 'AgentExecutor'
this.category = 'Agents' this.category = 'Agents'
this.icon = 'agent.svg' this.icon = 'agent.svg'
@ -42,7 +42,7 @@ class ConversationalRetrievalAgent_Agents implements INode {
{ {
label: 'OpenAI/Azure Chat Model', label: 'OpenAI/Azure Chat Model',
name: 'model', name: 'model',
type: 'ChatOpenAI | AzureChatOpenAI' type: 'BaseChatModel'
}, },
{ {
label: 'System Message', label: 'System Message',

View File

@ -23,7 +23,7 @@ class OpenAIAssistant_Agents implements INode {
constructor() { constructor() {
this.label = 'OpenAI Assistant' this.label = 'OpenAI Assistant'
this.name = 'openAIAssistant' this.name = 'openAIAssistant'
this.version = 1.0 this.version = 2.0
this.type = 'OpenAIAssistant' this.type = 'OpenAIAssistant'
this.category = 'Agents' this.category = 'Agents'
this.icon = 'openai.png' this.icon = 'openai.png'
@ -85,6 +85,8 @@ class OpenAIAssistant_Agents implements INode {
return null return null
} }
//@ts-ignore
memoryMethods = {
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
const selectedAssistantId = nodeData.inputs?.selectedAssistant as string const selectedAssistantId = nodeData.inputs?.selectedAssistant as string
const appDataSource = options.appDataSource as DataSource const appDataSource = options.appDataSource as DataSource
@ -123,6 +125,7 @@ class OpenAIAssistant_Agents implements INode {
if (sessionId) await openai.beta.threads.del(sessionId) if (sessionId) await openai.beta.threads.del(sessionId)
options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`) options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`)
} }
}
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string | object> { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string | object> {
const selectedAssistantId = nodeData.inputs?.selectedAssistant as string const selectedAssistantId = nodeData.inputs?.selectedAssistant as string
@ -359,7 +362,10 @@ class OpenAIAssistant_Agents implements INode {
} }
// Replace the text with a footnote // 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 returnVal += message_content.value

View File

@ -20,7 +20,7 @@ class OpenAIFunctionAgent_Agents implements INode {
constructor() { constructor() {
this.label = 'OpenAI Function Agent' this.label = 'OpenAI Function Agent'
this.name = 'openAIFunctionAgent' this.name = 'openAIFunctionAgent'
this.version = 2.0 this.version = 3.0
this.type = 'AgentExecutor' this.type = 'AgentExecutor'
this.category = 'Agents' this.category = 'Agents'
this.icon = 'openai.png' this.icon = 'openai.png'
@ -41,7 +41,7 @@ class OpenAIFunctionAgent_Agents implements INode {
{ {
label: 'OpenAI/Azure Chat Model', label: 'OpenAI/Azure Chat Model',
name: 'model', name: 'model',
type: 'ChatOpenAI | AzureChatOpenAI' type: 'BaseChatModel'
}, },
{ {
label: 'System Message', label: 'System Message',

View File

@ -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 { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb'
import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
@ -70,6 +79,8 @@ class DynamoDb_Memory implements INode {
return initalizeDynamoDB(nodeData, options) return initalizeDynamoDB(nodeData, options)
} }
//@ts-ignore
memoryMethods = {
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
const dynamodbMemory = await initalizeDynamoDB(nodeData, options) const dynamodbMemory = await initalizeDynamoDB(nodeData, options)
const sessionId = nodeData.inputs?.sessionId as string const sessionId = nodeData.inputs?.sessionId as string
@ -77,6 +88,14 @@ class DynamoDb_Memory implements INode {
options.logger.info(`Clearing DynamoDb memory session ${sessionId ? sessionId : chatId}`) options.logger.info(`Clearing DynamoDb memory session ${sessionId ? sessionId : chatId}`)
await dynamodbMemory.clear() await dynamodbMemory.clear()
options.logger.info(`Successfully cleared DynamoDb memory session ${sessionId ? sessionId : chatId}`) options.logger.info(`Successfully cleared DynamoDb memory session ${sessionId ? sessionId : chatId}`)
},
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
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])
}
} }
} }

View File

@ -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 { MongoDBChatMessageHistory } from 'langchain/stores/message/mongodb'
import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
import { BaseMessage, mapStoredMessageToChatMessage } from 'langchain/schema' import { BaseMessage, mapStoredMessageToChatMessage } from 'langchain/schema'
@ -67,6 +76,8 @@ class MongoDB_Memory implements INode {
return initializeMongoDB(nodeData, options) return initializeMongoDB(nodeData, options)
} }
//@ts-ignore
memoryMethods = {
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
const mongodbMemory = await initializeMongoDB(nodeData, options) const mongodbMemory = await initializeMongoDB(nodeData, options)
const sessionId = nodeData.inputs?.sessionId as string const sessionId = nodeData.inputs?.sessionId as string
@ -74,6 +85,14 @@ class MongoDB_Memory implements INode {
options.logger.info(`Clearing MongoDB memory session ${sessionId ? sessionId : chatId}`) options.logger.info(`Clearing MongoDB memory session ${sessionId ? sessionId : chatId}`)
await mongodbMemory.clear() await mongodbMemory.clear()
options.logger.info(`Successfully cleared MongoDB memory session ${sessionId ? sessionId : chatId}`) options.logger.info(`Successfully cleared MongoDB memory session ${sessionId ? sessionId : chatId}`)
},
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
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])
}
} }
} }

View File

@ -3,6 +3,7 @@ import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../
import { ICommonObject } from '../../../src' import { ICommonObject } from '../../../src'
import { MotorheadMemory, MotorheadMemoryInput } from 'langchain/memory' import { MotorheadMemory, MotorheadMemoryInput } from 'langchain/memory'
import fetch from 'node-fetch' import fetch from 'node-fetch'
import { getBufferString } from 'langchain/memory'
class MotorMemory_Memory implements INode { class MotorMemory_Memory implements INode {
label: string label: string
@ -64,6 +65,8 @@ class MotorMemory_Memory implements INode {
return initalizeMotorhead(nodeData, options) return initalizeMotorhead(nodeData, options)
} }
//@ts-ignore
memoryMethods = {
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
const motorhead = await initalizeMotorhead(nodeData, options) const motorhead = await initalizeMotorhead(nodeData, options)
const sessionId = nodeData.inputs?.sessionId as string const sessionId = nodeData.inputs?.sessionId as string
@ -71,6 +74,14 @@ class MotorMemory_Memory implements INode {
options.logger.info(`Clearing Motorhead memory session ${sessionId ? sessionId : chatId}`) options.logger.info(`Clearing Motorhead memory session ${sessionId ? sessionId : chatId}`)
await motorhead.clear() await motorhead.clear()
options.logger.info(`Successfully cleared Motorhead memory session ${sessionId ? sessionId : chatId}`) options.logger.info(`Successfully cleared Motorhead memory session ${sessionId ? sessionId : chatId}`)
},
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
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])
}
} }
} }

View File

@ -1,5 +1,5 @@
import { INode, INodeData, INodeParams, ICommonObject } from '../../../src/Interface' 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 { BufferMemory, BufferMemoryInput } from 'langchain/memory'
import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/ioredis' import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/ioredis'
import { mapStoredMessageToChatMessage, BaseMessage } from 'langchain/schema' import { mapStoredMessageToChatMessage, BaseMessage } from 'langchain/schema'
@ -65,6 +65,8 @@ class RedisBackedChatMemory_Memory implements INode {
return await initalizeRedis(nodeData, options) return await initalizeRedis(nodeData, options)
} }
//@ts-ignore
memoryMethods = {
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
const redis = await initalizeRedis(nodeData, options) const redis = await initalizeRedis(nodeData, options)
const sessionId = nodeData.inputs?.sessionId as string const sessionId = nodeData.inputs?.sessionId as string
@ -72,6 +74,14 @@ class RedisBackedChatMemory_Memory implements INode {
options.logger.info(`Clearing Redis memory session ${sessionId ? sessionId : chatId}`) options.logger.info(`Clearing Redis memory session ${sessionId ? sessionId : chatId}`)
await redis.clear() await redis.clear()
options.logger.info(`Successfully cleared Redis memory session ${sessionId ? sessionId : chatId}`) options.logger.info(`Successfully cleared Redis memory session ${sessionId ? sessionId : chatId}`)
},
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
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])
}
} }
} }

View File

@ -1,5 +1,5 @@
import { INode, INodeData, INodeParams } from '../../../src/Interface' 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 { ICommonObject } from '../../../src'
import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
import { UpstashRedisChatMessageHistory } from 'langchain/stores/message/upstash_redis' import { UpstashRedisChatMessageHistory } from 'langchain/stores/message/upstash_redis'
@ -63,6 +63,8 @@ class UpstashRedisBackedChatMemory_Memory implements INode {
return initalizeUpstashRedis(nodeData, options) return initalizeUpstashRedis(nodeData, options)
} }
//@ts-ignore
memoryMethods = {
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
const redis = await initalizeUpstashRedis(nodeData, options) const redis = await initalizeUpstashRedis(nodeData, options)
const sessionId = nodeData.inputs?.sessionId as string const sessionId = nodeData.inputs?.sessionId as string
@ -70,6 +72,13 @@ class UpstashRedisBackedChatMemory_Memory implements INode {
options.logger.info(`Clearing Upstash Redis memory session ${sessionId ? sessionId : chatId}`) options.logger.info(`Clearing Upstash Redis memory session ${sessionId ? sessionId : chatId}`)
await redis.clear() await redis.clear()
options.logger.info(`Successfully cleared Upstash Redis memory session ${sessionId ? sessionId : chatId}`) options.logger.info(`Successfully cleared Upstash Redis memory session ${sessionId ? sessionId : chatId}`)
},
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
const redis = await initalizeUpstashRedis(nodeData, options)
const key = 'chat_history'
const memoryResult = await redis.loadMemoryVariables({})
return serializeChatHistory(memoryResult[key])
}
} }
} }

View File

@ -3,6 +3,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep'
import { ICommonObject } from '../../../src' import { ICommonObject } from '../../../src'
import { getBufferString } from 'langchain/memory'
class ZepMemory_Memory implements INode { class ZepMemory_Memory implements INode {
label: string label: string
@ -140,6 +141,8 @@ class ZepMemory_Memory implements INode {
return zep return zep
} }
//@ts-ignore
memoryMethods = {
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
const zep = await initalizeZep(nodeData, options) const zep = await initalizeZep(nodeData, options)
const sessionId = nodeData.inputs?.sessionId as string const sessionId = nodeData.inputs?.sessionId as string
@ -147,6 +150,16 @@ class ZepMemory_Memory implements INode {
options.logger.info(`Clearing Zep memory session ${sessionId ? sessionId : chatId}`) options.logger.info(`Clearing Zep memory session ${sessionId ? sessionId : chatId}`)
await zep.clear() await zep.clear()
options.logger.info(`Successfully cleared Zep memory session ${sessionId ? sessionId : chatId}`) options.logger.info(`Successfully cleared Zep memory session ${sessionId ? sessionId : chatId}`)
},
async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise<string> {
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)
}
} }
} }

View File

@ -1,5 +1,6 @@
import { DynamicTool, DynamicToolInput } from 'langchain/tools' import { DynamicTool, DynamicToolInput } from 'langchain/tools'
import { BaseChain } from 'langchain/chains' import { BaseChain } from 'langchain/chains'
import { handleEscapeCharacters } from '../../../src/utils'
export interface ChainToolInput extends Omit<DynamicToolInput, 'func'> { export interface ChainToolInput extends Omit<DynamicToolInput, 'func'> {
chain: BaseChain chain: BaseChain
@ -14,7 +15,8 @@ export class ChainTool extends DynamicTool {
func: async (input, runManager) => { func: async (input, runManager) => {
// To enable LLM Chain which has promptValues // To enable LLM Chain which has promptValues
if ((chain as any).prompt && (chain as any).prompt.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 values?.text
} }
return chain.run(input, runManager?.getChild()) return chain.run(input, runManager?.getChild())

View File

@ -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<any> {
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const zapierNLAApiKey = getCredentialParam('zapierNLAApiKey', credentialData, nodeData)
const obj: Partial<ZapierNLAWrapperParams> = {
apiKey: zapierNLAApiKey
}
const zapier = new ZapierNLAWrapper(obj)
const toolkit = await ZapierToolKit.fromZapierNLAWrapper(zapier)
return toolkit.tools
}
}
module.exports = { nodeClass: ZapierNLA_Tools }

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<path d="M128.080089,-0.000183105 C135.311053,0.0131003068 142.422517,0.624138494 149.335663,1.77979593 L149.335663,1.77979593 L149.335663,76.2997796 L202.166953,23.6044907 C208.002065,27.7488446 213.460883,32.3582023 218.507811,37.3926715 C223.557281,42.4271407 228.192318,47.8867213 232.346817,53.7047992 L232.346817,53.7047992 L179.512985,106.400063 L254.227854,106.400063 C255.387249,113.29414 256,120.36111 256,127.587243 L256,127.587243 L256,127.759881 C256,134.986013 255.387249,142.066204 254.227854,148.960282 L254.227854,148.960282 L179.500273,148.960282 L232.346817,201.642324 C228.192318,207.460402 223.557281,212.919983 218.523066,217.954452 L218.523066,217.954452 L218.507811,217.954452 C213.460883,222.988921 208.002065,227.6115 202.182208,231.742607 L202.182208,231.742607 L149.335663,179.04709 L149.335663,253.5672 C142.435229,254.723036 135.323765,255.333244 128.092802,255.348499 L128.092802,255.348499 L127.907197,255.348499 C120.673691,255.333244 113.590195,254.723036 106.677048,253.5672 L106.677048,253.5672 L106.677048,179.04709 L53.8457596,231.742607 C42.1780766,223.466917 31.977435,213.278734 23.6658953,201.642324 L23.6658953,201.642324 L76.4997269,148.960282 L1.78485803,148.960282 C0.612750404,142.052729 0,134.946095 0,127.719963 L0,127.719963 L0,127.349037 C0.0121454869,125.473817 0.134939797,123.182933 0.311311815,120.812834 L0.36577283,120.099764 C0.887996182,113.428547 1.78485803,106.400063 1.78485803,106.400063 L1.78485803,106.400063 L76.4997269,106.400063 L23.6658953,53.7047992 C27.8076812,47.8867213 32.4300059,42.4403618 37.4769335,37.4193681 L37.4769335,37.4193681 L37.5023588,37.3926715 C42.5391163,32.3582023 48.0106469,27.7488446 53.8457596,23.6044907 L53.8457596,23.6044907 L106.677048,76.2997796 L106.677048,1.77979593 C113.590195,0.624138494 120.688946,0.0131003068 127.932622,-0.000183105 L127.932622,-0.000183105 L128.080089,-0.000183105 Z M128.067377,95.7600714 L127.945335,95.7600714 C118.436262,95.7600714 109.32891,97.5001809 100.910584,100.661566 C97.7553011,109.043534 96.0085811,118.129275 95.9958684,127.613685 L95.9958684,127.733184 C96.0085811,137.217594 97.7553011,146.303589 100.923296,154.685303 C109.32891,157.846943 118.436262,159.587052 127.945335,159.587052 L128.067377,159.587052 C137.576449,159.587052 146.683802,157.846943 155.089415,154.685303 C158.257411,146.290368 160.004131,137.217594 160.004131,127.733184 L160.004131,127.613685 C160.004131,118.129275 158.257411,109.043534 155.089415,100.661566 C146.683802,97.5001809 137.576449,95.7600714 128.067377,95.7600714 Z" fill="#FF4A00" fill-rule="nonzero">
</path>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,6 +1,6 @@
{ {
"name": "flowise-components", "name": "flowise-components",
"version": "1.4.4", "version": "1.4.5",
"description": "Flowiseai Components", "description": "Flowiseai Components",
"main": "dist/src/index", "main": "dist/src/index",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",

View File

@ -107,9 +107,12 @@ export interface INode extends INodeProperties {
search: (nodeData: INodeData, options?: ICommonObject) => Promise<any> search: (nodeData: INodeData, options?: ICommonObject) => Promise<any>
delete: (nodeData: INodeData, options?: ICommonObject) => Promise<void> delete: (nodeData: INodeData, options?: ICommonObject) => Promise<void>
} }
memoryMethods?: {
clearSessionMemory: (nodeData: INodeData, options?: ICommonObject) => Promise<void>
getChatMessages: (nodeData: INodeData, options?: ICommonObject) => Promise<string>
}
init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise<any> init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise<any>
run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise<string | ICommonObject> run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise<string | ICommonObject>
clearSessionMemory?(nodeData: INodeData, options?: ICommonObject): Promise<void>
} }
export interface INodeData extends INodeProperties { export interface INodeData extends INodeProperties {

View File

@ -549,6 +549,18 @@ export const convertChatHistoryToText = (chatHistory: IMessage[] = []): string =
.join('\n') .join('\n')
} }
/**
* Serialize array chat history to string
* @param {IMessage[]} chatHistory
* @returns {string}
*/
export const serializeChatHistory = (chatHistory: string | Array<string>) => {
if (Array.isArray(chatHistory)) {
return chatHistory.join('\n')
}
return chatHistory
}
/** /**
* Convert schema to zod schema * Convert schema to zod schema
* @param {string | object} schema * @param {string | object} schema

View File

@ -333,8 +333,8 @@
"data": { "data": {
"id": "openAIFunctionAgent_0", "id": "openAIFunctionAgent_0",
"label": "OpenAI Function Agent", "label": "OpenAI Function Agent",
"version": 2,
"name": "openAIFunctionAgent", "name": "openAIFunctionAgent",
"version": 3,
"type": "AgentExecutor", "type": "AgentExecutor",
"baseClasses": ["AgentExecutor", "BaseChain"], "baseClasses": ["AgentExecutor", "BaseChain"],
"category": "Agents", "category": "Agents",
@ -367,8 +367,8 @@
{ {
"label": "OpenAI/Azure Chat Model", "label": "OpenAI/Azure Chat Model",
"name": "model", "name": "model",
"type": "ChatOpenAI | AzureChatOpenAI", "type": "BaseChatModel",
"id": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI" "id": "openAIFunctionAgent_0-input-model-BaseChatModel"
} }
], ],
"inputs": { "inputs": {
@ -672,9 +672,9 @@
"source": "chatOpenAI_2", "source": "chatOpenAI_2",
"sourceHandle": "chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "sourceHandle": "chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel",
"target": "openAIFunctionAgent_0", "target": "openAIFunctionAgent_0",
"targetHandle": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel",
"type": "buttonedge", "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": { "data": {
"label": "" "label": ""
} }

View File

@ -98,7 +98,7 @@
"data": { "data": {
"id": "conversationalRetrievalAgent_0", "id": "conversationalRetrievalAgent_0",
"label": "Conversational Retrieval Agent", "label": "Conversational Retrieval Agent",
"version": 2, "version": 3,
"name": "conversationalRetrievalAgent", "name": "conversationalRetrievalAgent",
"type": "AgentExecutor", "type": "AgentExecutor",
"baseClasses": ["AgentExecutor", "BaseChain", "Runnable"], "baseClasses": ["AgentExecutor", "BaseChain", "Runnable"],
@ -132,8 +132,8 @@
{ {
"label": "OpenAI/Azure Chat Model", "label": "OpenAI/Azure Chat Model",
"name": "model", "name": "model",
"type": "ChatOpenAI | AzureChatOpenAI", "type": "BaseChatModel",
"id": "conversationalRetrievalAgent_0-input-model-ChatOpenAI | AzureChatOpenAI" "id": "conversationalRetrievalAgent_0-input-model-BaseChatModel"
} }
], ],
"inputs": { "inputs": {
@ -642,9 +642,9 @@
"source": "chatOpenAI_0", "source": "chatOpenAI_0",
"sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable",
"target": "conversationalRetrievalAgent_0", "target": "conversationalRetrievalAgent_0",
"targetHandle": "conversationalRetrievalAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", "targetHandle": "conversationalRetrievalAgent_0-input-model-BaseChatModel",
"type": "buttonedge", "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": { "data": {
"label": "" "label": ""
} }

View File

@ -205,8 +205,8 @@
"data": { "data": {
"id": "openAIFunctionAgent_0", "id": "openAIFunctionAgent_0",
"label": "OpenAI Function Agent", "label": "OpenAI Function Agent",
"version": 2,
"name": "openAIFunctionAgent", "name": "openAIFunctionAgent",
"version": 3,
"type": "AgentExecutor", "type": "AgentExecutor",
"baseClasses": ["AgentExecutor", "BaseChain"], "baseClasses": ["AgentExecutor", "BaseChain"],
"category": "Agents", "category": "Agents",
@ -239,8 +239,8 @@
{ {
"label": "OpenAI/Azure Chat Model", "label": "OpenAI/Azure Chat Model",
"name": "model", "name": "model",
"type": "ChatOpenAI | AzureChatOpenAI", "type": "BaseChatModel",
"id": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI" "id": "openAIFunctionAgent_0-input-model-BaseChatModel"
} }
], ],
"inputs": { "inputs": {
@ -488,9 +488,9 @@
"source": "chatOpenAI_0", "source": "chatOpenAI_0",
"sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel",
"target": "openAIFunctionAgent_0", "target": "openAIFunctionAgent_0",
"targetHandle": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel",
"type": "buttonedge", "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": { "data": {
"label": "" "label": ""
} }

View File

@ -14,7 +14,7 @@
"data": { "data": {
"id": "openAIAssistant_0", "id": "openAIAssistant_0",
"label": "OpenAI Assistant", "label": "OpenAI Assistant",
"version": 1, "version": 2,
"name": "openAIAssistant", "name": "openAIAssistant",
"type": "OpenAIAssistant", "type": "OpenAIAssistant",
"baseClasses": ["OpenAIAssistant"], "baseClasses": ["OpenAIAssistant"],
@ -27,6 +27,15 @@
"type": "asyncOptions", "type": "asyncOptions",
"loadMethod": "listAssistants", "loadMethod": "listAssistants",
"id": "openAIAssistant_0-input-selectedAssistant-asyncOptions" "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 <a target=\"_blank\" href=\"https://platform.openai.com/docs/assistants/how-it-works/managing-threads-and-messages\">docs</a>",
"optional": true,
"additionalParams": true,
"id": "openAIAssistant_0-input-disableFileDownload-boolean"
} }
], ],
"inputAnchors": [ "inputAnchors": [

View File

@ -190,12 +190,6 @@ export interface IOverrideConfig {
type: string type: string
} }
export interface IDatabaseExport {
chatmessages: IChatMessage[]
chatflows: IChatFlow[]
apikeys: ICommonObject[]
}
export type ICredentialDataDecrypted = ICommonObject export type ICredentialDataDecrypted = ICommonObject
// Plain credential object sent to server // Plain credential object sent to server

View File

@ -17,7 +17,6 @@ import {
IReactFlowNode, IReactFlowNode,
IReactFlowObject, IReactFlowObject,
INodeData, INodeData,
IDatabaseExport,
ICredentialReturnResponse, ICredentialReturnResponse,
chatType, chatType,
IChatMessage, IChatMessage,
@ -31,18 +30,11 @@ import {
constructGraphs, constructGraphs,
resolveVariables, resolveVariables,
isStartNodeDependOnInput, isStartNodeDependOnInput,
getAPIKeys,
addAPIKey,
updateAPIKey,
deleteAPIKey,
compareKeys,
mapMimeTypeToInputField, mapMimeTypeToInputField,
findAvailableConfigs, findAvailableConfigs,
isSameOverrideConfig, isSameOverrideConfig,
replaceAllAPIKeys,
isFlowValidForStream, isFlowValidForStream,
databaseEntities, databaseEntities,
getApiKey,
transformToCredentialEntity, transformToCredentialEntity,
decryptCredentialData, decryptCredentialData,
clearAllSessionMemory, clearAllSessionMemory,
@ -50,7 +42,8 @@ import {
getEncryptionKey, getEncryptionKey,
checkMemorySessionId, checkMemorySessionId,
clearSessionMemoryFromViewMessageDialog, clearSessionMemoryFromViewMessageDialog,
getUserHome getUserHome,
replaceChatHistory
} from './utils' } from './utils'
import { cloneDeep, omit, uniqWith, isEqual } from 'lodash' import { cloneDeep, omit, uniqWith, isEqual } from 'lodash'
import { getDataSource } from './DataSource' import { getDataSource } from './DataSource'
@ -62,8 +55,9 @@ import { Tool } from './database/entities/Tool'
import { Assistant } from './database/entities/Assistant' import { Assistant } from './database/entities/Assistant'
import { ChatflowPool } from './ChatflowPool' import { ChatflowPool } from './ChatflowPool'
import { CachePool } from './CachePool' 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 { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit'
import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey'
import axios from 'axios' import axios from 'axios'
import { Client } from 'langchainhub' import { Client } from 'langchainhub'
import { parsePrompt } from './utils/hub' 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 // Upsert
// ---------------------------------------- // ----------------------------------------
@ -1377,14 +1320,14 @@ export class App {
* @param {IReactFlowEdge[]} edges * @param {IReactFlowEdge[]} edges
* @returns {string | undefined} * @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 memoryNodes = nodes.filter((node) => node.data.category === 'Memory')
const memoryNodeIds = memoryNodes.map((mem) => mem.data.id) const memoryNodeIds = memoryNodes.map((mem) => mem.data.id)
for (const edge of edges) { for (const edge of edges) {
if (memoryNodeIds.includes(edge.source)) { if (memoryNodeIds.includes(edge.source)) {
const memoryNode = nodes.find((node) => node.data.id === edge.source) const memoryNode = nodes.find((node) => node.data.id === edge.source)
return memoryNode ? memoryNode.data.label : undefined return memoryNode
} }
} }
return undefined return undefined
@ -1505,6 +1448,19 @@ export class App {
isStreamValid = isFlowValidForStream(nodes, endingNodeData) 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 ***/ /*** Get Starting Nodes with Non-Directed Graph ***/
const constructedObj = constructGraphs(nodes, edges, true) const constructedObj = constructGraphs(nodes, edges, true)
const nonDirectedGraph = constructedObj.graph const nonDirectedGraph = constructedObj.graph
@ -1519,7 +1475,7 @@ export class App {
depthQueue, depthQueue,
this.nodesPool.componentNodes, this.nodesPool.componentNodes,
incomingInput.question, incomingInput.question,
incomingInput.history, chatHistory,
chatId, chatId,
chatflowid, chatflowid,
this.AppDataSource, this.AppDataSource,
@ -1539,7 +1495,7 @@ export class App {
nodeToExecute.data, nodeToExecute.data,
reactFlowNodes, reactFlowNodes,
incomingInput.question, incomingInput.question,
incomingInput.history chatHistory
) )
nodeToExecuteData = reactFlowNodeData nodeToExecuteData = reactFlowNodeData
@ -1556,11 +1512,17 @@ export class App {
let sessionId = undefined let sessionId = undefined
if (nodeToExecuteData.instance) sessionId = checkMemorySessionId(nodeToExecuteData.instance, chatId) 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 let result = isStreamValid
? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
chatHistory: incomingInput.history, chatHistory,
socketIO, socketIO,
socketIOClientId: incomingInput.socketIOClientId, socketIOClientId: incomingInput.socketIOClientId,
logger, logger,
@ -1570,7 +1532,7 @@ export class App {
chatId chatId
}) })
: await nodeInstance.run(nodeToExecuteData, incomingInput.question, { : await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
chatHistory: incomingInput.history, chatHistory,
logger, logger,
appDataSource: this.AppDataSource, appDataSource: this.AppDataSource,
databaseEntities, databaseEntities,

View File

@ -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<ICommonObject[]>}
*/
export const getAPIKeys = async (): Promise<ICommonObject[]> => {
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<ICommonObject[]>}
*/
export const addAPIKey = async (keyName: string): Promise<ICommonObject[]> => {
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<ICommonObject[]>}
*/
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<ICommonObject[]>}
*/
export const updateAPIKey = async (keyIdToUpdate: string, newKeyName: string): Promise<ICommonObject[]> => {
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<ICommonObject[]>}
*/
export const deleteAPIKey = async (keyIdToDelete: string): Promise<ICommonObject[]> => {
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<void>}
*/
export const replaceAllAPIKeys = async (content: ICommonObject[]): Promise<void> => {
try {
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8')
} catch (error) {
logger.error(error)
}
}

View File

@ -1,33 +1,33 @@
import path from 'path' import path from 'path'
import fs from 'fs' import fs from 'fs'
import moment from 'moment'
import logger from './logger' import logger from './logger'
import { import {
IComponentCredentials,
IComponentNodes, IComponentNodes,
ICredentialDataDecrypted,
ICredentialReqBody,
IDepthQueue, IDepthQueue,
IExploredNode, IExploredNode,
INodeData,
INodeDependencies, INodeDependencies,
INodeDirectedGraph, INodeDirectedGraph,
INodeQueue, INodeQueue,
IOverrideConfig,
IReactFlowEdge, IReactFlowEdge,
IReactFlowNode, IReactFlowNode,
IVariableDict, IVariableDict,
INodeData, IncomingInput
IOverrideConfig,
ICredentialDataDecrypted,
IComponentCredentials,
ICredentialReqBody
} from '../Interface' } from '../Interface'
import { cloneDeep, get, isEqual } from 'lodash' import { cloneDeep, get, isEqual } from 'lodash'
import { import {
ICommonObject, convertChatHistoryToText,
getInputVariables, getInputVariables,
IDatabaseEntity,
handleEscapeCharacters, handleEscapeCharacters,
IMessage, ICommonObject,
convertChatHistoryToText IDatabaseEntity,
IMessage
} from 'flowise-components' } from 'flowise-components'
import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' import { randomBytes } from 'crypto'
import { AES, enc } from 'crypto-js' import { AES, enc } from 'crypto-js'
import { ChatFlow } from '../database/entities/ChatFlow' import { ChatFlow } from '../database/entities/ChatFlow'
@ -217,7 +217,7 @@ export const buildLangchain = async (
depthQueue: IDepthQueue, depthQueue: IDepthQueue,
componentNodes: IComponentNodes, componentNodes: IComponentNodes,
question: string, question: string,
chatHistory: IMessage[], chatHistory: IMessage[] | string,
chatId: string, chatId: string,
chatflowid: string, chatflowid: string,
appDataSource: DataSource, appDataSource: DataSource,
@ -348,8 +348,8 @@ export const clearAllSessionMemory = async (
node.data.inputs.sessionId = sessionId node.data.inputs.sessionId = sessionId
} }
if (newNodeInstance.clearSessionMemory) { if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) {
await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) 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 (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId
if (newNodeInstance.clearSessionMemory) { if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) {
await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) await newNodeInstance.memoryMethods.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger })
return return
} }
} }
@ -400,7 +400,7 @@ export const getVariableValue = (
paramValue: string, paramValue: string,
reactFlowNodes: IReactFlowNode[], reactFlowNodes: IReactFlowNode[],
question: string, question: string,
chatHistory: IMessage[], chatHistory: IMessage[] | string,
isAcceptVariable = false isAcceptVariable = false
) => { ) => {
let returnVal = paramValue let returnVal = paramValue
@ -433,7 +433,10 @@ export const getVariableValue = (
} }
if (isAcceptVariable && variableFullPath === CHAT_HISTORY_VAR_PREFIX) { 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 // Split by first occurrence of '.' to get just nodeId
@ -476,7 +479,7 @@ export const resolveVariables = (
reactFlowNodeData: INodeData, reactFlowNodeData: INodeData,
reactFlowNodes: IReactFlowNode[], reactFlowNodes: IReactFlowNode[],
question: string, question: string,
chatHistory: IMessage[] chatHistory: IMessage[] | string
): INodeData => { ): INodeData => {
let flowNodeData = cloneDeep(reactFlowNodeData) let flowNodeData = cloneDeep(reactFlowNodeData)
const types = 'inputs' const types = 'inputs'
@ -593,147 +596,6 @@ export const isSameOverrideConfig = (
return false 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<ICommonObject[]>}
*/
export const getAPIKeys = async (): Promise<ICommonObject[]> => {
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<ICommonObject[]>}
*/
export const addAPIKey = async (keyName: string): Promise<ICommonObject[]> => {
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<ICommonObject[]>}
*/
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<ICommonObject[]>}
*/
export const updateAPIKey = async (keyIdToUpdate: string, newKeyName: string): Promise<ICommonObject[]> => {
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<ICommonObject[]>}
*/
export const deleteAPIKey = async (keyIdToDelete: string): Promise<ICommonObject[]> => {
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<void>}
*/
export const replaceAllAPIKeys = async (content: ICommonObject[]): Promise<void> => {
try {
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8')
} catch (error) {
logger.error(error)
}
}
/** /**
* Map MimeType to InputField * Map MimeType to InputField
* @param {string} mimeType * @param {string} mimeType
@ -1015,3 +877,39 @@ export const checkMemorySessionId = (instance: any, chatId: string): string | un
return instance.memory.chatHistory.sessionId return instance.memory.chatHistory.sessionId
return undefined 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<string> => {
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 ''
}

View File

@ -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
}

View File

@ -1,7 +1,6 @@
import { useState, useRef, useEffect } from 'react' import { useState, useRef, useEffect } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { useSelector, useDispatch } from 'react-redux' import { useSelector } from 'react-redux'
import { useNavigate } from 'react-router-dom'
// material-ui // material-ui
import { useTheme } from '@mui/material/styles' import { useTheme } from '@mui/material/styles'
@ -26,16 +25,10 @@ import PerfectScrollbar from 'react-perfect-scrollbar'
// project imports // project imports
import MainCard from 'ui-component/cards/MainCard' import MainCard from 'ui-component/cards/MainCard'
import Transitions from 'ui-component/extended/Transitions' import Transitions from 'ui-component/extended/Transitions'
import { BackdropLoader } from 'ui-component/loading/BackdropLoader'
import AboutDialog from 'ui-component/dialog/AboutDialog' import AboutDialog from 'ui-component/dialog/AboutDialog'
// assets // assets
import { IconLogout, IconSettings, IconFileExport, IconFileDownload, IconInfoCircle } from '@tabler/icons' import { IconLogout, IconSettings, IconInfoCircle } from '@tabler/icons'
// API
import databaseApi from 'api/database'
import { SET_MENU } from 'store/actions'
import './index.css' import './index.css'
@ -43,17 +36,13 @@ import './index.css'
const ProfileSection = ({ username, handleLogout }) => { const ProfileSection = ({ username, handleLogout }) => {
const theme = useTheme() const theme = useTheme()
const dispatch = useDispatch()
const navigate = useNavigate()
const customization = useSelector((state) => state.customization) const customization = useSelector((state) => state.customization)
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [loading, setLoading] = useState(false)
const [aboutDialogOpen, setAboutDialogOpen] = useState(false) const [aboutDialogOpen, setAboutDialogOpen] = useState(false)
const anchorRef = useRef(null) const anchorRef = useRef(null)
const uploadRef = useRef(null)
const handleClose = (event) => { const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) { if (anchorRef.current && anchorRef.current.contains(event.target)) {
@ -66,56 +55,6 @@ const ProfileSection = ({ username, handleLogout }) => {
setOpen((prevOpen) => !prevOpen) 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) const prevOpen = useRef(open)
useEffect(() => { useEffect(() => {
if (prevOpen.current === true && open === false) { if (prevOpen.current === true && open === false) {
@ -196,27 +135,6 @@ const ProfileSection = ({ username, handleLogout }) => {
} }
}} }}
> >
<ListItemButton
sx={{ borderRadius: `${customization.borderRadius}px` }}
onClick={() => {
setOpen(false)
uploadRef.current.click()
}}
>
<ListItemIcon>
<IconFileDownload stroke={1.5} size='1.3rem' />
</ListItemIcon>
<ListItemText primary={<Typography variant='body2'>Load Database</Typography>} />
</ListItemButton>
<ListItemButton
sx={{ borderRadius: `${customization.borderRadius}px` }}
onClick={handleExportDB}
>
<ListItemIcon>
<IconFileExport stroke={1.5} size='1.3rem' />
</ListItemIcon>
<ListItemText primary={<Typography variant='body2'>Export Database</Typography>} />
</ListItemButton>
<ListItemButton <ListItemButton
sx={{ borderRadius: `${customization.borderRadius}px` }} sx={{ borderRadius: `${customization.borderRadius}px` }}
onClick={() => { onClick={() => {
@ -249,8 +167,6 @@ const ProfileSection = ({ username, handleLogout }) => {
</Transitions> </Transitions>
)} )}
</Popper> </Popper>
<input ref={uploadRef} type='file' hidden accept='.json' onChange={(e) => handleFileUpload(e)} />
<BackdropLoader open={loading} />
<AboutDialog show={aboutDialogOpen} onCancel={() => setAboutDialogOpen(false)} /> <AboutDialog show={aboutDialogOpen} onCancel={() => setAboutDialogOpen(false)} />
</> </>
) )