Add chatflow config dialog

This commit is contained in:
Ilango
2024-03-05 16:25:35 +05:30
parent 8c65900bd2
commit 54c59024c5
6 changed files with 249 additions and 182 deletions
+14 -5
View File
@@ -7,11 +7,20 @@ import {
IconSearch, IconSearch,
IconMessage, IconMessage,
IconPictureInPictureOff, IconPictureInPictureOff,
IconMicrophone IconAdjustmentsHorizontal
} from '@tabler/icons' } from '@tabler/icons'
// constant // constant
const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage, IconPictureInPictureOff, IconMicrophone } const icons = {
IconTrash,
IconFileUpload,
IconFileExport,
IconCopy,
IconSearch,
IconMessage,
IconPictureInPictureOff,
IconAdjustmentsHorizontal
}
// ==============================|| SETTINGS MENU ITEMS ||============================== // // ==============================|| SETTINGS MENU ITEMS ||============================== //
@@ -35,11 +44,11 @@ const settings = {
icon: icons.IconMessage icon: icons.IconMessage
}, },
{ {
id: 'enableSpeechToText', id: 'chatflowConfiguration',
title: 'Speech to Text', title: 'Configuration',
type: 'item', type: 'item',
url: '', url: '',
icon: icons.IconMicrophone icon: icons.IconAdjustmentsHorizontal
}, },
{ {
id: 'duplicateChatflow', id: 'duplicateChatflow',
@@ -0,0 +1,102 @@
import PropTypes from 'prop-types'
import { useState } from 'react'
import { createPortal } from 'react-dom'
import { Box, Dialog, DialogContent, DialogTitle, Tabs, Tab } from '@mui/material'
import SpeechToText from './SpeechToTextDialog'
import Configuration from 'views/chatflows/Configuration'
const CHATFLOW_CONFIGURATION_TABS = [
{
label: 'Rate Limiting',
id: 'rateLimiting'
},
{
label: 'Speech to Text',
id: 'speechToText'
},
{
label: 'Chat Feedback',
id: 'chatFeedback'
},
{
label: 'Allowed Domains',
id: 'allowedDomains'
}
]
function TabPanel(props) {
const { children, value, index, ...other } = props
return (
<div
role='tabpanel'
hidden={value !== index}
id={`chatflow-config-tabpanel-${index}`}
aria-labelledby={`chatflow-config-tab-${index}`}
{...other}
>
{value === index && <Box sx={{ p: 1 }}>{children}</Box>}
</div>
)
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.number.isRequired,
value: PropTypes.number.isRequired
}
function a11yProps(index) {
return {
id: `chatflow-config-tab-${index}`,
'aria-controls': `chatflow-config-tabpanel-${index}`
}
}
const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
const portalElement = document.getElementById('portal')
const [tabValue, setTabValue] = useState(0)
const component = show ? (
<Dialog
onClose={onCancel}
open={show}
fullWidth
maxWidth={'md'}
aria-labelledby='alert-dialog-title'
aria-describedby='alert-dialog-description'
>
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
<div style={{ display: 'flex', flexDirection: 'row' }}>{dialogProps.title}</div>
</DialogTitle>
<DialogContent>
<Tabs
sx={{ position: 'relative', minHeight: '50px', height: '50px' }}
variant='fullWidth'
value={tabValue}
onChange={(event, value) => setTabValue(value)}
aria-label='tabs'
>
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
<Tab sx={{ minHeight: '50px', height: '50px' }} key={index} label={item.label} {...a11yProps(index)}></Tab>
))}
</Tabs>
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
<TabPanel key={index} value={tabValue} index={index}>
{item.id === 'rateLimiting' && <Configuration />}
{item.id === 'speechToText' ? <SpeechToText dialogProps={dialogProps} /> : null}
</TabPanel>
))}
</DialogContent>
</Dialog>
) : null
return createPortal(component, portalElement)
}
ChatflowConfigurationDialog.propTypes = {
show: PropTypes.bool,
dialogProps: PropTypes.object,
onCancel: PropTypes.func
}
export default ChatflowConfigurationDialog
@@ -1,25 +1,10 @@
import { createPortal } from 'react-dom'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { useState, useEffect } from 'react' import { useState, 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' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions'
// material-ui // material-ui
import { import { Typography, Box, Button, FormControl, ListItem, ListItemAvatar, ListItemText, MenuItem, Select } from '@mui/material'
Typography,
Box,
Button,
Dialog,
DialogContent,
DialogTitle,
DialogActions,
FormControl,
ListItem,
ListItemAvatar,
ListItemText,
MenuItem,
Select
} from '@mui/material'
import { IconX } from '@tabler/icons' import { IconX } from '@tabler/icons'
// Project import // Project import
@@ -33,7 +18,6 @@ import openAISVG from 'assets/images/openai.svg'
import assemblyAIPng from 'assets/images/assemblyai.png' import assemblyAIPng from 'assets/images/assemblyai.png'
// store // store
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
import useNotifier from 'utils/useNotifier' import useNotifier from 'utils/useNotifier'
// API // API
@@ -95,8 +79,7 @@ const speechToTextProviders = {
} }
} }
const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => { const SpeechToText = ({ dialogProps }) => {
const portalElement = document.getElementById('portal')
const dispatch = useDispatch() const dispatch = useDispatch()
useNotifier() useNotifier()
@@ -128,7 +111,6 @@ const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
}) })
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data })
} }
onCancel()
} catch (error) { } catch (error) {
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
enqueueSnackbar({ enqueueSnackbar({
@@ -199,150 +181,126 @@ const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
} }
}, [dialogProps]) }, [dialogProps])
useEffect(() => { return (
if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) <>
else dispatch({ type: HIDE_CANVAS_DIALOG }) <Box fullWidth sx={{ my: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
return () => dispatch({ type: HIDE_CANVAS_DIALOG }) <Typography>Speech To Text Providers</Typography>
}, [show, dispatch]) <FormControl fullWidth>
<Select value={selectedProvider} onChange={handleProviderChange}>
const component = ( <MenuItem value='none'>None</MenuItem>
<Dialog <MenuItem value='openAIWhisper'>OpenAI Whisper</MenuItem>
onClose={onCancel} <MenuItem value='assemblyAiTranscribe'>Assembly AI</MenuItem>
open={show} </Select>
fullWidth </FormControl>
maxWidth='sm' </Box>
aria-labelledby='alert-dialog-title' {selectedProvider !== 'none' && (
aria-describedby='alert-dialog-description' <>
> <ListItem style={{ padding: 0, margin: 0 }} alignItems='center'>
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'> <ListItemAvatar>
Speech To Text Configuration <div
</DialogTitle> style={{
<DialogContent> width: 50,
<Box fullWidth sx={{ my: 2, display: 'flex', flexDirection: 'column', gap: 1 }}> height: 50,
<Typography>Speech To Text Providers</Typography> borderRadius: '50%',
<FormControl fullWidth> backgroundColor: 'white'
<Select value={selectedProvider} onChange={handleProviderChange}> }}
<MenuItem value='none'>None</MenuItem> >
<MenuItem value='openAIWhisper'>OpenAI Whisper</MenuItem> <img
<MenuItem value='assemblyAiTranscribe'>Assembly AI</MenuItem>
</Select>
</FormControl>
</Box>
{selectedProvider !== 'none' && (
<>
<ListItem style={{ padding: 0, margin: 0 }} alignItems='center'>
<ListItemAvatar>
<div
style={{ style={{
width: 50, width: '100%',
height: 50, height: '100%',
borderRadius: '50%', padding: 10,
backgroundColor: 'white' objectFit: 'contain'
}} }}
> alt='AI'
<img src={speechToTextProviders[selectedProvider].icon}
style={{ />
width: '100%', </div>
height: '100%', </ListItemAvatar>
padding: 10, <ListItemText
objectFit: 'contain' sx={{ ml: 1 }}
}} primary={speechToTextProviders[selectedProvider].label}
alt='AI' secondary={
src={speechToTextProviders[selectedProvider].icon} <a target='_blank' rel='noreferrer' href={speechToTextProviders[selectedProvider].url}>
/> {speechToTextProviders[selectedProvider].url}
</div> </a>
</ListItemAvatar> }
<ListItemText />
sx={{ ml: 1 }} </ListItem>
primary={speechToTextProviders[selectedProvider].label} {speechToTextProviders[selectedProvider].inputs.map((inputParam, index) => (
secondary={ <Box key={index} sx={{ p: 2 }}>
<a target='_blank' rel='noreferrer' href={speechToTextProviders[selectedProvider].url}> <div style={{ display: 'flex', flexDirection: 'row' }}>
{speechToTextProviders[selectedProvider].url} <Typography>
</a> {inputParam.label}
} {!inputParam.optional && <span style={{ color: 'red' }}>&nbsp;*</span>}
/> {inputParam.description && (
</ListItem> <TooltipWithParser style={{ marginLeft: 10 }} title={inputParam.description} />
{speechToTextProviders[selectedProvider].inputs.map((inputParam, index) => ( )}
<Box key={index} sx={{ p: 2 }}> </Typography>
<div style={{ display: 'flex', flexDirection: 'row' }}> </div>
<Typography> {inputParam.type === 'credential' && (
{inputParam.label} <CredentialInputHandler
{!inputParam.optional && <span style={{ color: 'red' }}>&nbsp;*</span>} key={speechToText[selectedProvider]?.credentialId}
{inputParam.description && ( data={
<TooltipWithParser style={{ marginLeft: 10 }} title={inputParam.description} /> speechToText[selectedProvider]?.credentialId
)} ? { credential: speechToText[selectedProvider].credentialId }
</Typography> : {}
</div> }
{inputParam.type === 'credential' && ( inputParam={inputParam}
<CredentialInputHandler onSelect={(newValue) => setValue(newValue, selectedProvider, 'credentialId')}
key={speechToText[selectedProvider]?.credentialId} />
data={ )}
speechToText[selectedProvider]?.credentialId {inputParam.type === 'boolean' && (
? { credential: speechToText[selectedProvider].credentialId } <SwitchInput
: {} onChange={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
} value={
inputParam={inputParam} speechToText[selectedProvider]
onSelect={(newValue) => setValue(newValue, selectedProvider, 'credentialId')} ? speechToText[selectedProvider][inputParam.name]
/> : inputParam.default ?? false
)} }
{inputParam.type === 'boolean' && ( />
<SwitchInput )}
onChange={(newValue) => setValue(newValue, selectedProvider, inputParam.name)} {(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && (
value={ <Input
speechToText[selectedProvider] inputParam={inputParam}
? speechToText[selectedProvider][inputParam.name] onChange={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
: inputParam.default ?? false value={
} speechToText[selectedProvider]
/> ? speechToText[selectedProvider][inputParam.name]
)} : inputParam.default ?? ''
{(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && ( }
<Input />
inputParam={inputParam} )}
onChange={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
value={
speechToText[selectedProvider]
? speechToText[selectedProvider][inputParam.name]
: inputParam.default ?? ''
}
/>
)}
{inputParam.type === 'options' && ( {inputParam.type === 'options' && (
<Dropdown <Dropdown
name={inputParam.name} name={inputParam.name}
options={inputParam.options} options={inputParam.options}
onSelect={(newValue) => setValue(newValue, selectedProvider, inputParam.name)} onSelect={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
value={ value={
speechToText[selectedProvider] speechToText[selectedProvider]
? speechToText[selectedProvider][inputParam.name] ? speechToText[selectedProvider][inputParam.name]
: inputParam.default ?? 'choose an option' : inputParam.default ?? 'choose an option'
} }
/> />
)} )}
</Box> </Box>
))} ))}
</> </>
)} )}
</DialogContent> <StyledButton
<DialogActions> disabled={selectedProvider !== 'none' && !speechToText[selectedProvider]?.credentialId}
<StyledButton variant='contained'
disabled={selectedProvider !== 'none' && !speechToText[selectedProvider]?.credentialId} onClick={onSave}
variant='contained' >
onClick={onSave} Save
> </StyledButton>
Save </>
</StyledButton>
</DialogActions>
</Dialog>
) )
return createPortal(component, portalElement)
} }
SpeechToTextDialog.propTypes = { SpeechToText.propTypes = {
show: PropTypes.bool, dialogProps: PropTypes.object
dialogProps: PropTypes.object,
onCancel: PropTypes.func
} }
export default SpeechToTextDialog export default SpeechToText
+14 -14
View File
@@ -28,7 +28,7 @@ import useApi from 'hooks/useApi'
import { generateExportFlowData } from 'utils/genericHelper' import { generateExportFlowData } from 'utils/genericHelper'
import { uiBaseURL } from 'store/constant' import { uiBaseURL } from 'store/constant'
import { SET_CHATFLOW } from 'store/actions' import { SET_CHATFLOW } from 'store/actions'
import SpeechToTextDialog from '../../ui-component/dialog/SpeechToTextDialog' import ChatflowConfigurationDialog from 'ui-component/dialog/ChatflowConfigurationDialog'
// ==============================|| CANVAS HEADER ||============================== // // ==============================|| CANVAS HEADER ||============================== //
@@ -47,12 +47,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
const [apiDialogProps, setAPIDialogProps] = useState({}) const [apiDialogProps, setAPIDialogProps] = useState({})
const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false) const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false)
const [analyseDialogProps, setAnalyseDialogProps] = useState({}) const [analyseDialogProps, setAnalyseDialogProps] = useState({})
const [speechToAudioDialogOpen, setSpeechToAudioDialogOpen] = useState(false)
const [speechToAudioDialogProps, setSpeechToAudioialogProps] = useState({})
const [conversationStartersDialogOpen, setConversationStartersDialogOpen] = useState(false) const [conversationStartersDialogOpen, setConversationStartersDialogOpen] = useState(false)
const [conversationStartersDialogProps, setConversationStartersDialogProps] = useState({}) 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 [chatflowConfigurationDialogProps, setChatflowConfigurationDialogProps] = useState({})
const updateChatflowApi = useApi(chatflowsApi.updateChatflow) const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
const canvas = useSelector((state) => state.canvas) const canvas = useSelector((state) => state.canvas)
@@ -74,18 +74,18 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
chatflow: chatflow chatflow: chatflow
}) })
setAnalyseDialogOpen(true) setAnalyseDialogOpen(true)
} else if (setting === 'enableSpeechToText') {
setSpeechToAudioialogProps({
title: 'Speech to Text',
chatflow: chatflow
})
setSpeechToAudioDialogOpen(true)
} else if (setting === 'viewMessages') { } else if (setting === 'viewMessages') {
setViewMessagesDialogProps({ setViewMessagesDialogProps({
title: 'View Messages', title: 'View Messages',
chatflow: chatflow chatflow: chatflow
}) })
setViewMessagesDialogOpen(true) setViewMessagesDialogOpen(true)
} else if (setting === 'chatflowConfiguration') {
setChatflowConfigurationDialogProps({
title: 'Chatflow Configuration',
chatflow: chatflow
})
setChatflowConfigurationDialogOpen(true)
} else if (setting === 'duplicateChatflow') { } else if (setting === 'duplicateChatflow') {
try { try {
localStorage.setItem('duplicatedFlowData', chatflow.flowData) localStorage.setItem('duplicatedFlowData', chatflow.flowData)
@@ -394,11 +394,6 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
/> />
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} /> <APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
<AnalyseFlowDialog show={analyseDialogOpen} dialogProps={analyseDialogProps} onCancel={() => setAnalyseDialogOpen(false)} /> <AnalyseFlowDialog show={analyseDialogOpen} dialogProps={analyseDialogProps} onCancel={() => setAnalyseDialogOpen(false)} />
<SpeechToTextDialog
show={speechToAudioDialogOpen}
dialogProps={speechToAudioDialogProps}
onCancel={() => setSpeechToAudioDialogOpen(false)}
/>
<StarterPromptsDialog <StarterPromptsDialog
show={conversationStartersDialogOpen} show={conversationStartersDialogOpen}
dialogProps={conversationStartersDialogProps} dialogProps={conversationStartersDialogProps}
@@ -410,6 +405,11 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
dialogProps={viewMessagesDialogProps} dialogProps={viewMessagesDialogProps}
onCancel={() => setViewMessagesDialogOpen(false)} onCancel={() => setViewMessagesDialogOpen(false)}
/> />
<ChatflowConfigurationDialog
show={chatflowConfigurationDialogOpen}
dialogProps={chatflowConfigurationDialogProps}
onCancel={() => setChatflowConfigurationDialogOpen(false)}
/>
</> </>
) )
} }
@@ -23,7 +23,6 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import { Dropdown } from 'ui-component/dropdown/Dropdown' import { Dropdown } from 'ui-component/dropdown/Dropdown'
import ShareChatbot from './ShareChatbot' import ShareChatbot from './ShareChatbot'
import EmbedChat from './EmbedChat' import EmbedChat from './EmbedChat'
import Configuration from './Configuration'
// Const // Const
import { baseURL } from 'store/constant' import { baseURL } from 'store/constant'
@@ -84,7 +83,7 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
const navigate = useNavigate() const navigate = useNavigate()
const dispatch = useDispatch() const dispatch = useDispatch()
const codes = ['Embed', 'Python', 'JavaScript', 'cURL', 'Share Chatbot', 'Configuration'] const codes = ['Embed', 'Python', 'JavaScript', 'cURL', 'Share Chatbot']
const [value, setValue] = useState(0) const [value, setValue] = useState(0)
const [keyOptions, setKeyOptions] = useState([]) const [keyOptions, setKeyOptions] = useState([])
const [apiKeys, setAPIKeys] = useState([]) const [apiKeys, setAPIKeys] = useState([])
@@ -721,7 +720,6 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")`
{codeLang === 'Share Chatbot' && !chatflowApiKeyId && ( {codeLang === 'Share Chatbot' && !chatflowApiKeyId && (
<ShareChatbot isSessionMemory={dialogProps.isSessionMemory} /> <ShareChatbot isSessionMemory={dialogProps.isSessionMemory} />
)} )}
{codeLang === 'Configuration' && <Configuration />}
</TabPanel> </TabPanel>
))} ))}
</DialogContent> </DialogContent>
+1 -1
View File
@@ -65,7 +65,7 @@ const Settings = ({ chatflow, isSettingsOpen, anchorEl, onSettingsItemClick, onU
width: customization.isOpen.findIndex((id) => id === menu?.id) > -1 ? 8 : 6, width: customization.isOpen.findIndex((id) => id === menu?.id) > -1 ? 8 : 6,
height: customization.isOpen.findIndex((id) => id === menu?.id) > -1 ? 8 : 6 height: customization.isOpen.findIndex((id) => id === menu?.id) > -1 ? 8 : 6
}} }}
fontSize={level > 0 ? 'inherit' : 'medium'} fontSize={'inherit'}
/> />
) )
return ( return (