mirror of
https://github.com/FlowiseAI/Flowise.git
synced 2026-10-12 02:02:17 +00:00
guard prediction API against webhook/schedule trigger flows and surface trigger-specific API snippets
This commit is contained in:
@@ -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`)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>"No Authorization"</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>"No Authorization"</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.'
|
||||
}
|
||||
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.'
|
||||
}
|
||||
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
|
||||
<a
|
||||
rel='noreferrer'
|
||||
target='_blank'
|
||||
href='https://docs.flowiseai.com/using-flowise/streaming'
|
||||
>
|
||||
here
|
||||
</a>
|
||||
on how to stream response back to application
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{getIsChatflowStreamingApi.data?.isStreaming && (
|
||||
<p>
|
||||
Read
|
||||
<a rel='noreferrer' target='_blank' href='https://docs.flowiseai.com/using-flowise/streaming'>
|
||||
here
|
||||
</a>
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user