Update chatflow configuration dialog - add starter prompts, analyse chatflow. Rename files.
This commit is contained in:
parent
b7bb043d3a
commit
7020974b55
|
|
@ -1,14 +1,5 @@
|
||||||
// assets
|
// assets
|
||||||
import {
|
import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconMessage, IconAdjustmentsHorizontal } from '@tabler/icons'
|
||||||
IconTrash,
|
|
||||||
IconFileUpload,
|
|
||||||
IconFileExport,
|
|
||||||
IconCopy,
|
|
||||||
IconSearch,
|
|
||||||
IconMessage,
|
|
||||||
IconPictureInPictureOff,
|
|
||||||
IconAdjustmentsHorizontal
|
|
||||||
} from '@tabler/icons'
|
|
||||||
|
|
||||||
// constant
|
// constant
|
||||||
const icons = {
|
const icons = {
|
||||||
|
|
@ -16,9 +7,7 @@ const icons = {
|
||||||
IconFileUpload,
|
IconFileUpload,
|
||||||
IconFileExport,
|
IconFileExport,
|
||||||
IconCopy,
|
IconCopy,
|
||||||
IconSearch,
|
|
||||||
IconMessage,
|
IconMessage,
|
||||||
IconPictureInPictureOff,
|
|
||||||
IconAdjustmentsHorizontal
|
IconAdjustmentsHorizontal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,13 +18,6 @@ const settings = {
|
||||||
title: '',
|
title: '',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
id: 'conversationStarters',
|
|
||||||
title: 'Starter Prompts',
|
|
||||||
type: 'item',
|
|
||||||
url: '',
|
|
||||||
icon: icons.IconPictureInPictureOff
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'viewMessages',
|
id: 'viewMessages',
|
||||||
title: 'View Messages',
|
title: 'View Messages',
|
||||||
|
|
@ -71,13 +53,6 @@ const settings = {
|
||||||
url: '',
|
url: '',
|
||||||
icon: icons.IconFileExport
|
icon: icons.IconFileExport
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'analyseChatflow',
|
|
||||||
title: 'Analyse Chatflow',
|
|
||||||
type: 'item',
|
|
||||||
url: '',
|
|
||||||
icon: icons.IconSearch
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'deleteChatflow',
|
id: 'deleteChatflow',
|
||||||
title: 'Delete Chatflow',
|
title: 'Delete Chatflow',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,325 @@
|
||||||
|
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'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import {
|
||||||
|
Typography,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Accordion,
|
||||||
|
AccordionSummary,
|
||||||
|
AccordionDetails,
|
||||||
|
ListItem,
|
||||||
|
ListItemAvatar,
|
||||||
|
ListItemText
|
||||||
|
} from '@mui/material'
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||||
|
import { IconX } from '@tabler/icons'
|
||||||
|
|
||||||
|
// Project import
|
||||||
|
import CredentialInputHandler from 'views/canvas/CredentialInputHandler'
|
||||||
|
import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser'
|
||||||
|
import { SwitchInput } from 'ui-component/switch/Switch'
|
||||||
|
import { Input } from 'ui-component/input/Input'
|
||||||
|
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||||
|
import langsmithPNG from 'assets/images/langchain.png'
|
||||||
|
import langfuseSVG from 'assets/images/langfuse.svg'
|
||||||
|
import lunarySVG from 'assets/images/lunary.svg'
|
||||||
|
|
||||||
|
// store
|
||||||
|
import useNotifier from 'utils/useNotifier'
|
||||||
|
|
||||||
|
// API
|
||||||
|
import chatflowsApi from 'api/chatflows'
|
||||||
|
|
||||||
|
const analyticProviders = [
|
||||||
|
{
|
||||||
|
label: 'LangSmith',
|
||||||
|
name: 'langSmith',
|
||||||
|
icon: langsmithPNG,
|
||||||
|
url: 'https://smith.langchain.com',
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
label: 'Connect Credential',
|
||||||
|
name: 'credential',
|
||||||
|
type: 'credential',
|
||||||
|
credentialNames: ['langsmithApi']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Project Name',
|
||||||
|
name: 'projectName',
|
||||||
|
type: 'string',
|
||||||
|
optional: true,
|
||||||
|
description: 'If not provided, default will be used',
|
||||||
|
placeholder: 'default'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'On/Off',
|
||||||
|
name: 'status',
|
||||||
|
type: 'boolean',
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'LangFuse',
|
||||||
|
name: 'langFuse',
|
||||||
|
icon: langfuseSVG,
|
||||||
|
url: 'https://langfuse.com',
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
label: 'Connect Credential',
|
||||||
|
name: 'credential',
|
||||||
|
type: 'credential',
|
||||||
|
credentialNames: ['langfuseApi']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Release',
|
||||||
|
name: 'release',
|
||||||
|
type: 'string',
|
||||||
|
optional: true,
|
||||||
|
description: 'The release number/hash of the application to provide analytics grouped by release'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'On/Off',
|
||||||
|
name: 'status',
|
||||||
|
type: 'boolean',
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Lunary',
|
||||||
|
name: 'lunary',
|
||||||
|
icon: lunarySVG,
|
||||||
|
url: 'https://lunary.ai',
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
label: 'Connect Credential',
|
||||||
|
name: 'credential',
|
||||||
|
type: 'credential',
|
||||||
|
credentialNames: ['lunaryApi']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'On/Off',
|
||||||
|
name: 'status',
|
||||||
|
type: 'boolean',
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const AnalyseFlow = ({ dialogProps, onCancel }) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
useNotifier()
|
||||||
|
|
||||||
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
|
||||||
|
const [analytic, setAnalytic] = useState({})
|
||||||
|
const [providerExpanded, setProviderExpanded] = useState({})
|
||||||
|
|
||||||
|
const onSave = async () => {
|
||||||
|
try {
|
||||||
|
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||||
|
analytic: JSON.stringify(analytic)
|
||||||
|
})
|
||||||
|
if (saveResp.data) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'Analytic 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 })
|
||||||
|
}
|
||||||
|
onCancel()
|
||||||
|
} catch (error) {
|
||||||
|
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to save Analytic Configuration: ${errorData}`,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setValue = (value, providerName, inputParamName) => {
|
||||||
|
let newVal = {}
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(analytic, providerName)) {
|
||||||
|
newVal = { ...analytic, [providerName]: {} }
|
||||||
|
} else {
|
||||||
|
newVal = { ...analytic }
|
||||||
|
}
|
||||||
|
|
||||||
|
newVal[providerName][inputParamName] = value
|
||||||
|
setAnalytic(newVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAccordionChange = (providerName) => (event, isExpanded) => {
|
||||||
|
const accordianProviders = { ...providerExpanded }
|
||||||
|
accordianProviders[providerName] = isExpanded
|
||||||
|
setProviderExpanded(accordianProviders)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dialogProps.chatflow && dialogProps.chatflow.analytic) {
|
||||||
|
try {
|
||||||
|
setAnalytic(JSON.parse(dialogProps.chatflow.analytic))
|
||||||
|
} catch (e) {
|
||||||
|
setAnalytic({})
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setAnalytic({})
|
||||||
|
setProviderExpanded({})
|
||||||
|
}
|
||||||
|
}, [dialogProps])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{analyticProviders.map((provider, index) => (
|
||||||
|
<Accordion
|
||||||
|
expanded={providerExpanded[provider.name] || false}
|
||||||
|
onChange={handleAccordionChange(provider.name)}
|
||||||
|
disableGutters
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls={provider.name} id={provider.name}>
|
||||||
|
<ListItem style={{ padding: 0, margin: 0 }} alignItems='center'>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
padding: 10,
|
||||||
|
objectFit: 'contain'
|
||||||
|
}}
|
||||||
|
alt='AI'
|
||||||
|
src={provider.icon}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
sx={{ ml: 1 }}
|
||||||
|
primary={provider.label}
|
||||||
|
secondary={
|
||||||
|
<a target='_blank' rel='noreferrer' href={provider.url}>
|
||||||
|
{provider.url}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{analytic[provider.name] && analytic[provider.name].status && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
background: '#d8f3dc',
|
||||||
|
borderRadius: 15,
|
||||||
|
padding: 5,
|
||||||
|
paddingLeft: 7,
|
||||||
|
paddingRight: 7,
|
||||||
|
marginRight: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 15,
|
||||||
|
height: 15,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: '#70e000'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span style={{ color: '#006400', marginLeft: 10 }}>ON</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ListItem>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
{provider.inputs.map((inputParam, index) => (
|
||||||
|
<Box key={index} sx={{ p: 2 }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
|
<Typography>
|
||||||
|
{inputParam.label}
|
||||||
|
{!inputParam.optional && <span style={{ color: 'red' }}> *</span>}
|
||||||
|
{inputParam.description && (
|
||||||
|
<TooltipWithParser style={{ marginLeft: 10 }} title={inputParam.description} />
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
{providerExpanded[provider.name] && inputParam.type === 'credential' && (
|
||||||
|
<CredentialInputHandler
|
||||||
|
data={analytic[provider.name] ? { credential: analytic[provider.name].credentialId } : {}}
|
||||||
|
inputParam={inputParam}
|
||||||
|
onSelect={(newValue) => setValue(newValue, provider.name, 'credentialId')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{providerExpanded[provider.name] && inputParam.type === 'boolean' && (
|
||||||
|
<SwitchInput
|
||||||
|
onChange={(newValue) => setValue(newValue, provider.name, inputParam.name)}
|
||||||
|
value={
|
||||||
|
analytic[provider.name] ? analytic[provider.name][inputParam.name] : inputParam.default ?? false
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{providerExpanded[provider.name] &&
|
||||||
|
(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && (
|
||||||
|
<Input
|
||||||
|
inputParam={inputParam}
|
||||||
|
onChange={(newValue) => setValue(newValue, provider.name, inputParam.name)}
|
||||||
|
value={
|
||||||
|
analytic[provider.name]
|
||||||
|
? analytic[provider.name][inputParam.name]
|
||||||
|
: inputParam.default ?? ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
))}
|
||||||
|
<StyledButton style={{ marginBottom: 10, marginTop: 10 }} variant='contained' onClick={onSave}>
|
||||||
|
Save
|
||||||
|
</StyledButton>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalyseFlow.propTypes = {
|
||||||
|
show: PropTypes.bool,
|
||||||
|
dialogProps: PropTypes.object,
|
||||||
|
onCancel: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AnalyseFlow
|
||||||
|
|
@ -1,358 +0,0 @@
|
||||||
import { createPortal } from 'react-dom'
|
|
||||||
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'
|
|
||||||
|
|
||||||
// material-ui
|
|
||||||
import {
|
|
||||||
Typography,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogTitle,
|
|
||||||
DialogActions,
|
|
||||||
Accordion,
|
|
||||||
AccordionSummary,
|
|
||||||
AccordionDetails,
|
|
||||||
ListItem,
|
|
||||||
ListItemAvatar,
|
|
||||||
ListItemText
|
|
||||||
} from '@mui/material'
|
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
|
||||||
import { IconX } from '@tabler/icons'
|
|
||||||
|
|
||||||
// Project import
|
|
||||||
import CredentialInputHandler from 'views/canvas/CredentialInputHandler'
|
|
||||||
import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser'
|
|
||||||
import { SwitchInput } from 'ui-component/switch/Switch'
|
|
||||||
import { Input } from 'ui-component/input/Input'
|
|
||||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
|
||||||
import langsmithPNG from 'assets/images/langchain.png'
|
|
||||||
import langfuseSVG from 'assets/images/langfuse.svg'
|
|
||||||
import lunarySVG from 'assets/images/lunary.svg'
|
|
||||||
|
|
||||||
// store
|
|
||||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
|
||||||
import useNotifier from 'utils/useNotifier'
|
|
||||||
|
|
||||||
// API
|
|
||||||
import chatflowsApi from 'api/chatflows'
|
|
||||||
|
|
||||||
const analyticProviders = [
|
|
||||||
{
|
|
||||||
label: 'LangSmith',
|
|
||||||
name: 'langSmith',
|
|
||||||
icon: langsmithPNG,
|
|
||||||
url: 'https://smith.langchain.com',
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
label: 'Connect Credential',
|
|
||||||
name: 'credential',
|
|
||||||
type: 'credential',
|
|
||||||
credentialNames: ['langsmithApi']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Project Name',
|
|
||||||
name: 'projectName',
|
|
||||||
type: 'string',
|
|
||||||
optional: true,
|
|
||||||
description: 'If not provided, default will be used',
|
|
||||||
placeholder: 'default'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'On/Off',
|
|
||||||
name: 'status',
|
|
||||||
type: 'boolean',
|
|
||||||
optional: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'LangFuse',
|
|
||||||
name: 'langFuse',
|
|
||||||
icon: langfuseSVG,
|
|
||||||
url: 'https://langfuse.com',
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
label: 'Connect Credential',
|
|
||||||
name: 'credential',
|
|
||||||
type: 'credential',
|
|
||||||
credentialNames: ['langfuseApi']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Release',
|
|
||||||
name: 'release',
|
|
||||||
type: 'string',
|
|
||||||
optional: true,
|
|
||||||
description: 'The release number/hash of the application to provide analytics grouped by release'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'On/Off',
|
|
||||||
name: 'status',
|
|
||||||
type: 'boolean',
|
|
||||||
optional: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Lunary',
|
|
||||||
name: 'lunary',
|
|
||||||
icon: lunarySVG,
|
|
||||||
url: 'https://lunary.ai',
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
label: 'Connect Credential',
|
|
||||||
name: 'credential',
|
|
||||||
type: 'credential',
|
|
||||||
credentialNames: ['lunaryApi']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'On/Off',
|
|
||||||
name: 'status',
|
|
||||||
type: 'boolean',
|
|
||||||
optional: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const AnalyseFlowDialog = ({ show, dialogProps, onCancel }) => {
|
|
||||||
const portalElement = document.getElementById('portal')
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
|
|
||||||
useNotifier()
|
|
||||||
|
|
||||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
|
||||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
|
||||||
|
|
||||||
const [analytic, setAnalytic] = useState({})
|
|
||||||
const [providerExpanded, setProviderExpanded] = useState({})
|
|
||||||
|
|
||||||
const onSave = async () => {
|
|
||||||
try {
|
|
||||||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
|
||||||
analytic: JSON.stringify(analytic)
|
|
||||||
})
|
|
||||||
if (saveResp.data) {
|
|
||||||
enqueueSnackbar({
|
|
||||||
message: 'Analytic 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 })
|
|
||||||
}
|
|
||||||
onCancel()
|
|
||||||
} catch (error) {
|
|
||||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
|
||||||
enqueueSnackbar({
|
|
||||||
message: `Failed to save Analytic Configuration: ${errorData}`,
|
|
||||||
options: {
|
|
||||||
key: new Date().getTime() + Math.random(),
|
|
||||||
variant: 'error',
|
|
||||||
persist: true,
|
|
||||||
action: (key) => (
|
|
||||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
|
||||||
<IconX />
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setValue = (value, providerName, inputParamName) => {
|
|
||||||
let newVal = {}
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(analytic, providerName)) {
|
|
||||||
newVal = { ...analytic, [providerName]: {} }
|
|
||||||
} else {
|
|
||||||
newVal = { ...analytic }
|
|
||||||
}
|
|
||||||
|
|
||||||
newVal[providerName][inputParamName] = value
|
|
||||||
setAnalytic(newVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAccordionChange = (providerName) => (event, isExpanded) => {
|
|
||||||
const accordianProviders = { ...providerExpanded }
|
|
||||||
accordianProviders[providerName] = isExpanded
|
|
||||||
setProviderExpanded(accordianProviders)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (dialogProps.chatflow && dialogProps.chatflow.analytic) {
|
|
||||||
try {
|
|
||||||
setAnalytic(JSON.parse(dialogProps.chatflow.analytic))
|
|
||||||
} catch (e) {
|
|
||||||
setAnalytic({})
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
setAnalytic({})
|
|
||||||
setProviderExpanded({})
|
|
||||||
}
|
|
||||||
}, [dialogProps])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
|
||||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
|
||||||
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
|
||||||
}, [show, dispatch])
|
|
||||||
|
|
||||||
const component = show ? (
|
|
||||||
<Dialog
|
|
||||||
onClose={onCancel}
|
|
||||||
open={show}
|
|
||||||
fullWidth
|
|
||||||
maxWidth='sm'
|
|
||||||
aria-labelledby='alert-dialog-title'
|
|
||||||
aria-describedby='alert-dialog-description'
|
|
||||||
>
|
|
||||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
|
||||||
Analyse Chatflow
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
{analyticProviders.map((provider, index) => (
|
|
||||||
<Accordion
|
|
||||||
expanded={providerExpanded[provider.name] || false}
|
|
||||||
onChange={handleAccordionChange(provider.name)}
|
|
||||||
disableGutters
|
|
||||||
key={index}
|
|
||||||
>
|
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls={provider.name} id={provider.name}>
|
|
||||||
<ListItem style={{ padding: 0, margin: 0 }} alignItems='center'>
|
|
||||||
<ListItemAvatar>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: 50,
|
|
||||||
height: 50,
|
|
||||||
borderRadius: '50%',
|
|
||||||
backgroundColor: 'white'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
padding: 10,
|
|
||||||
objectFit: 'contain'
|
|
||||||
}}
|
|
||||||
alt='AI'
|
|
||||||
src={provider.icon}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ListItemAvatar>
|
|
||||||
<ListItemText
|
|
||||||
sx={{ ml: 1 }}
|
|
||||||
primary={provider.label}
|
|
||||||
secondary={
|
|
||||||
<a target='_blank' rel='noreferrer' href={provider.url}>
|
|
||||||
{provider.url}
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{analytic[provider.name] && analytic[provider.name].status && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
background: '#d8f3dc',
|
|
||||||
borderRadius: 15,
|
|
||||||
padding: 5,
|
|
||||||
paddingLeft: 7,
|
|
||||||
paddingRight: 7,
|
|
||||||
marginRight: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: 15,
|
|
||||||
height: 15,
|
|
||||||
borderRadius: '50%',
|
|
||||||
backgroundColor: '#70e000'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span style={{ color: '#006400', marginLeft: 10 }}>ON</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</ListItem>
|
|
||||||
</AccordionSummary>
|
|
||||||
<AccordionDetails>
|
|
||||||
{provider.inputs.map((inputParam, index) => (
|
|
||||||
<Box key={index} sx={{ p: 2 }}>
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
|
||||||
<Typography>
|
|
||||||
{inputParam.label}
|
|
||||||
{!inputParam.optional && <span style={{ color: 'red' }}> *</span>}
|
|
||||||
{inputParam.description && (
|
|
||||||
<TooltipWithParser style={{ marginLeft: 10 }} title={inputParam.description} />
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
{providerExpanded[provider.name] && inputParam.type === 'credential' && (
|
|
||||||
<CredentialInputHandler
|
|
||||||
data={analytic[provider.name] ? { credential: analytic[provider.name].credentialId } : {}}
|
|
||||||
inputParam={inputParam}
|
|
||||||
onSelect={(newValue) => setValue(newValue, provider.name, 'credentialId')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{providerExpanded[provider.name] && inputParam.type === 'boolean' && (
|
|
||||||
<SwitchInput
|
|
||||||
onChange={(newValue) => setValue(newValue, provider.name, inputParam.name)}
|
|
||||||
value={
|
|
||||||
analytic[provider.name]
|
|
||||||
? analytic[provider.name][inputParam.name]
|
|
||||||
: inputParam.default ?? false
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{providerExpanded[provider.name] &&
|
|
||||||
(inputParam.type === 'string' ||
|
|
||||||
inputParam.type === 'password' ||
|
|
||||||
inputParam.type === 'number') && (
|
|
||||||
<Input
|
|
||||||
inputParam={inputParam}
|
|
||||||
onChange={(newValue) => setValue(newValue, provider.name, inputParam.name)}
|
|
||||||
value={
|
|
||||||
analytic[provider.name]
|
|
||||||
? analytic[provider.name][inputParam.name]
|
|
||||||
: inputParam.default ?? ''
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</AccordionDetails>
|
|
||||||
</Accordion>
|
|
||||||
))}
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<StyledButton variant='contained' onClick={onSave}>
|
|
||||||
Save
|
|
||||||
</StyledButton>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
) : null
|
|
||||||
|
|
||||||
return createPortal(component, portalElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
AnalyseFlowDialog.propTypes = {
|
|
||||||
show: PropTypes.bool,
|
|
||||||
dialogProps: PropTypes.object,
|
|
||||||
onCancel: PropTypes.func
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AnalyseFlowDialog
|
|
||||||
|
|
@ -93,7 +93,7 @@ const ChatFeedback = ({ dialogProps }) => {
|
||||||
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||||
<SwitchInput label='Enable chat feedback' onChange={handleChange} value={chatFeedbackStatus} />
|
<SwitchInput label='Enable chat feedback' onChange={handleChange} value={chatFeedbackStatus} />
|
||||||
</Box>
|
</Box>
|
||||||
<StyledButton variant='contained' onClick={onSave}>
|
<StyledButton style={{ marginBottom: 10, marginTop: 10 }} variant='contained' onClick={onSave}>
|
||||||
Save
|
Save
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
</>
|
</>
|
||||||
|
|
@ -2,16 +2,22 @@ import PropTypes from 'prop-types'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { Box, Dialog, DialogContent, DialogTitle, Tabs, Tab } from '@mui/material'
|
import { Box, Dialog, DialogContent, DialogTitle, Tabs, Tab } from '@mui/material'
|
||||||
import SpeechToText from './SpeechToTextDialog'
|
import SpeechToText from './SpeechToText'
|
||||||
import Configuration from 'views/chatflows/Configuration'
|
import Configuration from 'views/chatflows/Configuration'
|
||||||
import AllowedDomains from './AllowedDomainsDialog'
|
import AllowedDomains from './AllowedDomains'
|
||||||
import ChatFeedback from './ChatFeedbackDialog'
|
import ChatFeedback from './ChatFeedback'
|
||||||
|
import AnalyseFlow from './AnalyseFlow'
|
||||||
|
import StarterPrompts from './StarterPrompts'
|
||||||
|
|
||||||
const CHATFLOW_CONFIGURATION_TABS = [
|
const CHATFLOW_CONFIGURATION_TABS = [
|
||||||
{
|
{
|
||||||
label: 'Rate Limiting',
|
label: 'Rate Limiting',
|
||||||
id: 'rateLimiting'
|
id: 'rateLimiting'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Starter Prompts',
|
||||||
|
id: 'conversationStarters'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Speech to Text',
|
label: 'Speech to Text',
|
||||||
id: 'speechToText'
|
id: 'speechToText'
|
||||||
|
|
@ -23,6 +29,10 @@ const CHATFLOW_CONFIGURATION_TABS = [
|
||||||
{
|
{
|
||||||
label: 'Allowed Domains',
|
label: 'Allowed Domains',
|
||||||
id: 'allowedDomains'
|
id: 'allowedDomains'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Analyse Chatflow',
|
||||||
|
id: 'analyseChatflow'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -34,7 +44,7 @@ function TabPanel(props) {
|
||||||
hidden={value !== index}
|
hidden={value !== index}
|
||||||
id={`chatflow-config-tabpanel-${index}`}
|
id={`chatflow-config-tabpanel-${index}`}
|
||||||
aria-labelledby={`chatflow-config-tab-${index}`}
|
aria-labelledby={`chatflow-config-tab-${index}`}
|
||||||
style={{ paddingTop: '1rem' }}
|
style={{ width: '100%', paddingTop: '1rem' }}
|
||||||
{...other}
|
{...other}
|
||||||
>
|
>
|
||||||
{value === index && <Box sx={{ p: 1 }}>{children}</Box>}
|
{value === index && <Box sx={{ p: 1 }}>{children}</Box>}
|
||||||
|
|
@ -73,22 +83,28 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Tabs
|
<Tabs
|
||||||
sx={{ position: 'relative', minHeight: '50px', height: '50px' }}
|
sx={{ position: 'relative', minHeight: '40px', height: '40px' }}
|
||||||
variant='fullWidth'
|
|
||||||
value={tabValue}
|
value={tabValue}
|
||||||
onChange={(event, value) => setTabValue(value)}
|
onChange={(event, value) => setTabValue(value)}
|
||||||
aria-label='tabs'
|
aria-label='tabs'
|
||||||
>
|
>
|
||||||
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
||||||
<Tab sx={{ minHeight: '50px', height: '50px' }} key={index} label={item.label} {...a11yProps(index)}></Tab>
|
<Tab
|
||||||
|
sx={{ minHeight: '40px', height: '40px', textAlign: 'left', display: 'flex', alignItems: 'start', mb: 1 }}
|
||||||
|
key={index}
|
||||||
|
label={item.label}
|
||||||
|
{...a11yProps(index)}
|
||||||
|
></Tab>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
||||||
<TabPanel key={index} value={tabValue} index={index}>
|
<TabPanel key={index} value={tabValue} index={index}>
|
||||||
{item.id === 'rateLimiting' && <Configuration />}
|
{item.id === 'rateLimiting' && <Configuration />}
|
||||||
|
{item.id === 'conversationStarters' ? <StarterPrompts dialogProps={dialogProps} /> : null}
|
||||||
{item.id === 'speechToText' ? <SpeechToText dialogProps={dialogProps} /> : null}
|
{item.id === 'speechToText' ? <SpeechToText dialogProps={dialogProps} /> : null}
|
||||||
{item.id === 'chatFeedback' ? <ChatFeedback dialogProps={dialogProps} /> : null}
|
{item.id === 'chatFeedback' ? <ChatFeedback dialogProps={dialogProps} /> : null}
|
||||||
{item.id === 'allowedDomains' ? <AllowedDomains dialogProps={dialogProps} /> : null}
|
{item.id === 'allowedDomains' ? <AllowedDomains dialogProps={dialogProps} /> : null}
|
||||||
|
{item.id === 'analyseChatflow' ? <AnalyseFlow dialogProps={dialogProps} /> : null}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
))}
|
))}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
|
||||||
|
|
@ -183,8 +183,10 @@ const SpeechToText = ({ dialogProps }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box fullWidth sx={{ my: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box fullWidth sx={{ mb: 1, display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Typography>Speech To Text Providers</Typography>
|
<Typography variant='h4' sx={{ mb: 1 }}>
|
||||||
|
Providers
|
||||||
|
</Typography>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<Select value={selectedProvider} onChange={handleProviderChange}>
|
<Select value={selectedProvider} onChange={handleProviderChange}>
|
||||||
<MenuItem value='none'>None</MenuItem>
|
<MenuItem value='none'>None</MenuItem>
|
||||||
|
|
@ -289,6 +291,7 @@ const SpeechToText = ({ dialogProps }) => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<StyledButton
|
<StyledButton
|
||||||
|
style={{ marginBottom: 10, marginTop: 10 }}
|
||||||
disabled={selectedProvider !== 'none' && !speechToText[selectedProvider]?.credentialId}
|
disabled={selectedProvider !== 'none' && !speechToText[selectedProvider]?.credentialId}
|
||||||
variant='contained'
|
variant='contained'
|
||||||
onClick={onSave}
|
onClick={onSave}
|
||||||
|
|
@ -0,0 +1,215 @@
|
||||||
|
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'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Button, IconButton, OutlinedInput, Box, List, InputAdornment } from '@mui/material'
|
||||||
|
import { IconX, IconTrash, IconPlus, IconBulb } from '@tabler/icons'
|
||||||
|
|
||||||
|
// Project import
|
||||||
|
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||||
|
|
||||||
|
// store
|
||||||
|
import useNotifier from 'utils/useNotifier'
|
||||||
|
|
||||||
|
// API
|
||||||
|
import chatflowsApi from 'api/chatflows'
|
||||||
|
|
||||||
|
const StarterPrompts = ({ dialogProps, onConfirm }) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
useNotifier()
|
||||||
|
|
||||||
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
|
||||||
|
const [inputFields, setInputFields] = useState([
|
||||||
|
{
|
||||||
|
prompt: ''
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const [chatbotConfig, setChatbotConfig] = useState({})
|
||||||
|
|
||||||
|
const addInputField = () => {
|
||||||
|
setInputFields([
|
||||||
|
...inputFields,
|
||||||
|
{
|
||||||
|
prompt: ''
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
const removeInputFields = (index) => {
|
||||||
|
const rows = [...inputFields]
|
||||||
|
rows.splice(index, 1)
|
||||||
|
setInputFields(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = (index, evnt) => {
|
||||||
|
const { name, value } = evnt.target
|
||||||
|
const list = [...inputFields]
|
||||||
|
list[index][name] = value
|
||||||
|
setInputFields(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSave = async () => {
|
||||||
|
try {
|
||||||
|
let value = {
|
||||||
|
starterPrompts: {
|
||||||
|
...inputFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chatbotConfig.starterPrompts = value.starterPrompts
|
||||||
|
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||||
|
chatbotConfig: JSON.stringify(chatbotConfig)
|
||||||
|
})
|
||||||
|
if (saveResp.data) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'Conversation Starter Prompts 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 })
|
||||||
|
}
|
||||||
|
onConfirm()
|
||||||
|
} catch (error) {
|
||||||
|
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to save Conversation Starter Prompts: ${errorData}`,
|
||||||
|
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 && dialogProps.chatflow.chatbotConfig) {
|
||||||
|
try {
|
||||||
|
let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig)
|
||||||
|
setChatbotConfig(chatbotConfig || {})
|
||||||
|
if (chatbotConfig.starterPrompts) {
|
||||||
|
let inputFields = []
|
||||||
|
Object.getOwnPropertyNames(chatbotConfig.starterPrompts).forEach((key) => {
|
||||||
|
if (chatbotConfig.starterPrompts[key]) {
|
||||||
|
inputFields.push(chatbotConfig.starterPrompts[key])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setInputFields(inputFields)
|
||||||
|
} else {
|
||||||
|
setInputFields([
|
||||||
|
{
|
||||||
|
prompt: ''
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setInputFields([
|
||||||
|
{
|
||||||
|
prompt: ''
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {}
|
||||||
|
}, [dialogProps])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
borderRadius: 10,
|
||||||
|
background: '#d8f3dc',
|
||||||
|
padding: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconBulb size={30} color='#2d6a4f' />
|
||||||
|
<span style={{ color: '#2d6a4f', marginLeft: 10, fontWeight: 500 }}>
|
||||||
|
Starter prompts will only be shown when there is no messages on the chat
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Box sx={{ '& > :not(style)': { m: 1 }, pt: 2 }}>
|
||||||
|
<List>
|
||||||
|
{inputFields.map((data, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index} style={{ display: 'flex', width: '100%' }}>
|
||||||
|
<Box sx={{ width: '95%', mb: 1 }}>
|
||||||
|
<OutlinedInput
|
||||||
|
sx={{ width: '100%' }}
|
||||||
|
key={index}
|
||||||
|
type='text'
|
||||||
|
onChange={(e) => handleChange(index, e)}
|
||||||
|
size='small'
|
||||||
|
value={data.prompt}
|
||||||
|
name='prompt'
|
||||||
|
endAdornment={
|
||||||
|
<InputAdornment position='end' sx={{ padding: '2px' }}>
|
||||||
|
{inputFields.length > 1 && (
|
||||||
|
<IconButton
|
||||||
|
sx={{ height: 30, width: 30 }}
|
||||||
|
size='small'
|
||||||
|
color='error'
|
||||||
|
disabled={inputFields.length === 1}
|
||||||
|
onClick={() => removeInputFields(index)}
|
||||||
|
edge='end'
|
||||||
|
>
|
||||||
|
<IconTrash />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</InputAdornment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ width: '5%', mb: 1 }}>
|
||||||
|
{index === inputFields.length - 1 && (
|
||||||
|
<IconButton color='primary' onClick={addInputField}>
|
||||||
|
<IconPlus />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
</Box>
|
||||||
|
<StyledButton variant='contained' onClick={onSave}>
|
||||||
|
Save
|
||||||
|
</StyledButton>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
StarterPrompts.propTypes = {
|
||||||
|
show: PropTypes.bool,
|
||||||
|
dialogProps: PropTypes.object,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
onConfirm: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StarterPrompts
|
||||||
|
|
@ -1,149 +1,24 @@
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
import { useState, useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions'
|
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import {
|
import { Dialog, DialogContent, DialogTitle } from '@mui/material'
|
||||||
Button,
|
|
||||||
IconButton,
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
OutlinedInput,
|
|
||||||
DialogTitle,
|
|
||||||
DialogActions,
|
|
||||||
Box,
|
|
||||||
List,
|
|
||||||
InputAdornment
|
|
||||||
} from '@mui/material'
|
|
||||||
import { IconX, IconTrash, IconPlus, IconBulb } from '@tabler/icons'
|
|
||||||
|
|
||||||
// Project import
|
|
||||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
|
||||||
|
|
||||||
// store
|
// store
|
||||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||||
import useNotifier from 'utils/useNotifier'
|
import useNotifier from 'utils/useNotifier'
|
||||||
|
|
||||||
// API
|
// Project imports
|
||||||
import chatflowsApi from 'api/chatflows'
|
import StarterPrompts from './StarterPrompts'
|
||||||
|
|
||||||
const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
useNotifier()
|
useNotifier()
|
||||||
|
|
||||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
|
||||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
|
||||||
|
|
||||||
const [inputFields, setInputFields] = useState([
|
|
||||||
{
|
|
||||||
prompt: ''
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
const [chatbotConfig, setChatbotConfig] = useState({})
|
|
||||||
|
|
||||||
const addInputField = () => {
|
|
||||||
setInputFields([
|
|
||||||
...inputFields,
|
|
||||||
{
|
|
||||||
prompt: ''
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
const removeInputFields = (index) => {
|
|
||||||
const rows = [...inputFields]
|
|
||||||
rows.splice(index, 1)
|
|
||||||
setInputFields(rows)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (index, evnt) => {
|
|
||||||
const { name, value } = evnt.target
|
|
||||||
const list = [...inputFields]
|
|
||||||
list[index][name] = value
|
|
||||||
setInputFields(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSave = async () => {
|
|
||||||
try {
|
|
||||||
let value = {
|
|
||||||
starterPrompts: {
|
|
||||||
...inputFields
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chatbotConfig.starterPrompts = value.starterPrompts
|
|
||||||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
|
||||||
chatbotConfig: JSON.stringify(chatbotConfig)
|
|
||||||
})
|
|
||||||
if (saveResp.data) {
|
|
||||||
enqueueSnackbar({
|
|
||||||
message: 'Conversation Starter Prompts 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 })
|
|
||||||
}
|
|
||||||
onConfirm()
|
|
||||||
} catch (error) {
|
|
||||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
|
||||||
enqueueSnackbar({
|
|
||||||
message: `Failed to save Conversation Starter Prompts: ${errorData}`,
|
|
||||||
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 && dialogProps.chatflow.chatbotConfig) {
|
|
||||||
try {
|
|
||||||
let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig)
|
|
||||||
setChatbotConfig(chatbotConfig || {})
|
|
||||||
if (chatbotConfig.starterPrompts) {
|
|
||||||
let inputFields = []
|
|
||||||
Object.getOwnPropertyNames(chatbotConfig.starterPrompts).forEach((key) => {
|
|
||||||
if (chatbotConfig.starterPrompts[key]) {
|
|
||||||
inputFields.push(chatbotConfig.starterPrompts[key])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setInputFields(inputFields)
|
|
||||||
} else {
|
|
||||||
setInputFields([
|
|
||||||
{
|
|
||||||
prompt: ''
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setInputFields([
|
|
||||||
{
|
|
||||||
prompt: ''
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {}
|
|
||||||
}, [dialogProps])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
|
|
@ -163,79 +38,8 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||||
{dialogProps.title || 'Conversation Starter Prompts'}
|
{dialogProps.title || 'Conversation Starter Prompts'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<div
|
<StarterPrompts dialogProps={dialogProps} />
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
borderRadius: 10,
|
|
||||||
background: '#d8f3dc',
|
|
||||||
padding: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconBulb size={30} color='#2d6a4f' />
|
|
||||||
<span style={{ color: '#2d6a4f', marginLeft: 10, fontWeight: 500 }}>
|
|
||||||
Starter prompts will only be shown when there is no messages on the chat
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Box sx={{ '& > :not(style)': { m: 1 }, pt: 2 }}>
|
|
||||||
<List>
|
|
||||||
{inputFields.map((data, index) => {
|
|
||||||
return (
|
|
||||||
<div key={index} style={{ display: 'flex', width: '100%' }}>
|
|
||||||
<Box sx={{ width: '95%', mb: 1 }}>
|
|
||||||
<OutlinedInput
|
|
||||||
sx={{ width: '100%' }}
|
|
||||||
key={index}
|
|
||||||
type='text'
|
|
||||||
onChange={(e) => handleChange(index, e)}
|
|
||||||
size='small'
|
|
||||||
value={data.prompt}
|
|
||||||
name='prompt'
|
|
||||||
endAdornment={
|
|
||||||
<InputAdornment position='end' sx={{ padding: '2px' }}>
|
|
||||||
{inputFields.length > 1 && (
|
|
||||||
<IconButton
|
|
||||||
sx={{ height: 30, width: 30 }}
|
|
||||||
size='small'
|
|
||||||
color='error'
|
|
||||||
disabled={inputFields.length === 1}
|
|
||||||
onClick={() => removeInputFields(index)}
|
|
||||||
edge='end'
|
|
||||||
>
|
|
||||||
<IconTrash />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</InputAdornment>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{ width: '5%', mb: 1 }}>
|
|
||||||
{index === inputFields.length - 1 && (
|
|
||||||
<IconButton color='primary' onClick={addInputField}>
|
|
||||||
<IconPlus />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</List>
|
|
||||||
</Box>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={onCancel}>Cancel</Button>
|
|
||||||
<StyledButton variant='contained' onClick={onSave}>
|
|
||||||
Save
|
|
||||||
</StyledButton>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,7 @@ import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck,
|
||||||
import Settings from 'views/settings'
|
import Settings from 'views/settings'
|
||||||
import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog'
|
import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog'
|
||||||
import APICodeDialog from 'views/chatflows/APICodeDialog'
|
import APICodeDialog from 'views/chatflows/APICodeDialog'
|
||||||
import AnalyseFlowDialog from 'ui-component/dialog/AnalyseFlowDialog'
|
|
||||||
import ViewMessagesDialog from 'ui-component/dialog/ViewMessagesDialog'
|
import ViewMessagesDialog from 'ui-component/dialog/ViewMessagesDialog'
|
||||||
import StarterPromptsDialog from 'ui-component/dialog/StarterPromptsDialog'
|
|
||||||
|
|
||||||
// API
|
// API
|
||||||
import chatflowsApi from 'api/chatflows'
|
import chatflowsApi from 'api/chatflows'
|
||||||
|
|
@ -45,10 +43,6 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||||
const [flowDialogOpen, setFlowDialogOpen] = useState(false)
|
const [flowDialogOpen, setFlowDialogOpen] = useState(false)
|
||||||
const [apiDialogOpen, setAPIDialogOpen] = useState(false)
|
const [apiDialogOpen, setAPIDialogOpen] = useState(false)
|
||||||
const [apiDialogProps, setAPIDialogProps] = useState({})
|
const [apiDialogProps, setAPIDialogProps] = useState({})
|
||||||
const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false)
|
|
||||||
const [analyseDialogProps, setAnalyseDialogProps] = useState({})
|
|
||||||
const [conversationStartersDialogOpen, setConversationStartersDialogOpen] = useState(false)
|
|
||||||
const [conversationStartersDialogProps, setConversationStartersDialogProps] = useState({})
|
|
||||||
const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false)
|
const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false)
|
||||||
const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({})
|
const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({})
|
||||||
const [chatflowConfigurationDialogOpen, setChatflowConfigurationDialogOpen] = useState(false)
|
const [chatflowConfigurationDialogOpen, setChatflowConfigurationDialogOpen] = useState(false)
|
||||||
|
|
@ -62,18 +56,6 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||||
|
|
||||||
if (setting === 'deleteChatflow') {
|
if (setting === 'deleteChatflow') {
|
||||||
handleDeleteFlow()
|
handleDeleteFlow()
|
||||||
} else if (setting === 'conversationStarters') {
|
|
||||||
setConversationStartersDialogProps({
|
|
||||||
title: 'Starter Prompts - ' + chatflow.name,
|
|
||||||
chatflow: chatflow
|
|
||||||
})
|
|
||||||
setConversationStartersDialogOpen(true)
|
|
||||||
} else if (setting === 'analyseChatflow') {
|
|
||||||
setAnalyseDialogProps({
|
|
||||||
title: 'Analyse Chatflow',
|
|
||||||
chatflow: chatflow
|
|
||||||
})
|
|
||||||
setAnalyseDialogOpen(true)
|
|
||||||
} else if (setting === 'viewMessages') {
|
} else if (setting === 'viewMessages') {
|
||||||
setViewMessagesDialogProps({
|
setViewMessagesDialogProps({
|
||||||
title: 'View Messages',
|
title: 'View Messages',
|
||||||
|
|
@ -393,13 +375,6 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||||
onConfirm={onConfirmSaveName}
|
onConfirm={onConfirmSaveName}
|
||||||
/>
|
/>
|
||||||
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
|
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
|
||||||
<AnalyseFlowDialog show={analyseDialogOpen} dialogProps={analyseDialogProps} onCancel={() => setAnalyseDialogOpen(false)} />
|
|
||||||
<StarterPromptsDialog
|
|
||||||
show={conversationStartersDialogOpen}
|
|
||||||
dialogProps={conversationStartersDialogProps}
|
|
||||||
onConfirm={() => setConversationStartersDialogOpen(false)}
|
|
||||||
onCancel={() => setConversationStartersDialogOpen(false)}
|
|
||||||
/>
|
|
||||||
<ViewMessagesDialog
|
<ViewMessagesDialog
|
||||||
show={viewMessagesDialogOpen}
|
show={viewMessagesDialogOpen}
|
||||||
dialogProps={viewMessagesDialogProps}
|
dialogProps={viewMessagesDialogProps}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue