Files
Flowise/packages/ui/src/views/chatflows/ShareChatbot.jsx
T
Henry Heng f4647044bd Feature/Hide or Show agent messages for share chatbot (#3140)
ability to hide/show agent messages for share chatbot
2024-09-03 22:53:43 +01:00

538 lines
22 KiB
React

import { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
import { SketchPicker } from 'react-color'
import PropTypes from 'prop-types'
import { Box, Typography, Button, Switch, OutlinedInput, Popover, Stack, IconButton } from '@mui/material'
import { useTheme } from '@mui/material/styles'
// Project import
import { StyledButton } from '@/ui-component/button/StyledButton'
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
// Icons
import { IconX, IconCopy, IconArrowUpRightCircle } from '@tabler/icons-react'
// API
import chatflowsApi from '@/api/chatflows'
// utils
import useNotifier from '@/utils/useNotifier'
// Const
import { baseURL } from '@/store/constant'
const defaultConfig = {
backgroundColor: '#ffffff',
fontSize: 16,
poweredByTextColor: '#303235',
botMessage: {
backgroundColor: '#f7f8ff',
textColor: '#303235'
},
userMessage: {
backgroundColor: '#3B81F6',
textColor: '#ffffff'
},
textInput: {
backgroundColor: '#ffffff',
textColor: '#303235',
sendButtonColor: '#3B81F6'
}
}
const ShareChatbot = ({ isSessionMemory, isAgentCanvas }) => {
const dispatch = useDispatch()
const theme = useTheme()
const chatflow = useSelector((state) => state.canvas.chatflow)
const chatflowid = chatflow.id
const chatbotConfig = chatflow.chatbotConfig ? JSON.parse(chatflow.chatbotConfig) : {}
useNotifier()
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
const [isPublicChatflow, setChatflowIsPublic] = useState(chatflow.isPublic ?? false)
const [generateNewSession, setGenerateNewSession] = useState(chatbotConfig?.generateNewSession ?? false)
const [title, setTitle] = useState(chatbotConfig?.title ?? '')
const [titleAvatarSrc, setTitleAvatarSrc] = useState(chatbotConfig?.titleAvatarSrc ?? '')
const [welcomeMessage, setWelcomeMessage] = useState(chatbotConfig?.welcomeMessage ?? '')
const [errorMessage, setErrorMessage] = useState(chatbotConfig?.errorMessage ?? '')
const [backgroundColor, setBackgroundColor] = useState(chatbotConfig?.backgroundColor ?? defaultConfig.backgroundColor)
const [fontSize, setFontSize] = useState(chatbotConfig?.fontSize ?? defaultConfig.fontSize)
const [poweredByTextColor, setPoweredByTextColor] = useState(chatbotConfig?.poweredByTextColor ?? defaultConfig.poweredByTextColor)
const [showAgentMessages, setShowAgentMessages] = useState(chatbotConfig?.showAgentMessages || (isAgentCanvas ? true : undefined))
const [botMessageBackgroundColor, setBotMessageBackgroundColor] = useState(
chatbotConfig?.botMessage?.backgroundColor ?? defaultConfig.botMessage.backgroundColor
)
const [botMessageTextColor, setBotMessageTextColor] = useState(
chatbotConfig?.botMessage?.textColor ?? defaultConfig.botMessage.textColor
)
const [botMessageAvatarSrc, setBotMessageAvatarSrc] = useState(chatbotConfig?.botMessage?.avatarSrc ?? '')
const [botMessageShowAvatar, setBotMessageShowAvatar] = useState(chatbotConfig?.botMessage?.showAvatar ?? false)
const [userMessageBackgroundColor, setUserMessageBackgroundColor] = useState(
chatbotConfig?.userMessage?.backgroundColor ?? defaultConfig.userMessage.backgroundColor
)
const [userMessageTextColor, setUserMessageTextColor] = useState(
chatbotConfig?.userMessage?.textColor ?? defaultConfig.userMessage.textColor
)
const [userMessageAvatarSrc, setUserMessageAvatarSrc] = useState(chatbotConfig?.userMessage?.avatarSrc ?? '')
const [userMessageShowAvatar, setUserMessageShowAvatar] = useState(chatbotConfig?.userMessage?.showAvatar ?? false)
const [textInputBackgroundColor, setTextInputBackgroundColor] = useState(
chatbotConfig?.textInput?.backgroundColor ?? defaultConfig.textInput.backgroundColor
)
const [textInputTextColor, setTextInputTextColor] = useState(chatbotConfig?.textInput?.textColor ?? defaultConfig.textInput.textColor)
const [textInputPlaceholder, setTextInputPlaceholder] = useState(chatbotConfig?.textInput?.placeholder ?? '')
const [textInputSendButtonColor, setTextInputSendButtonColor] = useState(
chatbotConfig?.textInput?.sendButtonColor ?? defaultConfig.textInput.sendButtonColor
)
const [colorAnchorEl, setColorAnchorEl] = useState(null)
const [selectedColorConfig, setSelectedColorConfig] = useState('')
const [sketchPickerColor, setSketchPickerColor] = useState('')
const openColorPopOver = Boolean(colorAnchorEl)
const [copyAnchorEl, setCopyAnchorEl] = useState(null)
const openCopyPopOver = Boolean(copyAnchorEl)
const formatObj = () => {
const obj = {
botMessage: {
showAvatar: false
},
userMessage: {
showAvatar: false
},
textInput: {},
overrideConfig: {}
}
if (title) obj.title = title
if (titleAvatarSrc) obj.titleAvatarSrc = titleAvatarSrc
if (welcomeMessage) obj.welcomeMessage = welcomeMessage
if (errorMessage) obj.errorMessage = errorMessage
if (backgroundColor) obj.backgroundColor = backgroundColor
if (fontSize) obj.fontSize = fontSize
if (poweredByTextColor) obj.poweredByTextColor = poweredByTextColor
if (botMessageBackgroundColor) obj.botMessage.backgroundColor = botMessageBackgroundColor
if (botMessageTextColor) obj.botMessage.textColor = botMessageTextColor
if (botMessageAvatarSrc) obj.botMessage.avatarSrc = botMessageAvatarSrc
if (botMessageShowAvatar) obj.botMessage.showAvatar = botMessageShowAvatar
if (userMessageBackgroundColor) obj.userMessage.backgroundColor = userMessageBackgroundColor
if (userMessageTextColor) obj.userMessage.textColor = userMessageTextColor
if (userMessageAvatarSrc) obj.userMessage.avatarSrc = userMessageAvatarSrc
if (userMessageShowAvatar) obj.userMessage.showAvatar = userMessageShowAvatar
if (textInputBackgroundColor) obj.textInput.backgroundColor = textInputBackgroundColor
if (textInputTextColor) obj.textInput.textColor = textInputTextColor
if (textInputPlaceholder) obj.textInput.placeholder = textInputPlaceholder
if (textInputSendButtonColor) obj.textInput.sendButtonColor = textInputSendButtonColor
if (isSessionMemory) obj.overrideConfig.generateNewSession = generateNewSession
if (chatbotConfig?.starterPrompts) obj.starterPrompts = chatbotConfig.starterPrompts
if (isAgentCanvas) {
// if showAgentMessages is undefined, default to true
if (showAgentMessages === undefined || showAgentMessages === null) {
obj.showAgentMessages = true
} else {
obj.showAgentMessages = showAgentMessages
}
}
return obj
}
const onSave = async () => {
try {
const saveResp = await chatflowsApi.updateChatflow(chatflowid, {
chatbotConfig: JSON.stringify(formatObj())
})
if (saveResp.data) {
enqueueSnackbar({
message: 'Chatbot 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) {
enqueueSnackbar({
message: `Failed to save Chatbot Configuration: ${
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
}`,
options: {
key: new Date().getTime() + Math.random(),
variant: 'error',
persist: true,
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
}
const onSwitchChange = async (checked) => {
try {
const saveResp = await chatflowsApi.updateChatflow(chatflowid, { isPublic: checked })
if (saveResp.data) {
enqueueSnackbar({
message: 'Chatbot 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) {
enqueueSnackbar({
message: `Failed to save Chatbot Configuration: ${
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
}`,
options: {
key: new Date().getTime() + Math.random(),
variant: 'error',
persist: true,
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
}
const handleClosePopOver = () => {
setColorAnchorEl(null)
}
const handleCloseCopyPopOver = () => {
setCopyAnchorEl(null)
}
const onColorSelected = (hexColor) => {
switch (selectedColorConfig) {
case 'backgroundColor':
setBackgroundColor(hexColor)
break
case 'poweredByTextColor':
setPoweredByTextColor(hexColor)
break
case 'botMessageBackgroundColor':
setBotMessageBackgroundColor(hexColor)
break
case 'botMessageTextColor':
setBotMessageTextColor(hexColor)
break
case 'userMessageBackgroundColor':
setUserMessageBackgroundColor(hexColor)
break
case 'userMessageTextColor':
setUserMessageTextColor(hexColor)
break
case 'textInputBackgroundColor':
setTextInputBackgroundColor(hexColor)
break
case 'textInputTextColor':
setTextInputTextColor(hexColor)
break
case 'textInputSendButtonColor':
setTextInputSendButtonColor(hexColor)
break
}
setSketchPickerColor(hexColor)
}
const onTextChanged = (value, fieldName) => {
switch (fieldName) {
case 'title':
setTitle(value)
break
case 'titleAvatarSrc':
setTitleAvatarSrc(value)
break
case 'welcomeMessage':
setWelcomeMessage(value)
break
case 'errorMessage':
setErrorMessage(value)
break
case 'fontSize':
setFontSize(value)
break
case 'botMessageAvatarSrc':
setBotMessageAvatarSrc(value)
break
case 'userMessageAvatarSrc':
setUserMessageAvatarSrc(value)
break
case 'textInputPlaceholder':
setTextInputPlaceholder(value)
break
}
}
const onBooleanChanged = (value, fieldName) => {
switch (fieldName) {
case 'botMessageShowAvatar':
setBotMessageShowAvatar(value)
break
case 'userMessageShowAvatar':
setUserMessageShowAvatar(value)
break
case 'generateNewSession':
setGenerateNewSession(value)
break
case 'showAgentMessages':
setShowAgentMessages(value)
break
}
}
const colorField = (color, fieldName, fieldLabel) => {
return (
<Box sx={{ pt: 2, pb: 2 }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
<Typography sx={{ mb: 1 }}>{fieldLabel}</Typography>
<Box
sx={{
cursor: 'pointer',
width: '30px',
height: '30px',
border: '1px solid #616161',
marginRight: '10px',
backgroundColor: color ?? '#ffffff',
borderRadius: '5px'
}}
onClick={(event) => {
setSelectedColorConfig(fieldName)
setSketchPickerColor(color ?? '#ffffff')
setColorAnchorEl(event.currentTarget)
}}
></Box>
</div>
</Box>
)
}
const booleanField = (value, fieldName, fieldLabel) => {
return (
<Box sx={{ pt: 2, pb: 2 }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
<Typography sx={{ mb: 1 }}>{fieldLabel}</Typography>
<Switch
id={fieldName}
checked={value}
onChange={(event) => {
onBooleanChanged(event.target.checked, fieldName)
}}
/>
</div>
</Box>
)
}
const textField = (message, fieldName, fieldLabel, fieldType = 'string', placeholder = '') => {
return (
<Box sx={{ pt: 2, pb: 2 }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
<Typography sx={{ mb: 1 }}>{fieldLabel}</Typography>
<OutlinedInput
id={fieldName}
type={fieldType}
fullWidth
value={message}
placeholder={placeholder}
name={fieldName}
onChange={(e) => {
onTextChanged(e.target.value, fieldName)
}}
/>
</div>
</Box>
)
}
return (
<>
<Stack direction='row'>
<Typography
sx={{
p: 1,
borderRadius: 10,
backgroundColor: theme.palette.primary.light,
width: 'max-content',
height: 'max-content'
}}
variant='h5'
>
{`${baseURL}/chatbot/${chatflowid}`}
</Typography>
<IconButton
title='Copy Link'
color='success'
onClick={(event) => {
navigator.clipboard.writeText(`${baseURL}/chatbot/${chatflowid}`)
setCopyAnchorEl(event.currentTarget)
setTimeout(() => {
handleCloseCopyPopOver()
}, 1500)
}}
>
<IconCopy />
</IconButton>
<IconButton title='Open New Tab' color='primary' onClick={() => window.open(`${baseURL}/chatbot/${chatflowid}`, '_blank')}>
<IconArrowUpRightCircle />
</IconButton>
<div style={{ flex: 1 }} />
<div style={{ display: 'flex', alignItems: 'center' }}>
<Switch
checked={isPublicChatflow}
onChange={(event) => {
setChatflowIsPublic(event.target.checked)
onSwitchChange(event.target.checked)
}}
/>
<Typography>Make Public</Typography>
<TooltipWithParser
style={{ marginLeft: 10 }}
title={'Making public will allow anyone to access the chatbot without username & password'}
/>
</div>
</Stack>
{textField(title, 'title', 'Title', 'string', 'Flowise Assistant')}
{textField(
titleAvatarSrc,
'titleAvatarSrc',
'Title Avatar Link',
'string',
`https://raw.githubusercontent.com/FlowiseAI/Flowise/main/assets/FloWiseAI_dark.png`
)}
{textField(welcomeMessage, 'welcomeMessage', 'Welcome Message', 'string', 'Hello! This is custom welcome message')}
{textField(errorMessage, 'errorMessage', 'Error Message', 'string', 'This is custom error message')}
{colorField(backgroundColor, 'backgroundColor', 'Background Color')}
{textField(fontSize, 'fontSize', 'Font Size', 'number')}
{colorField(poweredByTextColor, 'poweredByTextColor', 'PoweredBy TextColor')}
{booleanField(showAgentMessages, 'showAgentMessages', 'Show Agent Reasoning')}
{/*BOT Message*/}
<Typography variant='h4' sx={{ mb: 1, mt: 2 }}>
Bot Message
</Typography>
{colorField(botMessageBackgroundColor, 'botMessageBackgroundColor', 'Background Color')}
{colorField(botMessageTextColor, 'botMessageTextColor', 'Text Color')}
{textField(
botMessageAvatarSrc,
'botMessageAvatarSrc',
'Avatar Link',
'string',
`https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/parroticon.png`
)}
{booleanField(botMessageShowAvatar, 'botMessageShowAvatar', 'Show Avatar')}
{/*USER Message*/}
<Typography variant='h4' sx={{ mb: 1, mt: 2 }}>
User Message
</Typography>
{colorField(userMessageBackgroundColor, 'userMessageBackgroundColor', 'Background Color')}
{colorField(userMessageTextColor, 'userMessageTextColor', 'Text Color')}
{textField(
userMessageAvatarSrc,
'userMessageAvatarSrc',
'Avatar Link',
'string',
`https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/usericon.png`
)}
{booleanField(userMessageShowAvatar, 'userMessageShowAvatar', 'Show Avatar')}
{/*TEXT Input*/}
<Typography variant='h4' sx={{ mb: 1, mt: 2 }}>
Text Input
</Typography>
{colorField(textInputBackgroundColor, 'textInputBackgroundColor', 'Background Color')}
{colorField(textInputTextColor, 'textInputTextColor', 'Text Color')}
{textField(textInputPlaceholder, 'textInputPlaceholder', 'TextInput Placeholder', 'string', `Type question..`)}
{colorField(textInputSendButtonColor, 'textInputSendButtonColor', 'TextIntput Send Button Color')}
{/*Session Memory Input*/}
{isSessionMemory && (
<>
<Typography variant='h4' sx={{ mb: 1, mt: 2 }}>
Session Memory
</Typography>
{booleanField(generateNewSession, 'generateNewSession', 'Start new session when chatbot link is opened or refreshed')}
</>
)}
<StyledButton style={{ marginBottom: 10, marginTop: 10 }} variant='contained' onClick={() => onSave()}>
Save Changes
</StyledButton>
<Popover
open={openColorPopOver}
anchorEl={colorAnchorEl}
onClose={handleClosePopOver}
anchorOrigin={{
vertical: 'top',
horizontal: 'right'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left'
}}
>
<SketchPicker color={sketchPickerColor} onChange={(color) => onColorSelected(color.hex)} />
</Popover>
<Popover
open={openCopyPopOver}
anchorEl={copyAnchorEl}
onClose={handleCloseCopyPopOver}
anchorOrigin={{
vertical: 'top',
horizontal: 'right'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left'
}}
>
<Typography variant='h6' sx={{ pl: 1, pr: 1, color: 'white', background: theme.palette.success.dark }}>
Copied!
</Typography>
</Popover>
</>
)
}
ShareChatbot.propTypes = {
isSessionMemory: PropTypes.bool,
isAgentCanvas: PropTypes.bool
}
export default ShareChatbot