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,
IconMessage,
IconPictureInPictureOff,
IconMicrophone
IconAdjustmentsHorizontal
} from '@tabler/icons'
// constant
const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage, IconPictureInPictureOff, IconMicrophone }
const icons = {
IconTrash,
IconFileUpload,
IconFileExport,
IconCopy,
IconSearch,
IconMessage,
IconPictureInPictureOff,
IconAdjustmentsHorizontal
}
// ==============================|| SETTINGS MENU ITEMS ||============================== //
@@ -35,11 +44,11 @@ const settings = {
icon: icons.IconMessage
},
{
id: 'enableSpeechToText',
title: 'Speech to Text',
id: 'chatflowConfiguration',
title: 'Configuration',
type: 'item',
url: '',
icon: icons.IconMicrophone
icon: icons.IconAdjustmentsHorizontal
},
{
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 { 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,
FormControl,
ListItem,
ListItemAvatar,
ListItemText,
MenuItem,
Select
} from '@mui/material'
import { Typography, Box, Button, FormControl, ListItem, ListItemAvatar, ListItemText, MenuItem, Select } from '@mui/material'
import { IconX } from '@tabler/icons'
// Project import
@@ -33,7 +18,6 @@ import openAISVG from 'assets/images/openai.svg'
import assemblyAIPng from 'assets/images/assemblyai.png'
// store
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
import useNotifier from 'utils/useNotifier'
// API
@@ -95,8 +79,7 @@ const speechToTextProviders = {
}
}
const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
const portalElement = document.getElementById('portal')
const SpeechToText = ({ dialogProps }) => {
const dispatch = useDispatch()
useNotifier()
@@ -128,7 +111,6 @@ const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
})
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data })
}
onCancel()
} catch (error) {
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
enqueueSnackbar({
@@ -199,150 +181,126 @@ const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
}
}, [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 = (
<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'>
Speech To Text Configuration
</DialogTitle>
<DialogContent>
<Box fullWidth sx={{ my: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
<Typography>Speech To Text Providers</Typography>
<FormControl fullWidth>
<Select value={selectedProvider} onChange={handleProviderChange}>
<MenuItem value='none'>None</MenuItem>
<MenuItem value='openAIWhisper'>OpenAI Whisper</MenuItem>
<MenuItem value='assemblyAiTranscribe'>Assembly AI</MenuItem>
</Select>
</FormControl>
</Box>
{selectedProvider !== 'none' && (
<>
<ListItem style={{ padding: 0, margin: 0 }} alignItems='center'>
<ListItemAvatar>
<div
return (
<>
<Box fullWidth sx={{ my: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
<Typography>Speech To Text Providers</Typography>
<FormControl fullWidth>
<Select value={selectedProvider} onChange={handleProviderChange}>
<MenuItem value='none'>None</MenuItem>
<MenuItem value='openAIWhisper'>OpenAI Whisper</MenuItem>
<MenuItem value='assemblyAiTranscribe'>Assembly AI</MenuItem>
</Select>
</FormControl>
</Box>
{selectedProvider !== 'none' && (
<>
<ListItem style={{ padding: 0, margin: 0 }} alignItems='center'>
<ListItemAvatar>
<div
style={{
width: 50,
height: 50,
borderRadius: '50%',
backgroundColor: 'white'
}}
>
<img
style={{
width: 50,
height: 50,
borderRadius: '50%',
backgroundColor: 'white'
width: '100%',
height: '100%',
padding: 10,
objectFit: 'contain'
}}
>
<img
style={{
width: '100%',
height: '100%',
padding: 10,
objectFit: 'contain'
}}
alt='AI'
src={speechToTextProviders[selectedProvider].icon}
/>
</div>
</ListItemAvatar>
<ListItemText
sx={{ ml: 1 }}
primary={speechToTextProviders[selectedProvider].label}
secondary={
<a target='_blank' rel='noreferrer' href={speechToTextProviders[selectedProvider].url}>
{speechToTextProviders[selectedProvider].url}
</a>
}
/>
</ListItem>
{speechToTextProviders[selectedProvider].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' }}>&nbsp;*</span>}
{inputParam.description && (
<TooltipWithParser style={{ marginLeft: 10 }} title={inputParam.description} />
)}
</Typography>
</div>
{inputParam.type === 'credential' && (
<CredentialInputHandler
key={speechToText[selectedProvider]?.credentialId}
data={
speechToText[selectedProvider]?.credentialId
? { credential: speechToText[selectedProvider].credentialId }
: {}
}
inputParam={inputParam}
onSelect={(newValue) => setValue(newValue, selectedProvider, 'credentialId')}
/>
)}
{inputParam.type === 'boolean' && (
<SwitchInput
onChange={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
value={
speechToText[selectedProvider]
? speechToText[selectedProvider][inputParam.name]
: inputParam.default ?? false
}
/>
)}
{(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 ?? ''
}
/>
)}
alt='AI'
src={speechToTextProviders[selectedProvider].icon}
/>
</div>
</ListItemAvatar>
<ListItemText
sx={{ ml: 1 }}
primary={speechToTextProviders[selectedProvider].label}
secondary={
<a target='_blank' rel='noreferrer' href={speechToTextProviders[selectedProvider].url}>
{speechToTextProviders[selectedProvider].url}
</a>
}
/>
</ListItem>
{speechToTextProviders[selectedProvider].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' }}>&nbsp;*</span>}
{inputParam.description && (
<TooltipWithParser style={{ marginLeft: 10 }} title={inputParam.description} />
)}
</Typography>
</div>
{inputParam.type === 'credential' && (
<CredentialInputHandler
key={speechToText[selectedProvider]?.credentialId}
data={
speechToText[selectedProvider]?.credentialId
? { credential: speechToText[selectedProvider].credentialId }
: {}
}
inputParam={inputParam}
onSelect={(newValue) => setValue(newValue, selectedProvider, 'credentialId')}
/>
)}
{inputParam.type === 'boolean' && (
<SwitchInput
onChange={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
value={
speechToText[selectedProvider]
? speechToText[selectedProvider][inputParam.name]
: inputParam.default ?? false
}
/>
)}
{(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' && (
<Dropdown
name={inputParam.name}
options={inputParam.options}
onSelect={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
value={
speechToText[selectedProvider]
? speechToText[selectedProvider][inputParam.name]
: inputParam.default ?? 'choose an option'
}
/>
)}
</Box>
))}
</>
)}
</DialogContent>
<DialogActions>
<StyledButton
disabled={selectedProvider !== 'none' && !speechToText[selectedProvider]?.credentialId}
variant='contained'
onClick={onSave}
>
Save
</StyledButton>
</DialogActions>
</Dialog>
{inputParam.type === 'options' && (
<Dropdown
name={inputParam.name}
options={inputParam.options}
onSelect={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
value={
speechToText[selectedProvider]
? speechToText[selectedProvider][inputParam.name]
: inputParam.default ?? 'choose an option'
}
/>
)}
</Box>
))}
</>
)}
<StyledButton
disabled={selectedProvider !== 'none' && !speechToText[selectedProvider]?.credentialId}
variant='contained'
onClick={onSave}
>
Save
</StyledButton>
</>
)
return createPortal(component, portalElement)
}
SpeechToTextDialog.propTypes = {
show: PropTypes.bool,
dialogProps: PropTypes.object,
onCancel: PropTypes.func
SpeechToText.propTypes = {
dialogProps: PropTypes.object
}
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 { uiBaseURL } from 'store/constant'
import { SET_CHATFLOW } from 'store/actions'
import SpeechToTextDialog from '../../ui-component/dialog/SpeechToTextDialog'
import ChatflowConfigurationDialog from 'ui-component/dialog/ChatflowConfigurationDialog'
// ==============================|| CANVAS HEADER ||============================== //
@@ -47,12 +47,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
const [apiDialogProps, setAPIDialogProps] = useState({})
const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false)
const [analyseDialogProps, setAnalyseDialogProps] = useState({})
const [speechToAudioDialogOpen, setSpeechToAudioDialogOpen] = useState(false)
const [speechToAudioDialogProps, setSpeechToAudioialogProps] = useState({})
const [conversationStartersDialogOpen, setConversationStartersDialogOpen] = useState(false)
const [conversationStartersDialogProps, setConversationStartersDialogProps] = useState({})
const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false)
const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({})
const [chatflowConfigurationDialogOpen, setChatflowConfigurationDialogOpen] = useState(false)
const [chatflowConfigurationDialogProps, setChatflowConfigurationDialogProps] = useState({})
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
const canvas = useSelector((state) => state.canvas)
@@ -74,18 +74,18 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
chatflow: chatflow
})
setAnalyseDialogOpen(true)
} else if (setting === 'enableSpeechToText') {
setSpeechToAudioialogProps({
title: 'Speech to Text',
chatflow: chatflow
})
setSpeechToAudioDialogOpen(true)
} else if (setting === 'viewMessages') {
setViewMessagesDialogProps({
title: 'View Messages',
chatflow: chatflow
})
setViewMessagesDialogOpen(true)
} else if (setting === 'chatflowConfiguration') {
setChatflowConfigurationDialogProps({
title: 'Chatflow Configuration',
chatflow: chatflow
})
setChatflowConfigurationDialogOpen(true)
} else if (setting === 'duplicateChatflow') {
try {
localStorage.setItem('duplicatedFlowData', chatflow.flowData)
@@ -394,11 +394,6 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
/>
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
<AnalyseFlowDialog show={analyseDialogOpen} dialogProps={analyseDialogProps} onCancel={() => setAnalyseDialogOpen(false)} />
<SpeechToTextDialog
show={speechToAudioDialogOpen}
dialogProps={speechToAudioDialogProps}
onCancel={() => setSpeechToAudioDialogOpen(false)}
/>
<StarterPromptsDialog
show={conversationStartersDialogOpen}
dialogProps={conversationStartersDialogProps}
@@ -410,6 +405,11 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
dialogProps={viewMessagesDialogProps}
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 ShareChatbot from './ShareChatbot'
import EmbedChat from './EmbedChat'
import Configuration from './Configuration'
// Const
import { baseURL } from 'store/constant'
@@ -84,7 +83,7 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
const navigate = useNavigate()
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 [keyOptions, setKeyOptions] = useState([])
const [apiKeys, setAPIKeys] = useState([])
@@ -721,7 +720,6 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")`
{codeLang === 'Share Chatbot' && !chatflowApiKeyId && (
<ShareChatbot isSessionMemory={dialogProps.isSessionMemory} />
)}
{codeLang === 'Configuration' && <Configuration />}
</TabPanel>
))}
</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,
height: customization.isOpen.findIndex((id) => id === menu?.id) > -1 ? 8 : 6
}}
fontSize={level > 0 ? 'inherit' : 'medium'}
fontSize={'inherit'}
/>
)
return (