mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 21:00:58 +03:00
Merge pull request #1873 from 0xi4o/feature/chatflow-configuration
Feature: Add configuration dialog for chatflow
This commit is contained in:
@@ -24,7 +24,6 @@ import {
|
|||||||
IChatMessageFeedback,
|
IChatMessageFeedback,
|
||||||
IDepthQueue,
|
IDepthQueue,
|
||||||
INodeDirectedGraph,
|
INodeDirectedGraph,
|
||||||
ChatMessageRatingType,
|
|
||||||
IUploadFileSizeAndTypes
|
IUploadFileSizeAndTypes
|
||||||
} from './Interface'
|
} from './Interface'
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
// assets
|
// assets
|
||||||
import {
|
import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconMessage, IconAdjustmentsHorizontal } from '@tabler/icons'
|
||||||
IconTrash,
|
|
||||||
IconFileUpload,
|
|
||||||
IconFileExport,
|
|
||||||
IconCopy,
|
|
||||||
IconSearch,
|
|
||||||
IconMessage,
|
|
||||||
IconPictureInPictureOff,
|
|
||||||
IconLink,
|
|
||||||
IconMicrophone,
|
|
||||||
IconThumbUp
|
|
||||||
} from '@tabler/icons'
|
|
||||||
|
|
||||||
// constant
|
// constant
|
||||||
const icons = {
|
const icons = {
|
||||||
@@ -18,12 +7,8 @@ const icons = {
|
|||||||
IconFileUpload,
|
IconFileUpload,
|
||||||
IconFileExport,
|
IconFileExport,
|
||||||
IconCopy,
|
IconCopy,
|
||||||
IconSearch,
|
|
||||||
IconMessage,
|
IconMessage,
|
||||||
IconPictureInPictureOff,
|
IconAdjustmentsHorizontal
|
||||||
IconLink,
|
|
||||||
IconMicrophone,
|
|
||||||
IconThumbUp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
||||||
@@ -33,13 +18,6 @@ const settings = {
|
|||||||
title: '',
|
title: '',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
id: 'conversationStarters',
|
|
||||||
title: 'Starter Prompts',
|
|
||||||
type: 'item',
|
|
||||||
url: '',
|
|
||||||
icon: icons.IconPictureInPictureOff
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'viewMessages',
|
id: 'viewMessages',
|
||||||
title: 'View Messages',
|
title: 'View Messages',
|
||||||
@@ -48,25 +26,11 @@ const settings = {
|
|||||||
icon: icons.IconMessage
|
icon: icons.IconMessage
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'chatFeedback',
|
id: 'chatflowConfiguration',
|
||||||
title: 'Chat Feedback',
|
title: 'Configuration',
|
||||||
type: 'item',
|
type: 'item',
|
||||||
url: '',
|
url: '',
|
||||||
icon: icons.IconThumbUp
|
icon: icons.IconAdjustmentsHorizontal
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'allowedDomains',
|
|
||||||
title: 'Allowed Domains',
|
|
||||||
type: 'item',
|
|
||||||
url: '',
|
|
||||||
icon: icons.IconLink
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'enableSpeechToText',
|
|
||||||
title: 'Speech to Text',
|
|
||||||
type: 'item',
|
|
||||||
url: '',
|
|
||||||
icon: icons.IconMicrophone
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'duplicateChatflow',
|
id: 'duplicateChatflow',
|
||||||
@@ -89,13 +53,6 @@ const settings = {
|
|||||||
url: '',
|
url: '',
|
||||||
icon: icons.IconFileExport
|
icon: icons.IconFileExport
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'analyseChatflow',
|
|
||||||
title: 'Analyse Chatflow',
|
|
||||||
type: 'item',
|
|
||||||
url: '',
|
|
||||||
icon: icons.IconSearch
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'deleteChatflow',
|
id: 'deleteChatflow',
|
||||||
title: 'Delete Chatflow',
|
title: 'Delete Chatflow',
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import FileDownloadIcon from '@mui/icons-material/Downloading'
|
|||||||
import FileDeleteIcon from '@mui/icons-material/Delete'
|
import FileDeleteIcon from '@mui/icons-material/Delete'
|
||||||
import FileCategoryIcon from '@mui/icons-material/Category'
|
import FileCategoryIcon from '@mui/icons-material/Category'
|
||||||
import PictureInPictureAltIcon from '@mui/icons-material/PictureInPictureAlt'
|
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 Button from '@mui/material/Button'
|
||||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
|
||||||
import { IconX } from '@tabler/icons'
|
import { IconX } from '@tabler/icons'
|
||||||
@@ -29,6 +32,9 @@ import StarterPromptsDialog from '@/ui-component/dialog/StarterPromptsDialog'
|
|||||||
|
|
||||||
import { generateExportFlowData } from '@/utils/genericHelper'
|
import { generateExportFlowData } from '@/utils/genericHelper'
|
||||||
import useNotifier from '@/utils/useNotifier'
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
import ChatFeedbackDialog from '../dialog/ChatFeedbackDialog'
|
||||||
|
import AllowedDomainsDialog from '../dialog/AllowedDomainsDialog'
|
||||||
|
import SpeechToTextDialog from '../dialog/SpeechToTextDialog'
|
||||||
|
|
||||||
const StyledMenu = styled((props) => (
|
const StyledMenu = styled((props) => (
|
||||||
<Menu
|
<Menu
|
||||||
@@ -82,6 +88,12 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||||||
const open = Boolean(anchorEl)
|
const open = Boolean(anchorEl)
|
||||||
const [conversationStartersDialogOpen, setConversationStartersDialogOpen] = useState(false)
|
const [conversationStartersDialogOpen, setConversationStartersDialogOpen] = useState(false)
|
||||||
const [conversationStartersDialogProps, setConversationStartersDialogProps] = useState({})
|
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) => {
|
const handleClick = (event) => {
|
||||||
setAnchorEl(event.currentTarget)
|
setAnchorEl(event.currentTarget)
|
||||||
@@ -105,9 +117,31 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||||||
setConversationStartersDialogOpen(true)
|
setConversationStartersDialogOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveFlowStarterPrompts = async () => {
|
const handleFlowChatFeedback = () => {
|
||||||
setConversationStartersDialogOpen(false)
|
setAnchorEl(null)
|
||||||
await updateFlowsApi.request()
|
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) => {
|
const saveFlowRename = async (chatflowName) => {
|
||||||
@@ -275,6 +309,18 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||||||
<PictureInPictureAltIcon />
|
<PictureInPictureAltIcon />
|
||||||
Starter Prompts
|
Starter Prompts
|
||||||
</MenuItem>
|
</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>
|
<MenuItem onClick={handleFlowCategory} disableRipple>
|
||||||
<FileCategoryIcon />
|
<FileCategoryIcon />
|
||||||
Update Category
|
Update Category
|
||||||
@@ -304,9 +350,23 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||||||
<StarterPromptsDialog
|
<StarterPromptsDialog
|
||||||
show={conversationStartersDialogOpen}
|
show={conversationStartersDialogOpen}
|
||||||
dialogProps={conversationStartersDialogProps}
|
dialogProps={conversationStartersDialogProps}
|
||||||
onConfirm={saveFlowStarterPrompts}
|
|
||||||
onCancel={() => setConversationStartersDialogOpen(false)}
|
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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,130 +1,24 @@
|
|||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
import { useState, useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import {
|
import { Dialog, DialogContent, DialogTitle } from '@mui/material'
|
||||||
Button,
|
|
||||||
IconButton,
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
OutlinedInput,
|
|
||||||
DialogTitle,
|
|
||||||
DialogActions,
|
|
||||||
Box,
|
|
||||||
List,
|
|
||||||
InputAdornment
|
|
||||||
} from '@mui/material'
|
|
||||||
import { IconX, IconTrash, IconPlus } from '@tabler/icons'
|
|
||||||
|
|
||||||
// Project import
|
|
||||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
|
||||||
|
|
||||||
// store
|
// store
|
||||||
import {
|
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||||
enqueueSnackbar as enqueueSnackbarAction,
|
|
||||||
closeSnackbar as closeSnackbarAction,
|
|
||||||
SET_CHATFLOW,
|
|
||||||
HIDE_CANVAS_DIALOG,
|
|
||||||
SHOW_CANVAS_DIALOG
|
|
||||||
} from '@/store/actions'
|
|
||||||
import useNotifier from '@/utils/useNotifier'
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
|
||||||
// API
|
// Project imports
|
||||||
import chatflowsApi from '@/api/chatflows'
|
import AllowedDomains from '@/ui-component/extended/AllowedDomains'
|
||||||
|
|
||||||
const AllowedDomainsDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
const AllowedDomainsDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
useNotifier()
|
useNotifier()
|
||||||
|
|
||||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
|
||||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
|
||||||
|
|
||||||
const [inputFields, setInputFields] = useState([''])
|
|
||||||
|
|
||||||
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(() => {
|
useEffect(() => {
|
||||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
@@ -141,69 +35,11 @@ const AllowedDomainsDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||||||
aria-describedby='alert-dialog-description'
|
aria-describedby='alert-dialog-description'
|
||||||
>
|
>
|
||||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||||
{dialogProps.title || 'Allowed Origins'}
|
{dialogProps.title || 'Allowed Domains'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<div
|
<AllowedDomains dialogProps={dialogProps} />
|
||||||
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>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={onCancel}>Cancel</Button>
|
|
||||||
<StyledButton variant='contained' onClick={onSave}>
|
|
||||||
Save
|
|
||||||
</StyledButton>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
) : null
|
) : 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 { createPortal } from 'react-dom'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
import { useState, useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { Button, Dialog, DialogContent, DialogTitle, DialogActions, Box } from '@mui/material'
|
import { Dialog, DialogContent, DialogTitle } 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
|
// store
|
||||||
import {
|
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||||
enqueueSnackbar as enqueueSnackbarAction,
|
|
||||||
closeSnackbar as closeSnackbarAction,
|
|
||||||
SET_CHATFLOW,
|
|
||||||
HIDE_CANVAS_DIALOG,
|
|
||||||
SHOW_CANVAS_DIALOG
|
|
||||||
} from '@/store/actions'
|
|
||||||
import useNotifier from '@/utils/useNotifier'
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
|
||||||
// API
|
// Project imports
|
||||||
import chatflowsApi from '@/api/chatflows'
|
import ChatFeedback from '@/ui-component/extended/ChatFeedback'
|
||||||
|
|
||||||
const ChatFeedbackDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
const ChatFeedbackDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
useNotifier()
|
useNotifier()
|
||||||
|
|
||||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
|
||||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
|
||||||
|
|
||||||
const [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(() => {
|
useEffect(() => {
|
||||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
@@ -113,19 +35,11 @@ const ChatFeedbackDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||||||
aria-describedby='alert-dialog-description'
|
aria-describedby='alert-dialog-description'
|
||||||
>
|
>
|
||||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||||
{dialogProps.title || 'Chat Feedback'}
|
{dialogProps.title || 'Allowed Domains'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<ChatFeedback dialogProps={dialogProps} />
|
||||||
<SwitchInput label='Enable chat feedback' onChange={handleChange} value={chatFeedbackStatus} />
|
|
||||||
</Box>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={onCancel}>Cancel</Button>
|
|
||||||
<StyledButton variant='contained' onClick={onSave}>
|
|
||||||
Save
|
|
||||||
</StyledButton>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
) : null
|
) : 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 { createPortal } from 'react-dom'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
import { useState, useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import {
|
import { Dialog, DialogContent, DialogTitle } from '@mui/material'
|
||||||
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'
|
|
||||||
|
|
||||||
// store
|
// store
|
||||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||||
import useNotifier from '@/utils/useNotifier'
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
|
||||||
// API
|
// Project imports
|
||||||
import chatflowsApi from '@/api/chatflows'
|
import SpeechToText from '@/ui-component/extended/SpeechToText'
|
||||||
|
|
||||||
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 SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
|
const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
@@ -101,111 +19,13 @@ const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
|
|
||||||
useNotifier()
|
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(() => {
|
useEffect(() => {
|
||||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
}, [show, dispatch])
|
}, [show, dispatch])
|
||||||
|
|
||||||
const component = (
|
const component = show ? (
|
||||||
<Dialog
|
<Dialog
|
||||||
onClose={onCancel}
|
onClose={onCancel}
|
||||||
open={show}
|
open={show}
|
||||||
@@ -215,126 +35,13 @@ const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
aria-describedby='alert-dialog-description'
|
aria-describedby='alert-dialog-description'
|
||||||
>
|
>
|
||||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||||
Speech To Text Configuration
|
{dialogProps.title || 'Allowed Domains'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Box fullWidth sx={{ my: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<SpeechToText dialogProps={dialogProps} />
|
||||||
<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>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
|
||||||
<StyledButton
|
|
||||||
disabled={selectedProvider !== 'none' && !speechToText[selectedProvider]?.credentialId}
|
|
||||||
variant='contained'
|
|
||||||
onClick={onSave}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</StyledButton>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
) : null
|
||||||
|
|
||||||
return createPortal(component, portalElement)
|
return createPortal(component, portalElement)
|
||||||
}
|
}
|
||||||
@@ -342,7 +49,8 @@ const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
SpeechToTextDialog.propTypes = {
|
SpeechToTextDialog.propTypes = {
|
||||||
show: PropTypes.bool,
|
show: PropTypes.bool,
|
||||||
dialogProps: PropTypes.object,
|
dialogProps: PropTypes.object,
|
||||||
onCancel: PropTypes.func
|
onCancel: PropTypes.func,
|
||||||
|
onConfirm: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SpeechToTextDialog
|
export default SpeechToTextDialog
|
||||||
|
|||||||
@@ -1,149 +1,24 @@
|
|||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
import { useState, useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import {
|
import { Dialog, DialogContent, DialogTitle } from '@mui/material'
|
||||||
Button,
|
|
||||||
IconButton,
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
OutlinedInput,
|
|
||||||
DialogTitle,
|
|
||||||
DialogActions,
|
|
||||||
Box,
|
|
||||||
List,
|
|
||||||
InputAdornment
|
|
||||||
} from '@mui/material'
|
|
||||||
import { IconX, IconTrash, IconPlus, IconBulb } from '@tabler/icons'
|
|
||||||
|
|
||||||
// Project import
|
|
||||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
|
||||||
|
|
||||||
// store
|
// store
|
||||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||||
import useNotifier from '@/utils/useNotifier'
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
|
||||||
// API
|
// Project imports
|
||||||
import chatflowsApi from '@/api/chatflows'
|
import StarterPrompts from '@/ui-component/extended/StarterPrompts'
|
||||||
|
|
||||||
const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
useNotifier()
|
useNotifier()
|
||||||
|
|
||||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
|
||||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
|
||||||
|
|
||||||
const [inputFields, setInputFields] = useState([
|
|
||||||
{
|
|
||||||
prompt: ''
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
const [chatbotConfig, setChatbotConfig] = useState({})
|
|
||||||
|
|
||||||
const addInputField = () => {
|
|
||||||
setInputFields([
|
|
||||||
...inputFields,
|
|
||||||
{
|
|
||||||
prompt: ''
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
const removeInputFields = (index) => {
|
|
||||||
const rows = [...inputFields]
|
|
||||||
rows.splice(index, 1)
|
|
||||||
setInputFields(rows)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (index, evnt) => {
|
|
||||||
const { name, value } = evnt.target
|
|
||||||
const list = [...inputFields]
|
|
||||||
list[index][name] = value
|
|
||||||
setInputFields(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSave = async () => {
|
|
||||||
try {
|
|
||||||
let value = {
|
|
||||||
starterPrompts: {
|
|
||||||
...inputFields
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chatbotConfig.starterPrompts = value.starterPrompts
|
|
||||||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
|
||||||
chatbotConfig: JSON.stringify(chatbotConfig)
|
|
||||||
})
|
|
||||||
if (saveResp.data) {
|
|
||||||
enqueueSnackbar({
|
|
||||||
message: 'Conversation Starter Prompts Saved',
|
|
||||||
options: {
|
|
||||||
key: new Date().getTime() + Math.random(),
|
|
||||||
variant: 'success',
|
|
||||||
action: (key) => (
|
|
||||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
|
||||||
<IconX />
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data })
|
|
||||||
}
|
|
||||||
onConfirm()
|
|
||||||
} catch (error) {
|
|
||||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
|
||||||
enqueueSnackbar({
|
|
||||||
message: `Failed to save Conversation Starter Prompts: ${errorData}`,
|
|
||||||
options: {
|
|
||||||
key: new Date().getTime() + Math.random(),
|
|
||||||
variant: 'error',
|
|
||||||
persist: true,
|
|
||||||
action: (key) => (
|
|
||||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
|
||||||
<IconX />
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (dialogProps.chatflow && dialogProps.chatflow.chatbotConfig) {
|
|
||||||
try {
|
|
||||||
let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig)
|
|
||||||
setChatbotConfig(chatbotConfig || {})
|
|
||||||
if (chatbotConfig.starterPrompts) {
|
|
||||||
let inputFields = []
|
|
||||||
Object.getOwnPropertyNames(chatbotConfig.starterPrompts).forEach((key) => {
|
|
||||||
if (chatbotConfig.starterPrompts[key]) {
|
|
||||||
inputFields.push(chatbotConfig.starterPrompts[key])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setInputFields(inputFields)
|
|
||||||
} else {
|
|
||||||
setInputFields([
|
|
||||||
{
|
|
||||||
prompt: ''
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setInputFields([
|
|
||||||
{
|
|
||||||
prompt: ''
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {}
|
|
||||||
}, [dialogProps])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
@@ -163,79 +38,8 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||||||
{dialogProps.title || 'Conversation Starter Prompts'}
|
{dialogProps.title || 'Conversation Starter Prompts'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<div
|
<StarterPrompts dialogProps={dialogProps} />
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
borderRadius: 10,
|
|
||||||
background: '#d8f3dc',
|
|
||||||
padding: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconBulb size={30} color='#2d6a4f' />
|
|
||||||
<span style={{ color: '#2d6a4f', marginLeft: 10, fontWeight: 500 }}>
|
|
||||||
Starter prompts will only be shown when there is no messages on the chat
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Box sx={{ '& > :not(style)': { m: 1 }, pt: 2 }}>
|
|
||||||
<List>
|
|
||||||
{inputFields.map((data, index) => {
|
|
||||||
return (
|
|
||||||
<div key={index} style={{ display: 'flex', width: '100%' }}>
|
|
||||||
<Box sx={{ width: '95%', mb: 1 }}>
|
|
||||||
<OutlinedInput
|
|
||||||
sx={{ width: '100%' }}
|
|
||||||
key={index}
|
|
||||||
type='text'
|
|
||||||
onChange={(e) => handleChange(index, e)}
|
|
||||||
size='small'
|
|
||||||
value={data.prompt}
|
|
||||||
name='prompt'
|
|
||||||
endAdornment={
|
|
||||||
<InputAdornment position='end' sx={{ padding: '2px' }}>
|
|
||||||
{inputFields.length > 1 && (
|
|
||||||
<IconButton
|
|
||||||
sx={{ height: 30, width: 30 }}
|
|
||||||
size='small'
|
|
||||||
color='error'
|
|
||||||
disabled={inputFields.length === 1}
|
|
||||||
onClick={() => removeInputFields(index)}
|
|
||||||
edge='end'
|
|
||||||
>
|
|
||||||
<IconTrash />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</InputAdornment>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{ width: '5%', mb: 1 }}>
|
|
||||||
{index === inputFields.length - 1 && (
|
|
||||||
<IconButton color='primary' onClick={addInputField}>
|
|
||||||
<IconPlus />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</List>
|
|
||||||
</Box>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={onCancel}>Cancel</Button>
|
|
||||||
<StyledButton variant='contained' onClick={onSave}>
|
|
||||||
Save
|
|
||||||
</StyledButton>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
+6
-6
@@ -18,7 +18,7 @@ import chatflowsApi from '@/api/chatflows'
|
|||||||
import useNotifier from '@/utils/useNotifier'
|
import useNotifier from '@/utils/useNotifier'
|
||||||
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
||||||
|
|
||||||
const Configuration = () => {
|
const RateLimit = () => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const chatflow = useSelector((state) => state.canvas.chatflow)
|
const chatflow = useSelector((state) => state.canvas.chatflow)
|
||||||
const chatflowid = chatflow.id
|
const chatflowid = chatflow.id
|
||||||
@@ -59,7 +59,7 @@ const Configuration = () => {
|
|||||||
})
|
})
|
||||||
if (saveResp.data) {
|
if (saveResp.data) {
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: 'API Configuration Saved',
|
message: 'Rate Limit Configuration Saved',
|
||||||
options: {
|
options: {
|
||||||
key: new Date().getTime() + Math.random(),
|
key: new Date().getTime() + Math.random(),
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
@@ -78,7 +78,7 @@ const Configuration = () => {
|
|||||||
? error.response.data || `${error.response.status}: ${error.response.statusText}`
|
? error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||||
: error.message
|
: error.message
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to save API Configuration: ${errorData}`,
|
message: `Failed to save Rate Limit Configuration: ${errorData}`,
|
||||||
options: {
|
options: {
|
||||||
key: new Date().getTime() + Math.random(),
|
key: new Date().getTime() + Math.random(),
|
||||||
variant: 'error',
|
variant: 'error',
|
||||||
@@ -131,7 +131,7 @@ const Configuration = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/*Rate Limit*/}
|
{/*Rate Limit*/}
|
||||||
<Typography variant='h4' sx={{ mb: 1, mt: 2 }}>
|
<Typography variant='h4' sx={{ mb: 1 }}>
|
||||||
Rate Limit{' '}
|
Rate Limit{' '}
|
||||||
<TooltipWithParser
|
<TooltipWithParser
|
||||||
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
style={{ mb: 1, mt: 2, marginLeft: 10 }}
|
||||||
@@ -151,8 +151,8 @@ const Configuration = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Configuration.propTypes = {
|
RateLimit.propTypes = {
|
||||||
isSessionMemory: PropTypes.bool
|
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 Settings from '@/views/settings'
|
||||||
import SaveChatflowDialog from '@/ui-component/dialog/SaveChatflowDialog'
|
import SaveChatflowDialog from '@/ui-component/dialog/SaveChatflowDialog'
|
||||||
import APICodeDialog from '@/views/chatflows/APICodeDialog'
|
import APICodeDialog from '@/views/chatflows/APICodeDialog'
|
||||||
import AnalyseFlowDialog from '@/ui-component/dialog/AnalyseFlowDialog'
|
|
||||||
import ViewMessagesDialog from '@/ui-component/dialog/ViewMessagesDialog'
|
import ViewMessagesDialog from '@/ui-component/dialog/ViewMessagesDialog'
|
||||||
import StarterPromptsDialog from '@/ui-component/dialog/StarterPromptsDialog'
|
import ChatflowConfigurationDialog from '@/ui-component/dialog/ChatflowConfigurationDialog'
|
||||||
import SpeechToTextDialog from '@/ui-component/dialog/SpeechToTextDialog'
|
|
||||||
import ChatFeedbackDialog from '@/ui-component/dialog/ChatFeedbackDialog'
|
|
||||||
import AllowedDomainsDialog from '@/ui-component/dialog/AllowedDomainsDialog'
|
|
||||||
|
|
||||||
// API
|
// API
|
||||||
import chatflowsApi from '@/api/chatflows'
|
import chatflowsApi from '@/api/chatflows'
|
||||||
@@ -47,18 +43,10 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||||||
const [flowDialogOpen, setFlowDialogOpen] = useState(false)
|
const [flowDialogOpen, setFlowDialogOpen] = useState(false)
|
||||||
const [apiDialogOpen, setAPIDialogOpen] = useState(false)
|
const [apiDialogOpen, setAPIDialogOpen] = useState(false)
|
||||||
const [apiDialogProps, setAPIDialogProps] = useState({})
|
const [apiDialogProps, setAPIDialogProps] = useState({})
|
||||||
const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false)
|
|
||||||
const [analyseDialogProps, setAnalyseDialogProps] = useState({})
|
|
||||||
const [speechToAudioDialogOpen, setSpeechToAudioDialogOpen] = useState(false)
|
|
||||||
const [speechToAudioDialogProps, setSpeechToAudioialogProps] = useState({})
|
|
||||||
const [conversationStartersDialogOpen, setConversationStartersDialogOpen] = useState(false)
|
|
||||||
const [conversationStartersDialogProps, setConversationStartersDialogProps] = useState({})
|
|
||||||
const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false)
|
const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false)
|
||||||
const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({})
|
const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({})
|
||||||
const [chatFeedbackDialogOpen, setChatFeedbackDialogOpen] = useState(false)
|
const [chatflowConfigurationDialogOpen, setChatflowConfigurationDialogOpen] = useState(false)
|
||||||
const [chatFeedbackDialogProps, setChatFeedbackDialogProps] = useState({})
|
const [chatflowConfigurationDialogProps, setChatflowConfigurationDialogProps] = useState({})
|
||||||
const [allowedDomainsDialogOpen, setAllowedDomainsDialogOpen] = useState(false)
|
|
||||||
const [allowedDomainsDialogProps, setAllowedDomainsDialogProps] = useState({})
|
|
||||||
|
|
||||||
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
||||||
const canvas = useSelector((state) => state.canvas)
|
const canvas = useSelector((state) => state.canvas)
|
||||||
@@ -68,42 +56,18 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||||||
|
|
||||||
if (setting === 'deleteChatflow') {
|
if (setting === 'deleteChatflow') {
|
||||||
handleDeleteFlow()
|
handleDeleteFlow()
|
||||||
} else if (setting === 'conversationStarters') {
|
|
||||||
setConversationStartersDialogProps({
|
|
||||||
title: 'Starter Prompts - ' + chatflow.name,
|
|
||||||
chatflow: chatflow
|
|
||||||
})
|
|
||||||
setConversationStartersDialogOpen(true)
|
|
||||||
} else if (setting === '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') {
|
} else if (setting === 'viewMessages') {
|
||||||
setViewMessagesDialogProps({
|
setViewMessagesDialogProps({
|
||||||
title: 'View Messages',
|
title: 'View Messages',
|
||||||
chatflow: chatflow
|
chatflow: chatflow
|
||||||
})
|
})
|
||||||
setViewMessagesDialogOpen(true)
|
setViewMessagesDialogOpen(true)
|
||||||
|
} else if (setting === 'chatflowConfiguration') {
|
||||||
|
setChatflowConfigurationDialogProps({
|
||||||
|
title: 'Chatflow Configuration',
|
||||||
|
chatflow: chatflow
|
||||||
|
})
|
||||||
|
setChatflowConfigurationDialogOpen(true)
|
||||||
} else if (setting === 'duplicateChatflow') {
|
} else if (setting === 'duplicateChatflow') {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem('duplicatedFlowData', chatflow.flowData)
|
localStorage.setItem('duplicatedFlowData', chatflow.flowData)
|
||||||
@@ -207,8 +171,15 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chatflow) {
|
if (chatflow) {
|
||||||
setFlowName(chatflow.name)
|
setFlowName(chatflow.name)
|
||||||
|
// if configuration dialog is open, update its data
|
||||||
|
if (chatflowConfigurationDialogOpen) {
|
||||||
|
setChatflowConfigurationDialogProps({
|
||||||
|
title: 'Chatflow Configuration',
|
||||||
|
chatflow
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [chatflow])
|
}, [chatflow, chatflowConfigurationDialogOpen])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -411,35 +382,17 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||||||
onConfirm={onConfirmSaveName}
|
onConfirm={onConfirmSaveName}
|
||||||
/>
|
/>
|
||||||
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
|
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
|
||||||
<AnalyseFlowDialog show={analyseDialogOpen} dialogProps={analyseDialogProps} onCancel={() => setAnalyseDialogOpen(false)} />
|
|
||||||
<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
|
<ViewMessagesDialog
|
||||||
show={viewMessagesDialogOpen}
|
show={viewMessagesDialogOpen}
|
||||||
dialogProps={viewMessagesDialogProps}
|
dialogProps={viewMessagesDialogProps}
|
||||||
onCancel={() => setViewMessagesDialogOpen(false)}
|
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 { Dropdown } from '@/ui-component/dropdown/Dropdown'
|
||||||
import ShareChatbot from './ShareChatbot'
|
import ShareChatbot from './ShareChatbot'
|
||||||
import EmbedChat from './EmbedChat'
|
import EmbedChat from './EmbedChat'
|
||||||
import Configuration from './Configuration'
|
|
||||||
|
|
||||||
// Const
|
// Const
|
||||||
import { baseURL } from '@/store/constant'
|
import { baseURL } from '@/store/constant'
|
||||||
@@ -84,7 +83,7 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
const codes = ['Embed', 'Python', 'JavaScript', 'cURL', 'Share Chatbot', 'Configuration']
|
const codes = ['Embed', 'Python', 'JavaScript', 'cURL', 'Share Chatbot']
|
||||||
const [value, setValue] = useState(0)
|
const [value, setValue] = useState(0)
|
||||||
const [keyOptions, setKeyOptions] = useState([])
|
const [keyOptions, setKeyOptions] = useState([])
|
||||||
const [apiKeys, setAPIKeys] = useState([])
|
const [apiKeys, setAPIKeys] = useState([])
|
||||||
@@ -721,7 +720,6 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")`
|
|||||||
{codeLang === 'Share Chatbot' && !chatflowApiKeyId && (
|
{codeLang === 'Share Chatbot' && !chatflowApiKeyId && (
|
||||||
<ShareChatbot isSessionMemory={dialogProps.isSessionMemory} />
|
<ShareChatbot isSessionMemory={dialogProps.isSessionMemory} />
|
||||||
)}
|
)}
|
||||||
{codeLang === 'Configuration' && <Configuration />}
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
))}
|
))}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ const Settings = ({ chatflow, isSettingsOpen, anchorEl, onSettingsItemClick, onU
|
|||||||
width: customization.isOpen.findIndex((id) => id === menu?.id) > -1 ? 8 : 6,
|
width: customization.isOpen.findIndex((id) => id === menu?.id) > -1 ? 8 : 6,
|
||||||
height: customization.isOpen.findIndex((id) => id === menu?.id) > -1 ? 8 : 6
|
height: customization.isOpen.findIndex((id) => id === menu?.id) > -1 ? 8 : 6
|
||||||
}}
|
}}
|
||||||
fontSize={level > 0 ? 'inherit' : 'medium'}
|
fontSize={'inherit'}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
|
|||||||
Generated
+19
-1
@@ -4228,7 +4228,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash.camelcase: 4.3.0
|
lodash.camelcase: 4.3.0
|
||||||
long: 5.2.3
|
long: 5.2.3
|
||||||
protobufjs: 7.2.4
|
protobufjs: 7.2.6
|
||||||
yargs: 17.7.2
|
yargs: 17.7.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -23847,6 +23847,24 @@ packages:
|
|||||||
long: 5.2.3
|
long: 5.2.3
|
||||||
dev: false
|
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:
|
/protoc-gen-ts@0.8.7:
|
||||||
resolution: {integrity: sha512-jr4VJey2J9LVYCV7EVyVe53g1VMw28cCmYJhBe5e3YX5wiyiDwgxWxeDf9oTqAe4P1bN/YGAkW2jhlH8LohwiQ==}
|
resolution: {integrity: sha512-jr4VJey2J9LVYCV7EVyVe53g1VMw28cCmYJhBe5e3YX5wiyiDwgxWxeDf9oTqAe4P1bN/YGAkW2jhlH8LohwiQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|||||||
Reference in New Issue
Block a user