+
+ {data.iconSrc && (
+
+ )}
+ {!data.iconSrc && data.color && (
+
+ )}
- {data.name}
+ {data.templateName || data.name}
{data.description && (
@@ -61,13 +79,6 @@ const ItemCard = ({ isLoading, data, images, onClick }) => {
{data.description}
)}
-
- {data.deployed && (
-
-
-
- )}
-
{images && (
{
- const portalElement = document.getElementById('portal')
-
- const theme = useTheme()
- const customization = useSelector((state) => state.customization)
- const languageType = 'json'
-
- const [inputValue, setInputValue] = useState('')
- const [inputParam, setInputParam] = useState(null)
- const [textCursorPosition, setTextCursorPosition] = useState({})
-
- useEffect(() => {
- if (dialogProps.value) setInputValue(dialogProps.value)
- if (dialogProps.inputParam) setInputParam(dialogProps.inputParam)
-
- return () => {
- setInputValue('')
- setInputParam(null)
- setTextCursorPosition({})
- }
- }, [dialogProps])
-
- const onMouseUp = (e) => {
- if (e.target && e.target.selectionEnd && e.target.value) {
- const cursorPosition = e.target.selectionEnd
- const textBeforeCursorPosition = e.target.value.substring(0, cursorPosition)
- const textAfterCursorPosition = e.target.value.substring(cursorPosition, e.target.value.length)
- const body = {
- textBeforeCursorPosition,
- textAfterCursorPosition
- }
- setTextCursorPosition(body)
- } else {
- setTextCursorPosition({})
- }
- }
-
- const onSelectOutputResponseClick = (node, isUserQuestion = false) => {
- let variablePath = isUserQuestion ? `question` : `${node.id}.data.instance`
- if (textCursorPosition) {
- let newInput = ''
- if (textCursorPosition.textBeforeCursorPosition === undefined && textCursorPosition.textAfterCursorPosition === undefined)
- newInput = `${inputValue}${`{{${variablePath}}}`}`
- else newInput = `${textCursorPosition.textBeforeCursorPosition}{{${variablePath}}}${textCursorPosition.textAfterCursorPosition}`
- setInputValue(newInput)
- }
- }
-
- const component = show ? (
-
- ) : null
-
- return createPortal(component, portalElement)
-}
-
-EditPromptValuesDialog.propTypes = {
- show: PropTypes.bool,
- dialogProps: PropTypes.object,
- onCancel: PropTypes.func,
- onConfirm: PropTypes.func
-}
-
-export default EditPromptValuesDialog
diff --git a/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.css b/packages/ui/src/ui-component/dialog/ExpandTextDialog.css
similarity index 100%
rename from packages/ui/src/ui-component/dialog/EditPromptValuesDialog.css
rename to packages/ui/src/ui-component/dialog/ExpandTextDialog.css
diff --git a/packages/ui/src/ui-component/dialog/ExpandTextDialog.js b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js
new file mode 100644
index 00000000..b955ccdb
--- /dev/null
+++ b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js
@@ -0,0 +1,105 @@
+import { createPortal } from 'react-dom'
+import { useState, useEffect } from 'react'
+import { useSelector } from 'react-redux'
+import PropTypes from 'prop-types'
+import { Button, Dialog, DialogActions, DialogContent, Typography } from '@mui/material'
+import { useTheme } from '@mui/material/styles'
+import PerfectScrollbar from 'react-perfect-scrollbar'
+import { StyledButton } from 'ui-component/button/StyledButton'
+import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor'
+import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor'
+
+import './ExpandTextDialog.css'
+
+const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
+ const portalElement = document.getElementById('portal')
+
+ const theme = useTheme()
+ const customization = useSelector((state) => state.customization)
+ const languageType = 'json'
+
+ const [inputValue, setInputValue] = useState('')
+ const [inputParam, setInputParam] = useState(null)
+
+ useEffect(() => {
+ if (dialogProps.value) setInputValue(dialogProps.value)
+ if (dialogProps.inputParam) setInputParam(dialogProps.inputParam)
+
+ return () => {
+ setInputValue('')
+ setInputParam(null)
+ }
+ }, [dialogProps])
+
+ const component = show ? (
+
+ ) : null
+
+ return createPortal(component, portalElement)
+}
+
+ExpandTextDialog.propTypes = {
+ show: PropTypes.bool,
+ dialogProps: PropTypes.object,
+ onCancel: PropTypes.func,
+ onConfirm: PropTypes.func
+}
+
+export default ExpandTextDialog
diff --git a/packages/ui/src/ui-component/dialog/FormatPromptValuesDialog.js b/packages/ui/src/ui-component/dialog/FormatPromptValuesDialog.js
new file mode 100644
index 00000000..df1d357e
--- /dev/null
+++ b/packages/ui/src/ui-component/dialog/FormatPromptValuesDialog.js
@@ -0,0 +1,56 @@
+import { createPortal } from 'react-dom'
+import { useSelector } from 'react-redux'
+import PropTypes from 'prop-types'
+import { Dialog, DialogContent, DialogTitle } from '@mui/material'
+import PerfectScrollbar from 'react-perfect-scrollbar'
+import { JsonEditorInput } from 'ui-component/json/JsonEditor'
+
+const FormatPromptValuesDialog = ({ show, dialogProps, onChange, onCancel }) => {
+ const portalElement = document.getElementById('portal')
+ const customization = useSelector((state) => state.customization)
+
+ const component = show ? (
+
+ ) : null
+
+ return createPortal(component, portalElement)
+}
+
+FormatPromptValuesDialog.propTypes = {
+ show: PropTypes.bool,
+ dialogProps: PropTypes.object,
+ onChange: PropTypes.func,
+ onCancel: PropTypes.func
+}
+
+export default FormatPromptValuesDialog
diff --git a/packages/ui/src/ui-component/dialog/SourceDocDialog.js b/packages/ui/src/ui-component/dialog/SourceDocDialog.js
index a088a6c4..6bf8692f 100644
--- a/packages/ui/src/ui-component/dialog/SourceDocDialog.js
+++ b/packages/ui/src/ui-component/dialog/SourceDocDialog.js
@@ -3,7 +3,7 @@ import { useState, useEffect } from 'react'
import { useSelector } from 'react-redux'
import PropTypes from 'prop-types'
import { Dialog, DialogContent, DialogTitle } from '@mui/material'
-import ReactJson from 'react-json-view'
+import ReactJson from 'flowise-react-json-view'
const SourceDocDialog = ({ show, dialogProps, onCancel }) => {
const portalElement = document.getElementById('portal')
diff --git a/packages/ui/src/ui-component/dropdown/AsyncDropdown.js b/packages/ui/src/ui-component/dropdown/AsyncDropdown.js
new file mode 100644
index 00000000..8dfd782d
--- /dev/null
+++ b/packages/ui/src/ui-component/dropdown/AsyncDropdown.js
@@ -0,0 +1,147 @@
+import { useState, useEffect, Fragment } from 'react'
+import { useSelector } from 'react-redux'
+
+import PropTypes from 'prop-types'
+import axios from 'axios'
+
+import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete'
+import { Popper, CircularProgress, TextField, Box, Typography } from '@mui/material'
+import { styled } from '@mui/material/styles'
+
+import { baseURL } from 'store/constant'
+
+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 fetchList = async ({ name, nodeData }) => {
+ const loadMethod = nodeData.inputParams.find((param) => param.name === name)?.loadMethod
+ const username = localStorage.getItem('username')
+ const password = localStorage.getItem('password')
+
+ let lists = await axios
+ .post(
+ `${baseURL}/api/v1/node-load-method/${nodeData.name}`,
+ { ...nodeData, loadMethod },
+ { auth: username && password ? { username, password } : undefined }
+ )
+ .then(async function (response) {
+ return response.data
+ })
+ .catch(function (error) {
+ console.error(error)
+ })
+ return lists
+}
+
+export const AsyncDropdown = ({
+ name,
+ nodeData,
+ value,
+ onSelect,
+ isCreateNewOption,
+ onCreateNew,
+ disabled = false,
+ disableClearable = false
+}) => {
+ const customization = useSelector((state) => state.customization)
+
+ const [open, setOpen] = useState(false)
+ const [options, setOptions] = useState([])
+ const [loading, setLoading] = useState(false)
+ const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value)
+ const getDefaultOptionValue = () => ''
+ const addNewOption = [{ label: '- Create New -', name: '-create-' }]
+ let [internalValue, setInternalValue] = useState(value ?? 'choose an option')
+
+ useEffect(() => {
+ setLoading(true)
+ ;(async () => {
+ const fetchData = async () => {
+ let response = await fetchList({ name, nodeData })
+ if (isCreateNewOption) setOptions([...response, ...addNewOption])
+ else setOptions([...response])
+ setLoading(false)
+ }
+ fetchData()
+ })()
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
+
+ return (
+ <>
+
{
+ setOpen(true)
+ }}
+ onClose={() => {
+ setOpen(false)
+ }}
+ options={options}
+ value={findMatchingOptions(options, internalValue) || getDefaultOptionValue()}
+ onChange={(e, selection) => {
+ const value = selection ? selection.name : ''
+ if (isCreateNewOption && value === '-create-') {
+ onCreateNew()
+ } else {
+ setInternalValue(value)
+ onSelect(value)
+ }
+ }}
+ PopperComponent={StyledPopper}
+ loading={loading}
+ renderInput={(params) => (
+
+ {loading ? : null}
+ {params.InputProps.endAdornment}
+
+ )
+ }}
+ />
+ )}
+ renderOption={(props, option) => (
+
+
+ {option.label}
+ {option.description && (
+ {option.description}
+ )}
+
+
+ )}
+ />
+ >
+ )
+}
+
+AsyncDropdown.propTypes = {
+ name: PropTypes.string,
+ nodeData: PropTypes.object,
+ value: PropTypes.string,
+ onSelect: PropTypes.func,
+ onCreateNew: PropTypes.func,
+ disabled: PropTypes.bool,
+ disableClearable: PropTypes.bool,
+ isCreateNewOption: PropTypes.bool
+}
diff --git a/packages/ui/src/ui-component/editor/DarkCodeEditor.js b/packages/ui/src/ui-component/editor/DarkCodeEditor.js
index 3925f4a6..bf0719dd 100644
--- a/packages/ui/src/ui-component/editor/DarkCodeEditor.js
+++ b/packages/ui/src/ui-component/editor/DarkCodeEditor.js
@@ -21,6 +21,7 @@ export const DarkCodeEditor = ({ value, placeholder, disabled = false, type, sty
onValueChange={onValueChange}
onMouseUp={onMouseUp}
onBlur={onBlur}
+ tabSize={4}
style={{
...style,
background: theme.palette.codeEditor.main
diff --git a/packages/ui/src/ui-component/editor/LightCodeEditor.js b/packages/ui/src/ui-component/editor/LightCodeEditor.js
index 86f7057d..14dcbf29 100644
--- a/packages/ui/src/ui-component/editor/LightCodeEditor.js
+++ b/packages/ui/src/ui-component/editor/LightCodeEditor.js
@@ -21,6 +21,7 @@ export const LightCodeEditor = ({ value, placeholder, disabled = false, type, st
onValueChange={onValueChange}
onMouseUp={onMouseUp}
onBlur={onBlur}
+ tabSize={4}
style={{
...style,
background: theme.palette.card.main
diff --git a/packages/ui/src/ui-component/grid/Grid.js b/packages/ui/src/ui-component/grid/Grid.js
new file mode 100644
index 00000000..0670d69b
--- /dev/null
+++ b/packages/ui/src/ui-component/grid/Grid.js
@@ -0,0 +1,43 @@
+import PropTypes from 'prop-types'
+import { DataGrid } from '@mui/x-data-grid'
+import { IconPlus } from '@tabler/icons'
+import { Button } from '@mui/material'
+
+export const Grid = ({ columns, rows, style, disabled = false, onRowUpdate, addNewRow }) => {
+ const handleProcessRowUpdate = (newRow) => {
+ onRowUpdate(newRow)
+ return newRow
+ }
+
+ return (
+ <>
+ {!disabled && (
+ }>
+ Add Item
+
+ )}
+ {rows && columns && (
+
+ {
+ return !disabled
+ }}
+ onProcessRowUpdateError={(error) => console.error(error)}
+ rows={rows}
+ columns={columns}
+ />
+
+ )}
+ >
+ )
+}
+
+Grid.propTypes = {
+ rows: PropTypes.array,
+ columns: PropTypes.array,
+ style: PropTypes.any,
+ disabled: PropTypes.bool,
+ addNewRow: PropTypes.func,
+ onRowUpdate: PropTypes.func
+}
diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js
index 1861bf65..e7744764 100644
--- a/packages/ui/src/ui-component/input/Input.js
+++ b/packages/ui/src/ui-component/input/Input.js
@@ -1,7 +1,7 @@
import { useState } from 'react'
import PropTypes from 'prop-types'
import { FormControl, OutlinedInput } from '@mui/material'
-import EditPromptValuesDialog from 'ui-component/dialog/EditPromptValuesDialog'
+import ExpandTextDialog from 'ui-component/dialog/ExpandTextDialog'
export const Input = ({ inputParam, value, onChange, disabled = false, showDialog, dialogProps, onDialogCancel, onDialogConfirm }) => {
const [myValue, setMyValue] = useState(value ?? '')
@@ -37,6 +37,7 @@ export const Input = ({ inputParam, value, onChange, disabled = false, showDialo
onChange(e.target.value)
}}
inputProps={{
+ step: 0.1,
style: {
height: inputParam.rows ? '90px' : 'inherit'
}
@@ -44,7 +45,7 @@ export const Input = ({ inputParam, value, onChange, disabled = false, showDialo
/>
{showDialog && (
-
+ >
)}
>
)
diff --git a/packages/ui/src/ui-component/json/JsonEditor.js b/packages/ui/src/ui-component/json/JsonEditor.js
index 06442df2..4bf8f306 100644
--- a/packages/ui/src/ui-component/json/JsonEditor.js
+++ b/packages/ui/src/ui-component/json/JsonEditor.js
@@ -1,10 +1,32 @@
-import { useState } from 'react'
+import { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
-import { FormControl } from '@mui/material'
-import ReactJson from 'react-json-view'
+import { FormControl, Popover } from '@mui/material'
+import ReactJson from 'flowise-react-json-view'
+import SelectVariable from './SelectVariable'
+import { cloneDeep } from 'lodash'
+import { getAvailableNodesForVariable } from 'utils/genericHelper'
-export const JsonEditorInput = ({ value, onChange, disabled = false, isDarkMode = false }) => {
+export const JsonEditorInput = ({ value, onChange, inputParam, nodes, edges, nodeId, disabled = false, isDarkMode = false }) => {
const [myValue, setMyValue] = useState(value ? JSON.parse(value) : {})
+ const [availableNodesForVariable, setAvailableNodesForVariable] = useState([])
+ const [mouseUpKey, setMouseUpKey] = useState('')
+
+ const [anchorEl, setAnchorEl] = useState(null)
+ const openPopOver = Boolean(anchorEl)
+
+ const handleClosePopOver = () => {
+ setAnchorEl(null)
+ }
+
+ const setNewVal = (val) => {
+ const newVal = cloneDeep(myValue)
+ newVal[mouseUpKey] = val
+ onChange(JSON.stringify(newVal))
+ setMyValue((params) => ({
+ ...params,
+ [mouseUpKey]: val
+ }))
+ }
const onClipboardCopy = (e) => {
const src = e.src
@@ -15,6 +37,13 @@ export const JsonEditorInput = ({ value, onChange, disabled = false, isDarkMode
}
}
+ useEffect(() => {
+ if (!disabled && nodes && edges && nodeId && inputParam) {
+ const nodesForVariable = inputParam?.acceptVariable ? getAvailableNodesForVariable(nodes, edges, nodeId, inputParam.id) : []
+ setAvailableNodesForVariable(nodesForVariable)
+ }
+ }, [disabled, inputParam, nodes, edges, nodeId])
+
return (
<>
@@ -30,28 +59,60 @@ export const JsonEditorInput = ({ value, onChange, disabled = false, isDarkMode
/>
)}
{!disabled && (
- onClipboardCopy(e)}
- onEdit={(edit) => {
- setMyValue(edit.updated_src)
- onChange(JSON.stringify(edit.updated_src))
- }}
- onAdd={() => {
- //console.log(add)
- }}
- onDelete={(deleteobj) => {
- setMyValue(deleteobj.updated_src)
- onChange(JSON.stringify(deleteobj.updated_src))
- }}
- />
+
+ onClipboardCopy(e)}
+ onMouseUp={(event) => {
+ if (inputParam?.acceptVariable) {
+ setMouseUpKey(event.name)
+ setAnchorEl(event.currentTarget)
+ }
+ }}
+ onEdit={(edit) => {
+ setMyValue(edit.updated_src)
+ onChange(JSON.stringify(edit.updated_src))
+ }}
+ onAdd={() => {
+ //console.log(add)
+ }}
+ onDelete={(deleteobj) => {
+ setMyValue(deleteobj.updated_src)
+ onChange(JSON.stringify(deleteobj.updated_src))
+ }}
+ />
+
)}
+ {inputParam?.acceptVariable && (
+
+ {
+ setNewVal(val)
+ handleClosePopOver()
+ }}
+ />
+
+ )}
>
)
}
@@ -60,5 +121,9 @@ JsonEditorInput.propTypes = {
value: PropTypes.string,
onChange: PropTypes.func,
disabled: PropTypes.bool,
- isDarkMode: PropTypes.bool
+ isDarkMode: PropTypes.bool,
+ inputParam: PropTypes.object,
+ nodes: PropTypes.array,
+ edges: PropTypes.array,
+ nodeId: PropTypes.string
}
diff --git a/packages/ui/src/ui-component/json/SelectVariable.js b/packages/ui/src/ui-component/json/SelectVariable.js
new file mode 100644
index 00000000..1b891ed1
--- /dev/null
+++ b/packages/ui/src/ui-component/json/SelectVariable.js
@@ -0,0 +1,126 @@
+import { useSelector } from 'react-redux'
+import PropTypes from 'prop-types'
+import { Box, List, ListItemButton, ListItem, ListItemAvatar, ListItemText, Typography, Stack } from '@mui/material'
+import PerfectScrollbar from 'react-perfect-scrollbar'
+
+import { baseURL } from 'store/constant'
+
+const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectAndReturnVal }) => {
+ const customization = useSelector((state) => state.customization)
+
+ const onSelectOutputResponseClick = (node, isUserQuestion = false) => {
+ let variablePath = isUserQuestion ? `question` : `${node.id}.data.instance`
+ const newInput = `{{${variablePath}}}`
+ onSelectAndReturnVal(newInput)
+ }
+
+ return (
+ <>
+ {!disabled && (
+
+
+ Select Variable
+
+
+
+
+ onSelectOutputResponseClick(null, true)}
+ >
+
+
+
+

+
+
+
+
+
+ {availableNodesForVariable &&
+ availableNodesForVariable.length > 0 &&
+ availableNodesForVariable.map((node, index) => {
+ const selectedOutputAnchor = node.data.outputAnchors[0].options.find(
+ (ancr) => ancr.name === node.data.outputs['output']
+ )
+ return (
+ onSelectOutputResponseClick(node)}
+ >
+
+
+
+

+
+
+
+
+
+ )
+ })}
+
+
+
+
+ )}
+ >
+ )
+}
+
+SelectVariable.propTypes = {
+ availableNodesForVariable: PropTypes.array,
+ disabled: PropTypes.bool,
+ onSelectAndReturnVal: PropTypes.func
+}
+
+export default SelectVariable
diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js
index fac83225..42a63057 100644
--- a/packages/ui/src/utils/genericHelper.js
+++ b/packages/ui/src/utils/genericHelper.js
@@ -39,7 +39,7 @@ export const initNode = (nodeData, newNodeId) => {
const incoming = nodeData.inputs ? nodeData.inputs.length : 0
const outgoing = 1
- const whitelistTypes = ['options', 'string', 'number', 'boolean', 'password', 'json', 'code', 'date', 'file', 'folder']
+ const whitelistTypes = ['asyncOptions', 'options', 'string', 'number', 'boolean', 'password', 'json', 'code', 'date', 'file', 'folder']
for (let i = 0; i < incoming; i += 1) {
const newInput = {
@@ -285,7 +285,7 @@ export const generateExportFlowData = (flowData) => {
}
export const getAvailableNodesForVariable = (nodes, edges, target, targetHandle) => {
- // example edge id = "llmChain_0-llmChain_0-output-outputPrediction-string-llmChain_1-llmChain_1-input-promptValues-string"
+ // example edge id = "llmChain_0-llmChain_0-output-outputPrediction-string|json-llmChain_1-llmChain_1-input-promptValues-string"
// {source} -{sourceHandle} -{target} -{targetHandle}
const parentNodes = []
const inputEdges = edges.filter((edg) => edg.target === target && edg.targetHandle === targetHandle)
@@ -334,3 +334,50 @@ export const throttle = (func, limit) => {
}
}
}
+
+export const generateRandomGradient = () => {
+ function randomColor() {
+ var color = 'rgb('
+ for (var i = 0; i < 3; i++) {
+ var random = Math.floor(Math.random() * 256)
+ color += random
+ if (i < 2) {
+ color += ','
+ }
+ }
+ color += ')'
+ return color
+ }
+
+ var gradient = 'linear-gradient(' + randomColor() + ', ' + randomColor() + ')'
+
+ return gradient
+}
+
+export const getInputVariables = (paramValue) => {
+ let returnVal = paramValue
+ const variableStack = []
+ const inputVariables = []
+ let startIdx = 0
+ const endIdx = returnVal.length
+
+ while (startIdx < endIdx) {
+ const substr = returnVal.substring(startIdx, startIdx + 1)
+
+ // Store the opening double curly bracket
+ if (substr === '{') {
+ variableStack.push({ substr, startIdx: startIdx + 1 })
+ }
+
+ // Found the complete variable
+ if (substr === '}' && variableStack.length > 0 && variableStack[variableStack.length - 1].substr === '{') {
+ const variableStartIdx = variableStack[variableStack.length - 1].startIdx
+ const variableEndIdx = startIdx
+ const variableFullPath = returnVal.substring(variableStartIdx, variableEndIdx)
+ inputVariables.push(variableFullPath)
+ variableStack.pop()
+ }
+ startIdx += 1
+ }
+ return inputVariables
+}
diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js
index 1f4a1f93..1c1e5212 100644
--- a/packages/ui/src/views/canvas/CanvasHeader.js
+++ b/packages/ui/src/views/canvas/CanvasHeader.js
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'
import { useNavigate } from 'react-router-dom'
-import { useSelector } from 'react-redux'
+import { useSelector, useDispatch } from 'react-redux'
import { useEffect, useRef, useState } from 'react'
// material-ui
@@ -13,7 +13,7 @@ import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck,
// project imports
import Settings from 'views/settings'
import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog'
-import APICodeDialog from 'ui-component/dialog/APICodeDialog'
+import APICodeDialog from 'views/chatflows/APICodeDialog'
// API
import chatflowsApi from 'api/chatflows'
@@ -24,11 +24,13 @@ import useApi from 'hooks/useApi'
// utils
import { generateExportFlowData } from 'utils/genericHelper'
import { uiBaseURL } from 'store/constant'
+import { SET_CHATFLOW } from 'store/actions'
// ==============================|| CANVAS HEADER ||============================== //
const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFlow }) => {
const theme = useTheme()
+ const dispatch = useDispatch()
const navigate = useNavigate()
const flowNameRef = useRef()
const settingsRef = useRef()
@@ -125,6 +127,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
useEffect(() => {
if (updateChatflowApi.data) {
setFlowName(updateChatflowApi.data.name)
+ dispatch({ type: SET_CHATFLOW, chatflow: updateChatflowApi.data })
}
setEditingFlowName(false)
diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js
index d58f7a66..2d96bcb5 100644
--- a/packages/ui/src/views/canvas/NodeInputHandler.js
+++ b/packages/ui/src/views/canvas/NodeInputHandler.js
@@ -5,19 +5,26 @@ import { useSelector } from 'react-redux'
// material-ui
import { useTheme, styled } from '@mui/material/styles'
-import { Box, Typography, Tooltip, IconButton } from '@mui/material'
+import { Box, Typography, Tooltip, IconButton, Button } from '@mui/material'
import { tooltipClasses } from '@mui/material/Tooltip'
-import { IconArrowsMaximize } from '@tabler/icons'
+import { IconArrowsMaximize, IconEdit } from '@tabler/icons'
// project import
import { Dropdown } from 'ui-component/dropdown/Dropdown'
+import { AsyncDropdown } from 'ui-component/dropdown/AsyncDropdown'
import { Input } from 'ui-component/input/Input'
import { File } from 'ui-component/file/File'
import { SwitchInput } from 'ui-component/switch/Switch'
import { flowContext } from 'store/context/ReactFlowContext'
-import { isValidConnection, getAvailableNodesForVariable } from 'utils/genericHelper'
+import { isValidConnection } from 'utils/genericHelper'
import { JsonEditorInput } from 'ui-component/json/JsonEditor'
import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser'
+import ToolDialog from 'views/tools/ToolDialog'
+import FormatPromptValuesDialog from 'ui-component/dialog/FormatPromptValuesDialog'
+
+import { getInputVariables } from 'utils/genericHelper'
+
+const EDITABLE_TOOLS = ['selectedTool']
const CustomWidthTooltip = styled(({ className, ...props }) => )({
[`& .${tooltipClasses.tooltip}`]: {
@@ -36,6 +43,11 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
const [position, setPosition] = useState(0)
const [showExpandDialog, setShowExpandDialog] = useState(false)
const [expandDialogProps, setExpandDialogProps] = 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 onExpandDialogClicked = (value, inputParam) => {
const dialogProp = {
@@ -45,22 +57,75 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
confirmButtonName: 'Save',
cancelButtonName: 'Cancel'
}
-
- if (!disabled) {
- const nodes = reactFlowInstance.getNodes()
- const edges = reactFlowInstance.getEdges()
- const nodesForVariable = inputParam.acceptVariable ? getAvailableNodesForVariable(nodes, edges, data.id, inputParam.id) : []
- dialogProp.availableNodesForVariable = nodesForVariable
- }
setExpandDialogProps(dialogProp)
setShowExpandDialog(true)
}
+ const onFormatPromptValuesClicked = (value, inputParam) => {
+ // Preset values if the field is format prompt values
+ let inputValue = value
+ if (inputParam.name === 'promptValues' && !value) {
+ const obj = {}
+ const templateValue =
+ (data.inputs['template'] ?? '') + (data.inputs['systemMessagePrompt'] ?? '') + (data.inputs['humanMessagePrompt'] ?? '')
+ const inputVariables = getInputVariables(templateValue)
+ for (const inputVariable of inputVariables) {
+ obj[inputVariable] = ''
+ }
+ if (Object.keys(obj).length) inputValue = JSON.stringify(obj)
+ }
+ const dialogProp = {
+ value: inputValue,
+ inputParam,
+ nodes: reactFlowInstance.getNodes(),
+ edges: reactFlowInstance.getEdges(),
+ nodeId: data.id
+ }
+ setFormatPromptValuesDialogProps(dialogProp)
+ setShowFormatPromptValuesDialog(true)
+ }
+
const onExpandDialogSave = (newValue, inputParamName) => {
setShowExpandDialog(false)
data.inputs[inputParamName] = newValue
}
+ const editAsyncOption = (inputParamName, inputValue) => {
+ if (inputParamName === 'selectedTool') {
+ setAsyncOptionEditDialogProps({
+ title: 'Edit Tool',
+ type: 'EDIT',
+ cancelButtonName: 'Cancel',
+ confirmButtonName: 'Save',
+ toolId: inputValue
+ })
+ }
+ setAsyncOptionEditDialog(inputParamName)
+ }
+
+ const addAsyncOption = (inputParamName) => {
+ if (inputParamName === 'selectedTool') {
+ setAsyncOptionEditDialogProps({
+ title: 'Add New Tool',
+ type: 'ADD',
+ cancelButtonName: 'Cancel',
+ confirmButtonName: 'Add'
+ })
+ }
+ setAsyncOptionEditDialog(inputParamName)
+ }
+
+ const onConfirmAsyncOption = (selectedOptionId = '') => {
+ if (!selectedOptionId) {
+ data.inputs[showAsyncOptionDialog] = ''
+ } else {
+ data.inputs[showAsyncOptionDialog] = selectedOptionId
+ setReloadTimestamp(Date.now().toString())
+ }
+ setAsyncOptionEditDialogProps({})
+ setAsyncOptionEditDialog('')
+ }
+
useEffect(() => {
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
setPosition(ref.current.offsetTop + ref.current.clientHeight / 2)
@@ -162,6 +227,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
)}
{(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && (
(data.inputs[inputParam.name] = newValue)}
@@ -173,12 +239,33 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
/>
)}
{inputParam.type === 'json' && (
- (data.inputs[inputParam.name] = newValue)}
- value={data.inputs[inputParam.name] ?? inputParam.default ?? ''}
- isDarkMode={customization.isDarkMode}
- />
+ <>
+ {!inputParam?.acceptVariable && (
+ (data.inputs[inputParam.name] = newValue)}
+ value={data.inputs[inputParam.name] ?? inputParam.default ?? ''}
+ isDarkMode={customization.isDarkMode}
+ />
+ )}
+ {inputParam?.acceptVariable && (
+ <>
+
+ setShowFormatPromptValuesDialog(false)}
+ onChange={(newValue) => (data.inputs[inputParam.name] = newValue)}
+ >
+ >
+ )}
+ >
)}
{inputParam.type === 'options' && (
(data.inputs[inputParam.name] = newValue)}
- value={data.inputs[inputParam.name] ?? inputParam.default ?? 'chose an option'}
+ value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
/>
)}
+ {inputParam.type === 'asyncOptions' && (
+ <>
+ {data.inputParams.length === 1 && }
+
+
(data.inputs[inputParam.name] = newValue)}
+ onCreateNew={() => addAsyncOption(inputParam.name)}
+ />
+ {EDITABLE_TOOLS.includes(inputParam.name) && data.inputs[inputParam.name] && (
+ editAsyncOption(inputParam.name, data.inputs[inputParam.name])}
+ >
+
+
+ )}
+
+ >
+ )}
>
)}
+ setAsyncOptionEditDialog('')}
+ onConfirm={onConfirmAsyncOption}
+ >
)
}
diff --git a/packages/ui/src/views/canvas/index.js b/packages/ui/src/views/canvas/index.js
index 2d71f03a..1c6d610f 100644
--- a/packages/ui/src/views/canvas/index.js
+++ b/packages/ui/src/views/canvas/index.js
@@ -202,6 +202,7 @@ const Canvas = () => {
const newChatflowBody = {
name: chatflowName,
deployed: false,
+ isPublic: false,
flowData
}
createNewChatflowApi.request(newChatflowBody)
diff --git a/packages/ui/src/views/chatbot/index.js b/packages/ui/src/views/chatbot/index.js
new file mode 100644
index 00000000..f29c35ee
--- /dev/null
+++ b/packages/ui/src/views/chatbot/index.js
@@ -0,0 +1,109 @@
+import { useEffect, useState } from 'react'
+import { FullPageChat } from 'flowise-embed-react'
+import { useNavigate } from 'react-router-dom'
+
+// Project import
+import LoginDialog from 'ui-component/dialog/LoginDialog'
+
+// API
+import chatflowsApi from 'api/chatflows'
+
+// Hooks
+import useApi from 'hooks/useApi'
+
+//Const
+import { baseURL } from 'store/constant'
+
+// ==============================|| Chatbot ||============================== //
+
+const ChatbotFull = () => {
+ const URLpath = document.location.pathname.toString().split('/')
+ const chatflowId = URLpath[URLpath.length - 1] === 'chatbot' ? '' : URLpath[URLpath.length - 1]
+ const navigate = useNavigate()
+
+ const [chatflow, setChatflow] = useState(null)
+ const [chatbotTheme, setChatbotTheme] = useState({})
+ const [loginDialogOpen, setLoginDialogOpen] = useState(false)
+ const [loginDialogProps, setLoginDialogProps] = useState({})
+ const [isLoading, setLoading] = useState(true)
+
+ const getSpecificChatflowFromPublicApi = useApi(chatflowsApi.getSpecificChatflowFromPublicEndpoint)
+ const getSpecificChatflowApi = useApi(chatflowsApi.getSpecificChatflow)
+
+ const onLoginClick = (username, password) => {
+ localStorage.setItem('username', username)
+ localStorage.setItem('password', password)
+ navigate(0)
+ }
+
+ useEffect(() => {
+ getSpecificChatflowFromPublicApi.request(chatflowId)
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
+
+ useEffect(() => {
+ if (getSpecificChatflowFromPublicApi.error) {
+ if (getSpecificChatflowFromPublicApi.error?.response?.status === 401) {
+ if (localStorage.getItem('username') && localStorage.getItem('password')) {
+ getSpecificChatflowApi.request(chatflowId)
+ } else {
+ setLoginDialogProps({
+ title: 'Login',
+ confirmButtonName: 'Login'
+ })
+ setLoginDialogOpen(true)
+ }
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [getSpecificChatflowFromPublicApi.error])
+
+ useEffect(() => {
+ if (getSpecificChatflowApi.error) {
+ if (getSpecificChatflowApi.error?.response?.status === 401) {
+ setLoginDialogProps({
+ title: 'Login',
+ confirmButtonName: 'Login'
+ })
+ setLoginDialogOpen(true)
+ }
+ }
+ }, [getSpecificChatflowApi.error])
+
+ useEffect(() => {
+ if (getSpecificChatflowFromPublicApi.data || getSpecificChatflowApi.data) {
+ const chatflowData = getSpecificChatflowFromPublicApi.data || getSpecificChatflowApi.data
+ setChatflow(chatflowData)
+ if (chatflowData.chatbotConfig) {
+ try {
+ setChatbotTheme(JSON.parse(chatflowData.chatbotConfig))
+ } catch (e) {
+ console.error(e)
+ setChatbotTheme({})
+ }
+ }
+ }
+ }, [getSpecificChatflowFromPublicApi.data, getSpecificChatflowApi.data])
+
+ useEffect(() => {
+ setLoading(getSpecificChatflowFromPublicApi.loading || getSpecificChatflowApi.loading)
+ }, [getSpecificChatflowFromPublicApi.loading, getSpecificChatflowApi.loading])
+
+ return (
+ <>
+ {!isLoading ? (
+ <>
+ {!chatflow || chatflow.apikeyid ? (
+
Invalid Chatbot
+ ) : (
+
+ )}
+
+ >
+ ) : null}
+ >
+ )
+}
+
+export default ChatbotFull
diff --git a/packages/ui/src/ui-component/dialog/APICodeDialog.js b/packages/ui/src/views/chatflows/APICodeDialog.js
similarity index 73%
rename from packages/ui/src/ui-component/dialog/APICodeDialog.js
rename to packages/ui/src/views/chatflows/APICodeDialog.js
index e64f4bf8..5e32c1d4 100644
--- a/packages/ui/src/ui-component/dialog/APICodeDialog.js
+++ b/packages/ui/src/views/chatflows/APICodeDialog.js
@@ -9,6 +9,8 @@ import { CopyBlock, atomOneDark } from 'react-code-blocks'
// Project import
import { Dropdown } from 'ui-component/dropdown/Dropdown'
+import ShareChatbot from './ShareChatbot'
+import EmbedChat from './EmbedChat'
// Const
import { baseURL } from 'store/constant'
@@ -19,6 +21,7 @@ import pythonSVG from 'assets/images/python.svg'
import javascriptSVG from 'assets/images/javascript.svg'
import cURLSVG from 'assets/images/cURL.svg'
import EmbedSVG from 'assets/images/embed.svg'
+import ShareChatbotSVG from 'assets/images/sharing.png'
// API
import apiKeyApi from 'api/apikey'
@@ -119,77 +122,18 @@ const getConfigExamplesForCurl = (configData, bodyType) => {
return finalStr
}
-const embedCode = (chatflowid) => {
- return ``
-}
-
-const embedCodeCustomization = (chatflowid) => {
- return ``
-}
-
const APICodeDialog = ({ show, dialogProps, onCancel }) => {
const portalElement = document.getElementById('portal')
const navigate = useNavigate()
const dispatch = useDispatch()
- const codes = ['Embed', 'Python', 'JavaScript', 'cURL']
+
+ const codes = ['Embed', 'Python', 'JavaScript', 'cURL', 'Share Chatbot']
const [value, setValue] = useState(0)
const [keyOptions, setKeyOptions] = useState([])
const [apiKeys, setAPIKeys] = useState([])
const [chatflowApiKeyId, setChatflowApiKeyId] = useState('')
const [selectedApiKey, setSelectedApiKey] = useState({})
const [checkboxVal, setCheckbox] = useState(false)
- const [embedChatCheckboxVal, setEmbedChatCheckbox] = useState(false)
const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys)
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
@@ -203,10 +147,6 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
}
}
- const onCheckBoxEmbedChatChanged = (newVal) => {
- setEmbedChatCheckbox(newVal)
- }
-
const onApiKeySelected = (keyValue) => {
if (keyValue === 'addnewkey') {
navigate('/apikey')
@@ -265,8 +205,6 @@ query({"question": "Hey, how are you?"}).then((response) => {
return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\
-X POST \\
-d '{"question": "Hey, how are you?"}'`
- } else if (codeLang === 'Embed') {
- return embedCode(dialogProps.chatflowid)
}
return ''
}
@@ -309,8 +247,6 @@ query({"question": "Hey, how are you?"}).then((response) => {
-X POST \\
-d '{"question": "Hey, how are you?"}' \\
-H "Authorization: Bearer ${selectedApiKey?.apiKey}"`
- } else if (codeLang === 'Embed') {
- return embedCode(dialogProps.chatflowid)
}
return ''
}
@@ -318,7 +254,7 @@ query({"question": "Hey, how are you?"}).then((response) => {
const getLang = (codeLang) => {
if (codeLang === 'Python') {
return 'python'
- } else if (codeLang === 'JavaScript' || codeLang === 'Embed') {
+ } else if (codeLang === 'JavaScript') {
return 'javascript'
} else if (codeLang === 'cURL') {
return 'bash'
@@ -335,6 +271,8 @@ query({"question": "Hey, how are you?"}).then((response) => {
return EmbedSVG
} else if (codeLang === 'cURL') {
return cURLSVG
+ } else if (codeLang === 'Share Chatbot') {
+ return ShareChatbotSVG
}
return pythonSVG
}
@@ -593,93 +531,70 @@ query({
))}
- {value !== 0 && (
-