guard prediction API against webhook/schedule trigger flows and surface trigger-specific API snippets

This commit is contained in:
Henry
2026-05-07 12:28:03 +01:00
parent c0ed0e62e4
commit 6aed8334d4
8 changed files with 396 additions and 230 deletions
+2
View File
@@ -189,6 +189,8 @@ export interface IExecution {
export type ScheduleInputMode = 'text' | 'form' | 'none'
export type StartInputType = 'chatInput' | 'formInput' | 'webhookTrigger' | 'scheduleInput'
export interface IScheduleRecord {
id: string
triggerType: string
@@ -3,7 +3,7 @@ import { StatusCodes } from 'http-status-codes'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { getWebhookListenerRegistry } from '../../services/webhook-listener'
import { IReactFlowObject } from '../../Interface'
import { IReactFlowObject, StartInputType } from '../../Interface'
import chatflowsService from '../../services/chatflows'
import logger from '../../utils/logger'
@@ -16,7 +16,8 @@ const assertChatflowIsWebhookTriggered = async (chatflowid: string, workspaceId?
}
const parsedFlowData: IReactFlowObject = JSON.parse(chatflow.flowData)
const startNode = parsedFlowData.nodes.find((node) => node.data.name === 'startAgentflow')
if (startNode?.data?.inputs?.startInputType !== 'webhookTrigger') {
const startInputType = startNode?.data?.inputs?.startInputType as StartInputType | undefined
if (startInputType !== 'webhookTrigger') {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, `Chatflow ${chatflowid} is not configured as a webhook trigger`)
}
}
@@ -3,7 +3,7 @@ import { ICommonObject, removeFolderFromStorage } from 'flowise-components'
import { StatusCodes } from 'http-status-codes'
import { Brackets, In, QueryRunner } from 'typeorm'
import { validate as isValidUUID } from 'uuid'
import { ChatflowType, IReactFlowObject, ScheduleInputMode } from '../../Interface'
import { ChatflowType, IReactFlowObject, ScheduleInputMode, StartInputType } from '../../Interface'
import { FLOWISE_COUNTER_STATUS, FLOWISE_METRIC_COUNTERS } from '../../Interface.Metrics'
import { UsageCacheManager } from '../../UsageCacheManager'
import { ChatFlow, EnumChatflowType } from '../../database/entities/ChatFlow'
@@ -381,7 +381,7 @@ const saveChatflow = async (
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
const nodes = (parsedFlowData.nodes || []).filter((node) => node.data.name !== 'stickyNoteAgentflow')
const startNode = nodes.find((node) => node.data.name === 'startAgentflow')
const startInputType = startNode?.data?.inputs?.startInputType as 'chatInput' | 'formInput' | 'scheduleInput'
const startInputType = startNode?.data?.inputs?.startInputType as StartInputType | undefined
if (startInputType === 'scheduleInput') {
const scheduleInputMode = startNode?.data?.inputs?.scheduleInputMode as ScheduleInputMode | undefined
if (!scheduleInputMode) {
@@ -493,7 +493,7 @@ const updateChatflow = async (
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
const nodes = (parsedFlowData.nodes || []).filter((node) => node.data.name !== 'stickyNoteAgentflow')
const startNode = nodes.find((node) => node.data.name === 'startAgentflow')
const startInputType = startNode?.data?.inputs?.startInputType as 'chatInput' | 'formInput' | 'scheduleInput'
const startInputType = startNode?.data?.inputs?.startInputType as StartInputType | undefined
if (startInputType === 'scheduleInput') {
const scheduleInputMode = startNode?.data?.inputs?.scheduleInputMode as ScheduleInputMode | undefined
if (!scheduleInputMode) {
@@ -11,7 +11,7 @@ import { createMockRequest } from '../../utils/mockRequest'
import mcpServerService from '../mcp-server/index'
import { ChatFlow } from '../../database/entities/ChatFlow'
import logger from '../../utils/logger'
import { ChatType, IMcpServerConfig, IReactFlowObject } from '../../Interface'
import { ChatType, IMcpServerConfig, IReactFlowObject, StartInputType } from '../../Interface'
/**
* Build the MCP tool name from config + chatflow
@@ -49,7 +49,7 @@ function getToolInputType(chatflow: ChatFlow): 'question' | 'form' {
const flowData: IReactFlowObject = JSON.parse(chatflow.flowData)
const nodes = flowData.nodes || []
const startNode = nodes.find((node) => node.data.name === 'startAgentflow')
const startInputType = startNode?.data?.inputs?.startInputType as 'chatInput' | 'formInput'
const startInputType = startNode?.data?.inputs?.startInputType as StartInputType | undefined
return startInputType === 'formInput' ? 'form' : 'question'
} catch (error) {
logger.error(`Failed to parse flowData for chatflow ${chatflow.id}: ${getErrorMessage(error)}`)
@@ -19,7 +19,7 @@ import {
canScheduleEnable
} from './utils'
import { ICommonObject } from 'flowise-components'
import { ScheduleInputMode } from '../../Interface'
import { ScheduleInputMode, StartInputType } from '../../Interface'
export {
validateCronExpression,
@@ -220,7 +220,8 @@ const getScheduleStatus = async (
try {
const parsedFlowData = JSON.parse(chatflow.flowData)
const startNode = (parsedFlowData.nodes || []).find((n: any) => n.data?.name === 'startAgentflow')
if (!startNode || startNode.data?.inputs?.startInputType !== 'scheduleInput') {
const startInputType = startNode?.data?.inputs?.startInputType as StartInputType | undefined
if (!startNode || startInputType !== 'scheduleInput') {
return { record, canEnable: false, reason: 'Flow is not configured as a scheduled flow' }
}
@@ -1,5 +1,5 @@
import { StatusCodes } from 'http-status-codes'
import { IReactFlowObject } from '../../Interface'
import { IReactFlowObject, StartInputType } from '../../Interface'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { getErrorMessage } from '../../errors/utils'
import { verifyWebhookSignature, verifyPlainToken } from '../../utils/signatureVerification'
@@ -23,7 +23,7 @@ const validateWebhookChatflow = async (
const parsedFlowData: IReactFlowObject = JSON.parse(chatflow.flowData)
const startNode = parsedFlowData.nodes.find((node) => node.data.name === 'startAgentflow')
const startInputType = startNode?.data?.inputs?.startInputType
const startInputType = startNode?.data?.inputs?.startInputType as StartInputType | undefined
if (startInputType !== 'webhookTrigger') {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowId} is not configured as a webhook trigger`)
+20 -2
View File
@@ -30,7 +30,8 @@ import {
IComponentNodes,
INodeOverrides,
IVariableOverride,
INodeDirectedGraph
INodeDirectedGraph,
StartInputType
} from '../Interface'
import {
RUNTIME_MESSAGES_LENGTH_VAR_PREFIX,
@@ -60,6 +61,8 @@ import { Telemetry } from './telemetry'
import { getWorkspaceSearchOptions } from '../enterprise/utils/ControllerServiceUtils'
import { UsageCacheManager } from '../UsageCacheManager'
import { generateTTSForResponseStream, shouldAutoPlayTTS } from './buildChatflow'
import { InternalFlowiseError } from '../errors/internalFlowiseError'
import { StatusCodes } from 'http-status-codes'
interface IWaitingNode {
nodeId: string
@@ -1593,10 +1596,25 @@ export const executeAgentFlow = async ({
const { graph, nodeDependencies } = constructGraphs(nodes, edges)
const { graph: reversedGraph } = constructGraphs(nodes, edges, { isReversed: true })
const startNode = nodes.find((node) => node.data.name === 'startAgentflow')
const startInputType = startNode?.data.inputs?.startInputType as 'chatInput' | 'formInput' | 'webhookTrigger'
const startInputType = startNode?.data.inputs?.startInputType as StartInputType | undefined
if (!startInputType && !isRecursive) {
throw new Error('Start input type not found')
}
if (!isRecursive) {
if (startInputType === 'webhookTrigger' && chatType !== ChatType.WEBHOOK) {
throw new InternalFlowiseError(
StatusCodes.BAD_REQUEST,
`This flow is configured as a Webhook Trigger. Call POST /api/v1/webhook/${chatflowid} instead of the prediction API.`
)
}
if (startInputType === 'scheduleInput' && chatType !== ChatType.SCHEDULED) {
throw new InternalFlowiseError(
StatusCodes.BAD_REQUEST,
`This flow is configured as a Scheduled Trigger. It is fired by the scheduler and cannot be invoked via the API. Change the Start node Input Type to Chat or Form to call it from the prediction API.`
)
}
}
// @ts-ignore
if (isTool) sseStreamer = undefined // If the request is from ChatflowTool, don't stream the response
+361 -217
View File
@@ -93,7 +93,30 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
const apiConfig = chatflow?.apiConfig ? JSON.parse(chatflow.apiConfig) : {}
const overrideConfigStatus = apiConfig?.overrideConfig?.status !== undefined ? apiConfig.overrideConfig.status : false
const codes = ['Embed', 'Python', 'JavaScript', 'cURL', 'Share Chatbot']
const startNodeInputs = useMemo(() => {
if (!chatflow?.flowData) return null
try {
const parsed = JSON.parse(chatflow.flowData)
const startNode = (parsed.nodes || []).find((n) => n?.data?.name === 'startAgentflow')
return startNode?.data?.inputs ?? null
} catch {
return null
}
}, [chatflow?.flowData])
const startInputType = startNodeInputs?.startInputType
const isWebhookFlow = startInputType === 'webhookTrigger'
const isScheduleFlow = startInputType === 'scheduleInput'
const webhookMethod = (startNodeInputs?.webhookMethod || 'POST').toUpperCase()
const webhookContentType = startNodeInputs?.webhookContentType || 'application/json'
const webhookEnableAuth = startNodeInputs?.webhookEnableAuth === true
const webhookSignatureHeader = startNodeInputs?.webhookSignatureHeader || 'x-webhook-signature'
const webhookResponseMode = startNodeInputs?.webhookResponseMode || 'sync'
const codes = useMemo(() => {
if (isWebhookFlow || isScheduleFlow) return ['Python', 'JavaScript', 'cURL']
return ['Embed', 'Python', 'JavaScript', 'cURL', 'Share Chatbot']
}, [isWebhookFlow, isScheduleFlow])
const [value, setValue] = useState(0)
const [apiKeys, setAPIKeys] = useState([])
const [chatflowApiKeyId, setChatflowApiKeyId] = useState('')
@@ -282,7 +305,54 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
setValue(newValue)
}
const getWebhookCode = (codeLang, apiKey) => {
const url = `${baseURL}/api/v1/webhook/${dialogProps.chatflowid}`
const sendsBody = webhookMethod !== 'GET' && webhookMethod !== 'DELETE'
const isJson = webhookContentType === 'application/json'
const sampleBodyJs = isJson ? '{ example: "value" }' : '"example=value"'
const sampleBodyPy = isJson ? '{"example": "value"}' : '"example=value"'
const sampleBodyCurl = isJson ? '{"example": "value"}' : 'example=value'
if (codeLang === 'Python') {
const headers = [`"Content-Type": "${webhookContentType}"`]
if (apiKey) headers.push(`"Authorization": "Bearer ${apiKey}"`)
if (webhookEnableAuth) headers.push(`"${webhookSignatureHeader}": "<signature>"`)
const bodyArg = sendsBody ? (isJson ? `, json=${sampleBodyPy}` : `, data=${sampleBodyPy}`) : ''
return `import requests
API_URL = "${url}"
headers = {${headers.join(', ')}}
response = requests.${webhookMethod.toLowerCase()}(API_URL, headers=headers${bodyArg})
print(response.json())
`
} else if (codeLang === 'JavaScript') {
const headers = [`"Content-Type": "${webhookContentType}"`]
if (apiKey) headers.push(`Authorization: "Bearer ${apiKey}"`)
if (webhookEnableAuth) headers.push(`"${webhookSignatureHeader}": "<signature>"`)
const bodyLine = sendsBody ? `,\n body: ${isJson ? `JSON.stringify(${sampleBodyJs})` : sampleBodyJs}` : ''
return `const response = await fetch(
"${url}",
{
method: "${webhookMethod}",
headers: { ${headers.join(', ')} }${bodyLine}
}
);
const result = await response.json();
console.log(result);
`
} else if (codeLang === 'cURL') {
const parts = [`curl ${url}`, `-X ${webhookMethod}`, `-H "Content-Type: ${webhookContentType}"`]
if (apiKey) parts.push(`-H "Authorization: Bearer ${apiKey}"`)
if (webhookEnableAuth) parts.push(`-H "${webhookSignatureHeader}: <signature>"`)
if (sendsBody) parts.push(`-d '${sampleBodyCurl}'`)
return parts.join(' \\\n ')
}
return ''
}
const getCode = (codeLang) => {
if (isWebhookFlow) return getWebhookCode(codeLang, null)
if (codeLang === 'Python') {
return `import requests
@@ -291,7 +361,7 @@ API_URL = "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}"
def query(payload):
response = requests.post(API_URL, json=payload)
return response.json()
output = query({
"question": "Hey, how are you?",
})
@@ -326,6 +396,7 @@ query({"question": "Hey, how are you?"}).then((response) => {
}
const getCodeWithAuthorization = (codeLang) => {
if (isWebhookFlow) return getWebhookCode(codeLang, selectedApiKey?.apiKey)
if (codeLang === 'Python') {
return `import requests
@@ -710,241 +781,314 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")`
{dialogProps.title}
</DialogTitle>
<DialogContent>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<div style={{ flex: 80 }}>
<Tabs value={value} onChange={handleChange} aria-label='tabs'>
{codes.map((codeLang, index) => (
<Tab
icon={
<img style={{ objectFit: 'cover', height: 15, width: 'auto' }} src={getSVG(codeLang)} alt='code' />
}
iconPosition='start'
key={index}
label={codeLang}
{...a11yProps(index)}
></Tab>
))}
</Tabs>
{isScheduleFlow ? (
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-start',
borderRadius: 10,
background: 'rgb(254,252,191)',
padding: 12
}}
>
<IconExclamationCircle size={28} color='rgb(116,66,16)' style={{ flexShrink: 0 }} />
<span style={{ color: 'rgb(116,66,16)', marginLeft: 10, fontWeight: 500 }}>
This flow is configured as a <b>Scheduled Trigger</b>. It is fired automatically by the in-process scheduler on
its cron schedule and cannot be invoked via the prediction API. To call this flow from an API, change the Start
node Input Type to <b>Chat Input</b>, <b>Form Input</b>, or <b>Webhook Trigger</b>.
</span>
</div>
<div style={{ flex: 20 }}>
<Available permission={'chatflows:update,agentflows:update'}>
<Dropdown
name='SelectKey'
disableClearable={true}
options={keyOptions}
onSelect={(newValue) => onApiKeySelected(newValue)}
value={dialogProps.chatflowApiKeyId ?? chatflowApiKeyId ?? 'Choose an API key'}
/>
</Available>
</div>
</div>
<div style={{ marginTop: 10 }}></div>
{codes.map((codeLang, index) => (
<TabPanel key={index} value={value} index={index}>
{(codeLang === 'Embed' || codeLang === 'Share Chatbot') && chatflowApiKeyId && (
<>
<p>You cannot use API key while embedding/sharing chatbot.</p>
<p>
Please select <b>&quot;No Authorization&quot;</b> from the dropdown at the top right corner.
</p>
</>
) : (
<>
{isWebhookFlow && (
<div
style={{
display: 'flex',
flexDirection: 'column',
borderRadius: 10,
background: '#d8f3dc',
padding: 12,
marginBottom: 16
}}
>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<IconBulb size={28} color='#2d6a4f' />
<span style={{ color: '#2d6a4f', marginLeft: 10, fontWeight: 500 }}>
This flow is configured as a <b>Webhook Trigger</b>. Send <b>{webhookMethod}</b> requests to{' '}
<code>/api/v1/webhook/{dialogProps.chatflowid}</code> with Content-Type{' '}
<code>{webhookContentType}</code>.
{webhookEnableAuth && (
<>
{' '}
Each request must include a valid signature in the <code>
{webhookSignatureHeader}
</code>{' '}
header.
</>
)}{' '}
Response mode: <b>{webhookResponseMode}</b>
{webhookResponseMode === 'async' && ' (returns 202 immediately, optional callback POST when done)'}
{webhookResponseMode === 'stream' && ' (Server-Sent Events stream)'}.
</span>
</div>
</div>
)}
{codeLang === 'Embed' && !chatflowApiKeyId && <EmbedChat chatflowid={dialogProps.chatflowid} />}
{codeLang !== 'Embed' && codeLang !== 'Share Chatbot' && codeLang !== 'Configuration' && (
<>
<CopyBlock
theme={atomOneDark}
text={chatflowApiKeyId ? getCodeWithAuthorization(codeLang) : getCode(codeLang)}
language={getLang(codeLang)}
showLineNumbers={false}
wrapLines
/>
<CheckboxInput label='Show Override Config' value={checkboxVal} onChange={onCheckBoxChanged} />
{checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && (
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<div style={{ flex: 80 }}>
<Tabs value={value} onChange={handleChange} aria-label='tabs'>
{codes.map((codeLang, index) => (
<Tab
icon={
<img
style={{ objectFit: 'cover', height: 15, width: 'auto' }}
src={getSVG(codeLang)}
alt='code'
/>
}
iconPosition='start'
key={index}
label={codeLang}
{...a11yProps(index)}
></Tab>
))}
</Tabs>
</div>
<div style={{ flex: 20 }}>
<Available permission={'chatflows:update,agentflows:update'}>
<Dropdown
name='SelectKey'
disableClearable={true}
options={keyOptions}
onSelect={(newValue) => onApiKeySelected(newValue)}
value={dialogProps.chatflowApiKeyId ?? chatflowApiKeyId ?? 'Choose an API key'}
/>
</Available>
</div>
</div>
<div style={{ marginTop: 10 }}></div>
{codes.map((codeLang, index) => (
<TabPanel key={index} value={value} index={index}>
{(codeLang === 'Embed' || codeLang === 'Share Chatbot') && chatflowApiKeyId && (
<>
<p>You cannot use API key while embedding/sharing chatbot.</p>
<p>
Please select <b>&quot;No Authorization&quot;</b> from the dropdown at the top right corner.
</p>
</>
)}
{codeLang === 'Embed' && !chatflowApiKeyId && <EmbedChat chatflowid={dialogProps.chatflowid} />}
{codeLang !== 'Embed' && codeLang !== 'Share Chatbot' && codeLang !== 'Configuration' && (
<>
<Typography sx={{ mt: 2 }}>
You can override existing input configuration of the chatflow with overrideConfig property.
</Typography>
<div
style={{
display: 'flex',
flexDirection: 'column',
borderRadius: 10,
background: 'rgb(254,252,191)',
padding: 10,
marginTop: 10,
marginBottom: 10
}}
>
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center'
}}
>
<IconExclamationCircle size={30} color='rgb(116,66,16)' />
<span style={{ color: 'rgb(116,66,16)', marginLeft: 10, fontWeight: 500 }}>
{
'For security reason, override config is disabled by default. You can change this by going into Chatflow Configuration -> Security tab, and enable the property you want to override.'
}
&nbsp;Refer{' '}
<a
rel='noreferrer'
target='_blank'
href='https://docs.flowiseai.com/using-flowise/prediction#configuration-override'
>
here
</a>{' '}
for more details
</span>
</div>
</div>
<Stack direction='column' spacing={2} sx={{ width: '100%', my: 2 }}>
<Card sx={{ borderColor: theme.palette.primary[200] + 75, p: 2 }} variant='outlined'>
<Stack sx={{ mt: 1, mb: 2, ml: 1, alignItems: 'center' }} direction='row' spacing={2}>
<IconBox />
<Typography variant='h4'>Nodes</Typography>
</Stack>
{Object.keys(nodeConfig)
.sort()
.map((nodeLabel) => (
<Accordion
expanded={nodeConfigExpanded[nodeLabel] || false}
onChange={handleAccordionChange(nodeLabel)}
key={nodeLabel}
disableGutters
>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls={`nodes-accordian-${nodeLabel}`}
id={`nodes-accordian-header-${nodeLabel}`}
>
<Stack
flexDirection='row'
sx={{ gap: 2, alignItems: 'center', flexWrap: 'wrap' }}
>
<Typography variant='h5'>{nodeLabel}</Typography>
{nodeConfig[nodeLabel].nodeIds.length > 0 &&
nodeConfig[nodeLabel].nodeIds.map((nodeId, index) => (
<div
key={index}
style={{
display: 'flex',
flexDirection: 'row',
width: 'max-content',
borderRadius: 15,
background: 'rgb(254,252,191)',
padding: 5,
paddingLeft: 10,
paddingRight: 10
}}
>
<span
style={{
color: 'rgb(116,66,16)',
fontSize: '0.825rem'
}}
>
{nodeId}
</span>
</div>
))}
</Stack>
</AccordionSummary>
<AccordionDetails>
<TableViewOnly
rows={nodeOverrides[nodeLabel]}
columns={
nodeOverrides[nodeLabel].length > 0
? Object.keys(nodeOverrides[nodeLabel][0]).filter(
(key) => key !== 'schema'
)
: []
}
/>
</AccordionDetails>
</Accordion>
))}
</Card>
<Card sx={{ borderColor: theme.palette.primary[200] + 75, p: 2 }} variant='outlined'>
<Stack sx={{ mt: 1, mb: 2, ml: 1, alignItems: 'center' }} direction='row' spacing={2}>
<IconVariable />
<Typography variant='h4'>Variables</Typography>
</Stack>
<TableViewOnly rows={variableOverrides} columns={['name', 'type', 'enabled']} />
</Card>
</Stack>
<CopyBlock
theme={atomOneDark}
text={
chatflowApiKeyId
? dialogProps.isFormDataRequired
? getConfigCodeWithFormDataWithAuth(codeLang, getConfigApi.data)
: getConfigCodeWithAuthorization(codeLang, getConfigApi.data)
: dialogProps.isFormDataRequired
? getConfigCodeWithFormData(codeLang, getConfigApi.data)
: getConfigCode(codeLang, getConfigApi.data)
}
text={chatflowApiKeyId ? getCodeWithAuthorization(codeLang) : getCode(codeLang)}
language={getLang(codeLang)}
showLineNumbers={false}
wrapLines
/>
<div
style={{
display: 'flex',
flexDirection: 'column',
borderRadius: 10,
background: '#d8f3dc',
padding: 10,
marginTop: 10,
marginBottom: 10
}}
>
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center'
}}
>
<IconBulb size={30} color='#2d6a4f' />
<span style={{ color: '#2d6a4f', marginLeft: 10, fontWeight: 500 }}>
You can also specify multiple values for a config parameter by specifying the node id
</span>
</div>
<div style={{ padding: 10 }}>
<CheckboxInput label='Show Override Config' value={checkboxVal} onChange={onCheckBoxChanged} />
{checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && (
<>
<Typography sx={{ mt: 2 }}>
You can override existing input configuration of the chatflow with overrideConfig
property.
</Typography>
<div
style={{
display: 'flex',
flexDirection: 'column',
borderRadius: 10,
background: 'rgb(254,252,191)',
padding: 10,
marginTop: 10,
marginBottom: 10
}}
>
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center'
}}
>
<IconExclamationCircle size={30} color='rgb(116,66,16)' />
<span style={{ color: 'rgb(116,66,16)', marginLeft: 10, fontWeight: 500 }}>
{
'For security reason, override config is disabled by default. You can change this by going into Chatflow Configuration -> Security tab, and enable the property you want to override.'
}
&nbsp;Refer{' '}
<a
rel='noreferrer'
target='_blank'
href='https://docs.flowiseai.com/using-flowise/prediction#configuration-override'
>
here
</a>{' '}
for more details
</span>
</div>
</div>
<Stack direction='column' spacing={2} sx={{ width: '100%', my: 2 }}>
<Card sx={{ borderColor: theme.palette.primary[200] + 75, p: 2 }} variant='outlined'>
<Stack
sx={{ mt: 1, mb: 2, ml: 1, alignItems: 'center' }}
direction='row'
spacing={2}
>
<IconBox />
<Typography variant='h4'>Nodes</Typography>
</Stack>
{Object.keys(nodeConfig)
.sort()
.map((nodeLabel) => (
<Accordion
expanded={nodeConfigExpanded[nodeLabel] || false}
onChange={handleAccordionChange(nodeLabel)}
key={nodeLabel}
disableGutters
>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls={`nodes-accordian-${nodeLabel}`}
id={`nodes-accordian-header-${nodeLabel}`}
>
<Stack
flexDirection='row'
sx={{ gap: 2, alignItems: 'center', flexWrap: 'wrap' }}
>
<Typography variant='h5'>{nodeLabel}</Typography>
{nodeConfig[nodeLabel].nodeIds.length > 0 &&
nodeConfig[nodeLabel].nodeIds.map((nodeId, index) => (
<div
key={index}
style={{
display: 'flex',
flexDirection: 'row',
width: 'max-content',
borderRadius: 15,
background: 'rgb(254,252,191)',
padding: 5,
paddingLeft: 10,
paddingRight: 10
}}
>
<span
style={{
color: 'rgb(116,66,16)',
fontSize: '0.825rem'
}}
>
{nodeId}
</span>
</div>
))}
</Stack>
</AccordionSummary>
<AccordionDetails>
<TableViewOnly
rows={nodeOverrides[nodeLabel]}
columns={
nodeOverrides[nodeLabel].length > 0
? Object.keys(nodeOverrides[nodeLabel][0]).filter(
(key) => key !== 'schema'
)
: []
}
/>
</AccordionDetails>
</Accordion>
))}
</Card>
<Card sx={{ borderColor: theme.palette.primary[200] + 75, p: 2 }} variant='outlined'>
<Stack
sx={{ mt: 1, mb: 2, ml: 1, alignItems: 'center' }}
direction='row'
spacing={2}
>
<IconVariable />
<Typography variant='h4'>Variables</Typography>
</Stack>
<TableViewOnly rows={variableOverrides} columns={['name', 'type', 'enabled']} />
</Card>
</Stack>
<CopyBlock
theme={atomOneDark}
text={
dialogProps.isFormDataRequired
? getMultiConfigCodeWithFormData(codeLang)
: getMultiConfigCode()
chatflowApiKeyId
? dialogProps.isFormDataRequired
? getConfigCodeWithFormDataWithAuth(codeLang, getConfigApi.data)
: getConfigCodeWithAuthorization(codeLang, getConfigApi.data)
: dialogProps.isFormDataRequired
? getConfigCodeWithFormData(codeLang, getConfigApi.data)
: getConfigCode(codeLang, getConfigApi.data)
}
language={getLang(codeLang)}
showLineNumbers={false}
wrapLines
/>
</div>
</div>
<div
style={{
display: 'flex',
flexDirection: 'column',
borderRadius: 10,
background: '#d8f3dc',
padding: 10,
marginTop: 10,
marginBottom: 10
}}
>
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center'
}}
>
<IconBulb size={30} color='#2d6a4f' />
<span style={{ color: '#2d6a4f', marginLeft: 10, fontWeight: 500 }}>
You can also specify multiple values for a config parameter by specifying the
node id
</span>
</div>
<div style={{ padding: 10 }}>
<CopyBlock
theme={atomOneDark}
text={
dialogProps.isFormDataRequired
? getMultiConfigCodeWithFormData(codeLang)
: getMultiConfigCode()
}
language={getLang(codeLang)}
showLineNumbers={false}
wrapLines
/>
</div>
</div>
</>
)}
{getIsChatflowStreamingApi.data?.isStreaming && (
<p>
Read&nbsp;
<a
rel='noreferrer'
target='_blank'
href='https://docs.flowiseai.com/using-flowise/streaming'
>
here
</a>
&nbsp;on how to stream response back to application
</p>
)}
</>
)}
{getIsChatflowStreamingApi.data?.isStreaming && (
<p>
Read&nbsp;
<a rel='noreferrer' target='_blank' href='https://docs.flowiseai.com/using-flowise/streaming'>
here
</a>
&nbsp;on how to stream response back to application
</p>
{codeLang === 'Share Chatbot' && !chatflowApiKeyId && (
<ShareChatbot isSessionMemory={dialogProps.isSessionMemory} isAgentCanvas={dialogProps.isAgentCanvas} />
)}
</>
)}
{codeLang === 'Share Chatbot' && !chatflowApiKeyId && (
<ShareChatbot isSessionMemory={dialogProps.isSessionMemory} isAgentCanvas={dialogProps.isAgentCanvas} />
)}
</TabPanel>
))}
</TabPanel>
))}
</>
)}
</DialogContent>
</Dialog>
) : null