mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 17:01:00 +03:00
Enable/disable variables in override configuration (#3467)
* Add ability to enable/disable which variables can be overriden during external predictions * Remove duplicated code * Remove rate limit and allowed domains tab from chatflow configuration * Show tooltip in api code dialog for override config properties * Fix server crash when override config is not available * update UI for chatflow config security, file upload * Fix UI issues in security tab of chatflow configuration dialog * Fix override config options not updating when nodes change * Fix crash in api code dialog when overrideConfig is not available for a chatflow/agentflow. Also fix input config in api code dialog not updating when nodes change. * Refactor override config and add override config for variables * Update api code dialog - update how override config is read and show variable overrides * Update how node and variable overrides are read and resolved * Prevent api code dialog mounting on page load and only mount when api code dialog button is clicked. this should fix loading incorrect data. * Fix variables list not showing when overrideConfig is not available * add overrideconfig to agentflow and upsert vector * temporarily removed enable overrideconfig on upsert, fix linting issues --------- Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
@@ -4,8 +4,7 @@ import { createPortal } from 'react-dom'
|
||||
import { Box, Dialog, DialogContent, DialogTitle, Tabs, Tab } from '@mui/material'
|
||||
import { tabsClasses } from '@mui/material/Tabs'
|
||||
import SpeechToText from '@/ui-component/extended/SpeechToText'
|
||||
import RateLimit from '@/ui-component/extended/RateLimit'
|
||||
import AllowedDomains from '@/ui-component/extended/AllowedDomains'
|
||||
import Security from '@/ui-component/extended/Security'
|
||||
import ChatFeedback from '@/ui-component/extended/ChatFeedback'
|
||||
import AnalyseFlow from '@/ui-component/extended/AnalyseFlow'
|
||||
import StarterPrompts from '@/ui-component/extended/StarterPrompts'
|
||||
@@ -15,8 +14,8 @@ import FileUpload from '@/ui-component/extended/FileUpload'
|
||||
|
||||
const CHATFLOW_CONFIGURATION_TABS = [
|
||||
{
|
||||
label: 'Rate Limiting',
|
||||
id: 'rateLimiting'
|
||||
label: 'Security',
|
||||
id: 'security'
|
||||
},
|
||||
{
|
||||
label: 'Starter Prompts',
|
||||
@@ -34,10 +33,6 @@ const CHATFLOW_CONFIGURATION_TABS = [
|
||||
label: 'Chat Feedback',
|
||||
id: 'chatFeedback'
|
||||
},
|
||||
{
|
||||
label: 'Allowed Domains',
|
||||
id: 'allowedDomains'
|
||||
},
|
||||
{
|
||||
label: 'Analyse Chatflow',
|
||||
id: 'analyseChatflow'
|
||||
@@ -94,8 +89,8 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>{dialogProps.title}</div>
|
||||
<DialogTitle sx={{ fontSize: '1.25rem' }} id='alert-dialog-title'>
|
||||
{dialogProps.title}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Tabs
|
||||
@@ -115,7 +110,13 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
|
||||
>
|
||||
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
||||
<Tab
|
||||
sx={{ minHeight: '40px', height: '40px', textAlign: 'left', display: 'flex', alignItems: 'start', mb: 1 }}
|
||||
sx={{
|
||||
minHeight: '40px',
|
||||
height: '40px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
mb: 1
|
||||
}}
|
||||
key={index}
|
||||
label={item.label}
|
||||
{...a11yProps(index)}
|
||||
@@ -124,12 +125,11 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
|
||||
</Tabs>
|
||||
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
||||
<TabPanel key={index} value={tabValue} index={index}>
|
||||
{item.id === 'rateLimiting' && <RateLimit />}
|
||||
{item.id === 'security' && <Security dialogProps={dialogProps} />}
|
||||
{item.id === 'conversationStarters' ? <StarterPrompts dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'followUpPrompts' ? <FollowUpPrompts dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'speechToText' ? <SpeechToText dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'chatFeedback' ? <ChatFeedback dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'allowedDomains' ? <AllowedDomains dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'analyseChatflow' ? <AnalyseFlow dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'leads' ? <Leads dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'fileUpload' ? <FileUpload dialogProps={dialogProps} /> : null}
|
||||
|
||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||
|
||||
// material-ui
|
||||
import { Button, IconButton, OutlinedInput, Box, List, InputAdornment, Typography } from '@mui/material'
|
||||
import { Button, IconButton, OutlinedInput, Box, InputAdornment, Stack, Typography } from '@mui/material'
|
||||
import { IconX, IconTrash, IconPlus } from '@tabler/icons-react'
|
||||
|
||||
// Project import
|
||||
@@ -118,23 +118,17 @@ const AllowedDomains = ({ dialogProps }) => {
|
||||
}, [dialogProps])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ mb: 1 }}>
|
||||
Allowed Domains
|
||||
<TooltipWithParser
|
||||
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
||||
title={'Your chatbot will only work when used from the following domains.'}
|
||||
/>
|
||||
</Typography>
|
||||
</Box>
|
||||
<List>
|
||||
<Stack direction='column' spacing={2} sx={{ alignItems: 'start' }}>
|
||||
<Typography variant='h3'>
|
||||
Allowed Domains
|
||||
<TooltipWithParser
|
||||
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
||||
title={'Your chatbot will only work when used from the following domains.'}
|
||||
/>
|
||||
</Typography>
|
||||
<Stack direction='column' spacing={2} sx={{ width: '100%' }}>
|
||||
<Stack direction='column' spacing={2}>
|
||||
<Typography>Domains</Typography>
|
||||
{inputFields.map((origin, index) => {
|
||||
return (
|
||||
<div key={index} style={{ display: 'flex', width: '100%' }}>
|
||||
@@ -176,11 +170,9 @@ const AllowedDomains = ({ dialogProps }) => {
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
</Box>
|
||||
<Box sx={{ pt: 2, pb: 2 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
|
||||
<Typography sx={{ mb: 1 }}>
|
||||
</Stack>
|
||||
<Stack direction='column' spacing={1}>
|
||||
<Typography>
|
||||
Error Message
|
||||
<TooltipWithParser
|
||||
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
||||
@@ -198,12 +190,12 @@ const AllowedDomains = ({ dialogProps }) => {
|
||||
setErrorMessage(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<StyledButton variant='contained' onClick={onSave}>
|
||||
Save
|
||||
</StyledButton>
|
||||
</>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@ import { useDispatch } from 'react-redux'
|
||||
import { useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||
import parser from 'html-react-parser'
|
||||
|
||||
// material-ui
|
||||
import { Button, Box, Typography } from '@mui/material'
|
||||
import { IconX } from '@tabler/icons-react'
|
||||
import { Button, Box } from '@mui/material'
|
||||
import { IconX, IconBulb } from '@tabler/icons-react'
|
||||
|
||||
// Project import
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
||||
import { SwitchInput } from '@/ui-component/switch/Switch'
|
||||
|
||||
// store
|
||||
@@ -18,7 +18,9 @@ import useNotifier from '@/utils/useNotifier'
|
||||
// API
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
|
||||
const message = `Allow files to be uploaded from the chat. Uploaded files will be parsed as string and sent to LLM. If File Upload is enabled on Vector Store as well, this will override and takes precedence.`
|
||||
const message = `Uploaded files will be parsed as strings and sent to the LLM. If file upload is enabled on the Vector Store as well, this will override and take precedence.
|
||||
<br />
|
||||
Refer <a href='https://docs.flowiseai.com/using-flowise/uploads#files' target='_blank'>docs</a> for more details.`
|
||||
|
||||
const FileUpload = ({ dialogProps }) => {
|
||||
const dispatch = useDispatch()
|
||||
@@ -99,15 +101,41 @@ const FileUpload = ({ dialogProps }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', mb: 2 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<Typography>
|
||||
Enable Full File Upload
|
||||
<TooltipWithParser style={{ marginLeft: 10 }} title={message} />
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'start',
|
||||
justifyContent: 'start',
|
||||
gap: 3,
|
||||
mb: 2
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderRadius: 10,
|
||||
background: '#d8f3dc',
|
||||
width: '100%',
|
||||
padding: 10
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<IconBulb size={30} color='#2d6a4f' />
|
||||
<span style={{ color: '#2d6a4f', marginLeft: 10, fontWeight: 500 }}>{parser(message)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<SwitchInput onChange={handleChange} value={fullFileUpload} />
|
||||
<SwitchInput label='Enable Full File Upload' onChange={handleChange} value={fullFileUpload} />
|
||||
</Box>
|
||||
{/* TODO: Allow selection of allowed file types*/}
|
||||
<StyledButton style={{ marginBottom: 10, marginTop: 10 }} variant='contained' onClick={onSave}>
|
||||
Save
|
||||
</StyledButton>
|
||||
|
||||
@@ -0,0 +1,430 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Button,
|
||||
Paper,
|
||||
Stack,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
Card
|
||||
} from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
|
||||
// Project import
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
||||
import { SwitchInput } from '@/ui-component/switch/Switch'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||
|
||||
// Icons
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||
import { IconX, IconBox, IconVariable } from '@tabler/icons-react'
|
||||
|
||||
// API
|
||||
import useApi from '@/hooks/useApi'
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
import configApi from '@/api/config'
|
||||
import variablesApi from '@/api/variables'
|
||||
|
||||
// utils
|
||||
|
||||
const OverrideConfigTable = ({ columns, onToggle, rows, sx }) => {
|
||||
const handleChange = (enabled, row) => {
|
||||
onToggle(row, enabled)
|
||||
}
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table size='small' sx={{ minWidth: 650, ...sx }} aria-label='simple table'>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columns.map((col, index) => (
|
||||
<TableCell key={index}>{col.charAt(0).toUpperCase() + col.slice(1)}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{rows.map((row, index) => (
|
||||
<TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||
{Object.keys(row).map((key, index) => {
|
||||
if (key !== 'id') {
|
||||
return (
|
||||
<TableCell key={index}>
|
||||
{key === 'enabled' ? (
|
||||
<SwitchInput onChange={(enabled) => handleChange(enabled, row)} value={row.enabled} />
|
||||
) : (
|
||||
row[key]
|
||||
)}
|
||||
</TableCell>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)
|
||||
}
|
||||
|
||||
OverrideConfigTable.propTypes = {
|
||||
rows: PropTypes.array,
|
||||
columns: PropTypes.array,
|
||||
sx: PropTypes.object,
|
||||
onToggle: PropTypes.func
|
||||
}
|
||||
|
||||
const OverrideConfig = ({ dialogProps }) => {
|
||||
const dispatch = useDispatch()
|
||||
const chatflow = useSelector((state) => state.canvas.chatflow)
|
||||
const chatflowid = chatflow.id
|
||||
const apiConfig = chatflow.apiConfig ? JSON.parse(chatflow.apiConfig) : {}
|
||||
|
||||
useNotifier()
|
||||
const theme = useTheme()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [nodeConfig, setNodeConfig] = useState(null)
|
||||
const [nodeConfigExpanded, setNodeConfigExpanded] = useState({})
|
||||
const [overrideConfigStatus, setOverrideConfigStatus] = useState(
|
||||
apiConfig?.overrideConfig?.status !== undefined ? apiConfig.overrideConfig.status : false
|
||||
)
|
||||
const [nodeOverrides, setNodeOverrides] = useState(apiConfig?.overrideConfig?.nodes !== undefined ? apiConfig.overrideConfig.nodes : {})
|
||||
const [variableOverrides, setVariableOverrides] = useState(
|
||||
apiConfig?.overrideConfig?.variables !== undefined ? apiConfig.overrideConfig.variables : []
|
||||
)
|
||||
|
||||
const getConfigApi = useApi(configApi.getConfig)
|
||||
const getAllVariablesApi = useApi(variablesApi.getAllVariables)
|
||||
|
||||
const handleAccordionChange = (nodeLabel) => (event, isExpanded) => {
|
||||
const accordianNodes = { ...nodeConfigExpanded }
|
||||
accordianNodes[nodeLabel] = isExpanded
|
||||
setNodeConfigExpanded(accordianNodes)
|
||||
}
|
||||
|
||||
const formatObj = () => {
|
||||
const obj = {
|
||||
overrideConfig: { status: overrideConfigStatus }
|
||||
}
|
||||
|
||||
if (overrideConfigStatus) {
|
||||
obj.overrideConfig = {
|
||||
...obj.overrideConfig,
|
||||
nodes: nodeOverrides,
|
||||
variables: variableOverrides
|
||||
}
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
const onNodeOverrideToggle = (node, property, status) => {
|
||||
setNodeOverrides((prev) => {
|
||||
const newConfig = { ...prev }
|
||||
newConfig[node] = newConfig[node].map((item) => {
|
||||
if (item.name === property) {
|
||||
item.enabled = status
|
||||
}
|
||||
return item
|
||||
})
|
||||
return newConfig
|
||||
})
|
||||
}
|
||||
|
||||
const onVariableOverrideToggle = (id, status) => {
|
||||
setVariableOverrides((prev) => {
|
||||
return prev.map((item) => {
|
||||
if (item.id === id) {
|
||||
item.enabled = status
|
||||
}
|
||||
return item
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const groupByNodeLabel = (nodes) => {
|
||||
const result = {}
|
||||
const newNodeOverrides = {}
|
||||
const seenNodes = new Set()
|
||||
|
||||
nodes.forEach((item) => {
|
||||
const { node, nodeId, label, name, type } = item
|
||||
seenNodes.add(node)
|
||||
|
||||
if (!result[node]) {
|
||||
result[node] = {
|
||||
nodeIds: [],
|
||||
params: []
|
||||
}
|
||||
}
|
||||
|
||||
if (!newNodeOverrides[node]) {
|
||||
// If overrideConfigStatus is true, copy existing config for this node
|
||||
newNodeOverrides[node] = overrideConfigStatus ? [...(nodeOverrides[node] || [])] : []
|
||||
}
|
||||
|
||||
if (!result[node].nodeIds.includes(nodeId)) result[node].nodeIds.push(nodeId)
|
||||
|
||||
const param = { label, name, type }
|
||||
|
||||
if (!result[node].params.some((existingParam) => JSON.stringify(existingParam) === JSON.stringify(param))) {
|
||||
result[node].params.push(param)
|
||||
const paramExists = newNodeOverrides[node].some(
|
||||
(existingParam) => existingParam.label === label && existingParam.name === name && existingParam.type === type
|
||||
)
|
||||
if (!paramExists) {
|
||||
newNodeOverrides[node].push({ ...param, enabled: false })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Sort the nodeIds array
|
||||
for (const node in result) {
|
||||
result[node].nodeIds.sort()
|
||||
}
|
||||
setNodeConfig(result)
|
||||
|
||||
if (!overrideConfigStatus) {
|
||||
setNodeOverrides(newNodeOverrides)
|
||||
} else {
|
||||
const updatedNodeOverrides = { ...nodeOverrides }
|
||||
|
||||
Object.keys(updatedNodeOverrides).forEach((node) => {
|
||||
if (!seenNodes.has(node)) {
|
||||
delete updatedNodeOverrides[node]
|
||||
}
|
||||
})
|
||||
|
||||
seenNodes.forEach((node) => {
|
||||
if (!updatedNodeOverrides[node]) {
|
||||
updatedNodeOverrides[node] = newNodeOverrides[node]
|
||||
}
|
||||
})
|
||||
|
||||
setNodeOverrides(updatedNodeOverrides)
|
||||
}
|
||||
}
|
||||
|
||||
const groupByVariableLabel = (variables) => {
|
||||
const newVariables = []
|
||||
const seenVariables = new Set()
|
||||
|
||||
variables.forEach((item) => {
|
||||
const { id, name, type } = item
|
||||
seenVariables.add(id)
|
||||
|
||||
const param = { id, name, type }
|
||||
const existingVariable = variableOverrides?.find((existingParam) => existingParam.id === id)
|
||||
|
||||
if (existingVariable) {
|
||||
if (!newVariables.some((existingVariable) => existingVariable.id === id)) {
|
||||
newVariables.push({ ...existingVariable })
|
||||
}
|
||||
} else {
|
||||
if (!newVariables.some((existingVariable) => existingVariable.id === id)) {
|
||||
newVariables.push({ ...param, enabled: false })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (variableOverrides) {
|
||||
variableOverrides.forEach((existingVariable) => {
|
||||
if (!seenVariables.has(existingVariable.id)) {
|
||||
const index = newVariables.findIndex((newVariable) => newVariable.id === existingVariable.id)
|
||||
if (index !== -1) {
|
||||
newVariables.splice(index, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setVariableOverrides(newVariables)
|
||||
}
|
||||
|
||||
const onOverrideConfigSave = async () => {
|
||||
try {
|
||||
const saveResp = await chatflowsApi.updateChatflow(chatflowid, {
|
||||
apiConfig: JSON.stringify(formatObj())
|
||||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Override Configuration Saved',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data })
|
||||
}
|
||||
} catch (error) {
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save Override Configuration: ${
|
||||
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||
}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.chatflow) {
|
||||
getConfigApi.request(dialogProps.chatflow.id)
|
||||
getAllVariablesApi.request()
|
||||
}
|
||||
|
||||
return () => {}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dialogProps])
|
||||
|
||||
useEffect(() => {
|
||||
if (getConfigApi.data) {
|
||||
groupByNodeLabel(getConfigApi.data)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getConfigApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllVariablesApi.data) {
|
||||
groupByVariableLabel(getAllVariablesApi.data)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getAllVariablesApi.data])
|
||||
|
||||
return (
|
||||
<Stack direction='column' spacing={2} sx={{ alignItems: 'start' }}>
|
||||
<Typography variant='h3'>
|
||||
Override Configuration
|
||||
<TooltipWithParser
|
||||
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
||||
title={
|
||||
'Enable or disable which properties of the flow configuration can be overridden. Refer to the <a href="https://docs.flowiseai.com/using-flowise/api#override-config" target="_blank">documentation</a> for more information.'
|
||||
}
|
||||
/>
|
||||
</Typography>
|
||||
<Stack direction='column' spacing={2} sx={{ width: '100%' }}>
|
||||
<SwitchInput label='Enable Override Configuration' onChange={setOverrideConfigStatus} value={overrideConfigStatus} />
|
||||
{overrideConfigStatus && (
|
||||
<>
|
||||
{nodeOverrides && nodeConfig && (
|
||||
<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>
|
||||
<Stack direction='column'>
|
||||
{Object.keys(nodeOverrides)
|
||||
.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 sx={{ p: 0 }}>
|
||||
<OverrideConfigTable
|
||||
rows={nodeOverrides[nodeLabel]}
|
||||
columns={
|
||||
nodeOverrides[nodeLabel].length > 0
|
||||
? Object.keys(nodeOverrides[nodeLabel][0])
|
||||
: []
|
||||
}
|
||||
onToggle={(property, status) =>
|
||||
onNodeOverrideToggle(nodeLabel, property.name, status)
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
)}
|
||||
{variableOverrides && (
|
||||
<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>
|
||||
<OverrideConfigTable
|
||||
rows={variableOverrides}
|
||||
columns={['name', 'type', 'enabled']}
|
||||
onToggle={(property, status) => onVariableOverrideToggle(property.id, status)}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
<StyledButton variant='contained' onClick={onOverrideConfigSave}>
|
||||
Save
|
||||
</StyledButton>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
OverrideConfig.propTypes = {
|
||||
dialogProps: PropTypes.object
|
||||
}
|
||||
|
||||
export default OverrideConfig
|
||||
@@ -3,7 +3,7 @@ import { useDispatch, useSelector } from 'react-redux'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Box, Typography, Button, OutlinedInput } from '@mui/material'
|
||||
import { Typography, Button, OutlinedInput, Stack } from '@mui/material'
|
||||
|
||||
// Project import
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
@@ -126,55 +126,49 @@ const RateLimit = () => {
|
||||
|
||||
const textField = (message, fieldName, fieldLabel, fieldType = 'string', placeholder = '') => {
|
||||
return (
|
||||
<Box sx={{ pt: 2, pb: 2 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
|
||||
<Typography sx={{ mb: 1 }}>{fieldLabel}</Typography>
|
||||
<OutlinedInput
|
||||
id={fieldName}
|
||||
type={fieldType}
|
||||
fullWidth
|
||||
value={message}
|
||||
placeholder={placeholder}
|
||||
name={fieldName}
|
||||
size='small'
|
||||
onChange={(e) => {
|
||||
onTextChanged(e.target.value, fieldName)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
<Stack direction='column' spacing={1}>
|
||||
<Typography>{fieldLabel}</Typography>
|
||||
<OutlinedInput
|
||||
id={fieldName}
|
||||
type={fieldType}
|
||||
fullWidth
|
||||
value={message}
|
||||
placeholder={placeholder}
|
||||
name={fieldName}
|
||||
size='small'
|
||||
onChange={(e) => {
|
||||
onTextChanged(e.target.value, fieldName)
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/*Rate Limit*/}
|
||||
<Typography variant='h4' sx={{ mb: 1 }}>
|
||||
<Stack direction='column' spacing={2} sx={{ alignItems: 'start' }}>
|
||||
<Typography variant='h3'>
|
||||
Rate Limit{' '}
|
||||
<TooltipWithParser
|
||||
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
||||
style={{ marginLeft: 10 }}
|
||||
title={
|
||||
'Visit <a target="_blank" href="https://docs.flowiseai.com/rate-limit">Rate Limit Setup Guide</a> to set up Rate Limit correctly in your hosting environment.'
|
||||
}
|
||||
/>
|
||||
</Typography>
|
||||
<SwitchInput label='Enable Rate Limit' onChange={handleChange} value={rateLimitStatus} />
|
||||
{rateLimitStatus && (
|
||||
<>
|
||||
{textField(limitMax, 'limitMax', 'Message Limit per Duration', 'number', '5')}
|
||||
{textField(limitDuration, 'limitDuration', 'Duration in Second', 'number', '60')}
|
||||
{textField(limitMsg, 'limitMsg', 'Limit Message', 'string', 'You have reached the quota')}
|
||||
</>
|
||||
)}
|
||||
<StyledButton
|
||||
disabled={checkDisabled()}
|
||||
style={{ marginBottom: 10, marginTop: 10 }}
|
||||
variant='contained'
|
||||
onClick={() => onSave()}
|
||||
>
|
||||
Save Changes
|
||||
<Stack direction='column' spacing={2} sx={{ width: '100%' }}>
|
||||
<SwitchInput label='Enable Rate Limit' onChange={handleChange} value={rateLimitStatus} />
|
||||
{rateLimitStatus && (
|
||||
<Stack direction='column' spacing={2} sx={{ width: '100%' }}>
|
||||
{textField(limitMax, 'limitMax', 'Message Limit per Duration', 'number', '5')}
|
||||
{textField(limitDuration, 'limitDuration', 'Duration in Second', 'number', '60')}
|
||||
{textField(limitMsg, 'limitMsg', 'Limit Message', 'string', 'You have reached the quota')}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
<StyledButton disabled={checkDisabled()} variant='contained' onClick={() => onSave()} sx={{ width: 'auto' }}>
|
||||
Save
|
||||
</StyledButton>
|
||||
</>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { Divider, Stack } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
|
||||
// Project import
|
||||
import RateLimit from '@/ui-component/extended/RateLimit'
|
||||
import AllowedDomains from '@/ui-component/extended/AllowedDomains'
|
||||
import OverrideConfig from './OverrideConfig'
|
||||
|
||||
const Security = ({ dialogProps }) => {
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<Stack direction='column' divider={<Divider sx={{ my: 0.5, borderColor: theme.palette.grey[900] + 25 }} />} spacing={4}>
|
||||
<RateLimit />
|
||||
<AllowedDomains dialogProps={dialogProps} />
|
||||
<OverrideConfig dialogProps={dialogProps} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
Security.propTypes = {
|
||||
dialogProps: PropTypes.object
|
||||
}
|
||||
|
||||
export default Security
|
||||
@@ -248,9 +248,7 @@ const SpeechToText = ({ dialogProps }) => {
|
||||
return (
|
||||
<>
|
||||
<Box fullWidth sx={{ mb: 1, display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant='h4' sx={{ mb: 1 }}>
|
||||
Providers
|
||||
</Typography>
|
||||
<Typography>Providers</Typography>
|
||||
<FormControl fullWidth>
|
||||
<Select size='small' value={selectedProvider} onChange={handleProviderChange}>
|
||||
<MenuItem value='none'>None</MenuItem>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { TableContainer, Table, TableHead, TableCell, TableRow, TableBody, Paper } from '@mui/material'
|
||||
import { TableContainer, Table, TableHead, TableCell, TableRow, TableBody, Paper, Chip } from '@mui/material'
|
||||
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
||||
|
||||
export const TableViewOnly = ({ columns, rows, sx }) => {
|
||||
return (
|
||||
@@ -9,16 +10,44 @@ export const TableViewOnly = ({ columns, rows, sx }) => {
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columns.map((col, index) => (
|
||||
<TableCell key={index}>{col.charAt(0).toUpperCase() + col.slice(1)}</TableCell>
|
||||
<TableCell key={index}>
|
||||
{col === 'enabled' ? (
|
||||
<>
|
||||
Override
|
||||
<TooltipWithParser
|
||||
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
||||
title={
|
||||
'If enabled, this variable can be overridden in API calls and embeds. If disabled, any overrides will be ignored. To change this, go to Security settings in Chatflow Configuration.'
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
col.charAt(0).toUpperCase() + col.slice(1)
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{rows.map((row, index) => (
|
||||
<TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||
{Object.keys(row).map((key, index) => (
|
||||
<TableCell key={index}>{row[key]}</TableCell>
|
||||
))}
|
||||
{Object.keys(row).map((key, index) => {
|
||||
if (key !== 'id') {
|
||||
return (
|
||||
<TableCell key={index}>
|
||||
{key === 'enabled' ? (
|
||||
row[key] ? (
|
||||
<Chip label='Enabled' color='primary' />
|
||||
) : (
|
||||
<Chip label='Disabled' />
|
||||
)
|
||||
) : (
|
||||
row[key]
|
||||
)}
|
||||
</TableCell>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
@@ -449,7 +449,7 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, handleSaveFlow, handleDeleteFlo
|
||||
onCancel={() => setFlowDialogOpen(false)}
|
||||
onConfirm={onConfirmSaveName}
|
||||
/>
|
||||
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
|
||||
{apiDialogOpen && <APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />}
|
||||
<ViewMessagesDialog
|
||||
show={viewMessagesDialogOpen}
|
||||
dialogProps={viewMessagesDialogProps}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {
|
||||
@@ -15,10 +15,12 @@ import {
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Typography,
|
||||
Stack
|
||||
Stack,
|
||||
Card
|
||||
} from '@mui/material'
|
||||
import { CopyBlock, atomOneDark } from 'react-code-blocks'
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
|
||||
// Project import
|
||||
import { Dropdown } from '@/ui-component/dropdown/Dropdown'
|
||||
@@ -36,12 +38,13 @@ import cURLSVG from '@/assets/images/cURL.svg'
|
||||
import EmbedSVG from '@/assets/images/embed.svg'
|
||||
import ShareChatbotSVG from '@/assets/images/sharing.png'
|
||||
import settingsSVG from '@/assets/images/settings.svg'
|
||||
import { IconBulb } from '@tabler/icons-react'
|
||||
import { IconBulb, IconBox, IconVariable } from '@tabler/icons-react'
|
||||
|
||||
// API
|
||||
import apiKeyApi from '@/api/apikey'
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
import configApi from '@/api/config'
|
||||
import variablesApi from '@/api/variables'
|
||||
|
||||
// Hooks
|
||||
import useApi from '@/hooks/useApi'
|
||||
@@ -83,6 +86,10 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const navigate = useNavigate()
|
||||
const dispatch = useDispatch()
|
||||
const theme = useTheme()
|
||||
const chatflow = useSelector((state) => state.canvas.chatflow)
|
||||
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 [value, setValue] = useState(0)
|
||||
@@ -93,16 +100,20 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const [checkboxVal, setCheckbox] = useState(false)
|
||||
const [nodeConfig, setNodeConfig] = useState({})
|
||||
const [nodeConfigExpanded, setNodeConfigExpanded] = useState({})
|
||||
const [nodeOverrides, setNodeOverrides] = useState(apiConfig?.overrideConfig?.nodes ?? null)
|
||||
const [variableOverrides, setVariableOverrides] = useState(apiConfig?.overrideConfig?.variables ?? [])
|
||||
|
||||
const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys)
|
||||
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
||||
const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming)
|
||||
const getConfigApi = useApi(configApi.getConfig)
|
||||
const getAllVariablesApi = useApi(variablesApi.getAllVariables)
|
||||
|
||||
const onCheckBoxChanged = (newVal) => {
|
||||
setCheckbox(newVal)
|
||||
if (newVal) {
|
||||
getConfigApi.request(dialogProps.chatflowid)
|
||||
getAllVariablesApi.request()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,9 +132,12 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
||||
|
||||
const groupByNodeLabel = (nodes) => {
|
||||
const result = {}
|
||||
const newNodeOverrides = {}
|
||||
const seenNodes = new Set()
|
||||
|
||||
nodes.forEach((item) => {
|
||||
const { node, nodeId, label, name, type } = item
|
||||
seenNodes.add(node)
|
||||
|
||||
if (!result[node]) {
|
||||
result[node] = {
|
||||
@@ -132,12 +146,23 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!newNodeOverrides[node]) {
|
||||
// If overrideConfigStatus is true, copy existing config for this node
|
||||
newNodeOverrides[node] = overrideConfigStatus ? [...(nodeOverrides[node] || [])] : []
|
||||
}
|
||||
|
||||
if (!result[node].nodeIds.includes(nodeId)) result[node].nodeIds.push(nodeId)
|
||||
|
||||
const param = { label, name, type }
|
||||
|
||||
if (!result[node].params.some((existingParam) => JSON.stringify(existingParam) === JSON.stringify(param))) {
|
||||
result[node].params.push(param)
|
||||
const paramExists = newNodeOverrides[node].some(
|
||||
(existingParam) => existingParam.label === label && existingParam.name === name && existingParam.type === type
|
||||
)
|
||||
if (!paramExists) {
|
||||
newNodeOverrides[node].push({ ...param, enabled: false })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -145,8 +170,73 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
||||
for (const node in result) {
|
||||
result[node].nodeIds.sort()
|
||||
}
|
||||
|
||||
setNodeConfig(result)
|
||||
|
||||
if (!overrideConfigStatus) {
|
||||
setNodeOverrides(newNodeOverrides)
|
||||
} else {
|
||||
const updatedNodeOverrides = { ...nodeOverrides }
|
||||
|
||||
Object.keys(updatedNodeOverrides).forEach((node) => {
|
||||
if (!seenNodes.has(node)) {
|
||||
delete updatedNodeOverrides[node]
|
||||
}
|
||||
})
|
||||
|
||||
seenNodes.forEach((node) => {
|
||||
if (!updatedNodeOverrides[node]) {
|
||||
updatedNodeOverrides[node] = newNodeOverrides[node]
|
||||
}
|
||||
})
|
||||
|
||||
setNodeOverrides(updatedNodeOverrides)
|
||||
}
|
||||
}
|
||||
|
||||
const groupByVariableLabel = (variables) => {
|
||||
const newVariables = []
|
||||
const seenVariables = new Set()
|
||||
|
||||
variables.forEach((item) => {
|
||||
const { id, name, type } = item
|
||||
seenVariables.add(id)
|
||||
|
||||
const param = { id, name, type }
|
||||
|
||||
// If overrideConfigStatus is true, look for existing variable config
|
||||
// Otherwise, create new default config
|
||||
if (overrideConfigStatus) {
|
||||
const existingVariable = variableOverrides?.find((existingParam) => existingParam.id === id)
|
||||
if (existingVariable) {
|
||||
if (!newVariables.some((variable) => variable.id === id)) {
|
||||
newVariables.push({ ...existingVariable })
|
||||
}
|
||||
} else {
|
||||
if (!newVariables.some((variable) => variable.id === id)) {
|
||||
newVariables.push({ ...param, enabled: false })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// When no override config exists, create default values
|
||||
if (!newVariables.some((variable) => variable.id === id)) {
|
||||
newVariables.push({ ...param, enabled: false })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// If overrideConfigStatus is true, clean up any variables that no longer exist
|
||||
if (overrideConfigStatus && variableOverrides) {
|
||||
variableOverrides.forEach((existingVariable) => {
|
||||
if (!seenVariables.has(existingVariable.id)) {
|
||||
const index = newVariables.findIndex((newVariable) => newVariable.id === existingVariable.id)
|
||||
if (index !== -1) {
|
||||
newVariables.splice(index, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setVariableOverrides(newVariables)
|
||||
}
|
||||
|
||||
const handleAccordionChange = (nodeLabel) => (event, isExpanded) => {
|
||||
@@ -165,8 +255,16 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
||||
if (getConfigApi.data) {
|
||||
groupByNodeLabel(getConfigApi.data)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getConfigApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllVariablesApi.data) {
|
||||
groupByVariableLabel(getAllVariablesApi.data)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getAllVariablesApi.data])
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
setValue(newValue)
|
||||
}
|
||||
@@ -625,55 +723,85 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")`
|
||||
showLineNumbers={false}
|
||||
wrapLines
|
||||
/>
|
||||
<CheckboxInput label='Show Input Config' value={checkboxVal} onChange={onCheckBoxChanged} />
|
||||
<CheckboxInput label='Show Override Config' value={checkboxVal} onChange={onCheckBoxChanged} />
|
||||
{checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && (
|
||||
<>
|
||||
{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={nodeConfig[nodeLabel].params}
|
||||
columns={Object.keys(nodeConfig[nodeLabel].params[0]).slice(-3)}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
<Typography sx={{ mt: 2, mb: 3 }}>
|
||||
You can override existing input configuration of the chatflow with overrideConfig property.
|
||||
</Typography>
|
||||
<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])
|
||||
: []
|
||||
}
|
||||
/>
|
||||
</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={
|
||||
|
||||
@@ -140,9 +140,9 @@ const ShareChatbot = ({ isSessionMemory, isAgentCanvas }) => {
|
||||
if (isSessionMemory) obj.overrideConfig.generateNewSession = generateNewSession
|
||||
|
||||
if (renderHTML) {
|
||||
obj.overrideConfig.renderHTML = true
|
||||
obj.renderHTML = true
|
||||
} else {
|
||||
obj.overrideConfig.renderHTML = false
|
||||
obj.renderHTML = false
|
||||
}
|
||||
|
||||
if (chatbotConfig?.starterPrompts) obj.starterPrompts = chatbotConfig.starterPrompts
|
||||
|
||||
Reference in New Issue
Block a user