mirror of https://github.com/FlowiseAI/Flowise.git
Merge pull request #1873 from 0xi4o/feature/chatflow-configuration
Feature: Add configuration dialog for chatflowpull/1946/head
commit
0ebfa68b93
|
|
@ -24,7 +24,6 @@ import {
|
|||
IChatMessageFeedback,
|
||||
IDepthQueue,
|
||||
INodeDirectedGraph,
|
||||
ChatMessageRatingType,
|
||||
IUploadFileSizeAndTypes
|
||||
} from './Interface'
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,5 @@
|
|||
// assets
|
||||
import {
|
||||
IconTrash,
|
||||
IconFileUpload,
|
||||
IconFileExport,
|
||||
IconCopy,
|
||||
IconSearch,
|
||||
IconMessage,
|
||||
IconPictureInPictureOff,
|
||||
IconLink,
|
||||
IconMicrophone,
|
||||
IconThumbUp
|
||||
} from '@tabler/icons'
|
||||
import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconMessage, IconAdjustmentsHorizontal } from '@tabler/icons'
|
||||
|
||||
// constant
|
||||
const icons = {
|
||||
|
|
@ -18,12 +7,8 @@ const icons = {
|
|||
IconFileUpload,
|
||||
IconFileExport,
|
||||
IconCopy,
|
||||
IconSearch,
|
||||
IconMessage,
|
||||
IconPictureInPictureOff,
|
||||
IconLink,
|
||||
IconMicrophone,
|
||||
IconThumbUp
|
||||
IconAdjustmentsHorizontal
|
||||
}
|
||||
|
||||
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
||||
|
|
@ -33,13 +18,6 @@ const settings = {
|
|||
title: '',
|
||||
type: 'group',
|
||||
children: [
|
||||
{
|
||||
id: 'conversationStarters',
|
||||
title: 'Starter Prompts',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconPictureInPictureOff
|
||||
},
|
||||
{
|
||||
id: 'viewMessages',
|
||||
title: 'View Messages',
|
||||
|
|
@ -48,25 +26,11 @@ const settings = {
|
|||
icon: icons.IconMessage
|
||||
},
|
||||
{
|
||||
id: 'chatFeedback',
|
||||
title: 'Chat Feedback',
|
||||
id: 'chatflowConfiguration',
|
||||
title: 'Configuration',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconThumbUp
|
||||
},
|
||||
{
|
||||
id: 'allowedDomains',
|
||||
title: 'Allowed Domains',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconLink
|
||||
},
|
||||
{
|
||||
id: 'enableSpeechToText',
|
||||
title: 'Speech to Text',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconMicrophone
|
||||
icon: icons.IconAdjustmentsHorizontal
|
||||
},
|
||||
{
|
||||
id: 'duplicateChatflow',
|
||||
|
|
@ -89,13 +53,6 @@ const settings = {
|
|||
url: '',
|
||||
icon: icons.IconFileExport
|
||||
},
|
||||
{
|
||||
id: 'analyseChatflow',
|
||||
title: 'Analyse Chatflow',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconSearch
|
||||
},
|
||||
{
|
||||
id: 'deleteChatflow',
|
||||
title: 'Delete Chatflow',
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ import FileDownloadIcon from '@mui/icons-material/Downloading'
|
|||
import FileDeleteIcon from '@mui/icons-material/Delete'
|
||||
import FileCategoryIcon from '@mui/icons-material/Category'
|
||||
import PictureInPictureAltIcon from '@mui/icons-material/PictureInPictureAlt'
|
||||
import ThumbsUpDownOutlinedIcon from '@mui/icons-material/ThumbsUpDownOutlined'
|
||||
import VpnLockOutlinedIcon from '@mui/icons-material/VpnLockOutlined'
|
||||
import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined'
|
||||
import Button from '@mui/material/Button'
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
|
||||
import { IconX } from '@tabler/icons'
|
||||
|
|
@ -29,6 +32,9 @@ import StarterPromptsDialog from '@/ui-component/dialog/StarterPromptsDialog'
|
|||
|
||||
import { generateExportFlowData } from '@/utils/genericHelper'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
import ChatFeedbackDialog from '../dialog/ChatFeedbackDialog'
|
||||
import AllowedDomainsDialog from '../dialog/AllowedDomainsDialog'
|
||||
import SpeechToTextDialog from '../dialog/SpeechToTextDialog'
|
||||
|
||||
const StyledMenu = styled((props) => (
|
||||
<Menu
|
||||
|
|
@ -82,6 +88,12 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||
const open = Boolean(anchorEl)
|
||||
const [conversationStartersDialogOpen, setConversationStartersDialogOpen] = useState(false)
|
||||
const [conversationStartersDialogProps, setConversationStartersDialogProps] = useState({})
|
||||
const [chatFeedbackDialogOpen, setChatFeedbackDialogOpen] = useState(false)
|
||||
const [chatFeedbackDialogProps, setChatFeedbackDialogProps] = useState({})
|
||||
const [allowedDomainsDialogOpen, setAllowedDomainsDialogOpen] = useState(false)
|
||||
const [allowedDomainsDialogProps, setAllowedDomainsDialogProps] = useState({})
|
||||
const [speechToTextDialogOpen, setSpeechToTextDialogOpen] = useState(false)
|
||||
const [speechToTextDialogProps, setSpeechToTextDialogProps] = useState({})
|
||||
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget)
|
||||
|
|
@ -105,9 +117,31 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||
setConversationStartersDialogOpen(true)
|
||||
}
|
||||
|
||||
const saveFlowStarterPrompts = async () => {
|
||||
setConversationStartersDialogOpen(false)
|
||||
await updateFlowsApi.request()
|
||||
const handleFlowChatFeedback = () => {
|
||||
setAnchorEl(null)
|
||||
setChatFeedbackDialogProps({
|
||||
title: 'Chat Feedback - ' + chatflow.name,
|
||||
chatflow: chatflow
|
||||
})
|
||||
setChatFeedbackDialogOpen(true)
|
||||
}
|
||||
|
||||
const handleAllowedDomains = () => {
|
||||
setAnchorEl(null)
|
||||
setAllowedDomainsDialogProps({
|
||||
title: 'Allowed Domains - ' + chatflow.name,
|
||||
chatflow: chatflow
|
||||
})
|
||||
setAllowedDomainsDialogOpen(true)
|
||||
}
|
||||
|
||||
const handleSpeechToText = () => {
|
||||
setAnchorEl(null)
|
||||
setSpeechToTextDialogProps({
|
||||
title: 'Speech To Text - ' + chatflow.name,
|
||||
chatflow: chatflow
|
||||
})
|
||||
setSpeechToTextDialogOpen(true)
|
||||
}
|
||||
|
||||
const saveFlowRename = async (chatflowName) => {
|
||||
|
|
@ -275,6 +309,18 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||
<PictureInPictureAltIcon />
|
||||
Starter Prompts
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleFlowChatFeedback} disableRipple>
|
||||
<ThumbsUpDownOutlinedIcon />
|
||||
Chat Feedback
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleAllowedDomains} disableRipple>
|
||||
<VpnLockOutlinedIcon />
|
||||
Allowed Domains
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleSpeechToText} disableRipple>
|
||||
<MicNoneOutlinedIcon />
|
||||
Speech To Text
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleFlowCategory} disableRipple>
|
||||
<FileCategoryIcon />
|
||||
Update Category
|
||||
|
|
@ -304,9 +350,23 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||
<StarterPromptsDialog
|
||||
show={conversationStartersDialogOpen}
|
||||
dialogProps={conversationStartersDialogProps}
|
||||
onConfirm={saveFlowStarterPrompts}
|
||||
onCancel={() => setConversationStartersDialogOpen(false)}
|
||||
/>
|
||||
<ChatFeedbackDialog
|
||||
show={chatFeedbackDialogOpen}
|
||||
dialogProps={chatFeedbackDialogProps}
|
||||
onCancel={() => setChatFeedbackDialogOpen(false)}
|
||||
/>
|
||||
<AllowedDomainsDialog
|
||||
show={allowedDomainsDialogOpen}
|
||||
dialogProps={allowedDomainsDialogProps}
|
||||
onCancel={() => setAllowedDomainsDialogOpen(false)}
|
||||
/>
|
||||
<SpeechToTextDialog
|
||||
show={speechToTextDialogOpen}
|
||||
dialogProps={speechToTextDialogProps}
|
||||
onCancel={() => setSpeechToTextDialogOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,130 +1,24 @@
|
|||
import { createPortal } from 'react-dom'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
// material-ui
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
OutlinedInput,
|
||||
DialogTitle,
|
||||
DialogActions,
|
||||
Box,
|
||||
List,
|
||||
InputAdornment
|
||||
} from '@mui/material'
|
||||
import { IconX, IconTrash, IconPlus } from '@tabler/icons'
|
||||
|
||||
// Project import
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@mui/material'
|
||||
|
||||
// store
|
||||
import {
|
||||
enqueueSnackbar as enqueueSnackbarAction,
|
||||
closeSnackbar as closeSnackbarAction,
|
||||
SET_CHATFLOW,
|
||||
HIDE_CANVAS_DIALOG,
|
||||
SHOW_CANVAS_DIALOG
|
||||
} from '@/store/actions'
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
|
||||
// API
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
// Project imports
|
||||
import AllowedDomains from '@/ui-component/extended/AllowedDomains'
|
||||
|
||||
const AllowedDomainsDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
const AllowedDomainsDialog = ({ 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 [inputFields, setInputFields] = useState([''])
|
||||
|
||||
const [chatbotConfig, setChatbotConfig] = useState({})
|
||||
|
||||
const addInputField = () => {
|
||||
setInputFields([...inputFields, ''])
|
||||
}
|
||||
const removeInputFields = (index) => {
|
||||
const rows = [...inputFields]
|
||||
rows.splice(index, 1)
|
||||
setInputFields(rows)
|
||||
}
|
||||
|
||||
const handleChange = (index, evnt) => {
|
||||
const { value } = evnt.target
|
||||
const list = [...inputFields]
|
||||
list[index] = value
|
||||
setInputFields(list)
|
||||
}
|
||||
|
||||
const onSave = async () => {
|
||||
try {
|
||||
let value = {
|
||||
allowedOrigins: [...inputFields]
|
||||
}
|
||||
chatbotConfig.allowedOrigins = value.allowedOrigins
|
||||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||
chatbotConfig: JSON.stringify(chatbotConfig)
|
||||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Allowed Origins 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 Allowed Origins: ${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.allowedOrigins) {
|
||||
let inputFields = [...chatbotConfig.allowedOrigins]
|
||||
setInputFields(inputFields)
|
||||
} else {
|
||||
setInputFields([''])
|
||||
}
|
||||
} catch (e) {
|
||||
setInputFields([''])
|
||||
}
|
||||
}
|
||||
|
||||
return () => {}
|
||||
}, [dialogProps])
|
||||
|
||||
useEffect(() => {
|
||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
|
|
@ -141,69 +35,11 @@ const AllowedDomainsDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
{dialogProps.title || 'Allowed Origins'}
|
||||
{dialogProps.title || 'Allowed Domains'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<span>Your chatbot will only work when used from the following domains.</span>
|
||||
</div>
|
||||
<Box sx={{ '& > :not(style)': { m: 1 }, pt: 2 }}>
|
||||
<List>
|
||||
{inputFields.map((origin, index) => {
|
||||
return (
|
||||
<div key={index} style={{ display: 'flex', width: '100%' }}>
|
||||
<Box sx={{ width: '100%', mb: 1 }}>
|
||||
<OutlinedInput
|
||||
sx={{ width: '100%' }}
|
||||
key={index}
|
||||
type='text'
|
||||
onChange={(e) => handleChange(index, e)}
|
||||
size='small'
|
||||
value={origin}
|
||||
name='origin'
|
||||
placeholder='https://example.com'
|
||||
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>
|
||||
<AllowedDomains dialogProps={dialogProps} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel}>Cancel</Button>
|
||||
<StyledButton variant='contained' onClick={onSave}>
|
||||
Save
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,102 +1,24 @@
|
|||
import { createPortal } from 'react-dom'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
// material-ui
|
||||
import { Button, Dialog, DialogContent, DialogTitle, DialogActions, Box } from '@mui/material'
|
||||
import { IconX } from '@tabler/icons'
|
||||
|
||||
// Project import
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import { SwitchInput } from '@/ui-component/switch/Switch'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@mui/material'
|
||||
|
||||
// store
|
||||
import {
|
||||
enqueueSnackbar as enqueueSnackbarAction,
|
||||
closeSnackbar as closeSnackbarAction,
|
||||
SET_CHATFLOW,
|
||||
HIDE_CANVAS_DIALOG,
|
||||
SHOW_CANVAS_DIALOG
|
||||
} from '@/store/actions'
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
|
||||
// API
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
// Project imports
|
||||
import ChatFeedback from '@/ui-component/extended/ChatFeedback'
|
||||
|
||||
const ChatFeedbackDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
const ChatFeedbackDialog = ({ 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 [chatFeedbackStatus, setChatFeedbackStatus] = useState(false)
|
||||
const [chatbotConfig, setChatbotConfig] = useState({})
|
||||
|
||||
const handleChange = (value) => {
|
||||
setChatFeedbackStatus(value)
|
||||
}
|
||||
|
||||
const onSave = async () => {
|
||||
try {
|
||||
let value = {
|
||||
chatFeedback: {
|
||||
status: chatFeedbackStatus
|
||||
}
|
||||
}
|
||||
chatbotConfig.chatFeedback = value.chatFeedback
|
||||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||
chatbotConfig: JSON.stringify(chatbotConfig)
|
||||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Chat Feedback Settings 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 Chat Feedback Settings: ${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) {
|
||||
let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig)
|
||||
setChatbotConfig(chatbotConfig || {})
|
||||
if (chatbotConfig.chatFeedback) {
|
||||
setChatFeedbackStatus(chatbotConfig.chatFeedback.status)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {}
|
||||
}, [dialogProps])
|
||||
|
||||
useEffect(() => {
|
||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
|
|
@ -113,19 +35,11 @@ const ChatFeedbackDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
{dialogProps.title || 'Chat Feedback'}
|
||||
{dialogProps.title || 'Allowed Domains'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<SwitchInput label='Enable chat feedback' onChange={handleChange} value={chatFeedbackStatus} />
|
||||
</Box>
|
||||
<ChatFeedback dialogProps={dialogProps} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel}>Cancel</Button>
|
||||
<StyledButton variant='contained' onClick={onSave}>
|
||||
Save
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
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 '@/ui-component/extended/SpeechToText'
|
||||
import RateLimit from '@/ui-component/extended/RateLimit'
|
||||
import AllowedDomains from '@/ui-component/extended/AllowedDomains'
|
||||
import ChatFeedback from '@/ui-component/extended/ChatFeedback'
|
||||
import AnalyseFlow from '@/ui-component/extended/AnalyseFlow'
|
||||
import StarterPrompts from '@/ui-component/extended/StarterPrompts'
|
||||
|
||||
const CHATFLOW_CONFIGURATION_TABS = [
|
||||
{
|
||||
label: 'Rate Limiting',
|
||||
id: 'rateLimiting'
|
||||
},
|
||||
{
|
||||
label: 'Starter Prompts',
|
||||
id: 'conversationStarters'
|
||||
},
|
||||
{
|
||||
label: 'Speech to Text',
|
||||
id: 'speechToText'
|
||||
},
|
||||
{
|
||||
label: 'Chat Feedback',
|
||||
id: 'chatFeedback'
|
||||
},
|
||||
{
|
||||
label: 'Allowed Domains',
|
||||
id: 'allowedDomains'
|
||||
},
|
||||
{
|
||||
label: 'Analyse Chatflow',
|
||||
id: 'analyseChatflow'
|
||||
}
|
||||
]
|
||||
|
||||
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}`}
|
||||
style={{ width: '100%', paddingTop: '1rem' }}
|
||||
{...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: '40px', height: '40px' }}
|
||||
value={tabValue}
|
||||
onChange={(event, value) => setTabValue(value)}
|
||||
aria-label='tabs'
|
||||
>
|
||||
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
||||
<Tab
|
||||
sx={{ minHeight: '40px', height: '40px', textAlign: 'left', display: 'flex', alignItems: 'start', mb: 1 }}
|
||||
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' && <RateLimit />}
|
||||
{item.id === 'conversationStarters' ? <StarterPrompts dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'speechToText' ? <SpeechToText dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'chatFeedback' ? <ChatFeedback dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'allowedDomains' ? <AllowedDomains dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'analyseChatflow' ? <AnalyseFlow 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,99 +1,17 @@
|
|||
import { createPortal } from 'react-dom'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { 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 { 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 { Dropdown } from '@/ui-component/dropdown/Dropdown'
|
||||
import openAISVG from '@/assets/images/openai.svg'
|
||||
import assemblyAIPng from '@/assets/images/assemblyai.png'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@mui/material'
|
||||
|
||||
// store
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
|
||||
// API
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
|
||||
const speechToTextProviders = {
|
||||
openAIWhisper: {
|
||||
label: 'OpenAI Whisper',
|
||||
name: 'openAIWhisper',
|
||||
icon: openAISVG,
|
||||
url: 'https://platform.openai.com/docs/guides/speech-to-text',
|
||||
inputs: [
|
||||
{
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['openAIApi']
|
||||
},
|
||||
{
|
||||
label: 'Language',
|
||||
name: 'language',
|
||||
type: 'string',
|
||||
description:
|
||||
'The language of the input audio. Supplying the input language in ISO-639-1 format will improve accuracy and latency.',
|
||||
placeholder: 'en',
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Prompt',
|
||||
name: 'prompt',
|
||||
type: 'string',
|
||||
rows: 4,
|
||||
description: `An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language.`,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Temperature',
|
||||
name: 'temperature',
|
||||
type: 'number',
|
||||
step: 0.1,
|
||||
description: `The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.`,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
},
|
||||
assemblyAiTranscribe: {
|
||||
label: 'Assembly AI',
|
||||
name: 'assemblyAiTranscribe',
|
||||
icon: assemblyAIPng,
|
||||
url: 'https://www.assemblyai.com/',
|
||||
inputs: [
|
||||
{
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['assemblyAIApi']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
// Project imports
|
||||
import SpeechToText from '@/ui-component/extended/SpeechToText'
|
||||
|
||||
const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
|
|
@ -101,111 +19,13 @@ const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
|
|||
|
||||
useNotifier()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [speechToText, setSpeechToText] = useState({})
|
||||
const [selectedProvider, setSelectedProvider] = useState('none')
|
||||
|
||||
const onSave = async () => {
|
||||
const speechToText = setValue(true, selectedProvider, 'status')
|
||||
try {
|
||||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||
speechToText: JSON.stringify(speechToText)
|
||||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Speech To Text 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 Speech To Text 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(speechToText, providerName)) {
|
||||
newVal = { ...speechToText, [providerName]: {} }
|
||||
} else {
|
||||
newVal = { ...speechToText }
|
||||
}
|
||||
|
||||
newVal[providerName][inputParamName] = value
|
||||
if (inputParamName === 'status' && value === true) {
|
||||
// ensure that the others are turned off
|
||||
Object.keys(speechToTextProviders).forEach((key) => {
|
||||
const provider = speechToTextProviders[key]
|
||||
if (provider.name !== providerName) {
|
||||
newVal[provider.name] = { ...speechToText[provider.name], status: false }
|
||||
}
|
||||
})
|
||||
}
|
||||
setSpeechToText(newVal)
|
||||
return newVal
|
||||
}
|
||||
|
||||
const handleProviderChange = (event) => {
|
||||
setSelectedProvider(event.target.value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.chatflow && dialogProps.chatflow.speechToText) {
|
||||
try {
|
||||
const speechToText = JSON.parse(dialogProps.chatflow.speechToText)
|
||||
let selectedProvider = 'none'
|
||||
Object.keys(speechToTextProviders).forEach((key) => {
|
||||
const providerConfig = speechToText[key]
|
||||
if (providerConfig && providerConfig.status) {
|
||||
selectedProvider = key
|
||||
}
|
||||
})
|
||||
setSelectedProvider(selectedProvider)
|
||||
setSpeechToText(speechToText)
|
||||
} catch (e) {
|
||||
setSpeechToText({})
|
||||
setSelectedProvider('none')
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
setSpeechToText({})
|
||||
setSelectedProvider('none')
|
||||
}
|
||||
}, [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 = (
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
onClose={onCancel}
|
||||
open={show}
|
||||
|
|
@ -215,126 +35,13 @@ const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
|
|||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
Speech To Text Configuration
|
||||
{dialogProps.title || 'Allowed Domains'}
|
||||
</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
|
||||
style={{
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<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' }}> *</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>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<SpeechToText dialogProps={dialogProps} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<StyledButton
|
||||
disabled={selectedProvider !== 'none' && !speechToText[selectedProvider]?.credentialId}
|
||||
variant='contained'
|
||||
onClick={onSave}
|
||||
>
|
||||
Save
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
|
@ -342,7 +49,8 @@ const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
|
|||
SpeechToTextDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func
|
||||
onCancel: PropTypes.func,
|
||||
onConfirm: PropTypes.func
|
||||
}
|
||||
|
||||
export default SpeechToTextDialog
|
||||
|
|
|
|||
|
|
@ -1,149 +1,24 @@
|
|||
import { createPortal } from 'react-dom'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { 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,
|
||||
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'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@mui/material'
|
||||
|
||||
// store
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
|
||||
// API
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
// Project imports
|
||||
import StarterPrompts from '@/ui-component/extended/StarterPrompts'
|
||||
|
||||
const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
const StarterPromptsDialog = ({ 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 [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(() => {
|
||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
|
|
@ -163,79 +38,8 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
{dialogProps.title || 'Conversation Starter Prompts'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<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>
|
||||
<StarterPrompts dialogProps={dialogProps} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel}>Cancel</Button>
|
||||
<StyledButton variant='contained' onClick={onSave}>
|
||||
Save
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
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 } 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 AllowedDomains = ({ dialogProps }) => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useNotifier()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [inputFields, setInputFields] = useState([''])
|
||||
|
||||
const [chatbotConfig, setChatbotConfig] = useState({})
|
||||
|
||||
const addInputField = () => {
|
||||
setInputFields([...inputFields, ''])
|
||||
}
|
||||
const removeInputFields = (index) => {
|
||||
const rows = [...inputFields]
|
||||
rows.splice(index, 1)
|
||||
setInputFields(rows)
|
||||
}
|
||||
|
||||
const handleChange = (index, evnt) => {
|
||||
const { value } = evnt.target
|
||||
const list = [...inputFields]
|
||||
list[index] = value
|
||||
setInputFields(list)
|
||||
}
|
||||
|
||||
const onSave = async () => {
|
||||
try {
|
||||
let value = {
|
||||
allowedOrigins: [...inputFields]
|
||||
}
|
||||
chatbotConfig.allowedOrigins = value.allowedOrigins
|
||||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||
chatbotConfig: JSON.stringify(chatbotConfig)
|
||||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Allowed Origins 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 })
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save Allowed Origins: ${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.allowedOrigins) {
|
||||
let inputFields = [...chatbotConfig.allowedOrigins]
|
||||
setInputFields(inputFields)
|
||||
} else {
|
||||
setInputFields([''])
|
||||
}
|
||||
} catch (e) {
|
||||
setInputFields([''])
|
||||
}
|
||||
}
|
||||
|
||||
return () => {}
|
||||
}, [dialogProps])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<span>Your chatbot will only work when used from the following domains.</span>
|
||||
</div>
|
||||
<Box sx={{ '& > :not(style)': { m: 1 }, pt: 2 }}>
|
||||
<List>
|
||||
{inputFields.map((origin, index) => {
|
||||
return (
|
||||
<div key={index} style={{ display: 'flex', width: '100%' }}>
|
||||
<Box sx={{ width: '100%', mb: 1 }}>
|
||||
<OutlinedInput
|
||||
sx={{ width: '100%' }}
|
||||
key={index}
|
||||
type='text'
|
||||
onChange={(e) => handleChange(index, e)}
|
||||
size='small'
|
||||
value={origin}
|
||||
name='origin'
|
||||
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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
AllowedDomains.propTypes = {
|
||||
dialogProps: PropTypes.object
|
||||
}
|
||||
|
||||
export default AllowedDomains
|
||||
|
|
@ -0,0 +1,324 @@
|
|||
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 }) => {
|
||||
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 })
|
||||
}
|
||||
} 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
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
import { useDispatch } from 'react-redux'
|
||||
import { useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
// material-ui
|
||||
import { Button, Box } from '@mui/material'
|
||||
import { IconX } from '@tabler/icons'
|
||||
|
||||
// Project import
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import { SwitchInput } from '@/ui-component/switch/Switch'
|
||||
|
||||
// store
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
|
||||
// API
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
|
||||
const ChatFeedback = ({ dialogProps }) => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useNotifier()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [chatFeedbackStatus, setChatFeedbackStatus] = useState(false)
|
||||
const [chatbotConfig, setChatbotConfig] = useState({})
|
||||
|
||||
const handleChange = (value) => {
|
||||
setChatFeedbackStatus(value)
|
||||
}
|
||||
|
||||
const onSave = async () => {
|
||||
try {
|
||||
let value = {
|
||||
chatFeedback: {
|
||||
status: chatFeedbackStatus
|
||||
}
|
||||
}
|
||||
chatbotConfig.chatFeedback = value.chatFeedback
|
||||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||
chatbotConfig: JSON.stringify(chatbotConfig)
|
||||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Chat Feedback Settings 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 })
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save Chat Feedback Settings: ${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) {
|
||||
let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig)
|
||||
setChatbotConfig(chatbotConfig || {})
|
||||
if (chatbotConfig.chatFeedback) {
|
||||
setChatFeedbackStatus(chatbotConfig.chatFeedback.status)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {}
|
||||
}, [dialogProps])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||
<SwitchInput label='Enable chat feedback' onChange={handleChange} value={chatFeedbackStatus} />
|
||||
</Box>
|
||||
<StyledButton style={{ marginBottom: 10, marginTop: 10 }} variant='contained' onClick={onSave}>
|
||||
Save
|
||||
</StyledButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
ChatFeedback.propTypes = {
|
||||
dialogProps: PropTypes.object
|
||||
}
|
||||
|
||||
export default ChatFeedback
|
||||
|
|
@ -18,7 +18,7 @@ import chatflowsApi from '@/api/chatflows'
|
|||
import useNotifier from '@/utils/useNotifier'
|
||||
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
||||
|
||||
const Configuration = () => {
|
||||
const RateLimit = () => {
|
||||
const dispatch = useDispatch()
|
||||
const chatflow = useSelector((state) => state.canvas.chatflow)
|
||||
const chatflowid = chatflow.id
|
||||
|
|
@ -59,7 +59,7 @@ const Configuration = () => {
|
|||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'API Configuration Saved',
|
||||
message: 'Rate Limit Configuration Saved',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
|
|
@ -78,7 +78,7 @@ const Configuration = () => {
|
|||
? error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
: error.message
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save API Configuration: ${errorData}`,
|
||||
message: `Failed to save Rate Limit Configuration: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
|
|
@ -131,7 +131,7 @@ const Configuration = () => {
|
|||
return (
|
||||
<>
|
||||
{/*Rate Limit*/}
|
||||
<Typography variant='h4' sx={{ mb: 1, mt: 2 }}>
|
||||
<Typography variant='h4' sx={{ mb: 1 }}>
|
||||
Rate Limit{' '}
|
||||
<TooltipWithParser
|
||||
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
||||
|
|
@ -151,8 +151,8 @@ const Configuration = () => {
|
|||
)
|
||||
}
|
||||
|
||||
Configuration.propTypes = {
|
||||
RateLimit.propTypes = {
|
||||
isSessionMemory: PropTypes.bool
|
||||
}
|
||||
|
||||
export default Configuration
|
||||
export default RateLimit
|
||||
|
|
@ -0,0 +1,309 @@
|
|||
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, FormControl, ListItem, ListItemAvatar, ListItemText, MenuItem, Select } from '@mui/material'
|
||||
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 { Dropdown } from '@/ui-component/dropdown/Dropdown'
|
||||
import openAISVG from '@/assets/images/openai.svg'
|
||||
import assemblyAIPng from '@/assets/images/assemblyai.png'
|
||||
|
||||
// store
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
|
||||
// API
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
|
||||
const speechToTextProviders = {
|
||||
openAIWhisper: {
|
||||
label: 'OpenAI Whisper',
|
||||
name: 'openAIWhisper',
|
||||
icon: openAISVG,
|
||||
url: 'https://platform.openai.com/docs/guides/speech-to-text',
|
||||
inputs: [
|
||||
{
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['openAIApi']
|
||||
},
|
||||
{
|
||||
label: 'Language',
|
||||
name: 'language',
|
||||
type: 'string',
|
||||
description:
|
||||
'The language of the input audio. Supplying the input language in ISO-639-1 format will improve accuracy and latency.',
|
||||
placeholder: 'en',
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Prompt',
|
||||
name: 'prompt',
|
||||
type: 'string',
|
||||
rows: 4,
|
||||
description: `An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language.`,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Temperature',
|
||||
name: 'temperature',
|
||||
type: 'number',
|
||||
step: 0.1,
|
||||
description: `The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.`,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
},
|
||||
assemblyAiTranscribe: {
|
||||
label: 'Assembly AI',
|
||||
name: 'assemblyAiTranscribe',
|
||||
icon: assemblyAIPng,
|
||||
url: 'https://www.assemblyai.com/',
|
||||
inputs: [
|
||||
{
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['assemblyAIApi']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const SpeechToText = ({ dialogProps }) => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useNotifier()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [speechToText, setSpeechToText] = useState({})
|
||||
const [selectedProvider, setSelectedProvider] = useState('none')
|
||||
|
||||
const onSave = async () => {
|
||||
const speechToText = setValue(true, selectedProvider, 'status')
|
||||
try {
|
||||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||
speechToText: JSON.stringify(speechToText)
|
||||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Speech To Text 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 })
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save Speech To Text 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(speechToText, providerName)) {
|
||||
newVal = { ...speechToText, [providerName]: {} }
|
||||
} else {
|
||||
newVal = { ...speechToText }
|
||||
}
|
||||
|
||||
newVal[providerName][inputParamName] = value
|
||||
if (inputParamName === 'status' && value === true) {
|
||||
// ensure that the others are turned off
|
||||
Object.keys(speechToTextProviders).forEach((key) => {
|
||||
const provider = speechToTextProviders[key]
|
||||
if (provider.name !== providerName) {
|
||||
newVal[provider.name] = { ...speechToText[provider.name], status: false }
|
||||
}
|
||||
})
|
||||
}
|
||||
setSpeechToText(newVal)
|
||||
return newVal
|
||||
}
|
||||
|
||||
const handleProviderChange = (event) => {
|
||||
setSelectedProvider(event.target.value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.chatflow && dialogProps.chatflow.speechToText) {
|
||||
try {
|
||||
const speechToText = JSON.parse(dialogProps.chatflow.speechToText)
|
||||
let selectedProvider = 'none'
|
||||
Object.keys(speechToTextProviders).forEach((key) => {
|
||||
const providerConfig = speechToText[key]
|
||||
if (providerConfig && providerConfig.status) {
|
||||
selectedProvider = key
|
||||
}
|
||||
})
|
||||
setSelectedProvider(selectedProvider)
|
||||
setSpeechToText(speechToText)
|
||||
} catch (e) {
|
||||
setSpeechToText({})
|
||||
setSelectedProvider('none')
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
setSpeechToText({})
|
||||
setSelectedProvider('none')
|
||||
}
|
||||
}, [dialogProps])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box fullWidth sx={{ mb: 1, display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant='h4' sx={{ mb: 1 }}>
|
||||
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: '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' }}> *</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>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<StyledButton
|
||||
style={{ marginBottom: 10, marginTop: 10 }}
|
||||
disabled={selectedProvider !== 'none' && !speechToText[selectedProvider]?.credentialId}
|
||||
variant='contained'
|
||||
onClick={onSave}
|
||||
>
|
||||
Save
|
||||
</StyledButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
SpeechToText.propTypes = {
|
||||
dialogProps: PropTypes.object
|
||||
}
|
||||
|
||||
export default SpeechToText
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
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 }) => {
|
||||
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 })
|
||||
}
|
||||
} 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
|
||||
|
|
@ -14,12 +14,8 @@ import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck,
|
|||
import Settings from '@/views/settings'
|
||||
import SaveChatflowDialog from '@/ui-component/dialog/SaveChatflowDialog'
|
||||
import APICodeDialog from '@/views/chatflows/APICodeDialog'
|
||||
import AnalyseFlowDialog from '@/ui-component/dialog/AnalyseFlowDialog'
|
||||
import ViewMessagesDialog from '@/ui-component/dialog/ViewMessagesDialog'
|
||||
import StarterPromptsDialog from '@/ui-component/dialog/StarterPromptsDialog'
|
||||
import SpeechToTextDialog from '@/ui-component/dialog/SpeechToTextDialog'
|
||||
import ChatFeedbackDialog from '@/ui-component/dialog/ChatFeedbackDialog'
|
||||
import AllowedDomainsDialog from '@/ui-component/dialog/AllowedDomainsDialog'
|
||||
import ChatflowConfigurationDialog from '@/ui-component/dialog/ChatflowConfigurationDialog'
|
||||
|
||||
// API
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
|
|
@ -47,18 +43,10 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||
const [flowDialogOpen, setFlowDialogOpen] = useState(false)
|
||||
const [apiDialogOpen, setAPIDialogOpen] = useState(false)
|
||||
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 [chatFeedbackDialogOpen, setChatFeedbackDialogOpen] = useState(false)
|
||||
const [chatFeedbackDialogProps, setChatFeedbackDialogProps] = useState({})
|
||||
const [allowedDomainsDialogOpen, setAllowedDomainsDialogOpen] = useState(false)
|
||||
const [allowedDomainsDialogProps, setAllowedDomainsDialogProps] = useState({})
|
||||
const [chatflowConfigurationDialogOpen, setChatflowConfigurationDialogOpen] = useState(false)
|
||||
const [chatflowConfigurationDialogProps, setChatflowConfigurationDialogProps] = useState({})
|
||||
|
||||
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
||||
const canvas = useSelector((state) => state.canvas)
|
||||
|
|
@ -68,42 +56,18 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||
|
||||
if (setting === 'deleteChatflow') {
|
||||
handleDeleteFlow()
|
||||
} else if (setting === 'conversationStarters') {
|
||||
setConversationStartersDialogProps({
|
||||
title: 'Starter Prompts - ' + chatflow.name,
|
||||
chatflow: chatflow
|
||||
})
|
||||
setConversationStartersDialogOpen(true)
|
||||
} else if (setting === 'chatFeedback') {
|
||||
setChatFeedbackDialogProps({
|
||||
title: `Chat Feedback - ${chatflow.name}`,
|
||||
chatflow: chatflow
|
||||
})
|
||||
setChatFeedbackDialogOpen(true)
|
||||
} else if (setting === 'allowedDomains') {
|
||||
setAllowedDomainsDialogProps({
|
||||
title: 'Allowed Domains - ' + chatflow.name,
|
||||
chatflow: chatflow
|
||||
})
|
||||
setAllowedDomainsDialogOpen(true)
|
||||
} else if (setting === 'analyseChatflow') {
|
||||
setAnalyseDialogProps({
|
||||
title: 'Analyse Chatflow',
|
||||
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)
|
||||
|
|
@ -207,8 +171,15 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||
useEffect(() => {
|
||||
if (chatflow) {
|
||||
setFlowName(chatflow.name)
|
||||
// if configuration dialog is open, update its data
|
||||
if (chatflowConfigurationDialogOpen) {
|
||||
setChatflowConfigurationDialogProps({
|
||||
title: 'Chatflow Configuration',
|
||||
chatflow
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [chatflow])
|
||||
}, [chatflow, chatflowConfigurationDialogOpen])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -411,35 +382,17 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||
onConfirm={onConfirmSaveName}
|
||||
/>
|
||||
<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}
|
||||
onConfirm={() => setConversationStartersDialogOpen(false)}
|
||||
onCancel={() => setConversationStartersDialogOpen(false)}
|
||||
/>
|
||||
<ChatFeedbackDialog
|
||||
show={chatFeedbackDialogOpen}
|
||||
dialogProps={chatFeedbackDialogProps}
|
||||
onConfirm={() => setChatFeedbackDialogOpen(false)}
|
||||
onCancel={() => setChatFeedbackDialogOpen(false)}
|
||||
/>
|
||||
<AllowedDomainsDialog
|
||||
show={allowedDomainsDialogOpen}
|
||||
dialogProps={allowedDomainsDialogProps}
|
||||
onConfirm={() => setAllowedDomainsDialogOpen(false)}
|
||||
onCancel={() => setAllowedDomainsDialogOpen(false)}
|
||||
/>
|
||||
<ViewMessagesDialog
|
||||
show={viewMessagesDialogOpen}
|
||||
dialogProps={viewMessagesDialogProps}
|
||||
onCancel={() => setViewMessagesDialogOpen(false)}
|
||||
/>
|
||||
<ChatflowConfigurationDialog
|
||||
key='chatflowConfiguration'
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -4228,7 +4228,7 @@ packages:
|
|||
dependencies:
|
||||
lodash.camelcase: 4.3.0
|
||||
long: 5.2.3
|
||||
protobufjs: 7.2.4
|
||||
protobufjs: 7.2.6
|
||||
yargs: 17.7.2
|
||||
dev: false
|
||||
|
||||
|
|
@ -23847,6 +23847,24 @@ packages:
|
|||
long: 5.2.3
|
||||
dev: false
|
||||
|
||||
/protobufjs@7.2.6:
|
||||
resolution: {integrity: sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
'@protobufjs/aspromise': 1.1.2
|
||||
'@protobufjs/base64': 1.1.2
|
||||
'@protobufjs/codegen': 2.0.4
|
||||
'@protobufjs/eventemitter': 1.1.0
|
||||
'@protobufjs/fetch': 1.1.0
|
||||
'@protobufjs/float': 1.0.2
|
||||
'@protobufjs/inquire': 1.1.0
|
||||
'@protobufjs/path': 1.1.2
|
||||
'@protobufjs/pool': 1.1.0
|
||||
'@protobufjs/utf8': 1.1.0
|
||||
'@types/node': 20.11.26
|
||||
long: 5.2.3
|
||||
dev: false
|
||||
|
||||
/protoc-gen-ts@0.8.7:
|
||||
resolution: {integrity: sha512-jr4VJey2J9LVYCV7EVyVe53g1VMw28cCmYJhBe5e3YX5wiyiDwgxWxeDf9oTqAe4P1bN/YGAkW2jhlH8LohwiQ==}
|
||||
hasBin: true
|
||||
|
|
|
|||
Loading…
Reference in New Issue