1445 lines
69 KiB
JavaScript
1445 lines
69 KiB
JavaScript
import PropTypes from 'prop-types'
|
|
import { Handle, Position, useUpdateNodeInternals } from 'reactflow'
|
|
import { useEffect, useRef, useState, useContext } from 'react'
|
|
import { useSelector, useDispatch } from 'react-redux'
|
|
import { cloneDeep } from 'lodash'
|
|
import showdown from 'showdown'
|
|
import parser from 'html-react-parser'
|
|
|
|
// material-ui
|
|
import { useTheme, styled } from '@mui/material/styles'
|
|
import {
|
|
Popper,
|
|
Box,
|
|
Typography,
|
|
Tooltip,
|
|
IconButton,
|
|
Button,
|
|
TextField,
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions
|
|
} from '@mui/material'
|
|
import { useGridApiContext } from '@mui/x-data-grid'
|
|
import IconAutoFixHigh from '@mui/icons-material/AutoFixHigh'
|
|
import { tooltipClasses } from '@mui/material/Tooltip'
|
|
import { IconWand, IconVariable, IconArrowsMaximize, IconEdit, IconAlertTriangle, IconBulb, IconRefresh, IconX } from '@tabler/icons-react'
|
|
import { Tabs } from '@mui/base/Tabs'
|
|
import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete'
|
|
|
|
// project import
|
|
import { Dropdown } from '@/ui-component/dropdown/Dropdown'
|
|
import { MultiDropdown } from '@/ui-component/dropdown/MultiDropdown'
|
|
import { AsyncDropdown } from '@/ui-component/dropdown/AsyncDropdown'
|
|
import { Input } from '@/ui-component/input/Input'
|
|
import { RichInput } from '@/ui-component/input/RichInput'
|
|
import { DataGrid } from '@/ui-component/grid/DataGrid'
|
|
import { File } from '@/ui-component/file/File'
|
|
import { SwitchInput } from '@/ui-component/switch/Switch'
|
|
import { flowContext } from '@/store/context/ReactFlowContext'
|
|
import { JsonEditorInput } from '@/ui-component/json/JsonEditor'
|
|
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
|
import { CodeEditor } from '@/ui-component/editor/CodeEditor'
|
|
import { TabPanel } from '@/ui-component/tabs/TabPanel'
|
|
import { TabsList } from '@/ui-component/tabs/TabsList'
|
|
import { ArrayRenderer } from '@/ui-component/array/ArrayRenderer'
|
|
import { Tab } from '@/ui-component/tabs/Tab'
|
|
import { ConfigInput } from '@/views/agentflowsv2/ConfigInput'
|
|
import { BackdropLoader } from '@/ui-component/loading/BackdropLoader'
|
|
import DocStoreInputHandler from '@/views/docstore/DocStoreInputHandler'
|
|
|
|
import ToolDialog from '@/views/tools/ToolDialog'
|
|
import AssistantDialog from '@/views/assistants/openai/AssistantDialog'
|
|
import FormatPromptValuesDialog from '@/ui-component/dialog/FormatPromptValuesDialog'
|
|
import ExpandTextDialog from '@/ui-component/dialog/ExpandTextDialog'
|
|
import ExpandRichInputDialog from '@/ui-component/dialog/ExpandRichInputDialog'
|
|
import ConditionDialog from '@/ui-component/dialog/ConditionDialog'
|
|
import PromptLangsmithHubDialog from '@/ui-component/dialog/PromptLangsmithHubDialog'
|
|
import ManageScrapedLinksDialog from '@/ui-component/dialog/ManageScrapedLinksDialog'
|
|
import CredentialInputHandler from './CredentialInputHandler'
|
|
import InputHintDialog from '@/ui-component/dialog/InputHintDialog'
|
|
import NvidiaNIMDialog from '@/ui-component/dialog/NvidiaNIMDialog'
|
|
import PromptGeneratorDialog from '@/ui-component/dialog/PromptGeneratorDialog'
|
|
|
|
// API
|
|
import assistantsApi from '@/api/assistants'
|
|
import documentstoreApi from '@/api/documentstore'
|
|
|
|
// utils
|
|
import {
|
|
initNode,
|
|
getInputVariables,
|
|
getCustomConditionOutputs,
|
|
isValidConnection,
|
|
getAvailableNodesForVariable
|
|
} from '@/utils/genericHelper'
|
|
import useNotifier from '@/utils/useNotifier'
|
|
|
|
// const
|
|
import { baseURL, FLOWISE_CREDENTIAL_ID } from '@/store/constant'
|
|
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions'
|
|
|
|
const EDITABLE_OPTIONS = ['selectedTool', 'selectedAssistant']
|
|
|
|
const CustomWidthTooltip = styled(({ className, ...props }) => <Tooltip {...props} classes={{ popper: className }} />)({
|
|
[`& .${tooltipClasses.tooltip}`]: {
|
|
maxWidth: 500
|
|
}
|
|
})
|
|
|
|
const StyledPopper = styled(Popper)({
|
|
boxShadow: '0px 8px 10px -5px rgb(0 0 0 / 20%), 0px 16px 24px 2px rgb(0 0 0 / 14%), 0px 6px 30px 5px rgb(0 0 0 / 12%)',
|
|
borderRadius: '10px',
|
|
[`& .${autocompleteClasses.listbox}`]: {
|
|
boxSizing: 'border-box',
|
|
'& ul': {
|
|
padding: 10,
|
|
margin: 10
|
|
}
|
|
}
|
|
})
|
|
|
|
const markdownConverter = new showdown.Converter({
|
|
simplifiedAutoLink: true,
|
|
strikethrough: true,
|
|
tables: true,
|
|
tasklists: true
|
|
})
|
|
|
|
// ===========================|| NodeInputHandler ||=========================== //
|
|
|
|
const NodeInputHandler = ({
|
|
inputAnchor,
|
|
inputParam,
|
|
data,
|
|
disabled = false,
|
|
isAdditionalParams = false,
|
|
disablePadding = false,
|
|
parentParamForArray = null,
|
|
arrayIndex = null,
|
|
onHideNodeInfoDialog,
|
|
onCustomDataChange
|
|
}) => {
|
|
const theme = useTheme()
|
|
const customization = useSelector((state) => state.customization)
|
|
const ref = useRef(null)
|
|
const { reactFlowInstance, deleteEdge, onNodeDataChange } = useContext(flowContext)
|
|
const updateNodeInternals = useUpdateNodeInternals()
|
|
|
|
useNotifier()
|
|
const dispatch = useDispatch()
|
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
|
|
|
const [position, setPosition] = useState(0)
|
|
const [showExpandDialog, setShowExpandDialog] = useState(false)
|
|
const [expandDialogProps, setExpandDialogProps] = useState({})
|
|
const [showExpandRichDialog, setShowExpandRichDialog] = useState(false)
|
|
const [expandRichDialogProps, setExpandRichDialogProps] = useState({})
|
|
const [showAsyncOptionDialog, setAsyncOptionEditDialog] = useState('')
|
|
const [asyncOptionEditDialogProps, setAsyncOptionEditDialogProps] = useState({})
|
|
const [reloadTimestamp, setReloadTimestamp] = useState(Date.now().toString())
|
|
const [showFormatPromptValuesDialog, setShowFormatPromptValuesDialog] = useState(false)
|
|
const [formatPromptValuesDialogProps, setFormatPromptValuesDialogProps] = useState({})
|
|
const [showPromptHubDialog, setShowPromptHubDialog] = useState(false)
|
|
const [showManageScrapedLinksDialog, setShowManageScrapedLinksDialog] = useState(false)
|
|
const [manageScrapedLinksDialogProps, setManageScrapedLinksDialogProps] = useState({})
|
|
const [showInputHintDialog, setShowInputHintDialog] = useState(false)
|
|
const [inputHintDialogProps, setInputHintDialogProps] = useState({})
|
|
const [showConditionDialog, setShowConditionDialog] = useState(false)
|
|
const [conditionDialogProps, setConditionDialogProps] = useState({})
|
|
const [isNvidiaNIMDialogOpen, setIsNvidiaNIMDialogOpen] = useState(false)
|
|
const [tabValue, setTabValue] = useState(0)
|
|
|
|
const [modelSelectionDialogOpen, setModelSelectionDialogOpen] = useState(false)
|
|
const [availableChatModels, setAvailableChatModels] = useState([])
|
|
const [availableChatModelsOptions, setAvailableChatModelsOptions] = useState([])
|
|
const [selectedTempChatModel, setSelectedTempChatModel] = useState({})
|
|
const [modelSelectionCallback, setModelSelectionCallback] = useState(null)
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
const [promptGeneratorDialogOpen, setPromptGeneratorDialogOpen] = useState(false)
|
|
const [promptGeneratorDialogProps, setPromptGeneratorDialogProps] = useState({})
|
|
|
|
const handleDataChange = ({ inputParam, newValue }) => {
|
|
data.inputs[inputParam.name] = newValue
|
|
const allowedShowHideInputTypes = ['boolean', 'asyncOptions', 'asyncMultiOptions', 'options', 'multiOptions']
|
|
if (allowedShowHideInputTypes.includes(inputParam.type)) {
|
|
if (onCustomDataChange) {
|
|
onCustomDataChange({ nodeId: data.id, inputParam, newValue })
|
|
} else {
|
|
onNodeDataChange({ nodeId: data.id, inputParam, newValue })
|
|
}
|
|
}
|
|
}
|
|
|
|
const onInputHintDialogClicked = (hint) => {
|
|
const dialogProps = {
|
|
...hint
|
|
}
|
|
setInputHintDialogProps(dialogProps)
|
|
setShowInputHintDialog(true)
|
|
}
|
|
|
|
const onExpandDialogClicked = (value, inputParam, languageType) => {
|
|
const dialogProps = {
|
|
value,
|
|
inputParam,
|
|
disabled,
|
|
languageType,
|
|
nodes: reactFlowInstance?.getNodes() || [],
|
|
edges: reactFlowInstance?.getEdges() || [],
|
|
nodeId: data.id,
|
|
confirmButtonName: 'Save',
|
|
cancelButtonName: 'Cancel'
|
|
}
|
|
if (inputParam.acceptVariable) {
|
|
setExpandRichDialogProps(dialogProps)
|
|
setShowExpandRichDialog(true)
|
|
} else {
|
|
setExpandDialogProps(dialogProps)
|
|
setShowExpandDialog(true)
|
|
}
|
|
}
|
|
|
|
const onConditionDialogClicked = (inputParam) => {
|
|
const dialogProps = {
|
|
data,
|
|
inputParam,
|
|
disabled,
|
|
confirmButtonName: 'Save',
|
|
cancelButtonName: 'Cancel'
|
|
}
|
|
setConditionDialogProps(dialogProps)
|
|
setShowConditionDialog(true)
|
|
onHideNodeInfoDialog(true)
|
|
}
|
|
|
|
const onShowPromptHubButtonClicked = () => {
|
|
setShowPromptHubDialog(true)
|
|
}
|
|
|
|
const onShowPromptHubButtonSubmit = (templates) => {
|
|
setShowPromptHubDialog(false)
|
|
for (const t of templates) {
|
|
if (Object.prototype.hasOwnProperty.call(data.inputs, t.type)) {
|
|
data.inputs[t.type] = t.template
|
|
}
|
|
}
|
|
}
|
|
|
|
const onManageLinksDialogClicked = (url, selectedLinks, relativeLinksMethod, limit) => {
|
|
const dialogProps = {
|
|
url,
|
|
relativeLinksMethod,
|
|
limit,
|
|
selectedLinks,
|
|
confirmButtonName: 'Save',
|
|
cancelButtonName: 'Cancel'
|
|
}
|
|
setManageScrapedLinksDialogProps(dialogProps)
|
|
setShowManageScrapedLinksDialog(true)
|
|
}
|
|
|
|
const onManageLinksDialogSave = (url, links) => {
|
|
setShowManageScrapedLinksDialog(false)
|
|
data.inputs.url = url
|
|
data.inputs.selectedLinks = links
|
|
}
|
|
|
|
const getJSONValue = (templateValue) => {
|
|
if (!templateValue) return ''
|
|
const obj = {}
|
|
const inputVariables = getInputVariables(templateValue)
|
|
for (const inputVariable of inputVariables) {
|
|
obj[inputVariable] = ''
|
|
}
|
|
if (Object.keys(obj).length) return JSON.stringify(obj)
|
|
return ''
|
|
}
|
|
|
|
const getDataGridColDef = (columns, inputParam) => {
|
|
const colDef = []
|
|
for (const column of columns) {
|
|
const stateNode = reactFlowInstance ? reactFlowInstance.getNodes().find((node) => node.data.name === 'seqState') : null
|
|
if (column.type === 'asyncSingleSelect' && column.loadMethod && column.loadMethod.includes('loadStateKeys')) {
|
|
if (stateNode) {
|
|
const tabParam = stateNode.data.inputParams.find((param) => param.tabIdentifier)
|
|
if (tabParam && tabParam.tabs.length > 0) {
|
|
const selectedTabIdentifier = tabParam.tabIdentifier
|
|
|
|
const selectedTab =
|
|
stateNode.data.inputs[`${selectedTabIdentifier}_${stateNode.data.id}`] ||
|
|
tabParam.default ||
|
|
tabParam.tabs[0].name
|
|
|
|
const datagridValues = stateNode.data.inputs[selectedTab]
|
|
if (datagridValues) {
|
|
try {
|
|
const parsedDatagridValues = JSON.parse(datagridValues)
|
|
const keys = Array.isArray(parsedDatagridValues)
|
|
? parsedDatagridValues.map((item) => item.key)
|
|
: Object.keys(parsedDatagridValues)
|
|
colDef.push({
|
|
...column,
|
|
field: column.field,
|
|
headerName: column.headerName,
|
|
type: 'singleSelect',
|
|
editable: true,
|
|
valueOptions: keys
|
|
})
|
|
} catch (error) {
|
|
console.error('Error parsing stateMemory', error)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
colDef.push({
|
|
...column,
|
|
field: column.field,
|
|
headerName: column.headerName,
|
|
type: 'singleSelect',
|
|
editable: true,
|
|
valueOptions: []
|
|
})
|
|
}
|
|
} else if (column.type === 'freeSolo') {
|
|
const preLoadOptions = []
|
|
if (column.loadMethod && column.loadMethod.includes('getPreviousMessages')) {
|
|
const nodes = getAvailableNodesForVariable(
|
|
reactFlowInstance?.getNodes() || [],
|
|
reactFlowInstance?.getEdges() || [],
|
|
data.id,
|
|
inputParam.id
|
|
)
|
|
for (const node of nodes) {
|
|
preLoadOptions.push({
|
|
value: `$${node.id}`,
|
|
label: `Output from ${node.data.id}`
|
|
})
|
|
}
|
|
}
|
|
if (column.loadMethod && column.loadMethod.includes('loadStateKeys')) {
|
|
if (stateNode) {
|
|
const tabParam = stateNode.data.inputParams.find((param) => param.tabIdentifier)
|
|
if (tabParam && tabParam.tabs.length > 0) {
|
|
const selectedTabIdentifier = tabParam.tabIdentifier
|
|
|
|
const selectedTab =
|
|
stateNode.data.inputs[`${selectedTabIdentifier}_${stateNode.data.id}`] ||
|
|
tabParam.default ||
|
|
tabParam.tabs[0].name
|
|
|
|
const datagridValues = stateNode.data.inputs[selectedTab]
|
|
if (datagridValues) {
|
|
try {
|
|
const parsedDatagridValues = JSON.parse(datagridValues)
|
|
const keys = Array.isArray(parsedDatagridValues)
|
|
? parsedDatagridValues.map((item) => item.key)
|
|
: Object.keys(parsedDatagridValues)
|
|
for (const key of keys) {
|
|
preLoadOptions.push({
|
|
value: `$flow.state.${key}`,
|
|
label: `Value from ${key}`
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('Error parsing stateMemory', error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
colDef.push({
|
|
...column,
|
|
field: column.field,
|
|
headerName: column.headerName,
|
|
renderEditCell: ({ id, field, value }) => {
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
const apiRef = useGridApiContext()
|
|
return (
|
|
<Autocomplete
|
|
id={column.field}
|
|
freeSolo
|
|
fullWidth
|
|
options={[...preLoadOptions, ...column.valueOptions]}
|
|
value={value}
|
|
PopperComponent={StyledPopper}
|
|
renderInput={(params) => <TextField {...params} />}
|
|
renderOption={(props, option) => (
|
|
<li {...props}>
|
|
<div>
|
|
<strong>{option.value}</strong>
|
|
<br />
|
|
<small>{option.label}</small>
|
|
</div>
|
|
</li>
|
|
)}
|
|
getOptionLabel={(option) => {
|
|
return typeof option === 'string' ? option : option.value
|
|
}}
|
|
onInputChange={(event, newValue) => {
|
|
apiRef.current.setEditCellValue({ id, field, value: newValue })
|
|
}}
|
|
sx={{
|
|
'& .MuiInputBase-root': {
|
|
height: '50px' // Adjust this value as needed
|
|
},
|
|
'& .MuiOutlinedInput-root': {
|
|
border: 'none'
|
|
},
|
|
'& .MuiOutlinedInput-root .MuiOutlinedInput-notchedOutline': {
|
|
border: 'none'
|
|
}
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
})
|
|
} else {
|
|
colDef.push(column)
|
|
}
|
|
}
|
|
return colDef
|
|
}
|
|
|
|
const getDropdownOptions = (inputParam) => {
|
|
const preLoadOptions = []
|
|
if (inputParam.loadPreviousNodes) {
|
|
const nodes = getAvailableNodesForVariable(
|
|
reactFlowInstance?.getNodes() || [],
|
|
reactFlowInstance?.getEdges() || [],
|
|
data.id,
|
|
inputParam.id
|
|
)
|
|
for (const node of nodes) {
|
|
preLoadOptions.push({
|
|
name: `{{ ${node.data.id} }}`,
|
|
label: `{{ ${node.data.id} }}`,
|
|
description: `Output from ${node.data.id}`
|
|
})
|
|
}
|
|
}
|
|
return [...preLoadOptions, ...inputParam.options]
|
|
}
|
|
|
|
const getTabValue = (inputParam) => {
|
|
return inputParam.tabs.findIndex((item) => item.name === data.inputs[`${inputParam.tabIdentifier}_${data.id}`]) >= 0
|
|
? inputParam.tabs.findIndex((item) => item.name === data.inputs[`${inputParam.tabIdentifier}_${data.id}`])
|
|
: tabValue
|
|
}
|
|
|
|
const onEditJSONClicked = (value, inputParam) => {
|
|
// Preset values if the field is format prompt values
|
|
let inputValue = value
|
|
if (inputParam.name === 'promptValues' && !value) {
|
|
const templateValue =
|
|
(data.inputs['template'] ?? '') +
|
|
(data.inputs['systemMessagePrompt'] ?? '') +
|
|
(data.inputs['humanMessagePrompt'] ?? '') +
|
|
(data.inputs['workerPrompt'] ?? '')
|
|
inputValue = getJSONValue(templateValue)
|
|
}
|
|
const dialogProp = {
|
|
value: inputValue,
|
|
inputParam,
|
|
nodes: reactFlowInstance?.getNodes() || [],
|
|
edges: reactFlowInstance?.getEdges() || [],
|
|
nodeId: data.id,
|
|
data
|
|
}
|
|
setFormatPromptValuesDialogProps(dialogProp)
|
|
setShowFormatPromptValuesDialog(true)
|
|
}
|
|
|
|
const onExpandDialogSave = (newValue, inputParamName) => {
|
|
data.inputs[inputParamName] = newValue
|
|
setShowExpandDialog(false)
|
|
}
|
|
|
|
const onExpandRichDialogSave = (newValue, inputParamName) => {
|
|
data.inputs[inputParamName] = newValue
|
|
setShowExpandRichDialog(false)
|
|
}
|
|
|
|
const onConditionDialogSave = (newData, inputParam, tabValue) => {
|
|
data.inputs[`${inputParam.tabIdentifier}_${data.id}`] = inputParam.tabs[tabValue].name
|
|
|
|
const existingEdges = reactFlowInstance?.getEdges().filter((edge) => edge.source === data.id) || []
|
|
const { outputAnchors, toBeRemovedEdgeIds } = getCustomConditionOutputs(
|
|
newData.inputs[inputParam.tabs[tabValue].name],
|
|
data.id,
|
|
existingEdges,
|
|
inputParam.tabs[tabValue].type === 'datagrid'
|
|
)
|
|
if (!outputAnchors) return
|
|
data.outputAnchors = outputAnchors
|
|
for (const edgeId of toBeRemovedEdgeIds) {
|
|
deleteEdge(edgeId)
|
|
}
|
|
setShowConditionDialog(false)
|
|
onHideNodeInfoDialog(false)
|
|
}
|
|
|
|
const editAsyncOption = (inputParamName, inputValue) => {
|
|
if (inputParamName === 'selectedTool') {
|
|
setAsyncOptionEditDialogProps({
|
|
title: 'Edit Tool',
|
|
type: 'EDIT',
|
|
cancelButtonName: 'Cancel',
|
|
confirmButtonName: 'Save',
|
|
toolId: inputValue
|
|
})
|
|
} else if (inputParamName === 'selectedAssistant') {
|
|
setAsyncOptionEditDialogProps({
|
|
title: 'Edit Assistant',
|
|
type: 'EDIT',
|
|
cancelButtonName: 'Cancel',
|
|
confirmButtonName: 'Save',
|
|
assistantId: inputValue
|
|
})
|
|
}
|
|
setAsyncOptionEditDialog(inputParamName)
|
|
}
|
|
|
|
const addAsyncOption = (inputParamName) => {
|
|
if (inputParamName === 'selectedTool') {
|
|
setAsyncOptionEditDialogProps({
|
|
title: 'Add New Tool',
|
|
type: 'ADD',
|
|
cancelButtonName: 'Cancel',
|
|
confirmButtonName: 'Add'
|
|
})
|
|
} else if (inputParamName === 'selectedAssistant') {
|
|
setAsyncOptionEditDialogProps({
|
|
title: 'Add New Assistant',
|
|
type: 'ADD',
|
|
cancelButtonName: 'Cancel',
|
|
confirmButtonName: 'Add'
|
|
})
|
|
}
|
|
setAsyncOptionEditDialog(inputParamName)
|
|
}
|
|
|
|
const onConfirmAsyncOption = (selectedOptionId = '') => {
|
|
if (!selectedOptionId) {
|
|
data.inputs[showAsyncOptionDialog] = ''
|
|
handleDataChange({ inputParam: { name: showAsyncOptionDialog }, newValue: '' })
|
|
} else {
|
|
data.inputs[showAsyncOptionDialog] = selectedOptionId
|
|
handleDataChange({ inputParam: { name: showAsyncOptionDialog }, newValue: selectedOptionId })
|
|
setReloadTimestamp(Date.now().toString())
|
|
}
|
|
setAsyncOptionEditDialogProps({})
|
|
setAsyncOptionEditDialog('')
|
|
}
|
|
|
|
const handleNvidiaNIMDialogComplete = (containerData) => {
|
|
if (containerData) {
|
|
data.inputs['basePath'] = containerData.baseUrl
|
|
data.inputs['modelName'] = containerData.image
|
|
}
|
|
}
|
|
|
|
const loadChatModels = async () => {
|
|
try {
|
|
const resp = await assistantsApi.getChatModels()
|
|
if (resp.data) {
|
|
const chatModels = resp.data ?? []
|
|
const chatModelsOptions = chatModels.map((model) => ({
|
|
name: model.name,
|
|
label: model.label,
|
|
description: model.description,
|
|
imageSrc: `${baseURL}/api/v1/node-icon/${model.name}`
|
|
}))
|
|
setAvailableChatModels(chatModels)
|
|
setAvailableChatModelsOptions(chatModelsOptions)
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading chat models:', error)
|
|
}
|
|
}
|
|
|
|
const checkInputParamsMandatory = () => {
|
|
let canSubmit = true
|
|
|
|
if (selectedTempChatModel && Object.keys(selectedTempChatModel).length > 0) {
|
|
const inputParams = (selectedTempChatModel.inputParams ?? []).filter((inputParam) => !inputParam.hidden)
|
|
for (const inputParam of inputParams) {
|
|
if (!inputParam.optional && (!selectedTempChatModel.inputs[inputParam.name] || !selectedTempChatModel.credential)) {
|
|
if (inputParam.type === 'credential' && !selectedTempChatModel.credential) {
|
|
canSubmit = false
|
|
break
|
|
} else if (inputParam.type !== 'credential' && !selectedTempChatModel.inputs[inputParam.name]) {
|
|
canSubmit = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return canSubmit
|
|
}
|
|
|
|
const displayWarning = () => {
|
|
enqueueSnackbar({
|
|
message: 'Please fill in all mandatory fields.',
|
|
options: {
|
|
key: new Date().getTime() + Math.random(),
|
|
variant: 'warning',
|
|
action: (key) => (
|
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
|
<IconX />
|
|
</Button>
|
|
)
|
|
}
|
|
})
|
|
}
|
|
|
|
const generateDocStoreToolDesc = async (storeId) => {
|
|
if (!storeId) {
|
|
enqueueSnackbar({
|
|
message: 'Please select a knowledge base',
|
|
options: {
|
|
key: new Date().getTime() + Math.random(),
|
|
variant: 'error',
|
|
action: (key) => (
|
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
|
<IconX />
|
|
</Button>
|
|
)
|
|
}
|
|
})
|
|
return
|
|
}
|
|
storeId = storeId.split(':')[0]
|
|
const isValid = checkInputParamsMandatory()
|
|
if (!isValid) {
|
|
displayWarning()
|
|
return
|
|
}
|
|
|
|
// Check if model is already selected in the node
|
|
const currentNode = reactFlowInstance?.getNodes().find((node) => node.id === data.id)
|
|
const currentNodeInputs = currentNode?.data?.inputs
|
|
|
|
const existingModel = currentNodeInputs?.llmModel || currentNodeInputs?.agentModel || currentNodeInputs?.humanInputModel
|
|
if (existingModel) {
|
|
try {
|
|
setLoading(true)
|
|
const selectedChatModelObj = {
|
|
name: existingModel,
|
|
inputs:
|
|
currentNodeInputs?.llmModelConfig || currentNodeInputs?.agentModelConfig || currentNodeInputs?.humanInputModelConfig
|
|
}
|
|
const resp = await documentstoreApi.generateDocStoreToolDesc(storeId, { selectedChatModel: selectedChatModelObj })
|
|
if (resp.data) {
|
|
setLoading(false)
|
|
const content = resp.data?.content || resp.data.kwargs?.content
|
|
// Update the input value directly
|
|
data.inputs[inputParam.name] = content
|
|
enqueueSnackbar({
|
|
message: 'Document Store Tool Description generated successfully',
|
|
options: {
|
|
key: new Date().getTime() + Math.random(),
|
|
variant: 'success',
|
|
action: (key) => (
|
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
|
<IconX />
|
|
</Button>
|
|
)
|
|
}
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('Error generating doc store tool desc', error)
|
|
setLoading(false)
|
|
enqueueSnackbar({
|
|
message: 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>
|
|
)
|
|
}
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
// If no model selected, load chat models and open model selection dialog
|
|
await loadChatModels()
|
|
setModelSelectionCallback(() => async (selectedModel) => {
|
|
try {
|
|
setLoading(true)
|
|
const selectedChatModelObj = {
|
|
name: selectedModel.name,
|
|
inputs: selectedModel.inputs
|
|
}
|
|
const resp = await documentstoreApi.generateDocStoreToolDesc(storeId, { selectedChatModel: selectedChatModelObj })
|
|
if (resp.data) {
|
|
setLoading(false)
|
|
const content = resp.data?.content || resp.data.kwargs?.content
|
|
// Update the input value directly
|
|
data.inputs[inputParam.name] = content
|
|
enqueueSnackbar({
|
|
message: 'Document Store Tool Description generated successfully',
|
|
options: {
|
|
key: new Date().getTime() + Math.random(),
|
|
variant: 'success',
|
|
action: (key) => (
|
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
|
<IconX />
|
|
</Button>
|
|
)
|
|
}
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('Error generating doc store tool desc', error)
|
|
setLoading(false)
|
|
enqueueSnackbar({
|
|
message: 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>
|
|
)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
setModelSelectionDialogOpen(true)
|
|
}
|
|
|
|
const generateInstruction = async () => {
|
|
const isValid = checkInputParamsMandatory()
|
|
if (!isValid) {
|
|
displayWarning()
|
|
return
|
|
}
|
|
|
|
const currentNode = reactFlowInstance?.getNodes().find((node) => node.id === data.id)
|
|
const currentNodeInputs = currentNode?.data?.inputs
|
|
|
|
// Check if model is already selected in the node
|
|
const existingModel = currentNodeInputs?.llmModel || currentNodeInputs?.agentModel || currentNodeInputs?.humanInputModel
|
|
if (existingModel) {
|
|
// Open prompt generator dialog directly with existing model
|
|
setPromptGeneratorDialogProps({
|
|
title: 'Generate Instructions',
|
|
description: 'You can generate a prompt template by sharing basic details about your task.',
|
|
data: {
|
|
selectedChatModel: {
|
|
name: existingModel,
|
|
inputs:
|
|
currentNodeInputs?.llmModelConfig ||
|
|
currentNodeInputs?.agentModelConfig ||
|
|
currentNodeInputs?.humanInputModelConfig
|
|
}
|
|
}
|
|
})
|
|
setPromptGeneratorDialogOpen(true)
|
|
return
|
|
}
|
|
|
|
// If no model selected, load chat models and open model selection dialog
|
|
await loadChatModels()
|
|
setModelSelectionCallback(() => async (selectedModel) => {
|
|
// After model selection, open prompt generator dialog
|
|
setPromptGeneratorDialogProps({
|
|
title: 'Generate Instructions',
|
|
description: 'You can generate a prompt template by sharing basic details about your task.',
|
|
data: { selectedChatModel: selectedModel }
|
|
})
|
|
setPromptGeneratorDialogOpen(true)
|
|
})
|
|
setModelSelectionDialogOpen(true)
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
|
|
setPosition(ref.current.offsetTop + ref.current.clientHeight / 2)
|
|
updateNodeInternals(data.id)
|
|
}
|
|
}, [data.id, ref, updateNodeInternals])
|
|
|
|
useEffect(() => {
|
|
updateNodeInternals(data.id)
|
|
}, [data.id, position, updateNodeInternals])
|
|
|
|
return (
|
|
<div ref={ref}>
|
|
{inputAnchor && (
|
|
<>
|
|
<CustomWidthTooltip placement='left' title={inputAnchor.type}>
|
|
<Handle
|
|
type='target'
|
|
position={Position.Left}
|
|
key={inputAnchor.id}
|
|
id={inputAnchor.id}
|
|
isValidConnection={(connection) => isValidConnection(connection, reactFlowInstance)}
|
|
style={{
|
|
height: 10,
|
|
width: 10,
|
|
backgroundColor: data.selected ? theme.palette.primary.main : theme.palette.text.secondary,
|
|
top: position
|
|
}}
|
|
/>
|
|
</CustomWidthTooltip>
|
|
<Box sx={{ p: 2 }}>
|
|
<Typography>
|
|
{inputAnchor.label}
|
|
{!inputAnchor.optional && <span style={{ color: 'red' }}> *</span>}
|
|
{inputAnchor.description && <TooltipWithParser style={{ marginLeft: 10 }} title={inputAnchor.description} />}
|
|
</Typography>
|
|
</Box>
|
|
</>
|
|
)}
|
|
|
|
{((inputParam && !inputParam.additionalParams) || isAdditionalParams) && (
|
|
<>
|
|
{inputParam.acceptVariable && !isAdditionalParams && (
|
|
<CustomWidthTooltip placement='left' title={inputParam.type}>
|
|
<Handle
|
|
type='target'
|
|
position={Position.Left}
|
|
key={inputParam.id}
|
|
id={inputParam.id}
|
|
isValidConnection={(connection) => isValidConnection(connection, reactFlowInstance)}
|
|
style={{
|
|
height: 10,
|
|
width: 10,
|
|
backgroundColor: data.selected ? theme.palette.primary.main : theme.palette.text.secondary,
|
|
top: position
|
|
}}
|
|
/>
|
|
</CustomWidthTooltip>
|
|
)}
|
|
<Box sx={{ p: disablePadding ? 0 : 2 }}>
|
|
{(data.name === 'promptTemplate' || data.name === 'chatPromptTemplate') &&
|
|
(inputParam.name === 'template' || inputParam.name === 'systemMessagePrompt') && (
|
|
<>
|
|
<Button
|
|
style={{
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
width: '100%'
|
|
}}
|
|
disabled={disabled}
|
|
sx={{ borderRadius: 25, width: '100%', mb: 2, mt: 0 }}
|
|
variant='outlined'
|
|
onClick={() => onShowPromptHubButtonClicked()}
|
|
endIcon={<IconAutoFixHigh />}
|
|
>
|
|
Langchain Hub
|
|
</Button>
|
|
<PromptLangsmithHubDialog
|
|
promptType={inputParam.name}
|
|
show={showPromptHubDialog}
|
|
onCancel={() => setShowPromptHubDialog(false)}
|
|
onSubmit={onShowPromptHubButtonSubmit}
|
|
></PromptLangsmithHubDialog>
|
|
</>
|
|
)}
|
|
{data.name === 'chatNvidiaNIM' && inputParam.name === 'modelName' && (
|
|
<>
|
|
<Button
|
|
style={{
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
width: '100%'
|
|
}}
|
|
sx={{ borderRadius: '12px', width: '100%', mb: 2, mt: -1 }}
|
|
variant='outlined'
|
|
onClick={() => setIsNvidiaNIMDialogOpen(true)}
|
|
>
|
|
Setup NIM Locally
|
|
</Button>
|
|
</>
|
|
)}
|
|
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
|
<Typography>
|
|
{inputParam.label}
|
|
{!inputParam.optional && <span style={{ color: 'red' }}> *</span>}
|
|
{inputParam.description && <TooltipWithParser style={{ marginLeft: 10 }} title={inputParam.description} />}
|
|
</Typography>
|
|
<div style={{ flexGrow: 1 }}></div>
|
|
{inputParam.hint && !isAdditionalParams && (
|
|
<IconButton
|
|
size='small'
|
|
sx={{
|
|
height: 25,
|
|
width: 25
|
|
}}
|
|
title={inputParam.hint.label}
|
|
color='secondary'
|
|
onClick={() => onInputHintDialogClicked(inputParam.hint)}
|
|
>
|
|
<IconBulb />
|
|
</IconButton>
|
|
)}
|
|
{inputParam.hint && isAdditionalParams && (
|
|
<Button
|
|
sx={{ p: 0, px: 2 }}
|
|
color='secondary'
|
|
variant='text'
|
|
onClick={() => {
|
|
onInputHintDialogClicked(inputParam.hint)
|
|
}}
|
|
startIcon={<IconBulb size={17} />}
|
|
>
|
|
{inputParam.hint.label}
|
|
</Button>
|
|
)}
|
|
{inputParam.acceptVariable && inputParam.type === 'string' && (
|
|
<Tooltip title='Type {{ to select variables'>
|
|
<IconVariable size={20} style={{ color: 'teal' }} />
|
|
</Tooltip>
|
|
)}
|
|
{inputParam.generateDocStoreDescription && (
|
|
<IconButton
|
|
title='Generate knowledge base description'
|
|
sx={{
|
|
height: 25,
|
|
width: 25
|
|
}}
|
|
size='small'
|
|
color='secondary'
|
|
onClick={() => generateDocStoreToolDesc(data.inputs['documentStore'])}
|
|
>
|
|
<IconWand />
|
|
</IconButton>
|
|
)}
|
|
{inputParam.generateInstruction && (
|
|
<IconButton
|
|
title='Generate instructions'
|
|
sx={{
|
|
height: 25,
|
|
width: 25,
|
|
ml: 0.5
|
|
}}
|
|
size='small'
|
|
color='secondary'
|
|
onClick={() => generateInstruction()}
|
|
>
|
|
<IconWand />
|
|
</IconButton>
|
|
)}
|
|
{((inputParam.type === 'string' && inputParam.rows) || inputParam.type === 'code') && (
|
|
<IconButton
|
|
size='small'
|
|
sx={{
|
|
height: 25,
|
|
width: 25,
|
|
ml: 0.5
|
|
}}
|
|
title='Expand'
|
|
color='primary'
|
|
onClick={() =>
|
|
onExpandDialogClicked(data.inputs[inputParam.name] ?? inputParam.default ?? '', inputParam)
|
|
}
|
|
>
|
|
<IconArrowsMaximize />
|
|
</IconButton>
|
|
)}
|
|
</div>
|
|
{inputParam.warning && (
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
borderRadius: 10,
|
|
background: 'rgb(254,252,191)',
|
|
padding: 10,
|
|
marginTop: 10,
|
|
marginBottom: 10
|
|
}}
|
|
>
|
|
<IconAlertTriangle size={30} color='orange' />
|
|
<span style={{ color: 'rgb(116,66,16)', marginLeft: 10 }}>{parser(inputParam.warning)}</span>
|
|
</div>
|
|
)}
|
|
{inputParam.type === 'credential' && (
|
|
<CredentialInputHandler
|
|
disabled={disabled}
|
|
data={data}
|
|
inputParam={inputParam}
|
|
onSelect={(newValue) => {
|
|
data.credential = newValue
|
|
data.inputs[FLOWISE_CREDENTIAL_ID] = newValue // in case data.credential is not updated
|
|
}}
|
|
/>
|
|
)}
|
|
{inputParam.type === 'tabs' && (
|
|
<>
|
|
<Tabs
|
|
value={getTabValue(inputParam)}
|
|
onChange={(event, val) => {
|
|
setTabValue(val)
|
|
data.inputs[`${inputParam.tabIdentifier}_${data.id}`] = inputParam.tabs[val].name
|
|
}}
|
|
aria-label='tabs'
|
|
variant='fullWidth'
|
|
defaultValue={getTabValue(inputParam)}
|
|
>
|
|
<TabsList>
|
|
{inputParam.tabs.map((inputChildParam, index) => (
|
|
<Tab key={index}>{inputChildParam.label}</Tab>
|
|
))}
|
|
</TabsList>
|
|
</Tabs>
|
|
{inputParam.tabs
|
|
.filter((inputParam) => inputParam.display !== false)
|
|
.map((inputChildParam, index) => (
|
|
<TabPanel key={index} value={getTabValue(inputParam)} index={index}>
|
|
<NodeInputHandler
|
|
disabled={inputChildParam.disabled}
|
|
inputParam={inputChildParam}
|
|
data={data}
|
|
isAdditionalParams={true}
|
|
disablePadding={true}
|
|
/>
|
|
</TabPanel>
|
|
))}
|
|
</>
|
|
)}
|
|
{inputParam.type === 'file' && (
|
|
<File
|
|
disabled={disabled}
|
|
fileType={inputParam.fileType || '*'}
|
|
onChange={(newValue) => (data.inputs[inputParam.name] = newValue)}
|
|
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'Choose a file to upload'}
|
|
/>
|
|
)}
|
|
{inputParam.type === 'boolean' && (
|
|
<SwitchInput
|
|
disabled={disabled}
|
|
onChange={(newValue) => handleDataChange({ inputParam, newValue })}
|
|
value={data.inputs[inputParam.name] ?? inputParam.default ?? false}
|
|
/>
|
|
)}
|
|
{inputParam.type === 'datagrid' && (
|
|
<DataGrid
|
|
disabled={disabled}
|
|
columns={getDataGridColDef(inputParam.datagrid, inputParam)}
|
|
hideFooter={true}
|
|
rows={data.inputs[inputParam.name] ?? JSON.stringify(inputParam.default) ?? []}
|
|
onChange={(newValue) => (data.inputs[inputParam.name] = newValue)}
|
|
/>
|
|
)}
|
|
{inputParam.type === 'code' && (
|
|
<>
|
|
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-start' }}>
|
|
{inputParam.codeExample && (
|
|
<Button
|
|
variant='outlined'
|
|
onClick={() => {
|
|
data.inputs[inputParam.name] = inputParam.codeExample
|
|
setReloadTimestamp(Date.now().toString())
|
|
}}
|
|
>
|
|
See Example
|
|
</Button>
|
|
)}
|
|
</div>
|
|
<div
|
|
key={`${reloadTimestamp}_${data.id}}`}
|
|
style={{
|
|
marginTop: '10px',
|
|
border: '1px solid',
|
|
borderColor: theme.palette.grey[900] + 25,
|
|
borderRadius: '6px',
|
|
height: inputParam.rows ? '100px' : '200px'
|
|
}}
|
|
>
|
|
<CodeEditor
|
|
disabled={disabled}
|
|
value={data.inputs[inputParam.name] ?? inputParam.default ?? ''}
|
|
height={inputParam.rows ? '100px' : '200px'}
|
|
theme={customization.isDarkMode ? 'dark' : 'light'}
|
|
lang={'js'}
|
|
placeholder={inputParam.placeholder}
|
|
onValueChange={(code) => (data.inputs[inputParam.name] = code)}
|
|
basicSetup={{ highlightActiveLine: false, highlightActiveLineGutter: false }}
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') &&
|
|
(inputParam?.acceptVariable &&
|
|
(window.location.href.includes('v2/agentcanvas') || window.location.href.includes('v2/marketplace')) ? (
|
|
<RichInput
|
|
key={data.inputs[inputParam.name]}
|
|
placeholder={inputParam.placeholder}
|
|
disabled={disabled}
|
|
inputParam={inputParam}
|
|
onChange={(newValue) => (data.inputs[inputParam.name] = newValue)}
|
|
value={data.inputs[inputParam.name] ?? inputParam.default ?? ''}
|
|
nodes={reactFlowInstance ? reactFlowInstance.getNodes() : []}
|
|
edges={reactFlowInstance ? reactFlowInstance.getEdges() : []}
|
|
nodeId={data.id}
|
|
/>
|
|
) : (
|
|
<Input
|
|
key={data.inputs[inputParam.name]}
|
|
placeholder={inputParam.placeholder}
|
|
disabled={disabled}
|
|
inputParam={inputParam}
|
|
onChange={(newValue) => (data.inputs[inputParam.name] = newValue)}
|
|
value={data.inputs[inputParam.name] ?? inputParam.default ?? ''}
|
|
nodes={[]}
|
|
edges={[]}
|
|
nodeId={data.id}
|
|
/>
|
|
))}
|
|
{inputParam.type === 'json' && (
|
|
<>
|
|
{!inputParam?.acceptVariable && (
|
|
<JsonEditorInput
|
|
disabled={disabled}
|
|
onChange={(newValue) => (data.inputs[inputParam.name] = newValue)}
|
|
value={
|
|
data.inputs[inputParam.name] ||
|
|
inputParam.default ||
|
|
getJSONValue(data.inputs['workerPrompt']) ||
|
|
''
|
|
}
|
|
isSequentialAgent={data.category === 'Sequential Agents'}
|
|
isDarkMode={customization.isDarkMode}
|
|
/>
|
|
)}
|
|
{inputParam?.acceptVariable && (
|
|
<>
|
|
<Button
|
|
sx={{
|
|
borderRadius: 25,
|
|
width: '100%',
|
|
mb: 0,
|
|
mt: 2
|
|
}}
|
|
variant='outlined'
|
|
disabled={disabled}
|
|
onClick={() => onEditJSONClicked(data.inputs[inputParam.name] ?? '', inputParam)}
|
|
>
|
|
{inputParam.label}
|
|
</Button>
|
|
<FormatPromptValuesDialog
|
|
show={showFormatPromptValuesDialog}
|
|
dialogProps={formatPromptValuesDialogProps}
|
|
onCancel={() => setShowFormatPromptValuesDialog(false)}
|
|
onChange={(newValue) => (data.inputs[inputParam.name] = newValue)}
|
|
></FormatPromptValuesDialog>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
{inputParam.type === 'options' && (
|
|
<div key={`${data.id}_${JSON.stringify(data.inputs[inputParam.name])}`}>
|
|
<Dropdown
|
|
disabled={disabled}
|
|
name={inputParam.name}
|
|
options={getDropdownOptions(inputParam)}
|
|
freeSolo={inputParam.freeSolo}
|
|
onSelect={(newValue) => handleDataChange({ inputParam, newValue })}
|
|
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
|
|
/>
|
|
</div>
|
|
)}
|
|
{inputParam.type === 'multiOptions' && (
|
|
<div key={`${data.id}_${JSON.stringify(data.inputs[inputParam.name])}`}>
|
|
<MultiDropdown
|
|
disabled={disabled}
|
|
name={inputParam.name}
|
|
options={getDropdownOptions(inputParam)}
|
|
onSelect={(newValue) => handleDataChange({ inputParam, newValue })}
|
|
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
|
|
/>
|
|
</div>
|
|
)}
|
|
{(inputParam.type === 'asyncOptions' || inputParam.type === 'asyncMultiOptions') && (
|
|
<>
|
|
{data.inputParams.length === 1 && <div style={{ marginTop: 10 }} />}
|
|
<div
|
|
key={`${reloadTimestamp}_${data.id}_${JSON.stringify(data.inputs[inputParam.name])}`}
|
|
style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 1 }}
|
|
>
|
|
<AsyncDropdown
|
|
disabled={disabled}
|
|
name={inputParam.name}
|
|
nodeData={data}
|
|
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
|
|
freeSolo={inputParam.freeSolo}
|
|
multiple={inputParam.type === 'asyncMultiOptions'}
|
|
isCreateNewOption={EDITABLE_OPTIONS.includes(inputParam.name)}
|
|
onSelect={(newValue) => {
|
|
if (inputParam.loadConfig) setReloadTimestamp(Date.now().toString())
|
|
handleDataChange({ inputParam, newValue })
|
|
}}
|
|
onCreateNew={() => addAsyncOption(inputParam.name)}
|
|
/>
|
|
{EDITABLE_OPTIONS.includes(inputParam.name) && data.inputs[inputParam.name] && (
|
|
<IconButton
|
|
title='Edit'
|
|
color='primary'
|
|
size='small'
|
|
onClick={() => editAsyncOption(inputParam.name, data.inputs[inputParam.name])}
|
|
>
|
|
<IconEdit />
|
|
</IconButton>
|
|
)}
|
|
{inputParam.refresh && (
|
|
<IconButton
|
|
title='Refresh'
|
|
color='primary'
|
|
size='small'
|
|
onClick={() => setReloadTimestamp(Date.now().toString())}
|
|
>
|
|
<IconRefresh />
|
|
</IconButton>
|
|
)}
|
|
</div>
|
|
</>
|
|
)}
|
|
{inputParam.type === 'array' && <ArrayRenderer inputParam={inputParam} data={data} disabled={disabled} />}
|
|
{/* CUSTOM INPUT LOGIC */}
|
|
{inputParam.type.includes('conditionFunction') && (
|
|
<>
|
|
<Button
|
|
style={{
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
width: '100%'
|
|
}}
|
|
sx={{ borderRadius: '12px', width: '100%', mt: 1 }}
|
|
variant='outlined'
|
|
onClick={() => onConditionDialogClicked(inputParam)}
|
|
>
|
|
{inputParam.label}
|
|
</Button>
|
|
</>
|
|
)}
|
|
{(data.name === 'cheerioWebScraper' ||
|
|
data.name === 'puppeteerWebScraper' ||
|
|
data.name === 'playwrightWebScraper') &&
|
|
inputParam.name === 'url' && (
|
|
<>
|
|
<Button
|
|
style={{
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
width: '100%'
|
|
}}
|
|
disabled={disabled}
|
|
sx={{ borderRadius: '12px', width: '100%', mt: 1 }}
|
|
variant='outlined'
|
|
onClick={() =>
|
|
onManageLinksDialogClicked(
|
|
data.inputs[inputParam.name] ?? inputParam.default ?? '',
|
|
data.inputs.selectedLinks,
|
|
data.inputs['relativeLinksMethod'] ?? 'webCrawl',
|
|
parseInt(data.inputs['limit']) ?? 0
|
|
)
|
|
}
|
|
>
|
|
Manage Links
|
|
</Button>
|
|
<ManageScrapedLinksDialog
|
|
show={showManageScrapedLinksDialog}
|
|
dialogProps={manageScrapedLinksDialogProps}
|
|
onCancel={() => setShowManageScrapedLinksDialog(false)}
|
|
onSave={onManageLinksDialogSave}
|
|
/>
|
|
</>
|
|
)}
|
|
{inputParam.loadConfig && data && data.inputs && data.inputs[inputParam.name] && (
|
|
<>
|
|
<ConfigInput
|
|
key={`${data.id}_${JSON.stringify(data.inputs[inputParam.name])}_${arrayIndex}_${
|
|
parentParamForArray?.name
|
|
}`}
|
|
data={data}
|
|
inputParam={inputParam}
|
|
disabled={disabled}
|
|
arrayIndex={arrayIndex}
|
|
parentParamForArray={parentParamForArray}
|
|
/>
|
|
</>
|
|
)}
|
|
</Box>
|
|
</>
|
|
)}
|
|
<ToolDialog
|
|
show={showAsyncOptionDialog === 'selectedTool'}
|
|
dialogProps={asyncOptionEditDialogProps}
|
|
onCancel={() => setAsyncOptionEditDialog('')}
|
|
onConfirm={onConfirmAsyncOption}
|
|
></ToolDialog>
|
|
<AssistantDialog
|
|
show={showAsyncOptionDialog === 'selectedAssistant'}
|
|
dialogProps={asyncOptionEditDialogProps}
|
|
onCancel={() => setAsyncOptionEditDialog('')}
|
|
onConfirm={onConfirmAsyncOption}
|
|
></AssistantDialog>
|
|
<ExpandTextDialog
|
|
show={showExpandDialog}
|
|
dialogProps={expandDialogProps}
|
|
onCancel={() => setShowExpandDialog(false)}
|
|
onConfirm={(newValue, inputParamName) => onExpandDialogSave(newValue, inputParamName)}
|
|
onInputHintDialogClicked={onInputHintDialogClicked}
|
|
></ExpandTextDialog>
|
|
<ExpandRichInputDialog
|
|
show={showExpandRichDialog}
|
|
dialogProps={expandRichDialogProps}
|
|
onCancel={() => setShowExpandRichDialog(false)}
|
|
onConfirm={(newValue, inputParamName) => onExpandRichDialogSave(newValue, inputParamName)}
|
|
onInputHintDialogClicked={onInputHintDialogClicked}
|
|
></ExpandRichInputDialog>
|
|
<ConditionDialog
|
|
show={showConditionDialog}
|
|
dialogProps={conditionDialogProps}
|
|
onCancel={() => {
|
|
setShowConditionDialog(false)
|
|
onHideNodeInfoDialog(false)
|
|
}}
|
|
onConfirm={(newData, inputParam, tabValue) => onConditionDialogSave(newData, inputParam, tabValue)}
|
|
></ConditionDialog>
|
|
<InputHintDialog
|
|
show={showInputHintDialog}
|
|
dialogProps={inputHintDialogProps}
|
|
onCancel={() => setShowInputHintDialog(false)}
|
|
></InputHintDialog>
|
|
<NvidiaNIMDialog
|
|
open={isNvidiaNIMDialogOpen}
|
|
onClose={() => setIsNvidiaNIMDialogOpen(false)}
|
|
onComplete={handleNvidiaNIMDialogComplete}
|
|
></NvidiaNIMDialog>
|
|
<Dialog
|
|
open={modelSelectionDialogOpen}
|
|
onClose={() => {
|
|
setModelSelectionDialogOpen(false)
|
|
setSelectedTempChatModel({})
|
|
}}
|
|
aria-labelledby='model-selection-dialog-title'
|
|
maxWidth='sm'
|
|
fullWidth
|
|
>
|
|
<DialogTitle id='model-selection-dialog-title'>Select Model</DialogTitle>
|
|
<DialogContent>
|
|
<Box sx={{ mt: 2 }}>
|
|
<Box sx={{ px: 2 }}>
|
|
<Dropdown
|
|
name={'chatModel'}
|
|
options={availableChatModelsOptions ?? []}
|
|
onSelect={(newValue) => {
|
|
if (!newValue) {
|
|
setSelectedTempChatModel({})
|
|
} else {
|
|
const foundChatComponent = availableChatModels.find((chatModel) => chatModel.name === newValue)
|
|
if (foundChatComponent) {
|
|
const chatModelId = `${foundChatComponent.name}_0`
|
|
const clonedComponent = cloneDeep(foundChatComponent)
|
|
const initChatModelData = initNode(clonedComponent, chatModelId)
|
|
setSelectedTempChatModel(initChatModelData)
|
|
}
|
|
}
|
|
}}
|
|
value={selectedTempChatModel?.name ?? 'choose an option'}
|
|
/>
|
|
</Box>
|
|
{selectedTempChatModel && Object.keys(selectedTempChatModel).length > 0 && (
|
|
<Box sx={{ mt: 2 }}>
|
|
{(selectedTempChatModel.inputParams ?? [])
|
|
.filter((inputParam) => !inputParam.hidden)
|
|
.map((inputParam, index) => (
|
|
<DocStoreInputHandler key={index} inputParam={inputParam} data={selectedTempChatModel} />
|
|
))}
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button
|
|
onClick={() => {
|
|
setModelSelectionDialogOpen(false)
|
|
setSelectedTempChatModel({})
|
|
}}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
disabled={!selectedTempChatModel || Object.keys(selectedTempChatModel).length === 0}
|
|
onClick={async () => {
|
|
setModelSelectionDialogOpen(false)
|
|
if (modelSelectionCallback) {
|
|
await modelSelectionCallback(selectedTempChatModel)
|
|
}
|
|
setSelectedTempChatModel({})
|
|
}}
|
|
variant='contained'
|
|
>
|
|
Confirm
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
<PromptGeneratorDialog
|
|
show={promptGeneratorDialogOpen}
|
|
dialogProps={promptGeneratorDialogProps}
|
|
onCancel={() => setPromptGeneratorDialogOpen(false)}
|
|
onConfirm={(generatedInstruction) => {
|
|
try {
|
|
if (inputParam?.acceptVariable && window.location.href.includes('v2/agentcanvas')) {
|
|
const htmlContent = markdownConverter.makeHtml(generatedInstruction)
|
|
data.inputs[inputParam.name] = htmlContent
|
|
} else {
|
|
data.inputs[inputParam.name] = generatedInstruction
|
|
}
|
|
setPromptGeneratorDialogOpen(false)
|
|
} catch (error) {
|
|
enqueueSnackbar({
|
|
message: 'Error setting generated instruction',
|
|
options: {
|
|
key: new Date().getTime() + Math.random(),
|
|
variant: 'error',
|
|
persist: true,
|
|
action: (key) => (
|
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
|
<IconX />
|
|
</Button>
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}}
|
|
/>
|
|
{loading && <BackdropLoader open={loading} />}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
NodeInputHandler.propTypes = {
|
|
inputAnchor: PropTypes.object,
|
|
inputParam: PropTypes.object,
|
|
data: PropTypes.object,
|
|
disabled: PropTypes.bool,
|
|
isAdditionalParams: PropTypes.bool,
|
|
disablePadding: PropTypes.bool,
|
|
parentParamForArray: PropTypes.object,
|
|
arrayIndex: PropTypes.number,
|
|
onCustomDataChange: PropTypes.func,
|
|
onHideNodeInfoDialog: PropTypes.func
|
|
}
|
|
|
|
export default NodeInputHandler
|