add credentials
This commit is contained in:
parent
aee0a51f73
commit
413d654493
|
|
@ -1,6 +1,6 @@
|
|||
import { OpenAIBaseInput } from 'langchain/dist/types/openai-types'
|
||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { AzureOpenAIInput, ChatOpenAI } from 'langchain/chat_models/openai'
|
||||
|
||||
class AzureChatOpenAI_ChatModels implements INode {
|
||||
|
|
@ -11,6 +11,7 @@ class AzureChatOpenAI_ChatModels implements INode {
|
|||
category: string
|
||||
description: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
|
|
@ -21,12 +22,13 @@ class AzureChatOpenAI_ChatModels implements INode {
|
|||
this.category = 'Chat Models'
|
||||
this.description = 'Wrapper around Azure OpenAI large language models that use the Chat endpoint'
|
||||
this.baseClasses = [this.type, ...getBaseClasses(ChatOpenAI)]
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['azureOpenAIApi']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Azure OpenAI Api Key',
|
||||
name: 'azureOpenAIApiKey',
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
label: 'Model Name',
|
||||
name: 'modelName',
|
||||
|
|
@ -59,26 +61,6 @@ class AzureChatOpenAI_ChatModels implements INode {
|
|||
default: 0.9,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Azure OpenAI Api Instance Name',
|
||||
name: 'azureOpenAIApiInstanceName',
|
||||
type: 'string',
|
||||
placeholder: 'YOUR-INSTANCE-NAME'
|
||||
},
|
||||
{
|
||||
label: 'Azure OpenAI Api Deployment Name',
|
||||
name: 'azureOpenAIApiDeploymentName',
|
||||
type: 'string',
|
||||
placeholder: 'YOUR-DEPLOYMENT-NAME'
|
||||
},
|
||||
{
|
||||
label: 'Azure OpenAI Api Version',
|
||||
name: 'azureOpenAIApiVersion',
|
||||
type: 'string',
|
||||
placeholder: '2023-06-01-preview',
|
||||
description:
|
||||
'Description of Supported API Versions. Please refer <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cognitive-services/openai/reference#chat-completions">examples</a>'
|
||||
},
|
||||
{
|
||||
label: 'Max Tokens',
|
||||
name: 'maxTokens',
|
||||
|
|
@ -110,19 +92,21 @@ class AzureChatOpenAI_ChatModels implements INode {
|
|||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData): Promise<any> {
|
||||
const azureOpenAIApiKey = nodeData.inputs?.azureOpenAIApiKey as string
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const modelName = nodeData.inputs?.modelName as string
|
||||
const temperature = nodeData.inputs?.temperature as string
|
||||
const azureOpenAIApiInstanceName = nodeData.inputs?.azureOpenAIApiInstanceName as string
|
||||
const azureOpenAIApiDeploymentName = nodeData.inputs?.azureOpenAIApiDeploymentName as string
|
||||
const azureOpenAIApiVersion = nodeData.inputs?.azureOpenAIApiVersion as string
|
||||
const maxTokens = nodeData.inputs?.maxTokens as string
|
||||
const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string
|
||||
const presencePenalty = nodeData.inputs?.presencePenalty as string
|
||||
const timeout = nodeData.inputs?.timeout as string
|
||||
const streaming = nodeData.inputs?.streaming as boolean
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const azureOpenAIApiKey = getCredentialParam('azureOpenAIApiKey', credentialData, nodeData)
|
||||
const azureOpenAIApiInstanceName = getCredentialParam('azureOpenAIApiInstanceName', credentialData, nodeData)
|
||||
const azureOpenAIApiDeploymentName = getCredentialParam('azureOpenAIApiDeploymentName', credentialData, nodeData)
|
||||
const azureOpenAIApiVersion = getCredentialParam('azureOpenAIApiVersion', credentialData, nodeData)
|
||||
|
||||
const obj: Partial<AzureOpenAIInput> & Partial<OpenAIBaseInput> = {
|
||||
temperature: parseFloat(temperature),
|
||||
modelName,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
import { INodeParams, INodeCredential } from '../../../src/Interface'
|
||||
|
||||
class AzureOpenAIApi implements INodeCredential {
|
||||
label: string
|
||||
name: string
|
||||
description: string
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Azure OpenAI API'
|
||||
this.name = 'azureOpenAIApi'
|
||||
this.description =
|
||||
'Refer to <a target="_blank" href="https://azure.microsoft.com/en-us/products/cognitive-services/openai-service">official guide</a> of how to use Azure OpenAI service'
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Azure OpenAI Api Key',
|
||||
name: 'azureOpenAIApiKey',
|
||||
type: 'password',
|
||||
description: `Refer to <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cognitive-services/openai/quickstart?tabs=command-line&pivots=rest-api#set-up">official guide</a> on how to create API key on Azure OpenAI`
|
||||
},
|
||||
{
|
||||
label: 'Azure OpenAI Api Instance Name',
|
||||
name: 'azureOpenAIApiInstanceName',
|
||||
type: 'string',
|
||||
placeholder: 'YOUR-INSTANCE-NAME'
|
||||
},
|
||||
{
|
||||
label: 'Azure OpenAI Api Deployment Name',
|
||||
name: 'azureOpenAIApiDeploymentName',
|
||||
type: 'string',
|
||||
placeholder: 'YOUR-DEPLOYMENT-NAME'
|
||||
},
|
||||
{
|
||||
label: 'Azure OpenAI Api Version',
|
||||
name: 'azureOpenAIApiVersion',
|
||||
type: 'string',
|
||||
placeholder: '2023-06-01-preview',
|
||||
description:
|
||||
'Description of Supported API Versions. Please refer <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cognitive-services/openai/reference#chat-completions">examples</a>'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { credClass: AzureOpenAIApi }
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { INodeParams, INodeCredential } from '../../../src/Interface'
|
||||
|
||||
class AnthropicApi implements INodeCredential {
|
||||
label: string
|
||||
name: string
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Anthropic API'
|
||||
this.name = 'anthropicApi'
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Anthropic Api Key',
|
||||
name: 'anthropicApiKey',
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { credClass: AnthropicApi }
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { AnthropicInput, ChatAnthropic } from 'langchain/chat_models/anthropic'
|
||||
|
||||
class ChatAnthropic_ChatModels implements INode {
|
||||
|
|
@ -10,6 +10,7 @@ class ChatAnthropic_ChatModels implements INode {
|
|||
category: string
|
||||
description: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
|
|
@ -20,12 +21,13 @@ class ChatAnthropic_ChatModels implements INode {
|
|||
this.category = 'Chat Models'
|
||||
this.description = 'Wrapper around ChatAnthropic large language models that use the Chat endpoint'
|
||||
this.baseClasses = [this.type, ...getBaseClasses(ChatAnthropic)]
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['anthropicApi']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'ChatAnthropic Api Key',
|
||||
name: 'anthropicApiKey',
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
label: 'Model Name',
|
||||
name: 'modelName',
|
||||
|
|
@ -110,15 +112,17 @@ class ChatAnthropic_ChatModels implements INode {
|
|||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData): Promise<any> {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const temperature = nodeData.inputs?.temperature as string
|
||||
const modelName = nodeData.inputs?.modelName as string
|
||||
const anthropicApiKey = nodeData.inputs?.anthropicApiKey as string
|
||||
const maxTokensToSample = nodeData.inputs?.maxTokensToSample as string
|
||||
const topP = nodeData.inputs?.topP as string
|
||||
const topK = nodeData.inputs?.topK as string
|
||||
const streaming = nodeData.inputs?.streaming as boolean
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const anthropicApiKey = getCredentialParam('anthropicApiKey', credentialData, nodeData)
|
||||
|
||||
const obj: Partial<AnthropicInput> & { anthropicApiKey?: string } = {
|
||||
temperature: parseFloat(temperature),
|
||||
modelName,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { ChatOpenAI, OpenAIChatInput } from 'langchain/chat_models/openai'
|
||||
|
||||
class ChatOpenAI_ChatModels implements INode {
|
||||
|
|
@ -10,6 +10,7 @@ class ChatOpenAI_ChatModels implements INode {
|
|||
category: string
|
||||
description: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
|
|
@ -20,12 +21,13 @@ class ChatOpenAI_ChatModels implements INode {
|
|||
this.category = 'Chat Models'
|
||||
this.description = 'Wrapper around OpenAI large language models that use the Chat endpoint'
|
||||
this.baseClasses = [this.type, ...getBaseClasses(ChatOpenAI)]
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['openAIApi']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'OpenAI Api Key',
|
||||
name: 'openAIApiKey',
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
label: 'Model Name',
|
||||
name: 'modelName',
|
||||
|
|
@ -119,10 +121,9 @@ class ChatOpenAI_ChatModels implements INode {
|
|||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData): Promise<any> {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const temperature = nodeData.inputs?.temperature as string
|
||||
const modelName = nodeData.inputs?.modelName as string
|
||||
const openAIApiKey = nodeData.inputs?.openAIApiKey as string
|
||||
const maxTokens = nodeData.inputs?.maxTokens as string
|
||||
const topP = nodeData.inputs?.topP as string
|
||||
const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string
|
||||
|
|
@ -131,6 +132,9 @@ class ChatOpenAI_ChatModels implements INode {
|
|||
const streaming = nodeData.inputs?.streaming as boolean
|
||||
const basePath = nodeData.inputs?.basepath as string
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData)
|
||||
|
||||
const obj: Partial<OpenAIChatInput> & { openAIApiKey?: string } = {
|
||||
temperature: parseFloat(temperature),
|
||||
modelName,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { INodeParams, INodeCredential } from '../../../src/Interface'
|
||||
|
||||
class OpenAIApi implements INodeCredential {
|
||||
label: string
|
||||
name: string
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'OpenAI API'
|
||||
this.name = 'openAIApi'
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'OpenAI Api Key',
|
||||
name: 'openAIApiKey',
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { credClass: OpenAIApi }
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { OpenAI, OpenAIInput } from 'langchain/llms/openai'
|
||||
|
||||
class OpenAI_LLMs implements INode {
|
||||
|
|
@ -10,6 +10,7 @@ class OpenAI_LLMs implements INode {
|
|||
category: string
|
||||
description: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
|
|
@ -20,12 +21,13 @@ class OpenAI_LLMs implements INode {
|
|||
this.category = 'LLMs'
|
||||
this.description = 'Wrapper around OpenAI large language models'
|
||||
this.baseClasses = [this.type, ...getBaseClasses(OpenAI)]
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['openAIApi']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'OpenAI Api Key',
|
||||
name: 'openAIApiKey',
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
label: 'Model Name',
|
||||
name: 'modelName',
|
||||
|
|
@ -117,10 +119,9 @@ class OpenAI_LLMs implements INode {
|
|||
]
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData): Promise<any> {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const temperature = nodeData.inputs?.temperature as string
|
||||
const modelName = nodeData.inputs?.modelName as string
|
||||
const openAIApiKey = nodeData.inputs?.openAIApiKey as string
|
||||
const maxTokens = nodeData.inputs?.maxTokens as string
|
||||
const topP = nodeData.inputs?.topP as string
|
||||
const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string
|
||||
|
|
@ -131,6 +132,9 @@ class OpenAI_LLMs implements INode {
|
|||
const streaming = nodeData.inputs?.streaming as boolean
|
||||
const basePath = nodeData.inputs?.basepath as string
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData)
|
||||
|
||||
const obj: Partial<OpenAIInput> & { openAIApiKey?: string } = {
|
||||
temperature: parseFloat(temperature),
|
||||
modelName,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { ICommonObject } from '../../../src'
|
||||
import { MotorheadMemory, MotorheadMemoryInput } from 'langchain/memory'
|
||||
|
||||
|
|
@ -11,6 +11,7 @@ class MotorMemory_Memory implements INode {
|
|||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
|
|
@ -21,6 +22,14 @@ class MotorMemory_Memory implements INode {
|
|||
this.category = 'Memory'
|
||||
this.description = 'Remembers previous conversational back and forths directly'
|
||||
this.baseClasses = [this.type, ...getBaseClasses(MotorheadMemory)]
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
optional: true,
|
||||
description: 'Only needed when using hosted solution - https://getmetal.io',
|
||||
credentialNames: ['motorheadMemoryApi']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Base URL',
|
||||
|
|
@ -43,22 +52,6 @@ class MotorMemory_Memory implements INode {
|
|||
default: '',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'password',
|
||||
description: 'Only needed when using hosted solution - https://getmetal.io',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Client ID',
|
||||
name: 'clientId',
|
||||
type: 'string',
|
||||
description: 'Only needed when using hosted solution - https://getmetal.io',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -67,11 +60,13 @@ class MotorMemory_Memory implements INode {
|
|||
const memoryKey = nodeData.inputs?.memoryKey as string
|
||||
const baseURL = nodeData.inputs?.baseURL as string
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
const apiKey = nodeData.inputs?.apiKey as string
|
||||
const clientId = nodeData.inputs?.clientId as string
|
||||
|
||||
const chatId = options?.chatId as string
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
|
||||
const clientId = getCredentialParam('clientId', credentialData, nodeData)
|
||||
|
||||
let obj: MotorheadMemoryInput = {
|
||||
returnMessages: true,
|
||||
sessionId: sessionId ? sessionId : chatId,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { INodeParams, INodeCredential } from '../../../src/Interface'
|
||||
|
||||
class MotorheadMemoryApi implements INodeCredential {
|
||||
label: string
|
||||
name: string
|
||||
description: string
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Motorhead Memory API'
|
||||
this.name = 'motorheadMemoryApi'
|
||||
this.description =
|
||||
'Refer to <a target="_blank" href="https://docs.getmetal.io/misc-get-keys">official guide</a> on how to create API key and Client ID on Motorhead Memory'
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Client ID',
|
||||
name: 'clientId',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
label: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { credClass: MotorheadMemoryApi }
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { SystemMessage } from 'langchain/schema'
|
||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep'
|
||||
import { ICommonObject } from '../../../src'
|
||||
|
||||
|
|
@ -12,6 +12,7 @@ class ZepMemory_Memory implements INode {
|
|||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
|
|
@ -22,6 +23,14 @@ class ZepMemory_Memory implements INode {
|
|||
this.category = 'Memory'
|
||||
this.description = 'Summarizes the conversation and stores the memory in zep server'
|
||||
this.baseClasses = [this.type, ...getBaseClasses(ZepMemory)]
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
optional: true,
|
||||
description: 'Configure JWT authentication on your Zep instance (Optional)',
|
||||
credentialNames: ['zepMemoryApi']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Base URL',
|
||||
|
|
@ -44,18 +53,12 @@ class ZepMemory_Memory implements INode {
|
|||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'password',
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Size',
|
||||
name: 'k',
|
||||
type: 'number',
|
||||
default: '10',
|
||||
step: 1,
|
||||
description: 'Window of size k to surface the last k back-and-forths to use as memory.'
|
||||
},
|
||||
{
|
||||
|
|
@ -112,11 +115,13 @@ class ZepMemory_Memory implements INode {
|
|||
const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string
|
||||
const autoSummary = nodeData.inputs?.autoSummary as boolean
|
||||
const sessionId = nodeData.inputs?.sessionId as string
|
||||
const apiKey = nodeData.inputs?.apiKey as string
|
||||
const k = nodeData.inputs?.k as string
|
||||
|
||||
const chatId = options?.chatId as string
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
|
||||
|
||||
const obj: ZepMemoryInput = {
|
||||
baseURL,
|
||||
sessionId: sessionId ? sessionId : chatId,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import { INodeParams, INodeCredential } from '../../../src/Interface'
|
||||
|
||||
class ZepMemoryApi implements INodeCredential {
|
||||
label: string
|
||||
name: string
|
||||
description: string
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Zep Memory Api'
|
||||
this.name = 'zepMemoryApi'
|
||||
this.description =
|
||||
'Refer to <a target="_blank" href="https://docs.getzep.com/deployment/auth/">official guide</a> on how to create API key on Zep'
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { credClass: ZepMemoryApi }
|
||||
|
|
@ -59,7 +59,9 @@ export interface INodeParams {
|
|||
description?: string
|
||||
warning?: string
|
||||
options?: Array<INodeOptionsValue>
|
||||
credentialNames?: Array<string>
|
||||
optional?: boolean | INodeDisplay
|
||||
step?: number
|
||||
rows?: number
|
||||
list?: boolean
|
||||
acceptVariable?: boolean
|
||||
|
|
@ -102,10 +104,18 @@ export interface INodeData extends INodeProperties {
|
|||
id: string
|
||||
inputs?: ICommonObject
|
||||
outputs?: ICommonObject
|
||||
credential?: string
|
||||
instance?: any
|
||||
loadMethod?: string // method to load async options
|
||||
}
|
||||
|
||||
export interface INodeCredential {
|
||||
label: string
|
||||
name: string
|
||||
description?: string
|
||||
inputs?: INodeParams[]
|
||||
}
|
||||
|
||||
export interface IMessage {
|
||||
message: string
|
||||
type: MessageType
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import { JSDOM } from 'jsdom'
|
|||
import { BaseCallbackHandler } from 'langchain/callbacks'
|
||||
import { Server } from 'socket.io'
|
||||
import { ChainValues } from 'langchain/dist/schema'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { ICommonObject, IDatabaseEntity, INodeData } from './Interface'
|
||||
import { AES, enc } from 'crypto-js'
|
||||
|
||||
export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}}
|
||||
export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank
|
||||
|
|
@ -350,6 +353,89 @@ export const getEnvironmentVariable = (name: string): string | undefined => {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of encryption key
|
||||
* @returns {string}
|
||||
*/
|
||||
const getEncryptionKeyFilePath = (): string => {
|
||||
const checkPaths = [
|
||||
path.join(__dirname, '..', '..', 'server', 'encryption.key'),
|
||||
path.join(__dirname, '..', '..', '..', 'server', 'encryption.key'),
|
||||
path.join(__dirname, '..', '..', '..', '..', 'server', 'encryption.key'),
|
||||
path.join(__dirname, '..', '..', '..', '..', '..', 'server', 'encryption.key')
|
||||
]
|
||||
for (const checkPath of checkPaths) {
|
||||
if (fs.existsSync(checkPath)) {
|
||||
return checkPath
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const getEncryptionKeyPath = (): string => {
|
||||
return process.env.SECRETKEY_PATH ? path.join(process.env.SECRETKEY_PATH, 'encryption.key') : getEncryptionKeyFilePath()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encryption key
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
const getEncryptionKey = async (): Promise<string> => {
|
||||
try {
|
||||
return await fs.promises.readFile(getEncryptionKeyPath(), 'utf8')
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt credential data
|
||||
* @param {string} encryptedData
|
||||
* @param {string} componentCredentialName
|
||||
* @param {IComponentCredentials} componentCredentials
|
||||
* @returns {Promise<ICommonObject>}
|
||||
*/
|
||||
const decryptCredentialData = async (encryptedData: string): Promise<ICommonObject> => {
|
||||
const encryptKey = await getEncryptionKey()
|
||||
const decryptedData = AES.decrypt(encryptedData, encryptKey)
|
||||
try {
|
||||
return JSON.parse(decryptedData.toString(enc.Utf8))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
throw new Error('Credentials could not be decrypted.')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get credential data
|
||||
* @param {string} selectedCredentialId
|
||||
* @param {ICommonObject} options
|
||||
* @returns {Promise<ICommonObject>}
|
||||
*/
|
||||
export const getCredentialData = async (selectedCredentialId: string, options: ICommonObject): Promise<ICommonObject> => {
|
||||
const appDataSource = options.appDataSource as DataSource
|
||||
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||
|
||||
try {
|
||||
const credential = await appDataSource.getRepository(databaseEntities['Credential']).findOneBy({
|
||||
id: selectedCredentialId
|
||||
})
|
||||
|
||||
if (!credential) throw new Error(`Credential ${selectedCredentialId} not found`)
|
||||
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
|
||||
return decryptedCredentialData
|
||||
} catch (e) {
|
||||
throw new Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
export const getCredentialParam = (paramName: string, credentialData: ICommonObject, nodeData: INodeData): any => {
|
||||
return (nodeData.inputs as ICommonObject)[paramName] ?? credentialData[paramName]
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom chain handler class
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
"@oclif/core": "^1.13.10",
|
||||
"axios": "^0.27.2",
|
||||
"cors": "^2.8.5",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dotenv": "^16.0.0",
|
||||
"express": "^4.17.3",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
|
|
@ -63,6 +64,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/multer": "^1.4.7",
|
||||
"concurrently": "^7.1.0",
|
||||
"nodemon": "^2.0.15",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { DataSource } from 'typeorm'
|
|||
import { ChatFlow } from './entity/ChatFlow'
|
||||
import { ChatMessage } from './entity/ChatMessage'
|
||||
import { Tool } from './entity/Tool'
|
||||
import { Credential } from './entity/Credential'
|
||||
|
||||
export class ChildProcess {
|
||||
/**
|
||||
|
|
@ -133,7 +134,7 @@ async function initDB() {
|
|||
type: 'sqlite',
|
||||
database: path.resolve(homePath, 'database.sqlite'),
|
||||
synchronize: true,
|
||||
entities: [ChatFlow, ChatMessage, Tool],
|
||||
entities: [ChatFlow, ChatMessage, Tool, Credential],
|
||||
migrations: []
|
||||
})
|
||||
return await childAppDataSource.initialize()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import path from 'path'
|
|||
import { DataSource } from 'typeorm'
|
||||
import { ChatFlow } from './entity/ChatFlow'
|
||||
import { ChatMessage } from './entity/ChatMessage'
|
||||
import { Credential } from './entity/Credential'
|
||||
import { Tool } from './entity/Tool'
|
||||
import { getUserHome } from './utils'
|
||||
|
||||
|
|
@ -15,7 +16,7 @@ export const init = async (): Promise<void> => {
|
|||
type: 'sqlite',
|
||||
database: path.resolve(homePath, 'database.sqlite'),
|
||||
synchronize: true,
|
||||
entities: [ChatFlow, ChatMessage, Tool],
|
||||
entities: [ChatFlow, ChatMessage, Tool, Credential],
|
||||
migrations: []
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,10 +38,23 @@ export interface ITool {
|
|||
createdDate: Date
|
||||
}
|
||||
|
||||
export interface ICredential {
|
||||
id: string
|
||||
name: string
|
||||
credentialName: string
|
||||
encryptedData: string
|
||||
updatedDate: Date
|
||||
createdDate: Date
|
||||
}
|
||||
|
||||
export interface IComponentNodes {
|
||||
[key: string]: INode
|
||||
}
|
||||
|
||||
export interface IComponentCredentials {
|
||||
[key: string]: INode
|
||||
}
|
||||
|
||||
export interface IVariableDict {
|
||||
[key: string]: string
|
||||
}
|
||||
|
|
@ -167,3 +180,17 @@ export interface IChildProcessMessage {
|
|||
key: string
|
||||
value?: any
|
||||
}
|
||||
|
||||
export type ICredentialDataDecrypted = ICommonObject
|
||||
|
||||
// Plain credential object sent to server
|
||||
export interface ICredentialReqBody {
|
||||
name: string
|
||||
credentialName: string
|
||||
plainDataObj: ICredentialDataDecrypted
|
||||
}
|
||||
|
||||
// Decrypted credential object sent back to client
|
||||
export interface ICredentialReturnResponse extends ICredential {
|
||||
plainDataObj: ICredentialDataDecrypted
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,33 @@
|
|||
import { IComponentNodes } from './Interface'
|
||||
|
||||
import { IComponentNodes, IComponentCredentials } from './Interface'
|
||||
import path from 'path'
|
||||
import { Dirent } from 'fs'
|
||||
import { getNodeModulesPackagePath } from './utils'
|
||||
import { promises } from 'fs'
|
||||
import { ICommonObject } from 'flowise-components'
|
||||
|
||||
export class NodesPool {
|
||||
componentNodes: IComponentNodes = {}
|
||||
componentCredentials: IComponentCredentials = {}
|
||||
private credentialIconPath: ICommonObject = {}
|
||||
|
||||
/**
|
||||
* Initialize to get all nodes
|
||||
* Initialize to get all nodes & credentials
|
||||
*/
|
||||
async initialize() {
|
||||
await this.initializeNodes()
|
||||
await this.initializeCrdentials()
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize nodes
|
||||
*/
|
||||
private async initializeNodes() {
|
||||
const packagePath = getNodeModulesPackagePath('flowise-components')
|
||||
const nodesPath = path.join(packagePath, 'dist', 'nodes')
|
||||
const nodeFiles = await this.getFiles(nodesPath)
|
||||
return Promise.all(
|
||||
nodeFiles.map(async (file) => {
|
||||
if (file.endsWith('.js')) {
|
||||
if (file.endsWith('.js') && !file.endsWith('.credential.js')) {
|
||||
const nodeModule = await require(file)
|
||||
|
||||
if (nodeModule.nodeClass) {
|
||||
|
|
@ -37,6 +47,13 @@ export class NodesPool {
|
|||
filePath.pop()
|
||||
const nodeIconAbsolutePath = `${filePath.join('/')}/${newNodeInstance.icon}`
|
||||
this.componentNodes[newNodeInstance.name].icon = nodeIconAbsolutePath
|
||||
|
||||
// Store icon path for componentCredentials
|
||||
if (newNodeInstance.credential) {
|
||||
for (const credName of newNodeInstance.credential.credentialNames) {
|
||||
this.credentialIconPath[credName] = nodeIconAbsolutePath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,12 +61,33 @@ export class NodesPool {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize credentials
|
||||
*/
|
||||
private async initializeCrdentials() {
|
||||
const packagePath = getNodeModulesPackagePath('flowise-components')
|
||||
const nodesPath = path.join(packagePath, 'dist', 'nodes')
|
||||
const nodeFiles = await this.getFiles(nodesPath)
|
||||
return Promise.all(
|
||||
nodeFiles.map(async (file) => {
|
||||
if (file.endsWith('.credential.js')) {
|
||||
const credentialModule = await require(file)
|
||||
if (credentialModule.credClass) {
|
||||
const newCredInstance = new credentialModule.credClass()
|
||||
newCredInstance.icon = this.credentialIconPath[newCredInstance.name] ?? ''
|
||||
this.componentCredentials[newCredInstance.name] = newCredInstance
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive function to get node files
|
||||
* @param {string} dir
|
||||
* @returns {string[]}
|
||||
*/
|
||||
async getFiles(dir: string): Promise<string[]> {
|
||||
private async getFiles(dir: string): Promise<string[]> {
|
||||
const dirents = await promises.readdir(dir, { withFileTypes: true })
|
||||
const files = await Promise.all(
|
||||
dirents.map((dirent: Dirent) => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
/* eslint-disable */
|
||||
import { Entity, Column, PrimaryGeneratedColumn, Index, CreateDateColumn, UpdateDateColumn } from 'typeorm'
|
||||
import { ICredential } from '../Interface'
|
||||
|
||||
@Entity()
|
||||
export class Credential implements ICredential {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string
|
||||
|
||||
@Column()
|
||||
name: string
|
||||
|
||||
@Column()
|
||||
credentialName: string
|
||||
|
||||
@Column()
|
||||
encryptedData: string
|
||||
|
||||
@CreateDateColumn()
|
||||
createdDate: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedDate: Date
|
||||
}
|
||||
|
|
@ -17,7 +17,8 @@ import {
|
|||
INodeData,
|
||||
IDatabaseExport,
|
||||
IRunChatflowMessageValue,
|
||||
IChildProcessMessage
|
||||
IChildProcessMessage,
|
||||
ICredentialReturnResponse
|
||||
} from './Interface'
|
||||
import {
|
||||
getNodeModulesPackagePath,
|
||||
|
|
@ -39,17 +40,20 @@ import {
|
|||
isFlowValidForStream,
|
||||
isVectorStoreFaiss,
|
||||
databaseEntities,
|
||||
getApiKey
|
||||
getApiKey,
|
||||
transformToCredentialEntity,
|
||||
decryptCredentialData
|
||||
} from './utils'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { cloneDeep, omit } from 'lodash'
|
||||
import { getDataSource } from './DataSource'
|
||||
import { NodesPool } from './NodesPool'
|
||||
import { ChatFlow } from './entity/ChatFlow'
|
||||
import { ChatMessage } from './entity/ChatMessage'
|
||||
import { Credential } from './entity/Credential'
|
||||
import { Tool } from './entity/Tool'
|
||||
import { ChatflowPool } from './ChatflowPool'
|
||||
import { ICommonObject, INodeOptionsValue } from 'flowise-components'
|
||||
import { fork } from 'child_process'
|
||||
import { Tool } from './entity/Tool'
|
||||
|
||||
export class App {
|
||||
app: express.Application
|
||||
|
|
@ -70,10 +74,11 @@ export class App {
|
|||
.then(async () => {
|
||||
logger.info('📦 [server]: Data Source has been initialized!')
|
||||
|
||||
// Initialize pools
|
||||
// Initialize nodes pool
|
||||
this.nodesPool = new NodesPool()
|
||||
await this.nodesPool.initialize()
|
||||
|
||||
// Initialize chatflow pool
|
||||
this.chatflowPool = new ChatflowPool()
|
||||
|
||||
// Initialize API keys
|
||||
|
|
@ -104,6 +109,7 @@ export class App {
|
|||
'/api/v1/public-chatflows',
|
||||
'/api/v1/prediction/',
|
||||
'/api/v1/node-icon/',
|
||||
'/api/v1/components-credentials-icon/',
|
||||
'/api/v1/chatflows-streaming'
|
||||
]
|
||||
this.app.use((req, res, next) => {
|
||||
|
|
@ -116,7 +122,7 @@ export class App {
|
|||
const upload = multer({ dest: `${path.join(__dirname, '..', 'uploads')}/` })
|
||||
|
||||
// ----------------------------------------
|
||||
// Nodes
|
||||
// Components
|
||||
// ----------------------------------------
|
||||
|
||||
// Get all component nodes
|
||||
|
|
@ -129,6 +135,16 @@ export class App {
|
|||
return res.json(returnData)
|
||||
})
|
||||
|
||||
// Get all component credentials
|
||||
this.app.get('/api/v1/components-credentials', async (req: Request, res: Response) => {
|
||||
const returnData = []
|
||||
for (const credName in this.nodesPool.componentCredentials) {
|
||||
const clonedCred = cloneDeep(this.nodesPool.componentCredentials[credName])
|
||||
returnData.push(clonedCred)
|
||||
}
|
||||
return res.json(returnData)
|
||||
})
|
||||
|
||||
// Get specific component node via name
|
||||
this.app.get('/api/v1/nodes/:name', (req: Request, res: Response) => {
|
||||
if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, req.params.name)) {
|
||||
|
|
@ -138,6 +154,27 @@ export class App {
|
|||
}
|
||||
})
|
||||
|
||||
// Get component credential via name
|
||||
this.app.get('/api/v1/components-credentials/:name', (req: Request, res: Response) => {
|
||||
if (!req.params.name.includes('&')) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, req.params.name)) {
|
||||
return res.json(this.nodesPool.componentCredentials[req.params.name])
|
||||
} else {
|
||||
throw new Error(`Credential ${req.params.name} not found`)
|
||||
}
|
||||
} else {
|
||||
const returnResponse = []
|
||||
for (const name of req.params.name.split('&')) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, name)) {
|
||||
returnResponse.push(this.nodesPool.componentCredentials[name])
|
||||
} else {
|
||||
throw new Error(`Credential ${name} not found`)
|
||||
}
|
||||
}
|
||||
return res.json(returnResponse)
|
||||
}
|
||||
})
|
||||
|
||||
// Returns specific component node icon via name
|
||||
this.app.get('/api/v1/node-icon/:name', (req: Request, res: Response) => {
|
||||
if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, req.params.name)) {
|
||||
|
|
@ -157,6 +194,25 @@ export class App {
|
|||
}
|
||||
})
|
||||
|
||||
// Returns specific component credential icon via name
|
||||
this.app.get('/api/v1/components-credentials-icon/:name', (req: Request, res: Response) => {
|
||||
if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, req.params.name)) {
|
||||
const credInstance = this.nodesPool.componentCredentials[req.params.name]
|
||||
if (credInstance.icon === undefined) {
|
||||
throw new Error(`Credential ${req.params.name} icon not found`)
|
||||
}
|
||||
|
||||
if (credInstance.icon.endsWith('.svg') || credInstance.icon.endsWith('.png') || credInstance.icon.endsWith('.jpg')) {
|
||||
const filepath = credInstance.icon
|
||||
res.sendFile(filepath)
|
||||
} else {
|
||||
throw new Error(`Credential ${req.params.name} icon is missing icon`)
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Credential ${req.params.name} not found`)
|
||||
}
|
||||
})
|
||||
|
||||
// load async options
|
||||
this.app.post('/api/v1/node-load-method/:name', async (req: Request, res: Response) => {
|
||||
const nodeData: INodeData = req.body
|
||||
|
|
@ -324,6 +380,91 @@ export class App {
|
|||
return res.json(results)
|
||||
})
|
||||
|
||||
// ----------------------------------------
|
||||
// Credentials
|
||||
// ----------------------------------------
|
||||
|
||||
// Create new credential
|
||||
this.app.post('/api/v1/credentials', async (req: Request, res: Response) => {
|
||||
const body = req.body
|
||||
const newCredential = await transformToCredentialEntity(body)
|
||||
const credential = this.AppDataSource.getRepository(Credential).create(newCredential)
|
||||
const results = await this.AppDataSource.getRepository(Credential).save(credential)
|
||||
return res.json(results)
|
||||
})
|
||||
|
||||
// Get all credentials
|
||||
this.app.get('/api/v1/credentials', async (req: Request, res: Response) => {
|
||||
if (req.query.credentialName) {
|
||||
let returnCredentials = []
|
||||
if (Array.isArray(req.query.credentialName)) {
|
||||
for (let i = 0; i < req.query.credentialName.length; i += 1) {
|
||||
const name = req.query.credentialName[i] as string
|
||||
const credentials = await this.AppDataSource.getRepository(Credential).findBy({
|
||||
credentialName: name
|
||||
})
|
||||
returnCredentials.push(...credentials)
|
||||
}
|
||||
} else {
|
||||
const credentials = await this.AppDataSource.getRepository(Credential).findBy({
|
||||
credentialName: req.query.credentialName as string
|
||||
})
|
||||
returnCredentials = [...credentials]
|
||||
}
|
||||
return res.json(returnCredentials)
|
||||
} else {
|
||||
const credentials = await this.AppDataSource.getRepository(Credential).find()
|
||||
const returnCredentials = []
|
||||
for (const credential of credentials) {
|
||||
returnCredentials.push(omit(credential, ['encryptedData']))
|
||||
}
|
||||
return res.json(returnCredentials)
|
||||
}
|
||||
})
|
||||
|
||||
// Get specific credential
|
||||
this.app.get('/api/v1/credentials/:id', async (req: Request, res: Response) => {
|
||||
const credential = await this.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: req.params.id
|
||||
})
|
||||
|
||||
if (!credential) return res.status(404).send(`Credential ${req.params.id} not found`)
|
||||
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(
|
||||
credential.encryptedData,
|
||||
credential.credentialName,
|
||||
this.nodesPool.componentCredentials
|
||||
)
|
||||
const returnCredential: ICredentialReturnResponse = {
|
||||
...credential,
|
||||
plainDataObj: decryptedCredentialData
|
||||
}
|
||||
return res.json(omit(returnCredential, ['encryptedData']))
|
||||
})
|
||||
|
||||
// Update credential
|
||||
this.app.put('/api/v1/credentials/:id', async (req: Request, res: Response) => {
|
||||
const credential = await this.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: req.params.id
|
||||
})
|
||||
|
||||
if (!credential) return res.status(404).send(`Credential ${req.params.id} not found`)
|
||||
|
||||
const body = req.body
|
||||
const updateCredential = await transformToCredentialEntity(body)
|
||||
this.AppDataSource.getRepository(Credential).merge(credential, updateCredential)
|
||||
const result = await this.AppDataSource.getRepository(Credential).save(credential)
|
||||
|
||||
return res.json(result)
|
||||
})
|
||||
|
||||
// Delete all chatmessages from chatflowid
|
||||
this.app.delete('/api/v1/credentials/:id', async (req: Request, res: Response) => {
|
||||
const results = await this.AppDataSource.getRepository(Credential).delete({ id: req.params.id })
|
||||
return res.json(results)
|
||||
})
|
||||
|
||||
// ----------------------------------------
|
||||
// Tools
|
||||
// ----------------------------------------
|
||||
|
|
@ -393,7 +534,7 @@ export class App {
|
|||
const flowData = chatflow.flowData
|
||||
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
|
||||
const nodes = parsedFlowData.nodes
|
||||
const availableConfigs = findAvailableConfigs(nodes)
|
||||
const availableConfigs = findAvailableConfigs(nodes, this.nodesPool.componentCredentials)
|
||||
return res.json(availableConfigs)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -13,18 +13,26 @@ import {
|
|||
IReactFlowNode,
|
||||
IVariableDict,
|
||||
INodeData,
|
||||
IOverrideConfig
|
||||
IOverrideConfig,
|
||||
ICredentialDataDecrypted,
|
||||
IComponentCredentials,
|
||||
ICredentialReqBody
|
||||
} from '../Interface'
|
||||
import { cloneDeep, get, omit, merge } from 'lodash'
|
||||
import { ICommonObject, getInputVariables, IDatabaseEntity } from 'flowise-components'
|
||||
import { scryptSync, randomBytes, timingSafeEqual } from 'crypto'
|
||||
import { lib, PBKDF2, AES, enc } from 'crypto-js'
|
||||
|
||||
import { ChatFlow } from '../entity/ChatFlow'
|
||||
import { ChatMessage } from '../entity/ChatMessage'
|
||||
import { Credential } from '../entity/Credential'
|
||||
import { Tool } from '../entity/Tool'
|
||||
import { DataSource } from 'typeorm'
|
||||
|
||||
const QUESTION_VAR_PREFIX = 'question'
|
||||
export const databaseEntities: IDatabaseEntity = { ChatFlow: ChatFlow, ChatMessage: ChatMessage, Tool: Tool }
|
||||
const REDACTED_CREDENTIAL_VALUE = '_FLOWISE_BLANK_07167752-1a71-43b1-bf8f-4f32252165db'
|
||||
|
||||
export const databaseEntities: IDatabaseEntity = { ChatFlow: ChatFlow, ChatMessage: ChatMessage, Tool: Tool, Credential: Credential }
|
||||
|
||||
/**
|
||||
* Returns the home folder path of the user if
|
||||
|
|
@ -399,9 +407,8 @@ export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig:
|
|||
const types = 'inputs'
|
||||
|
||||
const getParamValues = (paramsObj: ICommonObject) => {
|
||||
for (const key in paramsObj) {
|
||||
const paramValue: string = paramsObj[key]
|
||||
paramsObj[key] = overrideConfig[key] ?? paramValue
|
||||
for (const config in overrideConfig) {
|
||||
paramsObj[config] = overrideConfig[config]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -623,11 +630,12 @@ export const mapMimeTypeToInputField = (mimeType: string) => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Find all available inpur params config
|
||||
* Find all available input params config
|
||||
* @param {IReactFlowNode[]} reactFlowNodes
|
||||
* @returns {Promise<IOverrideConfig[]>}
|
||||
* @param {IComponentCredentials} componentCredentials
|
||||
* @returns {IOverrideConfig[]}
|
||||
*/
|
||||
export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => {
|
||||
export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], componentCredentials: IComponentCredentials) => {
|
||||
const configs: IOverrideConfig[] = []
|
||||
|
||||
for (const flowNode of reactFlowNodes) {
|
||||
|
|
@ -653,6 +661,23 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => {
|
|||
.join(', ')
|
||||
: 'string'
|
||||
}
|
||||
} else if (inputParam.type === 'credential') {
|
||||
// get component credential inputs
|
||||
for (const name of inputParam.credentialNames ?? []) {
|
||||
if (Object.prototype.hasOwnProperty.call(componentCredentials, name)) {
|
||||
const inputs = componentCredentials[name]?.inputs ?? []
|
||||
for (const input of inputs) {
|
||||
obj = {
|
||||
node: flowNode.data.label,
|
||||
label: input.label,
|
||||
name: input.name,
|
||||
type: input.type === 'password' ? 'string' : input.type
|
||||
}
|
||||
configs.push(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
obj = {
|
||||
node: flowNode.data.label,
|
||||
|
|
@ -705,3 +730,118 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod
|
|||
|
||||
return isChatOrLLMsExist && isValidChainOrAgent && !isVectorStoreFaiss(endingNodeData) && process.env.EXECUTION_MODE !== 'child'
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of encryption key
|
||||
* @returns {string}
|
||||
*/
|
||||
export const getEncryptionKeyPath = (): string => {
|
||||
return process.env.SECRETKEY_PATH
|
||||
? path.join(process.env.SECRETKEY_PATH, 'encryption.key')
|
||||
: path.join(__dirname, '..', '..', 'encryption.key')
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an encryption key
|
||||
* @returns {string}
|
||||
*/
|
||||
export const generateEncryptKey = (): string => {
|
||||
const salt = lib.WordArray.random(128 / 8)
|
||||
const key256Bits = PBKDF2(process.env.PASSPHRASE || 'MYPASSPHRASE', salt, {
|
||||
keySize: 256 / 32,
|
||||
iterations: 1000
|
||||
})
|
||||
return key256Bits.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encryption key
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export const getEncryptionKey = async (): Promise<string> => {
|
||||
try {
|
||||
return await fs.promises.readFile(getEncryptionKeyPath(), 'utf8')
|
||||
} catch (error) {
|
||||
const encryptKey = generateEncryptKey()
|
||||
await fs.promises.writeFile(getEncryptionKeyPath(), encryptKey)
|
||||
return encryptKey
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt credential data
|
||||
* @param {ICredentialDataDecrypted} plainDataObj
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export const encryptCredentialData = async (plainDataObj: ICredentialDataDecrypted): Promise<string> => {
|
||||
const encryptKey = await getEncryptionKey()
|
||||
return AES.encrypt(JSON.stringify(plainDataObj), encryptKey).toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt credential data
|
||||
* @param {string} encryptedData
|
||||
* @param {string} componentCredentialName
|
||||
* @param {IComponentCredentials} componentCredentials
|
||||
* @returns {Promise<ICredentialDataDecrypted>}
|
||||
*/
|
||||
export const decryptCredentialData = async (
|
||||
encryptedData: string,
|
||||
componentCredentialName?: string,
|
||||
componentCredentials?: IComponentCredentials
|
||||
): Promise<ICredentialDataDecrypted> => {
|
||||
const encryptKey = await getEncryptionKey()
|
||||
const decryptedData = AES.decrypt(encryptedData, encryptKey)
|
||||
try {
|
||||
if (componentCredentialName && componentCredentials) {
|
||||
const plainDataObj = JSON.parse(decryptedData.toString(enc.Utf8))
|
||||
return redactCredentialWithPasswordType(componentCredentialName, plainDataObj, componentCredentials)
|
||||
}
|
||||
return JSON.parse(decryptedData.toString(enc.Utf8))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
throw new Error('Credentials could not be decrypted.')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform ICredentialBody from req to Credential entity
|
||||
* @param {ICredentialReqBody} body
|
||||
* @returns {Credential}
|
||||
*/
|
||||
export const transformToCredentialEntity = async (body: ICredentialReqBody): Promise<Credential> => {
|
||||
const encryptedData = await encryptCredentialData(body.plainDataObj)
|
||||
|
||||
const credentialBody = {
|
||||
name: body.name,
|
||||
credentialName: body.credentialName,
|
||||
encryptedData
|
||||
}
|
||||
|
||||
const newCredential = new Credential()
|
||||
Object.assign(newCredential, credentialBody)
|
||||
|
||||
return newCredential
|
||||
}
|
||||
|
||||
/**
|
||||
* Redact values that are of password type to avoid sending back to client
|
||||
* @param {string} componentCredentialName
|
||||
* @param {ICredentialDataDecrypted} decryptedCredentialObj
|
||||
* @param {IComponentCredentials} componentCredentials
|
||||
* @returns {ICredentialDataDecrypted}
|
||||
*/
|
||||
export const redactCredentialWithPasswordType = (
|
||||
componentCredentialName: string,
|
||||
decryptedCredentialObj: ICredentialDataDecrypted,
|
||||
componentCredentials: IComponentCredentials
|
||||
): ICredentialDataDecrypted => {
|
||||
const plainDataObj = cloneDeep(decryptedCredentialObj)
|
||||
for (const cred in plainDataObj) {
|
||||
const inputParam = componentCredentials[componentCredentialName].inputs?.find((inp) => inp.type === 'password' && inp.name === cred)
|
||||
if (inputParam) {
|
||||
plainDataObj[cred] = REDACTED_CREDENTIAL_VALUE
|
||||
}
|
||||
}
|
||||
return plainDataObj
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,8 @@ export function expressRequestLogger(req: Request, res: Response, next: NextFunc
|
|||
GET: '⬇️',
|
||||
POST: '⬆️',
|
||||
PUT: '🖊',
|
||||
DELETE: '❌'
|
||||
DELETE: '❌',
|
||||
OPTION: '🔗'
|
||||
}
|
||||
|
||||
return requetsEmojis[method] || '?'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
import client from './client'
|
||||
|
||||
const getAllCredentials = () => client.get('/credentials')
|
||||
|
||||
const getCredentialsByName = (componentCredentialName) => client.get(`/credentials?credentialName=${componentCredentialName}`)
|
||||
|
||||
const getAllComponentsCredentials = () => client.get('/components-credentials')
|
||||
|
||||
const getSpecificCredential = (id) => client.get(`/credentials/${id}`)
|
||||
|
||||
const getSpecificComponentCredential = (name) => client.get(`/components-credentials/${name}`)
|
||||
|
||||
const createCredential = (body) => client.post(`/credentials`, body)
|
||||
|
||||
const updateCredential = (id, body) => client.put(`/credentials/${id}`, body)
|
||||
|
||||
const deleteCredential = (id) => client.delete(`/credentials/${id}`)
|
||||
|
||||
export default {
|
||||
getAllCredentials,
|
||||
getCredentialsByName,
|
||||
getAllComponentsCredentials,
|
||||
getSpecificCredential,
|
||||
getSpecificComponentCredential,
|
||||
createCredential,
|
||||
updateCredential,
|
||||
deleteCredential
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.2 KiB |
|
|
@ -1,8 +1,8 @@
|
|||
// assets
|
||||
import { IconHierarchy, IconBuildingStore, IconKey, IconTool } from '@tabler/icons'
|
||||
import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock } from '@tabler/icons'
|
||||
|
||||
// constant
|
||||
const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool }
|
||||
const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock }
|
||||
|
||||
// ==============================|| DASHBOARD MENU ITEMS ||============================== //
|
||||
|
||||
|
|
@ -35,6 +35,14 @@ const dashboard = {
|
|||
icon: icons.IconTool,
|
||||
breadcrumbs: true
|
||||
},
|
||||
{
|
||||
id: 'credentials',
|
||||
title: 'Credentials',
|
||||
type: 'item',
|
||||
url: '/credentials',
|
||||
icon: icons.IconLock,
|
||||
breadcrumbs: true
|
||||
},
|
||||
{
|
||||
id: 'apikey',
|
||||
title: 'API Keys',
|
||||
|
|
|
|||
|
|
@ -13,9 +13,12 @@ const Marketplaces = Loadable(lazy(() => import('views/marketplaces')))
|
|||
// apikey routing
|
||||
const APIKey = Loadable(lazy(() => import('views/apikey')))
|
||||
|
||||
// apikey routing
|
||||
// tools routing
|
||||
const Tools = Loadable(lazy(() => import('views/tools')))
|
||||
|
||||
// credentials routing
|
||||
const Credentials = Loadable(lazy(() => import('views/credentials')))
|
||||
|
||||
// ==============================|| MAIN ROUTING ||============================== //
|
||||
|
||||
const MainRoutes = {
|
||||
|
|
@ -41,6 +44,10 @@ const MainRoutes = {
|
|||
{
|
||||
path: '/tools',
|
||||
element: <Tools />
|
||||
},
|
||||
{
|
||||
path: '/credentials',
|
||||
element: <Credentials />
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ export const appDrawerWidth = 320
|
|||
export const maxScroll = 100000
|
||||
export const baseURL = process.env.NODE_ENV === 'production' ? window.location.origin : window.location.origin.replace(':8080', ':3000')
|
||||
export const uiBaseURL = window.location.origin
|
||||
export const FLOWISE_CREDENTIAL_ID = 'FLOWISE_CREDENTIAL_ID'
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
import { useState, useEffect, Fragment } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import PropTypes from 'prop-types'
|
||||
import axios from 'axios'
|
||||
|
||||
// Material
|
||||
import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete'
|
||||
import { Popper, CircularProgress, TextField, Box, Typography } from '@mui/material'
|
||||
import { styled } from '@mui/material/styles'
|
||||
|
||||
// API
|
||||
import credentialsApi from 'api/credentials'
|
||||
|
||||
// const
|
||||
import { baseURL } from 'store/constant'
|
||||
|
||||
const StyledPopper = styled(Popper)({
|
||||
|
|
@ -49,6 +53,7 @@ export const AsyncDropdown = ({
|
|||
onSelect,
|
||||
isCreateNewOption,
|
||||
onCreateNew,
|
||||
credentialNames = [],
|
||||
disabled = false,
|
||||
disableClearable = false
|
||||
}) => {
|
||||
|
|
@ -62,11 +67,36 @@ export const AsyncDropdown = ({
|
|||
const addNewOption = [{ label: '- Create New -', name: '-create-' }]
|
||||
let [internalValue, setInternalValue] = useState(value ?? 'choose an option')
|
||||
|
||||
const fetchCredentialList = async () => {
|
||||
try {
|
||||
let names = ''
|
||||
if (credentialNames.length > 1) {
|
||||
names = credentialNames.join('&credentialName=')
|
||||
} else {
|
||||
names = credentialNames[0]
|
||||
}
|
||||
const resp = await credentialsApi.getCredentialsByName(names)
|
||||
if (resp.data) {
|
||||
const returnList = []
|
||||
for (let i = 0; i < resp.data.length; i += 1) {
|
||||
const data = {
|
||||
label: resp.data[i].name,
|
||||
name: resp.data[i].id
|
||||
}
|
||||
returnList.push(data)
|
||||
}
|
||||
return returnList
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
;(async () => {
|
||||
const fetchData = async () => {
|
||||
let response = await fetchList({ name, nodeData })
|
||||
let response = credentialNames.length ? await fetchCredentialList() : await fetchList({ name, nodeData })
|
||||
if (isCreateNewOption) setOptions([...response, ...addNewOption])
|
||||
else setOptions([...response])
|
||||
setLoading(false)
|
||||
|
|
@ -142,6 +172,7 @@ AsyncDropdown.propTypes = {
|
|||
onSelect: PropTypes.func,
|
||||
onCreateNew: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
credentialNames: PropTypes.array,
|
||||
disableClearable: PropTypes.bool,
|
||||
isCreateNewOption: PropTypes.bool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export const Input = ({ inputParam, value, onChange, disabled = false, showDialo
|
|||
onChange(e.target.value)
|
||||
}}
|
||||
inputProps={{
|
||||
step: 0.1,
|
||||
step: inputParam.step ?? 0.1,
|
||||
style: {
|
||||
height: inputParam.rows ? '90px' : 'inherit'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export const initNode = (nodeData, newNodeId) => {
|
|||
|
||||
const whitelistTypes = ['asyncOptions', 'options', 'string', 'number', 'boolean', 'password', 'json', 'code', 'date', 'file', 'folder']
|
||||
|
||||
// Inputs
|
||||
for (let i = 0; i < incoming; i += 1) {
|
||||
const newInput = {
|
||||
...nodeData.inputs[i],
|
||||
|
|
@ -53,6 +54,16 @@ export const initNode = (nodeData, newNodeId) => {
|
|||
}
|
||||
}
|
||||
|
||||
// Credential
|
||||
if (nodeData.credential) {
|
||||
const newInput = {
|
||||
...nodeData.credential,
|
||||
id: `${newNodeId}-input-${nodeData.credential.name}-${nodeData.credential.type}`
|
||||
}
|
||||
inputParams.unshift(newInput)
|
||||
}
|
||||
|
||||
// Outputs
|
||||
const outputAnchors = []
|
||||
for (let i = 0; i < outgoing; i += 1) {
|
||||
if (nodeData.outputs && nodeData.outputs.length) {
|
||||
|
|
@ -129,6 +140,8 @@ export const initNode = (nodeData, newNodeId) => {
|
|||
}
|
||||
]
|
||||
*/
|
||||
|
||||
// Inputs
|
||||
if (nodeData.inputs) {
|
||||
nodeData.inputAnchors = inputAnchors
|
||||
nodeData.inputParams = inputParams
|
||||
|
|
@ -139,13 +152,17 @@ export const initNode = (nodeData, newNodeId) => {
|
|||
nodeData.inputs = {}
|
||||
}
|
||||
|
||||
// Outputs
|
||||
if (nodeData.outputs) {
|
||||
nodeData.outputs = initializeDefaultNodeData(outputAnchors)
|
||||
} else {
|
||||
nodeData.outputs = {}
|
||||
}
|
||||
|
||||
nodeData.outputAnchors = outputAnchors
|
||||
|
||||
// Credential
|
||||
if (nodeData.credential) nodeData.credential = ''
|
||||
|
||||
nodeData.id = newNodeId
|
||||
|
||||
return nodeData
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import { useRef, useState } from 'react'
|
||||
|
||||
// material-ui
|
||||
import { IconButton } from '@mui/material'
|
||||
import { IconEdit } from '@tabler/icons'
|
||||
|
||||
// project import
|
||||
import { AsyncDropdown } from 'ui-component/dropdown/AsyncDropdown'
|
||||
import AddEditCredentialDialog from 'views/credentials/AddEditCredentialDialog'
|
||||
import CredentialListDialog from 'views/credentials/CredentialListDialog'
|
||||
|
||||
// API
|
||||
import credentialsApi from 'api/credentials'
|
||||
|
||||
// ===========================|| CredentialInputHandler ||=========================== //
|
||||
|
||||
const CredentialInputHandler = ({ inputParam, data, onSelect, disabled = false }) => {
|
||||
const ref = useRef(null)
|
||||
const [credentialId, setCredentialId] = useState(data?.credential ?? '')
|
||||
const [showCredentialListDialog, setShowCredentialListDialog] = useState(false)
|
||||
const [credentialListDialogProps, setCredentialListDialogProps] = useState({})
|
||||
const [showSpecificCredentialDialog, setShowSpecificCredentialDialog] = useState(false)
|
||||
const [specificCredentialDialogProps, setSpecificCredentialDialogProps] = useState({})
|
||||
const [reloadTimestamp, setReloadTimestamp] = useState(Date.now().toString())
|
||||
|
||||
const editCredential = (credentialId) => {
|
||||
const dialogProp = {
|
||||
type: 'EDIT',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Save',
|
||||
credentialId
|
||||
}
|
||||
setSpecificCredentialDialogProps(dialogProp)
|
||||
setShowSpecificCredentialDialog(true)
|
||||
}
|
||||
|
||||
const addAsyncOption = async () => {
|
||||
try {
|
||||
let names = ''
|
||||
if (inputParam.credentialNames.length > 1) {
|
||||
names = inputParam.credentialNames.join('&')
|
||||
} else {
|
||||
names = inputParam.credentialNames[0]
|
||||
}
|
||||
const componentCredentialsResp = await credentialsApi.getSpecificComponentCredential(names)
|
||||
if (componentCredentialsResp.data) {
|
||||
if (Array.isArray(componentCredentialsResp.data)) {
|
||||
const dialogProp = {
|
||||
title: 'Add New Credential',
|
||||
componentsCredentials: componentCredentialsResp.data
|
||||
}
|
||||
setCredentialListDialogProps(dialogProp)
|
||||
setShowCredentialListDialog(true)
|
||||
} else {
|
||||
const dialogProp = {
|
||||
type: 'ADD',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Add',
|
||||
credentialComponent: componentCredentialsResp.data
|
||||
}
|
||||
setSpecificCredentialDialogProps(dialogProp)
|
||||
setShowSpecificCredentialDialog(true)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
const onConfirmAsyncOption = (selectedCredentialId = '') => {
|
||||
setCredentialId(selectedCredentialId)
|
||||
setReloadTimestamp(Date.now().toString())
|
||||
setSpecificCredentialDialogProps({})
|
||||
setShowSpecificCredentialDialog(false)
|
||||
onSelect(selectedCredentialId)
|
||||
}
|
||||
|
||||
const onCredentialSelected = (credentialComponent) => {
|
||||
setShowCredentialListDialog(false)
|
||||
const dialogProp = {
|
||||
type: 'ADD',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Add',
|
||||
credentialComponent
|
||||
}
|
||||
setSpecificCredentialDialogProps(dialogProp)
|
||||
setShowSpecificCredentialDialog(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{inputParam && (
|
||||
<>
|
||||
{inputParam.type === 'credential' && (
|
||||
<>
|
||||
<div style={{ marginTop: 10 }} />
|
||||
<div key={reloadTimestamp} style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<AsyncDropdown
|
||||
disabled={disabled}
|
||||
name={inputParam.name}
|
||||
nodeData={data}
|
||||
value={credentialId ?? 'choose an option'}
|
||||
isCreateNewOption={true}
|
||||
credentialNames={inputParam.credentialNames}
|
||||
onSelect={(newValue) => {
|
||||
setCredentialId(newValue)
|
||||
onSelect(newValue)
|
||||
}}
|
||||
onCreateNew={() => addAsyncOption(inputParam.name)}
|
||||
/>
|
||||
{credentialId && (
|
||||
<IconButton title='Edit' color='primary' size='small' onClick={() => editCredential(credentialId)}>
|
||||
<IconEdit />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{showSpecificCredentialDialog && (
|
||||
<AddEditCredentialDialog
|
||||
show={showSpecificCredentialDialog}
|
||||
dialogProps={specificCredentialDialogProps}
|
||||
onCancel={() => setShowSpecificCredentialDialog(false)}
|
||||
onConfirm={onConfirmAsyncOption}
|
||||
></AddEditCredentialDialog>
|
||||
)}
|
||||
{showCredentialListDialog && (
|
||||
<CredentialListDialog
|
||||
show={showCredentialListDialog}
|
||||
dialogProps={credentialListDialogProps}
|
||||
onCancel={() => setShowCredentialListDialog(false)}
|
||||
onCredentialSelected={onCredentialSelected}
|
||||
></CredentialListDialog>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
CredentialInputHandler.propTypes = {
|
||||
inputParam: PropTypes.object,
|
||||
data: PropTypes.object,
|
||||
onSelect: PropTypes.func,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
export default CredentialInputHandler
|
||||
|
|
@ -21,9 +21,14 @@ import { JsonEditorInput } from 'ui-component/json/JsonEditor'
|
|||
import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser'
|
||||
import ToolDialog from 'views/tools/ToolDialog'
|
||||
import FormatPromptValuesDialog from 'ui-component/dialog/FormatPromptValuesDialog'
|
||||
import CredentialInputHandler from './CredentialInputHandler'
|
||||
|
||||
// utils
|
||||
import { getInputVariables } from 'utils/genericHelper'
|
||||
|
||||
// const
|
||||
import { FLOWISE_CREDENTIAL_ID } from 'store/constant'
|
||||
|
||||
const EDITABLE_TOOLS = ['selectedTool']
|
||||
|
||||
const CustomWidthTooltip = styled(({ className, ...props }) => <Tooltip {...props} classes={{ popper: className }} />)({
|
||||
|
|
@ -226,6 +231,17 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
|
|||
<span style={{ color: 'rgb(116,66,16)', marginLeft: 10 }}>{inputParam.warning}</span>
|
||||
</div>
|
||||
)}
|
||||
{inputParam.type === 'credential' && (
|
||||
<CredentialInputHandler
|
||||
disabled={disabled}
|
||||
data={data}
|
||||
inputParam={inputParam}
|
||||
onSelect={(newValue) => {
|
||||
data.credential = newValue
|
||||
data.inputs[FLOWISE_CREDENTIAL_ID] = newValue // in case data.credential is not updated
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{inputParam.type === 'file' && (
|
||||
<File
|
||||
disabled={disabled}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
enqueueSnackbar as enqueueSnackbarAction,
|
||||
closeSnackbar as closeSnackbarAction
|
||||
} from 'store/actions'
|
||||
import { omit, cloneDeep } from 'lodash'
|
||||
|
||||
// material-ui
|
||||
import { Toolbar, Box, AppBar, Button } from '@mui/material'
|
||||
|
|
@ -41,6 +42,9 @@ import { IconX } from '@tabler/icons'
|
|||
import { getUniqueNodeId, initNode, getEdgeLabelName, rearrangeToolsOrdering } from 'utils/genericHelper'
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
// const
|
||||
import { FLOWISE_CREDENTIAL_ID } from 'store/constant'
|
||||
|
||||
const nodeTypes = { customNode: CanvasNode }
|
||||
const edgeTypes = { buttonedge: ButtonEdge }
|
||||
|
||||
|
|
@ -185,17 +189,21 @@ const Canvas = () => {
|
|||
|
||||
const handleSaveFlow = (chatflowName) => {
|
||||
if (reactFlowInstance) {
|
||||
setNodes((nds) =>
|
||||
nds.map((node) => {
|
||||
node.data = {
|
||||
...node.data,
|
||||
selected: false
|
||||
}
|
||||
return node
|
||||
})
|
||||
)
|
||||
const nodes = reactFlowInstance.getNodes().map((node) => {
|
||||
const nodeData = cloneDeep(node.data)
|
||||
if (Object.prototype.hasOwnProperty.call(nodeData.inputs, FLOWISE_CREDENTIAL_ID)) {
|
||||
nodeData.credential = nodeData.inputs[FLOWISE_CREDENTIAL_ID]
|
||||
nodeData.inputs = omit(nodeData.inputs, [FLOWISE_CREDENTIAL_ID])
|
||||
}
|
||||
node.data = {
|
||||
...nodeData,
|
||||
selected: false
|
||||
}
|
||||
return node
|
||||
})
|
||||
|
||||
const rfInstanceObject = reactFlowInstance.toObject()
|
||||
rfInstanceObject.nodes = nodes
|
||||
const flowData = JSON.stringify(rfInstanceObject)
|
||||
|
||||
if (!chatflow.id) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,276 @@
|
|||
import { createPortal } from 'react-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions'
|
||||
import parser from 'html-react-parser'
|
||||
|
||||
// Material
|
||||
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Box, Stack, OutlinedInput, Typography } from '@mui/material'
|
||||
|
||||
// Project imports
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
import ConfirmDialog from 'ui-component/dialog/ConfirmDialog'
|
||||
import CredentialInputHandler from './CredentialInputHandler'
|
||||
|
||||
// Icons
|
||||
import { IconX } from '@tabler/icons'
|
||||
|
||||
// API
|
||||
import credentialsApi from 'api/credentials'
|
||||
|
||||
// Hooks
|
||||
import useApi from 'hooks/useApi'
|
||||
|
||||
// utils
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
// const
|
||||
import { baseURL } from 'store/constant'
|
||||
|
||||
const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
// ==============================|| Snackbar ||============================== //
|
||||
|
||||
useNotifier()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const getSpecificCredentialApi = useApi(credentialsApi.getSpecificCredential)
|
||||
const getSpecificComponentCredentialApi = useApi(credentialsApi.getSpecificComponentCredential)
|
||||
|
||||
const [credential, setCredential] = useState({})
|
||||
const [name, setName] = useState('')
|
||||
const [credentialData, setCredentialData] = useState({})
|
||||
const [componentCredential, setComponentCredential] = useState({})
|
||||
|
||||
useEffect(() => {
|
||||
if (getSpecificCredentialApi.data) {
|
||||
setCredential(getSpecificCredentialApi.data)
|
||||
if (getSpecificCredentialApi.data.name) {
|
||||
setName(getSpecificCredentialApi.data.name)
|
||||
}
|
||||
if (getSpecificCredentialApi.data.plainDataObj) {
|
||||
setCredentialData(getSpecificCredentialApi.data.plainDataObj)
|
||||
}
|
||||
getSpecificComponentCredentialApi.request(getSpecificCredentialApi.data.credentialName)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getSpecificCredentialApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (getSpecificComponentCredentialApi.data) {
|
||||
setComponentCredential(getSpecificComponentCredentialApi.data)
|
||||
}
|
||||
}, [getSpecificComponentCredentialApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.type === 'EDIT' && dialogProps.data) {
|
||||
// When credential dialog is opened from Credentials dashboard
|
||||
getSpecificCredentialApi.request(dialogProps.data.id)
|
||||
} else if (dialogProps.type === 'EDIT' && dialogProps.credentialId) {
|
||||
// When credential dialog is opened from node in canvas
|
||||
getSpecificCredentialApi.request(dialogProps.credentialId)
|
||||
} else if (dialogProps.type === 'ADD' && dialogProps.credentialComponent) {
|
||||
// When credential dialog is to add a new credential
|
||||
setName('')
|
||||
setCredential({})
|
||||
setCredentialData({})
|
||||
setComponentCredential(dialogProps.credentialComponent)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dialogProps])
|
||||
|
||||
const addNewCredential = async () => {
|
||||
try {
|
||||
const obj = {
|
||||
name,
|
||||
credentialName: componentCredential.name,
|
||||
plainDataObj: credentialData
|
||||
}
|
||||
const createResp = await credentialsApi.createCredential(obj)
|
||||
if (createResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'New Credential added',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm(createResp.data.id)
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to add new Credential: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
const saveCredential = async () => {
|
||||
try {
|
||||
const saveResp = await credentialsApi.updateCredential(credential.id, {
|
||||
name,
|
||||
credentialName: componentCredential.name,
|
||||
plainDataObj: credentialData
|
||||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Credential saved',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm(saveResp.data.id)
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save Credential: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
fullWidth
|
||||
maxWidth='sm'
|
||||
open={show}
|
||||
onClose={onCancel}
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
{componentCredential && componentCredential.label && (
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<div
|
||||
style={{
|
||||
width: 50,
|
||||
height: 50,
|
||||
marginRight: 10,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 7,
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
alt={componentCredential.name}
|
||||
src={`${baseURL}/api/v1/components-credentials-icon/${componentCredential.name}`}
|
||||
/>
|
||||
</div>
|
||||
{componentCredential.label}
|
||||
</div>
|
||||
)}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{componentCredential && componentCredential.description && (
|
||||
<Box sx={{ pl: 2, pr: 2 }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
borderRadius: 10,
|
||||
background: 'rgb(254,252,191)',
|
||||
padding: 10,
|
||||
marginTop: 10,
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<span style={{ color: 'rgb(116,66,16)' }}>{parser(componentCredential.description)}</span>
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
{componentCredential && componentCredential.label && (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
Credential Name
|
||||
<span style={{ color: 'red' }}> *</span>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='credName'
|
||||
type='string'
|
||||
fullWidth
|
||||
placeholder={componentCredential.label}
|
||||
value={name}
|
||||
name='name'
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{componentCredential &&
|
||||
componentCredential.inputs &&
|
||||
componentCredential.inputs.map((inputParam, index) => (
|
||||
<CredentialInputHandler key={index} inputParam={inputParam} data={credentialData} />
|
||||
))}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<StyledButton
|
||||
disabled={!name}
|
||||
variant='contained'
|
||||
onClick={() => (dialogProps.type === 'ADD' ? addNewCredential() : saveCredential())}
|
||||
>
|
||||
{dialogProps.confirmButtonName}
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
<ConfirmDialog />
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
AddEditCredentialDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func,
|
||||
onConfirm: PropTypes.func
|
||||
}
|
||||
|
||||
export default AddEditCredentialDialog
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
// material-ui
|
||||
import { Box, Typography, IconButton } from '@mui/material'
|
||||
import { IconArrowsMaximize, IconAlertTriangle } from '@tabler/icons'
|
||||
|
||||
// project import
|
||||
import { Dropdown } from 'ui-component/dropdown/Dropdown'
|
||||
import { Input } from 'ui-component/input/Input'
|
||||
import { SwitchInput } from 'ui-component/switch/Switch'
|
||||
import { JsonEditorInput } from 'ui-component/json/JsonEditor'
|
||||
import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser'
|
||||
|
||||
// ===========================|| NodeInputHandler ||=========================== //
|
||||
|
||||
const CredentialInputHandler = ({ inputParam, data, disabled = false }) => {
|
||||
const customization = useSelector((state) => state.customization)
|
||||
const ref = useRef(null)
|
||||
|
||||
const [showExpandDialog, setShowExpandDialog] = useState(false)
|
||||
const [expandDialogProps, setExpandDialogProps] = useState({})
|
||||
|
||||
const onExpandDialogClicked = (value, inputParam) => {
|
||||
const dialogProp = {
|
||||
value,
|
||||
inputParam,
|
||||
disabled,
|
||||
confirmButtonName: 'Save',
|
||||
cancelButtonName: 'Cancel'
|
||||
}
|
||||
setExpandDialogProps(dialogProp)
|
||||
setShowExpandDialog(true)
|
||||
}
|
||||
|
||||
const onExpandDialogSave = (newValue, inputParamName) => {
|
||||
setShowExpandDialog(false)
|
||||
data[inputParamName] = newValue
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{inputParam && (
|
||||
<>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<Typography>
|
||||
{inputParam.label}
|
||||
{!inputParam.optional && <span style={{ color: 'red' }}> *</span>}
|
||||
{inputParam.description && <TooltipWithParser style={{ marginLeft: 10 }} title={inputParam.description} />}
|
||||
</Typography>
|
||||
<div style={{ flexGrow: 1 }}></div>
|
||||
{inputParam.type === 'string' && inputParam.rows && (
|
||||
<IconButton
|
||||
size='small'
|
||||
sx={{
|
||||
height: 25,
|
||||
width: 25
|
||||
}}
|
||||
title='Expand'
|
||||
color='primary'
|
||||
onClick={() => onExpandDialogClicked(data[inputParam.name] ?? inputParam.default ?? '', inputParam)}
|
||||
>
|
||||
<IconArrowsMaximize />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
{inputParam.warning && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
borderRadius: 10,
|
||||
background: 'rgb(254,252,191)',
|
||||
padding: 10,
|
||||
marginTop: 10,
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<IconAlertTriangle size={36} color='orange' />
|
||||
<span style={{ color: 'rgb(116,66,16)', marginLeft: 10 }}>{inputParam.warning}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{inputParam.type === 'boolean' && (
|
||||
<SwitchInput
|
||||
disabled={disabled}
|
||||
onChange={(newValue) => (data[inputParam.name] = newValue)}
|
||||
value={data[inputParam.name] ?? inputParam.default ?? false}
|
||||
/>
|
||||
)}
|
||||
{(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && (
|
||||
<Input
|
||||
key={data[inputParam.name]}
|
||||
disabled={disabled}
|
||||
inputParam={inputParam}
|
||||
onChange={(newValue) => (data[inputParam.name] = newValue)}
|
||||
value={data[inputParam.name] ?? inputParam.default ?? ''}
|
||||
showDialog={showExpandDialog}
|
||||
dialogProps={expandDialogProps}
|
||||
onDialogCancel={() => setShowExpandDialog(false)}
|
||||
onDialogConfirm={(newValue, inputParamName) => onExpandDialogSave(newValue, inputParamName)}
|
||||
/>
|
||||
)}
|
||||
{inputParam.type === 'json' && (
|
||||
<JsonEditorInput
|
||||
disabled={disabled}
|
||||
onChange={(newValue) => (data[inputParam.name] = newValue)}
|
||||
value={data[inputParam.name] ?? inputParam.default ?? ''}
|
||||
isDarkMode={customization.isDarkMode}
|
||||
/>
|
||||
)}
|
||||
{inputParam.type === 'options' && (
|
||||
<Dropdown
|
||||
disabled={disabled}
|
||||
name={inputParam.name}
|
||||
options={inputParam.options}
|
||||
onSelect={(newValue) => (data[inputParam.name] = newValue)}
|
||||
value={data[inputParam.name] ?? inputParam.default ?? 'choose an option'}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
CredentialInputHandler.propTypes = {
|
||||
inputAnchor: PropTypes.object,
|
||||
inputParam: PropTypes.object,
|
||||
data: PropTypes.object,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
export default CredentialInputHandler
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import {
|
||||
List,
|
||||
ListItemButton,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Box,
|
||||
OutlinedInput,
|
||||
InputAdornment
|
||||
} from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import { IconSearch, IconX } from '@tabler/icons'
|
||||
|
||||
// const
|
||||
import { baseURL } from 'store/constant'
|
||||
|
||||
const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelected }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const customization = useSelector((state) => state.customization)
|
||||
|
||||
const theme = useTheme()
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
const [componentsCredentials, setComponentsCredentials] = useState([])
|
||||
|
||||
const filterSearch = (value) => {
|
||||
setSearchValue(value)
|
||||
setTimeout(() => {
|
||||
if (value) {
|
||||
const searchData = dialogProps.componentsCredentials.filter((crd) => crd.name.toLowerCase().includes(value.toLowerCase()))
|
||||
setComponentsCredentials(searchData)
|
||||
} else if (value === '') {
|
||||
setComponentsCredentials(dialogProps.componentsCredentials)
|
||||
}
|
||||
// scrollTop()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (show && dialogProps.componentsCredentials) {
|
||||
setComponentsCredentials(dialogProps.componentsCredentials)
|
||||
}
|
||||
}, [show, dialogProps])
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
fullWidth
|
||||
maxWidth='xs'
|
||||
open={show}
|
||||
onClose={onCancel}
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
{dialogProps.title}
|
||||
<Box sx={{ p: 2 }}>
|
||||
<OutlinedInput
|
||||
sx={{ width: '100%', pr: 2, pl: 2, my: 2 }}
|
||||
id='input-search-credential'
|
||||
value={searchValue}
|
||||
onChange={(e) => filterSearch(e.target.value)}
|
||||
placeholder='Search credential'
|
||||
startAdornment={
|
||||
<InputAdornment position='start'>
|
||||
<IconSearch stroke={1.5} size='1rem' color={theme.palette.grey[500]} />
|
||||
</InputAdornment>
|
||||
}
|
||||
endAdornment={
|
||||
<InputAdornment
|
||||
position='end'
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
color: theme.palette.grey[500],
|
||||
'&:hover': {
|
||||
color: theme.palette.grey[900]
|
||||
}
|
||||
}}
|
||||
title='Clear Search'
|
||||
>
|
||||
<IconX
|
||||
stroke={1.5}
|
||||
size='1rem'
|
||||
onClick={() => filterSearch('')}
|
||||
style={{
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
/>
|
||||
</InputAdornment>
|
||||
}
|
||||
aria-describedby='search-helper-text'
|
||||
inputProps={{
|
||||
'aria-label': 'weight'
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<List
|
||||
sx={{
|
||||
width: '100%',
|
||||
py: 0,
|
||||
borderRadius: '10px',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
maxWidth: 370
|
||||
},
|
||||
'& .MuiListItemSecondaryAction-root': {
|
||||
top: 22
|
||||
},
|
||||
'& .MuiDivider-root': {
|
||||
my: 0
|
||||
},
|
||||
'& .list-container': {
|
||||
pl: 7
|
||||
}
|
||||
}}
|
||||
>
|
||||
{[...componentsCredentials].map((componentCredential) => (
|
||||
<div key={componentCredential.name}>
|
||||
<ListItemButton
|
||||
onClick={() => onCredentialSelected(componentCredential)}
|
||||
sx={{ p: 0, borderRadius: `${customization.borderRadius}px` }}
|
||||
>
|
||||
<ListItem alignItems='center'>
|
||||
<ListItemAvatar>
|
||||
<div
|
||||
style={{
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 7,
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
alt={componentCredential.name}
|
||||
src={`${baseURL}/api/v1/components-credentials-icon/${componentCredential.name}`}
|
||||
/>
|
||||
</div>
|
||||
</ListItemAvatar>
|
||||
<ListItemText sx={{ ml: 1 }} primary={componentCredential.label} />
|
||||
</ListItem>
|
||||
</ListItemButton>
|
||||
</div>
|
||||
))}
|
||||
</List>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
CredentialListDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func,
|
||||
onCredentialSelected: PropTypes.func
|
||||
}
|
||||
|
||||
export default CredentialListDialog
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions'
|
||||
import moment from 'moment'
|
||||
|
||||
// material-ui
|
||||
import { Button, Box, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, IconButton } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
|
||||
// project imports
|
||||
import MainCard from 'ui-component/cards/MainCard'
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
import CredentialListDialog from './CredentialListDialog'
|
||||
import ConfirmDialog from 'ui-component/dialog/ConfirmDialog'
|
||||
import AddEditCredentialDialog from './AddEditCredentialDialog'
|
||||
|
||||
// API
|
||||
import credentialsApi from 'api/credentials'
|
||||
|
||||
// Hooks
|
||||
import useApi from 'hooks/useApi'
|
||||
import useConfirm from 'hooks/useConfirm'
|
||||
|
||||
// utils
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
// Icons
|
||||
import { IconTrash, IconEdit, IconX, IconPlus } from '@tabler/icons'
|
||||
import CredentialEmptySVG from 'assets/images/credential_empty.svg'
|
||||
|
||||
// const
|
||||
import { baseURL } from 'store/constant'
|
||||
|
||||
// ==============================|| Credentials ||============================== //
|
||||
|
||||
const Credentials = () => {
|
||||
const theme = useTheme()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
|
||||
const dispatch = useDispatch()
|
||||
useNotifier()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [showCredentialListDialog, setShowCredentialListDialog] = useState(false)
|
||||
const [credentialListDialogProps, setCredentialListDialogProps] = useState({})
|
||||
const [showSpecificCredentialDialog, setShowSpecificCredentialDialog] = useState(false)
|
||||
const [specificCredentialDialogProps, setSpecificCredentialDialogProps] = useState({})
|
||||
const [credentials, setCredentials] = useState([])
|
||||
const [componentsCredentials, setComponentsCredentials] = useState([])
|
||||
|
||||
const { confirm } = useConfirm()
|
||||
|
||||
const getAllCredentialsApi = useApi(credentialsApi.getAllCredentials)
|
||||
const getAllComponentsCredentialsApi = useApi(credentialsApi.getAllComponentsCredentials)
|
||||
|
||||
const listCredential = () => {
|
||||
const dialogProp = {
|
||||
title: 'Add New Credential',
|
||||
componentsCredentials
|
||||
}
|
||||
setCredentialListDialogProps(dialogProp)
|
||||
setShowCredentialListDialog(true)
|
||||
}
|
||||
|
||||
const addNew = (credentialComponent) => {
|
||||
const dialogProp = {
|
||||
type: 'ADD',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Add',
|
||||
credentialComponent
|
||||
}
|
||||
setSpecificCredentialDialogProps(dialogProp)
|
||||
setShowSpecificCredentialDialog(true)
|
||||
}
|
||||
|
||||
const edit = (credential) => {
|
||||
const dialogProp = {
|
||||
type: 'EDIT',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Save',
|
||||
data: credential
|
||||
}
|
||||
setSpecificCredentialDialogProps(dialogProp)
|
||||
setShowSpecificCredentialDialog(true)
|
||||
}
|
||||
|
||||
const deleteCredential = async (credential) => {
|
||||
const confirmPayload = {
|
||||
title: `Delete`,
|
||||
description: `Delete credential ${credential.name}?`,
|
||||
confirmButtonName: 'Delete',
|
||||
cancelButtonName: 'Cancel'
|
||||
}
|
||||
const isConfirmed = await confirm(confirmPayload)
|
||||
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
const deleteResp = await credentialsApi.deleteCredential(credential.id)
|
||||
if (deleteResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Credential deleted',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm()
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to delete Credential: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onCredentialSelected = (credentialComponent) => {
|
||||
setShowCredentialListDialog(false)
|
||||
addNew(credentialComponent)
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
setShowCredentialListDialog(false)
|
||||
setShowSpecificCredentialDialog(false)
|
||||
getAllCredentialsApi.request()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllCredentialsApi.request()
|
||||
getAllComponentsCredentialsApi.request()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllCredentialsApi.data) {
|
||||
setCredentials(getAllCredentialsApi.data)
|
||||
}
|
||||
}, [getAllCredentialsApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllComponentsCredentialsApi.data) {
|
||||
setComponentsCredentials(getAllComponentsCredentialsApi.data)
|
||||
}
|
||||
}, [getAllComponentsCredentialsApi.data])
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
||||
<Stack flexDirection='row'>
|
||||
<h1>Credentials </h1>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
|
||||
<StyledButton
|
||||
variant='contained'
|
||||
sx={{ color: 'white', mr: 1, height: 37 }}
|
||||
onClick={listCredential}
|
||||
startIcon={<IconPlus />}
|
||||
>
|
||||
Add Credential
|
||||
</StyledButton>
|
||||
</Stack>
|
||||
{credentials.length <= 0 && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '30vh', width: 'auto' }}
|
||||
src={CredentialEmptySVG}
|
||||
alt='CredentialEmptySVG'
|
||||
/>
|
||||
</Box>
|
||||
<div>No Credentials Yet</div>
|
||||
</Stack>
|
||||
)}
|
||||
{credentials.length > 0 && (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Last Updated</TableCell>
|
||||
<TableCell>Created</TableCell>
|
||||
<TableCell> </TableCell>
|
||||
<TableCell> </TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{credentials.map((credential, index) => (
|
||||
<TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||
<TableCell component='th' scope='row'>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 25,
|
||||
height: 25,
|
||||
marginRight: 10,
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
alt={credential.credentialName}
|
||||
src={`${baseURL}/api/v1/components-credentials-icon/${credential.credentialName}`}
|
||||
/>
|
||||
</div>
|
||||
{credential.name}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{moment(credential.updatedDate).format('DD-MMM-YY')}</TableCell>
|
||||
<TableCell>{moment(credential.createdDate).format('DD-MMM-YY')}</TableCell>
|
||||
<TableCell>
|
||||
<IconButton title='Edit' color='primary' onClick={() => edit(credential)}>
|
||||
<IconEdit />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton title='Delete' color='error' onClick={() => deleteCredential(credential)}>
|
||||
<IconTrash />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</MainCard>
|
||||
<CredentialListDialog
|
||||
show={showCredentialListDialog}
|
||||
dialogProps={credentialListDialogProps}
|
||||
onCancel={() => setShowCredentialListDialog(false)}
|
||||
onCredentialSelected={onCredentialSelected}
|
||||
></CredentialListDialog>
|
||||
<AddEditCredentialDialog
|
||||
show={showSpecificCredentialDialog}
|
||||
dialogProps={specificCredentialDialogProps}
|
||||
onCancel={() => setShowSpecificCredentialDialog(false)}
|
||||
onConfirm={onConfirm}
|
||||
></AddEditCredentialDialog>
|
||||
<ConfirmDialog />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Credentials
|
||||
Loading…
Reference in New Issue