Merge branch 'main' into chore/Allow-Built-In-Dep
This commit is contained in:
commit
6a2816798e
|
|
@ -14,6 +14,7 @@ DATABASE_PATH=/root/.flowise
|
|||
# DATABASE_USER=root
|
||||
# DATABASE_PASSWORD=mypassword
|
||||
# DATABASE_SSL=true
|
||||
# DATABASE_REJECT_UNAUTHORIZED=true
|
||||
# DATABASE_SSL_KEY_BASE64=<Self signed certificate in BASE64>
|
||||
|
||||
|
||||
|
|
@ -163,4 +164,14 @@ JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
|
|||
# REDIS_KEY=
|
||||
# REDIS_CA=
|
||||
# REDIS_KEEP_ALIVE=
|
||||
# ENABLE_BULLMQ_DASHBOARD=
|
||||
# ENABLE_BULLMQ_DASHBOARD=
|
||||
|
||||
|
||||
############################################################################################################
|
||||
############################################## SECURITY ####################################################
|
||||
############################################################################################################
|
||||
|
||||
# HTTP_DENY_LIST=
|
||||
# CUSTOM_MCP_SECURITY_CHECK=true
|
||||
# CUSTOM_MCP_PROTOCOL=sse #(stdio | sse)
|
||||
# TRUST_PROXY=true #(true | false | 1 | loopback| linklocal | uniquelocal | IP addresses | loopback, IP addresses)
|
||||
|
|
|
|||
|
|
@ -139,6 +139,12 @@ services:
|
|||
- REDIS_CA=${REDIS_CA}
|
||||
- REDIS_KEEP_ALIVE=${REDIS_KEEP_ALIVE}
|
||||
- ENABLE_BULLMQ_DASHBOARD=${ENABLE_BULLMQ_DASHBOARD}
|
||||
|
||||
# SECURITY
|
||||
- CUSTOM_MCP_SECURITY_CHECK=${CUSTOM_MCP_SECURITY_CHECK}
|
||||
- CUSTOM_MCP_PROTOCOL=${CUSTOM_MCP_PROTOCOL}
|
||||
- HTTP_DENY_LIST=${HTTP_DENY_LIST}
|
||||
- TRUST_PROXY=${TRUST_PROXY}
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:${PORT:-3000}/api/v1/ping']
|
||||
interval: 10s
|
||||
|
|
@ -276,6 +282,12 @@ services:
|
|||
- REDIS_CA=${REDIS_CA}
|
||||
- REDIS_KEEP_ALIVE=${REDIS_KEEP_ALIVE}
|
||||
- ENABLE_BULLMQ_DASHBOARD=${ENABLE_BULLMQ_DASHBOARD}
|
||||
|
||||
# SECURITY
|
||||
- CUSTOM_MCP_SECURITY_CHECK=${CUSTOM_MCP_SECURITY_CHECK}
|
||||
- CUSTOM_MCP_PROTOCOL=${CUSTOM_MCP_PROTOCOL}
|
||||
- HTTP_DENY_LIST=${HTTP_DENY_LIST}
|
||||
- TRUST_PROXY=${TRUST_PROXY}
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:${WORKER_PORT:-5566}/healthz']
|
||||
interval: 10s
|
||||
|
|
|
|||
|
|
@ -124,6 +124,12 @@ services:
|
|||
- REDIS_CA=${REDIS_CA}
|
||||
- REDIS_KEEP_ALIVE=${REDIS_KEEP_ALIVE}
|
||||
- ENABLE_BULLMQ_DASHBOARD=${ENABLE_BULLMQ_DASHBOARD}
|
||||
|
||||
# SECURITY
|
||||
- CUSTOM_MCP_SECURITY_CHECK=${CUSTOM_MCP_SECURITY_CHECK}
|
||||
- CUSTOM_MCP_PROTOCOL=${CUSTOM_MCP_PROTOCOL}
|
||||
- HTTP_DENY_LIST=${HTTP_DENY_LIST}
|
||||
- TRUST_PROXY=${TRUST_PROXY}
|
||||
ports:
|
||||
- '${PORT}:${PORT}'
|
||||
healthcheck:
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ DATABASE_PATH=/root/.flowise
|
|||
# DATABASE_USER=root
|
||||
# DATABASE_PASSWORD=mypassword
|
||||
# DATABASE_SSL=true
|
||||
# DATABASE_REJECT_UNAUTHORIZED=true
|
||||
# DATABASE_SSL_KEY_BASE64=<Self signed certificate in BASE64>
|
||||
|
||||
|
||||
|
|
@ -163,4 +164,14 @@ JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
|
|||
# REDIS_KEY=
|
||||
# REDIS_CA=
|
||||
# REDIS_KEEP_ALIVE=
|
||||
# ENABLE_BULLMQ_DASHBOARD=
|
||||
# ENABLE_BULLMQ_DASHBOARD=
|
||||
|
||||
|
||||
############################################################################################################
|
||||
############################################## SECURITY ####################################################
|
||||
############################################################################################################
|
||||
|
||||
# HTTP_DENY_LIST=
|
||||
# CUSTOM_MCP_SECURITY_CHECK=true
|
||||
# CUSTOM_MCP_PROTOCOL=sse #(stdio | sse)
|
||||
# TRUST_PROXY=true #(true | false | 1 | loopback| linklocal | uniquelocal | IP addresses | loopback, IP addresses)
|
||||
|
|
|
|||
|
|
@ -124,6 +124,12 @@ services:
|
|||
- REDIS_CA=${REDIS_CA}
|
||||
- REDIS_KEEP_ALIVE=${REDIS_KEEP_ALIVE}
|
||||
- ENABLE_BULLMQ_DASHBOARD=${ENABLE_BULLMQ_DASHBOARD}
|
||||
|
||||
# SECURITY
|
||||
- CUSTOM_MCP_SECURITY_CHECK=${CUSTOM_MCP_SECURITY_CHECK}
|
||||
- CUSTOM_MCP_PROTOCOL=${CUSTOM_MCP_PROTOCOL}
|
||||
- HTTP_DENY_LIST=${HTTP_DENY_LIST}
|
||||
- TRUST_PROXY=${TRUST_PROXY}
|
||||
ports:
|
||||
- '${WORKER_PORT}:${WORKER_PORT}'
|
||||
healthcheck:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { INodeParams, INodeCredential } from '../src/Interface'
|
||||
|
||||
class TeradataBearerTokenCredential implements INodeCredential {
|
||||
label: string
|
||||
name: string
|
||||
description: string
|
||||
version: number
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Teradata Bearer Token'
|
||||
this.name = 'teradataBearerToken'
|
||||
this.version = 1.0
|
||||
this.description =
|
||||
'Refer to <a target="_blank" href="https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/Teradata-Vector-Store-User-Guide/Setting-up-Vector-Store/Importing-Modules-Required-for-Vector-Store">official guide</a> on how to get Teradata Bearer Token'
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Token',
|
||||
name: 'token',
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { credClass: TeradataBearerTokenCredential }
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { INodeParams, INodeCredential } from '../src/Interface'
|
||||
|
||||
class TeradataTD2Credential implements INodeCredential {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Teradata TD2 Auth'
|
||||
this.name = 'teradataTD2Auth'
|
||||
this.version = 1.0
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Teradata TD2 Auth Username',
|
||||
name: 'tdUsername',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
label: 'Teradata TD2 Auth Password',
|
||||
name: 'tdPassword',
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { credClass: TeradataTD2Credential }
|
||||
|
|
@ -175,8 +175,7 @@ class CustomFunction_Agentflow implements INode {
|
|||
try {
|
||||
const response = await executeJavaScriptCode(javascriptFunction, sandbox, {
|
||||
libraries: ['axios'],
|
||||
streamOutput,
|
||||
timeout: 10000
|
||||
streamOutput
|
||||
})
|
||||
|
||||
let finalOutput = response
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ import {
|
|||
IServerSideEventStreamer
|
||||
} from '../../../src/Interface'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import { getCredentialData, getCredentialParam, processTemplateVariables } from '../../../src/utils'
|
||||
import JSON5 from 'json5'
|
||||
import { getCredentialData, getCredentialParam, processTemplateVariables, parseJsonBody } from '../../../src/utils'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { BaseMessageLike } from '@langchain/core/messages'
|
||||
import { updateFlowState } from '../utils'
|
||||
|
|
@ -168,7 +167,7 @@ class ExecuteFlow_Agentflow implements INode {
|
|||
let overrideConfig = nodeData.inputs?.executeFlowOverrideConfig
|
||||
if (typeof overrideConfig === 'string' && overrideConfig.startsWith('{') && overrideConfig.endsWith('}')) {
|
||||
try {
|
||||
overrideConfig = JSON5.parse(overrideConfig)
|
||||
overrideConfig = parseJsonBody(overrideConfig)
|
||||
} catch (parseError) {
|
||||
throw new Error(`Invalid JSON in executeFlowOverrideConfig: ${parseError.message}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Inter
|
|||
import { AxiosRequestConfig, Method, ResponseType } from 'axios'
|
||||
import FormData from 'form-data'
|
||||
import * as querystring from 'querystring'
|
||||
import { getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { getCredentialData, getCredentialParam, parseJsonBody } from '../../../src/utils'
|
||||
import { secureAxiosRequest } from '../../../src/httpSecurity'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
class HTTP_Agentflow implements INode {
|
||||
label: string
|
||||
|
|
@ -20,16 +19,6 @@ class HTTP_Agentflow implements INode {
|
|||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
private parseJsonBody(body: string): any {
|
||||
try {
|
||||
return JSON5.parse(body)
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Invalid JSON format in body. Original error: ${error.message}. Please ensure your JSON is properly formatted with quoted strings and valid escape sequences.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.label = 'HTTP'
|
||||
this.name = 'httpAgentflow'
|
||||
|
|
@ -285,7 +274,7 @@ class HTTP_Agentflow implements INode {
|
|||
if (method !== 'GET' && body) {
|
||||
switch (bodyType) {
|
||||
case 'json': {
|
||||
requestConfig.data = typeof body === 'string' ? this.parseJsonBody(body) : body
|
||||
requestConfig.data = typeof body === 'string' ? parseJsonBody(body) : body
|
||||
requestHeaders['Content-Type'] = 'application/json'
|
||||
break
|
||||
}
|
||||
|
|
@ -303,7 +292,7 @@ class HTTP_Agentflow implements INode {
|
|||
break
|
||||
}
|
||||
case 'xWwwFormUrlencoded':
|
||||
requestConfig.data = querystring.stringify(typeof body === 'string' ? this.parseJsonBody(body) : body)
|
||||
requestConfig.data = querystring.stringify(typeof body === 'string' ? parseJsonBody(body) : body)
|
||||
requestHeaders['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import JSON5 from 'json5'
|
||||
import { parseJsonBody } from '../../../src/utils'
|
||||
|
||||
class Iteration_Agentflow implements INode {
|
||||
label: string
|
||||
|
|
@ -42,10 +42,10 @@ class Iteration_Agentflow implements INode {
|
|||
// Helper function to clean JSON strings with redundant backslashes
|
||||
const safeParseJson = (str: string): string => {
|
||||
try {
|
||||
return JSON5.parse(str)
|
||||
return parseJsonBody(str)
|
||||
} catch {
|
||||
// Try parsing after cleaning
|
||||
return JSON5.parse(str.replace(/\\(["'[\]{}])/g, '$1'))
|
||||
return parseJsonBody(str.replace(/\\(["'[\]{}])/g, '$1'))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.72492 9.35559L6.5 24L15 5.5L6.33616 7.15025C5.30261 7.34712 4.59832 8.3111 4.72492 9.35559Z" fill="#97D700" stroke="#97D700" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M26.6204 20.5943L26.5699 20.6161L19.5 4.5L24.0986 4.14626C25.163 4.06438 26.1041 4.83296 26.2365 5.8923L27.8137 18.5094C27.9241 19.3925 27.4377 20.2422 26.6204 20.5943Z" fill="#71C5E8" stroke="#71C5E8" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M17.5 10L9.5 28L23 22L17.5 10Z" fill="#FF9114" stroke="#FF9114" stroke-width="2" stroke-linejoin="round"/>
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_267_4154)">
|
||||
<path d="M114.695 0H196.97C198.643 0 200 1.35671 200 3.03031V128.766C200 131.778 196.083 132.945 194.434 130.425L112.159 4.68953C110.841 2.67412 112.287 0 114.695 0Z" fill="#246DFF"/>
|
||||
<path d="M85.3048 0H3.0303C1.35671 0 0 1.35671 0 3.03031V128.766C0 131.778 3.91698 132.945 5.566 130.425L87.8405 4.68953C89.1593 2.67412 87.7134 0 85.3048 0Z" fill="#20A34E"/>
|
||||
<path d="M98.5909 100.668L5.12683 194.835C3.22886 196.747 4.58334 200 7.27759 200H192.8C195.483 200 196.842 196.77 194.967 194.852L102.908 100.685C101.726 99.4749 99.7824 99.4676 98.5909 100.668Z" fill="#F86606"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_267_4154">
|
||||
<rect width="200" height="200" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 653 B After Width: | Height: | Size: 827 B |
|
|
@ -119,8 +119,7 @@ class CustomDocumentLoader_DocumentLoaders implements INode {
|
|||
|
||||
try {
|
||||
const response = await executeJavaScriptCode(javascriptFunction, sandbox, {
|
||||
libraries: ['axios'],
|
||||
timeout: 10000
|
||||
libraries: ['axios']
|
||||
})
|
||||
|
||||
if (output === 'document' && Array.isArray(response)) {
|
||||
|
|
|
|||
|
|
@ -130,8 +130,7 @@ class ChatPromptTemplate_Prompts implements INode {
|
|||
|
||||
try {
|
||||
const response = await executeJavaScriptCode(messageHistoryCode, sandbox, {
|
||||
libraries: ['axios', '@langchain/core'],
|
||||
timeout: 10000
|
||||
libraries: ['axios', '@langchain/core']
|
||||
})
|
||||
|
||||
const parsedResponse = JSON.parse(response)
|
||||
|
|
|
|||
|
|
@ -940,9 +940,7 @@ const getReturnOutput = async (nodeData: INodeData, input: string, options: ICom
|
|||
const sandbox = createCodeExecutionSandbox(input, variables, flow)
|
||||
|
||||
try {
|
||||
const response = await executeJavaScriptCode(updateStateMemoryCode, sandbox, {
|
||||
timeout: 10000
|
||||
})
|
||||
const response = await executeJavaScriptCode(updateStateMemoryCode, sandbox)
|
||||
|
||||
if (typeof response !== 'object') throw new Error('Return output must be an object')
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -282,9 +282,7 @@ const runCondition = async (nodeData: INodeData, input: string, options: ICommon
|
|||
const sandbox = createCodeExecutionSandbox(input, variables, flow)
|
||||
|
||||
try {
|
||||
const response = await executeJavaScriptCode(conditionFunction, sandbox, {
|
||||
timeout: 10000
|
||||
})
|
||||
const response = await executeJavaScriptCode(conditionFunction, sandbox)
|
||||
|
||||
if (typeof response !== 'string') throw new Error('Condition function must return a string')
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -549,9 +549,7 @@ const runCondition = async (
|
|||
const sandbox = createCodeExecutionSandbox(input, variables, flow)
|
||||
|
||||
try {
|
||||
const response = await executeJavaScriptCode(conditionFunction, sandbox, {
|
||||
timeout: 10000
|
||||
})
|
||||
const response = await executeJavaScriptCode(conditionFunction, sandbox)
|
||||
|
||||
if (typeof response !== 'string') throw new Error('Condition function must return a string')
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -166,9 +166,7 @@ class CustomFunction_SeqAgents implements INode {
|
|||
const sandbox = createCodeExecutionSandbox(input, variables, flow, additionalSandbox)
|
||||
|
||||
try {
|
||||
const response = await executeJavaScriptCode(javascriptFunction, sandbox, {
|
||||
timeout: 10000
|
||||
})
|
||||
const response = await executeJavaScriptCode(javascriptFunction, sandbox)
|
||||
|
||||
if (returnValueAs === 'stateObj') {
|
||||
if (typeof response !== 'object') {
|
||||
|
|
|
|||
|
|
@ -264,8 +264,7 @@ class ExecuteFlow_SeqAgents implements INode {
|
|||
|
||||
try {
|
||||
let response = await executeJavaScriptCode(code, sandbox, {
|
||||
useSandbox: false,
|
||||
timeout: 10000
|
||||
useSandbox: false
|
||||
})
|
||||
|
||||
if (typeof response === 'object') {
|
||||
|
|
|
|||
|
|
@ -712,9 +712,7 @@ const getReturnOutput = async (nodeData: INodeData, input: string, options: ICom
|
|||
const sandbox = createCodeExecutionSandbox(input, variables, flow)
|
||||
|
||||
try {
|
||||
const response = await executeJavaScriptCode(updateStateMemoryCode, sandbox, {
|
||||
timeout: 10000
|
||||
})
|
||||
const response = await executeJavaScriptCode(updateStateMemoryCode, sandbox)
|
||||
|
||||
if (typeof response !== 'object') throw new Error('Return output must be an object')
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -204,9 +204,7 @@ class State_SeqAgents implements INode {
|
|||
const sandbox = createCodeExecutionSandbox('', variables, flow)
|
||||
|
||||
try {
|
||||
const response = await executeJavaScriptCode(`return ${stateMemoryCode}`, sandbox, {
|
||||
timeout: 10000
|
||||
})
|
||||
const response = await executeJavaScriptCode(`return ${stateMemoryCode}`, sandbox)
|
||||
|
||||
if (typeof response !== 'object') throw new Error('State must be an object')
|
||||
const returnOutput: ISeqAgentNode = {
|
||||
|
|
|
|||
|
|
@ -575,9 +575,7 @@ const getReturnOutput = async (
|
|||
const sandbox = createCodeExecutionSandbox(input, variables, flow)
|
||||
|
||||
try {
|
||||
const response = await executeJavaScriptCode(updateStateMemoryCode, sandbox, {
|
||||
timeout: 10000
|
||||
})
|
||||
const response = await executeJavaScriptCode(updateStateMemoryCode, sandbox)
|
||||
|
||||
if (typeof response !== 'object') throw new Error('Return output must be an object')
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -396,9 +396,7 @@ export const checkMessageHistory = async (
|
|||
const sandbox = createCodeExecutionSandbox('', variables, flow)
|
||||
|
||||
try {
|
||||
const response = await executeJavaScriptCode(messageHistory, sandbox, {
|
||||
timeout: 10000
|
||||
})
|
||||
const response = await executeJavaScriptCode(messageHistory, sandbox)
|
||||
|
||||
if (!Array.isArray(response)) throw new Error('Returned message history must be an array')
|
||||
if (sysPrompt) {
|
||||
|
|
|
|||
|
|
@ -364,8 +364,7 @@ try {
|
|||
const sandbox = createCodeExecutionSandbox('', [], {}, additionalSandbox)
|
||||
|
||||
let response = await executeJavaScriptCode(code, sandbox, {
|
||||
useSandbox: false,
|
||||
timeout: 10000
|
||||
useSandbox: false
|
||||
})
|
||||
|
||||
if (typeof response === 'object') {
|
||||
|
|
|
|||
|
|
@ -372,8 +372,7 @@ try {
|
|||
const sandbox = createCodeExecutionSandbox('', [], {}, additionalSandbox)
|
||||
|
||||
let response = await executeJavaScriptCode(code, sandbox, {
|
||||
useSandbox: false,
|
||||
timeout: 10000
|
||||
useSandbox: false
|
||||
})
|
||||
|
||||
if (typeof response === 'object') {
|
||||
|
|
|
|||
|
|
@ -124,9 +124,7 @@ export class DynamicStructuredTool<
|
|||
|
||||
const sandbox = createCodeExecutionSandbox('', this.variables || [], flow, additionalSandbox)
|
||||
|
||||
let response = await executeJavaScriptCode(this.code, sandbox, {
|
||||
timeout: 10000
|
||||
})
|
||||
let response = await executeJavaScriptCode(this.code, sandbox)
|
||||
|
||||
if (typeof response === 'object') {
|
||||
response = JSON.stringify(response)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { Tool } from '@langchain/core/tools'
|
||||
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface'
|
||||
import { MCPToolkit } from '../core'
|
||||
import { getVars, prepareSandboxVars } from '../../../../src/utils'
|
||||
import { MCPToolkit, validateMCPServerConfig } from '../core'
|
||||
import { getVars, prepareSandboxVars, parseJsonBody } from '../../../../src/utils'
|
||||
import { DataSource } from 'typeorm'
|
||||
import hash from 'object-hash'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
const mcpServerConfig = `{
|
||||
"command": "npx",
|
||||
|
|
@ -75,8 +74,8 @@ class Custom_MCP implements INode {
|
|||
},
|
||||
placeholder: mcpServerConfig,
|
||||
warning:
|
||||
process.env.CUSTOM_MCP_SECURITY_CHECK === 'true'
|
||||
? 'In next release, only Remote MCP with url is supported. Read more <a href="https://docs.flowiseai.com/tutorials/tools-and-mcp#streamable-http-recommended" target="_blank">here</a>'
|
||||
process.env.CUSTOM_MCP_PROTOCOL === 'sse'
|
||||
? 'Only Remote MCP with url is supported. Read more <a href="https://docs.flowiseai.com/tutorials/tools-and-mcp#streamable-http-recommended" target="_blank">here</a>'
|
||||
: undefined
|
||||
},
|
||||
{
|
||||
|
|
@ -174,6 +173,14 @@ class Custom_MCP implements INode {
|
|||
serverParams = JSON.parse(serverParamsString)
|
||||
}
|
||||
|
||||
if (process.env.CUSTOM_MCP_SECURITY_CHECK !== 'false') {
|
||||
try {
|
||||
validateMCPServerConfig(serverParams)
|
||||
} catch (error) {
|
||||
throw new Error(`Security validation failed: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Compatible with stdio and SSE
|
||||
let toolkit: MCPToolkit
|
||||
if (process.env.CUSTOM_MCP_PROTOCOL === 'sse') {
|
||||
|
|
@ -262,7 +269,7 @@ function substituteVariablesInString(str: string, sandbox: any): string {
|
|||
|
||||
function convertToValidJSONString(inputString: string) {
|
||||
try {
|
||||
const jsObject = JSON5.parse(inputString)
|
||||
const jsObject = parseJsonBody(inputString)
|
||||
return JSON.stringify(jsObject, null, 2)
|
||||
} catch (error) {
|
||||
console.error('Error converting to JSON:', error)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Tool } from '@langchain/core/tools'
|
||||
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface'
|
||||
import { getNodeModulesPackagePath } from '../../../../src/utils'
|
||||
import { MCPToolkit, validateArgsForLocalFileAccess } from '../core'
|
||||
import { MCPToolkit, validateMCPServerConfig } from '../core'
|
||||
|
||||
class Supergateway_MCP implements INode {
|
||||
label: string
|
||||
|
|
@ -106,9 +106,9 @@ class Supergateway_MCP implements INode {
|
|||
args: [packagePath, ...processedArgs]
|
||||
}
|
||||
|
||||
if (process.env.CUSTOM_MCP_SECURITY_CHECK === 'true') {
|
||||
if (process.env.CUSTOM_MCP_SECURITY_CHECK !== 'false') {
|
||||
try {
|
||||
validateArgsForLocalFileAccess(processedArgs)
|
||||
validateMCPServerConfig(serverParams)
|
||||
} catch (error) {
|
||||
throw new Error(`Security validation failed: ${error.message}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
import { Tool } from '@langchain/core/tools'
|
||||
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../../src/Interface'
|
||||
import { getCredentialData, getCredentialParam } from '../../../../src/utils'
|
||||
import { MCPToolkit } from '../core'
|
||||
import hash from 'object-hash'
|
||||
|
||||
class Teradata_MCP implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
documentation: string
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Teradata MCP'
|
||||
this.name = 'teradataMCP'
|
||||
this.version = 1.0
|
||||
this.type = 'Teradata MCP Tool'
|
||||
this.icon = 'teradata.svg'
|
||||
this.category = 'Tools (MCP)'
|
||||
this.description = 'MCP Server for Teradata (remote HTTP streamable)'
|
||||
this.documentation = 'https://github.com/Teradata/teradata-mcp-server'
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['teradataTD2Auth', 'teradataBearerToken'],
|
||||
description: 'Needed when using Teradata MCP server with authentication'
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'MCP Server URL',
|
||||
name: 'mcpUrl',
|
||||
type: 'string',
|
||||
placeholder: 'http://teradata-mcp-server:8001/mcp',
|
||||
description: 'URL of your Teradata MCP server',
|
||||
optional: false
|
||||
},
|
||||
{
|
||||
label: 'Bearer Token',
|
||||
name: 'bearerToken',
|
||||
type: 'string',
|
||||
optional: true,
|
||||
description: 'Optional to override Default set credentials'
|
||||
},
|
||||
{
|
||||
label: 'Available Actions',
|
||||
name: 'mcpActions',
|
||||
type: 'asyncMultiOptions',
|
||||
loadMethod: 'listActions',
|
||||
refresh: true
|
||||
}
|
||||
]
|
||||
this.baseClasses = ['Tool']
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
loadMethods = {
|
||||
listActions: async (nodeData: INodeData, options: ICommonObject): Promise<INodeOptionsValue[]> => {
|
||||
try {
|
||||
const toolset = await this.getTools(nodeData, options)
|
||||
toolset.sort((a: any, b: any) => a.name.localeCompare(b.name))
|
||||
return toolset.map(({ name, ...rest }) => ({
|
||||
label: name.toUpperCase(),
|
||||
name: name,
|
||||
description: rest.description || name
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('Error listing actions:', error)
|
||||
return [
|
||||
{
|
||||
label: 'No Available Actions',
|
||||
name: 'error',
|
||||
description: 'No available actions, please check your MCP server URL and credentials, then refresh.'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const tools = await this.getTools(nodeData, options)
|
||||
const _mcpActions = nodeData.inputs?.mcpActions
|
||||
let mcpActions = []
|
||||
if (_mcpActions) {
|
||||
try {
|
||||
mcpActions = typeof _mcpActions === 'string' ? JSON.parse(_mcpActions) : _mcpActions
|
||||
} catch (error) {
|
||||
console.error('Error parsing mcp actions:', error)
|
||||
}
|
||||
}
|
||||
return tools.filter((tool: any) => mcpActions.includes(tool.name))
|
||||
}
|
||||
|
||||
async getTools(nodeData: INodeData, options: ICommonObject): Promise<Tool[]> {
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const mcpUrl = nodeData.inputs?.mcpUrl || 'http://teradata-mcp-server:8001/mcp'
|
||||
if (!mcpUrl) {
|
||||
throw new Error('Missing MCP Server URL')
|
||||
}
|
||||
// Determine auth method from credentials
|
||||
let serverParams: any = {
|
||||
url: mcpUrl,
|
||||
headers: {}
|
||||
}
|
||||
// Get Bearer token from node input (from agent flow) or credential store
|
||||
const bearerToken = nodeData.inputs?.bearerToken || getCredentialParam('token', credentialData, nodeData)
|
||||
const username = getCredentialParam('tdUsername', credentialData, nodeData)
|
||||
const password = getCredentialParam('tdPassword', credentialData, nodeData)
|
||||
|
||||
if (bearerToken) {
|
||||
serverParams.headers['Authorization'] = `Bearer ${bearerToken}`
|
||||
} else if (username && password) {
|
||||
serverParams.headers['Authorization'] = 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64')
|
||||
} else {
|
||||
throw new Error('Missing credentials: provide Bearer token from flow/credentials OR username/password from credentials')
|
||||
}
|
||||
const workspaceId = options?.searchOptions?.workspaceId?._value || options?.workspaceId || 'tdws_default'
|
||||
let sandbox: ICommonObject = {}
|
||||
const cacheKey = hash({ workspaceId, serverParams, sandbox })
|
||||
if (options.cachePool) {
|
||||
const cachedResult = await options.cachePool.getMCPCache(cacheKey)
|
||||
if (cachedResult) {
|
||||
if (cachedResult.tools.length > 0) {
|
||||
return cachedResult.tools
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use SSE for remote HTTP MCP servers
|
||||
const toolkit = new MCPToolkit(serverParams, 'sse')
|
||||
await toolkit.initialize()
|
||||
const tools = toolkit.tools ?? []
|
||||
if (options.cachePool) {
|
||||
await options.cachePool.addMCPCache(cacheKey, { toolkit, tools })
|
||||
}
|
||||
return tools as Tool[]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: Teradata_MCP }
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_15769_12621)">
|
||||
<path d="M49.3232 8H14.6768C13.1984 8 12 9.19843 12 10.6768V45.3232C12 46.8016 13.1984 48 14.6768 48H49.3232C50.8016 48 52 46.8016 52 45.3232V10.6768C52 9.19843 50.8016 8 49.3232 8Z" fill="#FF5F02"/>
|
||||
<path d="M25.098 32.467V15.5882H30.1292V20.2286H35.7465V24.6834H30.1292V32.467C30.1292 34.4794 31.1745 35.1364 32.6447 35.1364H35.7391V39.5863H32.6447C27.4915 39.5814 25.098 37.3369 25.098 32.467Z" fill="white"/>
|
||||
<path d="M37.8688 37.376C37.8688 36.668 38.1501 35.989 38.6507 35.4884C39.1513 34.9878 39.8303 34.7066 40.5383 34.7066C41.2462 34.7066 41.9252 34.9878 42.4258 35.4884C42.9265 35.989 43.2077 36.668 43.2077 37.376C43.2077 38.084 42.9265 38.7629 42.4258 39.2636C41.9252 39.7642 41.2462 40.0454 40.5383 40.0454C39.8303 40.0454 39.1513 39.7642 38.6507 39.2636C38.1501 38.7629 37.8688 38.084 37.8688 37.376Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_15769_12621" x="0" y="0" width="64" height="64" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="6"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_15769_12621"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_15769_12621" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -219,3 +219,67 @@ export const validateArgsForLocalFileAccess = (args: string[]): void => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const validateCommandInjection = (args: string[]): void => {
|
||||
const dangerousPatterns = [
|
||||
// Shell metacharacters
|
||||
/[;&|`$(){}[\]<>]/,
|
||||
// Command chaining
|
||||
/&&|\|\||;;/,
|
||||
// Redirections
|
||||
/>>|<<|>/,
|
||||
// Backticks and command substitution
|
||||
/`|\$\(/,
|
||||
// Process substitution
|
||||
/<\(|>\(/
|
||||
]
|
||||
|
||||
for (const arg of args) {
|
||||
if (typeof arg !== 'string') continue
|
||||
|
||||
for (const pattern of dangerousPatterns) {
|
||||
if (pattern.test(arg)) {
|
||||
throw new Error(`Argument contains potentially dangerous characters: "${arg}"`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const validateEnvironmentVariables = (env: Record<string, any>): void => {
|
||||
const dangerousEnvVars = ['PATH', 'LD_LIBRARY_PATH', 'DYLD_LIBRARY_PATH']
|
||||
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
if (dangerousEnvVars.includes(key)) {
|
||||
throw new Error(`Environment variable '${key}' modification is not allowed`)
|
||||
}
|
||||
|
||||
if (typeof value === 'string' && value.includes('\0')) {
|
||||
throw new Error(`Environment variable '${key}' contains null byte`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const validateMCPServerConfig = (serverParams: any): void => {
|
||||
// Validate the entire server configuration
|
||||
if (!serverParams || typeof serverParams !== 'object') {
|
||||
throw new Error('Invalid server configuration')
|
||||
}
|
||||
|
||||
// Command allowlist - only allow specific safe commands
|
||||
const allowedCommands = ['node', 'npx', 'python', 'python3', 'docker']
|
||||
|
||||
if (serverParams.command && !allowedCommands.includes(serverParams.command)) {
|
||||
throw new Error(`Command '${serverParams.command}' is not allowed. Allowed commands: ${allowedCommands.join(', ')}`)
|
||||
}
|
||||
|
||||
// Validate arguments if present
|
||||
if (serverParams.args && Array.isArray(serverParams.args)) {
|
||||
validateArgsForLocalFileAccess(serverParams.args)
|
||||
validateCommandInjection(serverParams.args)
|
||||
}
|
||||
|
||||
// Validate environment variables
|
||||
if (serverParams.env) {
|
||||
validateEnvironmentVariables(serverParams.env)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -253,9 +253,7 @@ export class DynamicStructuredTool<
|
|||
|
||||
const sandbox = createCodeExecutionSandbox('', this.variables || [], flow, additionalSandbox)
|
||||
|
||||
let response = await executeJavaScriptCode(this.customCode || defaultCode, sandbox, {
|
||||
timeout: 10000
|
||||
})
|
||||
let response = await executeJavaScriptCode(this.customCode || defaultCode, sandbox)
|
||||
|
||||
if (typeof response === 'object') {
|
||||
response = JSON.stringify(response)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, stripHTMLFromToolInput } from '../../../src/utils'
|
||||
import { getBaseClasses, stripHTMLFromToolInput, parseJsonBody } from '../../../src/utils'
|
||||
import { desc, RequestParameters, RequestsDeleteTool } from './core'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
const codeExample = `{
|
||||
"id": {
|
||||
|
|
@ -131,7 +130,7 @@ class RequestsDelete_Tools implements INode {
|
|||
if (queryParamsSchema) obj.queryParamsSchema = queryParamsSchema
|
||||
if (maxOutputLength) obj.maxOutputLength = parseInt(maxOutputLength, 10)
|
||||
if (headers) {
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : JSON5.parse(stripHTMLFromToolInput(headers))
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : parseJsonBody(stripHTMLFromToolInput(headers))
|
||||
obj.headers = parsedHeaders
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { z } from 'zod'
|
||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||
import { secureFetch } from '../../../src/httpSecurity'
|
||||
import JSON5 from 'json5'
|
||||
import { parseJsonBody } from '../../../src/utils'
|
||||
|
||||
export const desc = `Use this when you need to execute a DELETE request to remove data from a website.`
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ const createRequestsDeleteSchema = (queryParamsSchema?: string) => {
|
|||
// If queryParamsSchema is provided, parse it and add dynamic query params
|
||||
if (queryParamsSchema) {
|
||||
try {
|
||||
const parsedSchema = JSON5.parse(queryParamsSchema)
|
||||
const parsedSchema = parseJsonBody(queryParamsSchema)
|
||||
const queryParamsObject: Record<string, z.ZodTypeAny> = {}
|
||||
|
||||
Object.entries(parsedSchema).forEach(([key, config]: [string, any]) => {
|
||||
|
|
@ -109,7 +109,7 @@ export class RequestsDeleteTool extends DynamicStructuredTool {
|
|||
|
||||
if (this.queryParamsSchema && params.queryParams && Object.keys(params.queryParams).length > 0) {
|
||||
try {
|
||||
const parsedSchema = JSON5.parse(this.queryParamsSchema)
|
||||
const parsedSchema = parseJsonBody(this.queryParamsSchema)
|
||||
const pathParams: Array<{ key: string; value: string }> = []
|
||||
|
||||
Object.entries(params.queryParams).forEach(([key, value]) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, stripHTMLFromToolInput } from '../../../src/utils'
|
||||
import { getBaseClasses, stripHTMLFromToolInput, parseJsonBody } from '../../../src/utils'
|
||||
import { desc, RequestParameters, RequestsGetTool } from './core'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
const codeExample = `{
|
||||
"id": {
|
||||
|
|
@ -131,7 +130,7 @@ class RequestsGet_Tools implements INode {
|
|||
if (queryParamsSchema) obj.queryParamsSchema = queryParamsSchema
|
||||
if (maxOutputLength) obj.maxOutputLength = parseInt(maxOutputLength, 10)
|
||||
if (headers) {
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : JSON5.parse(stripHTMLFromToolInput(headers))
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : parseJsonBody(stripHTMLFromToolInput(headers))
|
||||
obj.headers = parsedHeaders
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { z } from 'zod'
|
||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||
import { secureFetch } from '../../../src/httpSecurity'
|
||||
import JSON5 from 'json5'
|
||||
import { parseJsonBody } from '../../../src/utils'
|
||||
|
||||
export const desc = `Use this when you need to execute a GET request to get data from a website.`
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ const createRequestsGetSchema = (queryParamsSchema?: string) => {
|
|||
// If queryParamsSchema is provided, parse it and add dynamic query params
|
||||
if (queryParamsSchema) {
|
||||
try {
|
||||
const parsedSchema = JSON5.parse(queryParamsSchema)
|
||||
const parsedSchema = parseJsonBody(queryParamsSchema)
|
||||
const queryParamsObject: Record<string, z.ZodTypeAny> = {}
|
||||
|
||||
Object.entries(parsedSchema).forEach(([key, config]: [string, any]) => {
|
||||
|
|
@ -109,7 +109,7 @@ export class RequestsGetTool extends DynamicStructuredTool {
|
|||
|
||||
if (this.queryParamsSchema && params.queryParams && Object.keys(params.queryParams).length > 0) {
|
||||
try {
|
||||
const parsedSchema = JSON5.parse(this.queryParamsSchema)
|
||||
const parsedSchema = parseJsonBody(this.queryParamsSchema)
|
||||
const pathParams: Array<{ key: string; value: string }> = []
|
||||
|
||||
Object.entries(params.queryParams).forEach(([key, value]) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, stripHTMLFromToolInput } from '../../../src/utils'
|
||||
import { getBaseClasses, stripHTMLFromToolInput, parseJsonBody } from '../../../src/utils'
|
||||
import { RequestParameters, desc, RequestsPostTool } from './core'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
const codeExample = `{
|
||||
"name": {
|
||||
|
|
@ -141,11 +140,11 @@ class RequestsPost_Tools implements INode {
|
|||
if (bodySchema) obj.bodySchema = stripHTMLFromToolInput(bodySchema)
|
||||
if (maxOutputLength) obj.maxOutputLength = parseInt(maxOutputLength, 10)
|
||||
if (headers) {
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : JSON5.parse(stripHTMLFromToolInput(headers))
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : parseJsonBody(stripHTMLFromToolInput(headers))
|
||||
obj.headers = parsedHeaders
|
||||
}
|
||||
if (body) {
|
||||
const parsedBody = typeof body === 'object' ? body : JSON5.parse(body)
|
||||
const parsedBody = typeof body === 'object' ? body : parseJsonBody(body)
|
||||
obj.body = parsedBody
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { z } from 'zod'
|
||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||
import { secureFetch } from '../../../src/httpSecurity'
|
||||
import JSON5 from 'json5'
|
||||
import { parseJsonBody } from '../../../src/utils'
|
||||
|
||||
export const desc = `Use this when you want to execute a POST request to create or update a resource.`
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ const createRequestsPostSchema = (bodySchema?: string) => {
|
|||
// If bodySchema is provided, parse it and add dynamic body params
|
||||
if (bodySchema) {
|
||||
try {
|
||||
const parsedSchema = JSON5.parse(bodySchema)
|
||||
const parsedSchema = parseJsonBody(bodySchema)
|
||||
const bodyParamsObject: Record<string, z.ZodTypeAny> = {}
|
||||
|
||||
Object.entries(parsedSchema).forEach(([key, config]: [string, any]) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, stripHTMLFromToolInput } from '../../../src/utils'
|
||||
import { getBaseClasses, stripHTMLFromToolInput, parseJsonBody } from '../../../src/utils'
|
||||
import { RequestParameters, desc, RequestsPutTool } from './core'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
const codeExample = `{
|
||||
"name": {
|
||||
|
|
@ -141,11 +140,11 @@ class RequestsPut_Tools implements INode {
|
|||
if (bodySchema) obj.bodySchema = stripHTMLFromToolInput(bodySchema)
|
||||
if (maxOutputLength) obj.maxOutputLength = parseInt(maxOutputLength, 10)
|
||||
if (headers) {
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : JSON5.parse(stripHTMLFromToolInput(headers))
|
||||
const parsedHeaders = typeof headers === 'object' ? headers : parseJsonBody(stripHTMLFromToolInput(headers))
|
||||
obj.headers = parsedHeaders
|
||||
}
|
||||
if (body) {
|
||||
const parsedBody = typeof body === 'object' ? body : JSON5.parse(body)
|
||||
const parsedBody = typeof body === 'object' ? body : parseJsonBody(body)
|
||||
obj.body = parsedBody
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { z } from 'zod'
|
||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||
import { secureFetch } from '../../../src/httpSecurity'
|
||||
import JSON5 from 'json5'
|
||||
import { parseJsonBody } from '../../../src/utils'
|
||||
|
||||
export const desc = `Use this when you want to execute a PUT request to update or replace a resource.`
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ const createRequestsPutSchema = (bodySchema?: string) => {
|
|||
// If bodySchema is provided, parse it and add dynamic body params
|
||||
if (bodySchema) {
|
||||
try {
|
||||
const parsedSchema = JSON5.parse(bodySchema)
|
||||
const parsedSchema = parseJsonBody(bodySchema)
|
||||
const bodyParamsObject: Record<string, z.ZodTypeAny> = {}
|
||||
|
||||
Object.entries(parsedSchema).forEach(([key, config]: [string, any]) => {
|
||||
|
|
|
|||
|
|
@ -132,9 +132,7 @@ class CustomFunction_Utilities implements INode {
|
|||
const sandbox = createCodeExecutionSandbox(input, variables, flow, additionalSandbox)
|
||||
|
||||
try {
|
||||
const response = await executeJavaScriptCode(javascriptFunction, sandbox, {
|
||||
timeout: 10000
|
||||
})
|
||||
const response = await executeJavaScriptCode(javascriptFunction, sandbox)
|
||||
|
||||
if (typeof response === 'string' && !isEndingNode) {
|
||||
return handleEscapeCharacters(response, false)
|
||||
|
|
|
|||
|
|
@ -131,16 +131,12 @@ class IfElseFunction_Utilities implements INode {
|
|||
const sandbox = createCodeExecutionSandbox(input, variables, flow, additionalSandbox)
|
||||
|
||||
try {
|
||||
const responseTrue = await executeJavaScriptCode(ifFunction, sandbox, {
|
||||
timeout: 10000
|
||||
})
|
||||
const responseTrue = await executeJavaScriptCode(ifFunction, sandbox)
|
||||
|
||||
if (responseTrue)
|
||||
return { output: typeof responseTrue === 'string' ? handleEscapeCharacters(responseTrue, false) : responseTrue, type: true }
|
||||
|
||||
const responseFalse = await executeJavaScriptCode(elseFunction, sandbox, {
|
||||
timeout: 10000
|
||||
})
|
||||
const responseFalse = await executeJavaScriptCode(elseFunction, sandbox)
|
||||
|
||||
return { output: typeof responseFalse === 'string' ? handleEscapeCharacters(responseFalse, false) : responseFalse, type: false }
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -1408,7 +1408,7 @@ const parseOutput = (output: any): any => {
|
|||
// Check if it looks like JSON (starts with { or [)
|
||||
if ((trimmedOutput.startsWith('{') && trimmedOutput.endsWith('}')) || (trimmedOutput.startsWith('[') && trimmedOutput.endsWith(']'))) {
|
||||
try {
|
||||
const parsedOutput = JSON5.parse(trimmedOutput)
|
||||
const parsedOutput = parseJsonBody(trimmedOutput)
|
||||
return parsedOutput
|
||||
} catch (e) {
|
||||
return output
|
||||
|
|
@ -1439,6 +1439,10 @@ export const executeJavaScriptCode = async (
|
|||
): Promise<any> => {
|
||||
const { timeout = 300000, useSandbox = true, streamOutput, libraries = [], nodeVMOptions = {} } = options
|
||||
const shouldUseSandbox = useSandbox && process.env.E2B_APIKEY
|
||||
let timeoutMs = timeout
|
||||
if (process.env.SANDBOX_TIMEOUT) {
|
||||
timeoutMs = parseInt(process.env.SANDBOX_TIMEOUT, 10)
|
||||
}
|
||||
|
||||
if (shouldUseSandbox) {
|
||||
try {
|
||||
|
|
@ -1495,7 +1499,7 @@ export const executeJavaScriptCode = async (
|
|||
}
|
||||
}
|
||||
|
||||
const sbx = await Sandbox.create({ apiKey: process.env.E2B_APIKEY, timeoutMs: timeout })
|
||||
const sbx = await Sandbox.create({ apiKey: process.env.E2B_APIKEY, timeoutMs })
|
||||
|
||||
// Install libraries
|
||||
for (const library of libraries) {
|
||||
|
|
@ -1554,7 +1558,7 @@ export const executeJavaScriptCode = async (
|
|||
},
|
||||
eval: false,
|
||||
wasm: false,
|
||||
timeout
|
||||
timeout: timeoutMs
|
||||
}
|
||||
|
||||
// Merge with custom nodeVMOptions if provided
|
||||
|
|
@ -1659,3 +1663,71 @@ export const processTemplateVariables = (state: ICommonObject, finalOutput: any)
|
|||
|
||||
return newState
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse JSON body with comprehensive error handling and cleanup
|
||||
* @param {string} body - The JSON string to parse
|
||||
* @returns {any} - The parsed JSON object
|
||||
* @throws {Error} - Detailed error message with suggestions for common JSON issues
|
||||
*/
|
||||
export const parseJsonBody = (body: string): any => {
|
||||
try {
|
||||
// First try to parse as-is with JSON5 (which handles more cases than standard JSON)
|
||||
return JSON5.parse(body)
|
||||
} catch (error) {
|
||||
try {
|
||||
// If that fails, try to clean up common issues
|
||||
let cleanedBody = body
|
||||
|
||||
// 1. Remove unnecessary backslash escapes for square brackets and braces
|
||||
// eslint-disable-next-line
|
||||
cleanedBody = cleanedBody.replace(/\\(?=[\[\]{}])/g, '')
|
||||
|
||||
// 2. Fix single quotes to double quotes (but preserve quotes inside strings)
|
||||
cleanedBody = cleanedBody.replace(/'/g, '"')
|
||||
|
||||
// 3. Remove trailing commas before closing brackets/braces
|
||||
cleanedBody = cleanedBody.replace(/,(\s*[}\]])/g, '$1')
|
||||
|
||||
// 4. Remove comments (// and /* */)
|
||||
cleanedBody = cleanedBody
|
||||
.replace(/\/\/.*$/gm, '') // Remove single-line comments
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
|
||||
|
||||
return JSON5.parse(cleanedBody)
|
||||
} catch (secondError) {
|
||||
try {
|
||||
// 3rd attempt: try with standard JSON.parse on original body
|
||||
return JSON.parse(body)
|
||||
} catch (thirdError) {
|
||||
try {
|
||||
// 4th attempt: try with standard JSON.parse on cleaned body
|
||||
const finalCleanedBody = body
|
||||
// eslint-disable-next-line
|
||||
.replace(/\\(?=[\[\]{}])/g, '') // Basic escape cleanup
|
||||
.replace(/,(\s*[}\]])/g, '$1') // Remove trailing commas
|
||||
.trim()
|
||||
|
||||
return JSON.parse(finalCleanedBody)
|
||||
} catch (fourthError) {
|
||||
// Provide comprehensive error message with suggestions
|
||||
const suggestions = [
|
||||
'• Ensure all strings are enclosed in double quotes',
|
||||
'• Remove trailing commas',
|
||||
'• Remove comments (// or /* */)',
|
||||
'• Escape special characters properly (\\n for newlines, \\" for quotes)',
|
||||
'• Use double quotes instead of single quotes',
|
||||
'• Remove unnecessary backslashes before brackets [ ] { }'
|
||||
]
|
||||
|
||||
throw new Error(
|
||||
`Invalid JSON format in body. Original error: ${error.message}. ` +
|
||||
`After cleanup attempts: ${secondError.message}. 3rd attempt: ${thirdError.message}. Final attempt: ${fourthError.message}.\n\n` +
|
||||
`Common fixes:\n${suggestions.join('\n')}\n\n` +
|
||||
`Received body: ${body.substring(0, 200)}${body.length > 200 ? '...' : ''}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ PORT=3000
|
|||
# DATABASE_USER=root
|
||||
# DATABASE_PASSWORD=mypassword
|
||||
# DATABASE_SSL=true
|
||||
# DATABASE_REJECT_UNAUTHORIZED=true
|
||||
# DATABASE_SSL_KEY_BASE64=<Self signed certificate in BASE64>
|
||||
|
||||
|
||||
|
|
@ -171,6 +172,9 @@ JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
|
|||
############################################################################################################
|
||||
|
||||
# HTTP_DENY_LIST=
|
||||
# CUSTOM_MCP_SECURITY_CHECK=true
|
||||
# CUSTOM_MCP_PROTOCOL=sse #(stdio | sse)
|
||||
# TRUST_PROXY=true #(true | false | 1 | loopback| linklocal | uniquelocal | IP addresses | loopback, IP addresses)
|
||||
|
||||
|
||||
############################################################################################################
|
||||
|
|
@ -178,4 +182,4 @@ JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
|
|||
############################################################################################################
|
||||
|
||||
# PUPPETEER_EXECUTABLE_FILE_PATH='C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
|
||||
# PLAYWRIGHT_EXECUTABLE_FILE_PATH='C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
|
||||
# PLAYWRIGHT_EXECUTABLE_FILE_PATH='C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export function getDataSource(): DataSource {
|
|||
export const getDatabaseSSLFromEnv = () => {
|
||||
if (process.env.DATABASE_SSL_KEY_BASE64) {
|
||||
return {
|
||||
rejectUnauthorized: false,
|
||||
rejectUnauthorized: process.env.DATABASE_REJECT_UNAUTHORIZED === 'true',
|
||||
ca: Buffer.from(process.env.DATABASE_SSL_KEY_BASE64, 'base64')
|
||||
}
|
||||
} else if (process.env.DATABASE_SSL === 'true') {
|
||||
|
|
|
|||
|
|
@ -74,7 +74,11 @@ export abstract class BaseCommand extends Command {
|
|||
REDIS_KEY: Flags.string(),
|
||||
REDIS_CA: Flags.string(),
|
||||
REDIS_KEEP_ALIVE: Flags.string(),
|
||||
ENABLE_BULLMQ_DASHBOARD: Flags.string()
|
||||
ENABLE_BULLMQ_DASHBOARD: Flags.string(),
|
||||
CUSTOM_MCP_SECURITY_CHECK: Flags.string(),
|
||||
CUSTOM_MCP_PROTOCOL: Flags.string(),
|
||||
HTTP_DENY_LIST: Flags.string(),
|
||||
TRUST_PROXY: Flags.string()
|
||||
}
|
||||
|
||||
protected async stopProcess() {
|
||||
|
|
@ -202,5 +206,11 @@ export abstract class BaseCommand extends Command {
|
|||
if (flags.REMOVE_ON_COUNT) process.env.REMOVE_ON_COUNT = flags.REMOVE_ON_COUNT
|
||||
if (flags.REDIS_KEEP_ALIVE) process.env.REDIS_KEEP_ALIVE = flags.REDIS_KEEP_ALIVE
|
||||
if (flags.ENABLE_BULLMQ_DASHBOARD) process.env.ENABLE_BULLMQ_DASHBOARD = flags.ENABLE_BULLMQ_DASHBOARD
|
||||
|
||||
// Security
|
||||
if (flags.CUSTOM_MCP_SECURITY_CHECK) process.env.CUSTOM_MCP_SECURITY_CHECK = flags.CUSTOM_MCP_SECURITY_CHECK
|
||||
if (flags.CUSTOM_MCP_PROTOCOL) process.env.CUSTOM_MCP_PROTOCOL = flags.CUSTOM_MCP_PROTOCOL
|
||||
if (flags.HTTP_DENY_LIST) process.env.HTTP_DENY_LIST = flags.HTTP_DENY_LIST
|
||||
if (flags.TRUST_PROXY) process.env.TRUST_PROXY = flags.TRUST_PROXY
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,19 @@ export class App {
|
|||
this.app.use(express.urlencoded({ limit: flowise_file_size_limit, extended: true }))
|
||||
|
||||
// Enhanced trust proxy settings for load balancer
|
||||
this.app.set('trust proxy', true) // Trust all proxies
|
||||
let trustProxy: string | boolean | number | undefined = process.env.TRUST_PROXY
|
||||
if (typeof trustProxy === 'undefined' || trustProxy.trim() === '' || trustProxy === 'true') {
|
||||
// Default to trust all proxies
|
||||
trustProxy = true
|
||||
} else if (trustProxy === 'false') {
|
||||
// Disable trust proxy
|
||||
trustProxy = false
|
||||
} else if (!isNaN(Number(trustProxy))) {
|
||||
// Number: Trust specific number of proxies
|
||||
trustProxy = Number(trustProxy)
|
||||
}
|
||||
|
||||
this.app.set('trust proxy', trustProxy)
|
||||
|
||||
// Allow access from specified domains
|
||||
this.app.use(cors(getCorsOptions()))
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
"@mui/x-tree-view": "^7.25.0",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"@tabler/icons-react": "^3.30.0",
|
||||
"@tiptap/extension-code-block-lowlight": "^3.4.3",
|
||||
"@tiptap/extension-mention": "^2.11.5",
|
||||
"@tiptap/extension-placeholder": "^2.11.5",
|
||||
"@tiptap/pm": "^2.11.5",
|
||||
|
|
@ -46,6 +47,7 @@
|
|||
"history": "^5.0.0",
|
||||
"html-react-parser": "^3.0.4",
|
||||
"lodash": "^4.17.21",
|
||||
"lowlight": "^3.3.0",
|
||||
"moment": "^2.29.3",
|
||||
"notistack": "^2.0.4",
|
||||
"prop-types": "^15.7.2",
|
||||
|
|
|
|||
|
|
@ -132,6 +132,80 @@
|
|||
content: '\200B';
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--code-bg, #2d2d2d) !important;
|
||||
border-radius: 0.5rem;
|
||||
color: var(--code-color, #d4d4d4) !important;
|
||||
font-family: 'JetBrainsMono', 'Fira Code', 'Monaco', 'Cascadia Code', 'Roboto Mono', monospace;
|
||||
margin: 1.5rem 0;
|
||||
padding: 0.75rem 1rem;
|
||||
|
||||
code {
|
||||
background: none !important;
|
||||
color: inherit !important;
|
||||
font-size: 0.8rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Syntax highlighting matching the screenshot colors */
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: var(--hljs-comment, #6a9955) !important;
|
||||
}
|
||||
|
||||
.hljs-variable,
|
||||
.hljs-name {
|
||||
color: var(--hljs-variable, #9cdcfe) !important; /* Light blue for variables */
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-literal {
|
||||
color: var(--hljs-number, #b5cea8) !important; /* Light green for numbers */
|
||||
}
|
||||
|
||||
.hljs-string {
|
||||
color: var(--hljs-string, #ce9178) !important; /* Orange/peach for strings */
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name {
|
||||
color: var(--hljs-title, #dcdcaa) !important; /* Yellow for function names */
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color: var(--hljs-keyword, #569cd6) !important; /* Blue for keywords */
|
||||
}
|
||||
|
||||
/* Additional elements that should match the base text color */
|
||||
.hljs-operator,
|
||||
.hljs-punctuation,
|
||||
.hljs-template-variable,
|
||||
.hljs-attribute,
|
||||
.hljs-tag,
|
||||
.hljs-regexp,
|
||||
.hljs-link,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-meta,
|
||||
.hljs-type,
|
||||
.hljs-params,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-section {
|
||||
color: var(--code-color, #d4d4d4) !important; /* Default text color */
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spin-animation {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createPortal } from 'react-dom'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import PerfectScrollbar from 'react-perfect-scrollbar'
|
||||
|
||||
|
|
@ -17,14 +17,18 @@ import Placeholder from '@tiptap/extension-placeholder'
|
|||
import { mergeAttributes } from '@tiptap/core'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Mention from '@tiptap/extension-mention'
|
||||
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
||||
import { common, createLowlight } from 'lowlight'
|
||||
import { suggestionOptions } from '@/ui-component/input/suggestionOption'
|
||||
import { getAvailableNodesForVariable } from '@/utils/genericHelper'
|
||||
|
||||
const lowlight = createLowlight(common)
|
||||
|
||||
// Store
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||
|
||||
// Add styled component for editor wrapper
|
||||
const StyledEditorContent = styled(EditorContent)(({ theme, rows }) => ({
|
||||
const StyledEditorContent = styled(EditorContent)(({ theme, rows, disabled, isDarkMode }) => ({
|
||||
'& .ProseMirror': {
|
||||
padding: '0px 14px',
|
||||
height: rows ? `${rows * 1.4375}rem` : '2.4rem',
|
||||
|
|
@ -32,40 +36,48 @@ const StyledEditorContent = styled(EditorContent)(({ theme, rows }) => ({
|
|||
overflowX: rows ? 'auto' : 'hidden',
|
||||
lineHeight: rows ? '1.4375em' : '0.875em',
|
||||
fontWeight: 500,
|
||||
color: theme.palette.grey[900],
|
||||
border: `1px solid ${theme.palette.textBackground.border}`,
|
||||
color: disabled ? theme.palette.action.disabled : theme.palette.grey[900],
|
||||
border: `1px solid ${theme.palette.grey[900] + 25}`,
|
||||
borderRadius: '10px',
|
||||
backgroundColor: theme.palette.textBackground.main,
|
||||
boxSizing: 'border-box',
|
||||
whiteSpace: rows ? 'pre-wrap' : 'nowrap',
|
||||
'&:hover': {
|
||||
borderColor: theme.palette.text.primary,
|
||||
cursor: 'text'
|
||||
borderColor: disabled ? `${theme.palette.grey[900] + 25}` : theme.palette.text.primary,
|
||||
cursor: disabled ? 'default' : 'text'
|
||||
},
|
||||
'&:focus': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: `0 0 0 0px ${theme.palette.primary.main}`,
|
||||
borderColor: disabled ? `${theme.palette.grey[900] + 25}` : theme.palette.primary.main,
|
||||
outline: 'none'
|
||||
},
|
||||
'&[disabled]': {
|
||||
backgroundColor: theme.palette.action.disabledBackground,
|
||||
color: theme.palette.action.disabled
|
||||
},
|
||||
// Placeholder for first paragraph when editor is empty
|
||||
'& p.is-editor-empty:first-of-type::before': {
|
||||
content: 'attr(data-placeholder)',
|
||||
float: 'left',
|
||||
color: theme.palette.text.primary,
|
||||
opacity: 0.4,
|
||||
color: disabled ? theme.palette.action.disabled : theme.palette.text.primary,
|
||||
opacity: disabled ? 0.6 : 0.4,
|
||||
pointerEvents: 'none',
|
||||
height: 0
|
||||
}
|
||||
},
|
||||
// Set CSS custom properties for theme-aware styling based on the screenshot
|
||||
'--code-bg': isDarkMode ? '#2d2d2d' : '#f5f5f5',
|
||||
'--code-color': isDarkMode ? '#d4d4d4' : '#333333',
|
||||
'--hljs-comment': isDarkMode ? '#6a9955' : '#6a9955',
|
||||
'--hljs-variable': isDarkMode ? '#9cdcfe' : '#d73a49', // Light blue for variables (var, i)
|
||||
'--hljs-number': isDarkMode ? '#b5cea8' : '#e36209', // Light green for numbers (1, 20, 15, etc.)
|
||||
'--hljs-string': isDarkMode ? '#ce9178' : '#22863a', // Orange/peach for strings ("FizzBuzz", "Fizz", "Buzz")
|
||||
'--hljs-title': isDarkMode ? '#dcdcaa' : '#6f42c1', // Yellow for function names (log)
|
||||
'--hljs-keyword': isDarkMode ? '#569cd6' : '#005cc5', // Blue for keywords (for, if, else)
|
||||
'--hljs-operator': isDarkMode ? '#d4d4d4' : '#333333', // White/gray for operators (=, %, ==, etc.)
|
||||
'--hljs-punctuation': isDarkMode ? '#d4d4d4' : '#333333' // White/gray for punctuation ({, }, ;, etc.)
|
||||
}
|
||||
}))
|
||||
|
||||
// define your extension array
|
||||
const extensions = (availableNodesForVariable, availableState, acceptNodeOutputAsVariable, nodes, nodeData, isNodeInsideInteration) => [
|
||||
StarterKit,
|
||||
StarterKit.configure({
|
||||
codeBlock: false
|
||||
}),
|
||||
Mention.configure({
|
||||
HTMLAttributes: {
|
||||
class: 'variable'
|
||||
|
|
@ -86,6 +98,11 @@ const extensions = (availableNodesForVariable, availableState, acceptNodeOutputA
|
|||
isNodeInsideInteration
|
||||
),
|
||||
deleteTriggerWithBackspace: true
|
||||
}),
|
||||
CodeBlockLowlight.configure({
|
||||
lowlight,
|
||||
enableTabIndentation: true,
|
||||
tabSize: 2
|
||||
})
|
||||
]
|
||||
|
||||
|
|
@ -93,6 +110,8 @@ const ExpandRichInputDialog = ({ show, dialogProps, onCancel, onInputHintDialogC
|
|||
const portalElement = document.getElementById('portal')
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
const isDarkMode = customization.isDarkMode
|
||||
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
const [inputParam, setInputParam] = useState(null)
|
||||
|
|
@ -201,7 +220,12 @@ const ExpandRichInputDialog = ({ show, dialogProps, onCancel, onInputHintDialogC
|
|||
}}
|
||||
>
|
||||
<Box sx={{ mt: 1, border: '' }}>
|
||||
<StyledEditorContent editor={editor} rows={15} />
|
||||
<StyledEditorContent
|
||||
editor={editor}
|
||||
rows={15}
|
||||
disabled={dialogProps.disabled}
|
||||
isDarkMode={isDarkMode}
|
||||
/>
|
||||
</Box>
|
||||
</PerfectScrollbar>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import Placeholder from '@tiptap/extension-placeholder'
|
||||
import { mergeAttributes } from '@tiptap/core'
|
||||
|
|
@ -7,12 +8,18 @@ import StarterKit from '@tiptap/starter-kit'
|
|||
import { styled } from '@mui/material/styles'
|
||||
import { Box } from '@mui/material'
|
||||
import Mention from '@tiptap/extension-mention'
|
||||
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
||||
import { common, createLowlight } from 'lowlight'
|
||||
import { suggestionOptions } from './suggestionOption'
|
||||
import { getAvailableNodesForVariable } from '@/utils/genericHelper'
|
||||
|
||||
const lowlight = createLowlight(common)
|
||||
|
||||
// define your extension array
|
||||
const extensions = (availableNodesForVariable, availableState, acceptNodeOutputAsVariable, nodes, nodeData, isNodeInsideInteration) => [
|
||||
StarterKit,
|
||||
StarterKit.configure({
|
||||
codeBlock: false
|
||||
}),
|
||||
Mention.configure({
|
||||
HTMLAttributes: {
|
||||
class: 'variable'
|
||||
|
|
@ -33,11 +40,16 @@ const extensions = (availableNodesForVariable, availableState, acceptNodeOutputA
|
|||
isNodeInsideInteration
|
||||
),
|
||||
deleteTriggerWithBackspace: true
|
||||
}),
|
||||
CodeBlockLowlight.configure({
|
||||
lowlight,
|
||||
enableTabIndentation: true,
|
||||
tabSize: 2
|
||||
})
|
||||
]
|
||||
|
||||
// Add styled component for editor wrapper
|
||||
const StyledEditorContent = styled(EditorContent)(({ theme, rows, disabled }) => ({
|
||||
const StyledEditorContent = styled(EditorContent)(({ theme, rows, disabled, isDarkMode }) => ({
|
||||
'& .ProseMirror': {
|
||||
padding: '0px 14px',
|
||||
height: rows ? `${rows * 1.4375}rem` : '2.4rem',
|
||||
|
|
@ -67,11 +79,24 @@ const StyledEditorContent = styled(EditorContent)(({ theme, rows, disabled }) =>
|
|||
opacity: disabled ? 0.6 : 0.4,
|
||||
pointerEvents: 'none',
|
||||
height: 0
|
||||
}
|
||||
},
|
||||
// Set CSS custom properties for theme-aware styling based on the screenshot
|
||||
'--code-bg': isDarkMode ? '#2d2d2d' : '#f5f5f5',
|
||||
'--code-color': isDarkMode ? '#d4d4d4' : '#333333',
|
||||
'--hljs-comment': isDarkMode ? '#6a9955' : '#6a9955',
|
||||
'--hljs-variable': isDarkMode ? '#9cdcfe' : '#d73a49', // Light blue for variables (var, i)
|
||||
'--hljs-number': isDarkMode ? '#b5cea8' : '#e36209', // Light green for numbers (1, 20, 15, etc.)
|
||||
'--hljs-string': isDarkMode ? '#ce9178' : '#22863a', // Orange/peach for strings ("FizzBuzz", "Fizz", "Buzz")
|
||||
'--hljs-title': isDarkMode ? '#dcdcaa' : '#6f42c1', // Yellow for function names (log)
|
||||
'--hljs-keyword': isDarkMode ? '#569cd6' : '#005cc5', // Blue for keywords (for, if, else)
|
||||
'--hljs-operator': isDarkMode ? '#d4d4d4' : '#333333', // White/gray for operators (=, %, ==, etc.)
|
||||
'--hljs-punctuation': isDarkMode ? '#d4d4d4' : '#333333' // White/gray for punctuation ({, }, ;, etc.)
|
||||
}
|
||||
}))
|
||||
|
||||
export const RichInput = ({ inputParam, value, nodes, edges, nodeId, onChange, disabled = false }) => {
|
||||
const customization = useSelector((state) => state.customization)
|
||||
const isDarkMode = customization.isDarkMode
|
||||
const [availableNodesForVariable, setAvailableNodesForVariable] = useState([])
|
||||
const [availableState, setAvailableState] = useState([])
|
||||
const [nodeData, setNodeData] = useState({})
|
||||
|
|
@ -117,7 +142,7 @@ export const RichInput = ({ inputParam, value, nodes, edges, nodeId, onChange, d
|
|||
|
||||
return (
|
||||
<Box sx={{ mt: 1, border: '' }}>
|
||||
<StyledEditorContent editor={editor} rows={inputParam?.rows} disabled={disabled} />
|
||||
<StyledEditorContent editor={editor} rows={inputParam?.rows} disabled={disabled} isDarkMode={isDarkMode} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { CodeBlock } from '../markdown/CodeBlock'
|
|||
import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
import rehypeMathjax from 'rehype-mathjax'
|
||||
import rehypeRaw from 'rehype-raw'
|
||||
|
||||
/**
|
||||
* Checks if text likely contains LaTeX math notation
|
||||
|
|
@ -91,7 +90,7 @@ export const MemoizedReactMarkdown = memo(
|
|||
|
||||
const rehypePlugins = useMemo(() => {
|
||||
if (props.rehypePlugins) return props.rehypePlugins
|
||||
return shouldEnableMath ? [rehypeMathjax, rehypeRaw] : [rehypeRaw]
|
||||
return shouldEnableMath ? [rehypeMathjax] : []
|
||||
}, [props.rehypePlugins, shouldEnableMath])
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Reference in New Issue