diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 1eafcae68ee..9473638f426 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -13,6 +13,7 @@ export interface IChatFlow { deployed: boolean updatedDate: Date createdDate: Date + chatbotConfig?: string } export interface IChatMessage { diff --git a/packages/server/src/entity/ChatFlow.ts b/packages/server/src/entity/ChatFlow.ts index d9b12929412..910272ad6c6 100644 --- a/packages/server/src/entity/ChatFlow.ts +++ b/packages/server/src/entity/ChatFlow.ts @@ -19,6 +19,9 @@ export class ChatFlow implements IChatFlow { @Column() deployed: boolean + @Column({ nullable: true }) + chatbotConfig?: string + @CreateDateColumn() createdDate: Date diff --git a/packages/ui/craco.config.js b/packages/ui/craco.config.js new file mode 100644 index 00000000000..142305e01b5 --- /dev/null +++ b/packages/ui/craco.config.js @@ -0,0 +1,16 @@ +module.exports = { + webpack: { + configure: { + module: { + rules: [ + { + test: /\.m?js$/, + resolve: { + fullySpecified: false + } + } + ] + } + } + } +} diff --git a/packages/ui/package.json b/packages/ui/package.json index 258b54716e4..1e55f1c8a6f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -16,6 +16,8 @@ "@mui/x-data-grid": "^6.8.0", "@tabler/icons": "^1.39.1", "clsx": "^1.1.1", + "flowise-embed": "*", + "flowise-embed-react": "*", "formik": "^2.2.6", "framer-motion": "^4.1.13", "history": "^5.0.0", @@ -27,6 +29,7 @@ "prop-types": "^15.7.2", "react": "^18.2.0", "react-code-blocks": "^0.0.9-0", + "react-color": "^2.19.3", "react-datepicker": "^4.8.0", "react-device-detect": "^1.17.0", "react-dom": "^18.2.0", @@ -47,11 +50,11 @@ "yup": "^0.32.9" }, "scripts": { - "start": "react-scripts start", - "dev": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" + "start": "craco start", + "dev": "craco start", + "build": "craco build", + "test": "craco test", + "eject": "craco eject" }, "babel": { "presets": [ @@ -72,6 +75,7 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.15.8", + "@craco/craco": "^7.1.0", "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^12.8.3", diff --git a/packages/ui/src/assets/images/sharing.png b/packages/ui/src/assets/images/sharing.png new file mode 100644 index 00000000000..1e538f2efd2 Binary files /dev/null and b/packages/ui/src/assets/images/sharing.png differ diff --git a/packages/ui/src/routes/ChatbotRoutes.js b/packages/ui/src/routes/ChatbotRoutes.js new file mode 100644 index 00000000000..25d298d6854 --- /dev/null +++ b/packages/ui/src/routes/ChatbotRoutes.js @@ -0,0 +1,23 @@ +import { lazy } from 'react' + +// project imports +import Loadable from 'ui-component/loading/Loadable' +import MinimalLayout from 'layout/MinimalLayout' + +// canvas routing +const ChatbotFull = Loadable(lazy(() => import('views/chatbot'))) + +// ==============================|| CANVAS ROUTING ||============================== // + +const ChatbotRoutes = { + path: '/', + element: , + children: [ + { + path: '/chatbot/:id', + element: + } + ] +} + +export default ChatbotRoutes diff --git a/packages/ui/src/routes/index.js b/packages/ui/src/routes/index.js index 15fe4dcab63..ff8c19200ed 100644 --- a/packages/ui/src/routes/index.js +++ b/packages/ui/src/routes/index.js @@ -3,10 +3,11 @@ import { useRoutes } from 'react-router-dom' // routes import MainRoutes from './MainRoutes' import CanvasRoutes from './CanvasRoutes' +import ChatbotRoutes from './ChatbotRoutes' import config from 'config' // ==============================|| ROUTING RENDER ||============================== // export default function ThemeRoutes() { - return useRoutes([MainRoutes, CanvasRoutes], config.basename) + return useRoutes([MainRoutes, CanvasRoutes, ChatbotRoutes], config.basename) } diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index 1f4a1f93bc4..521aa9d31a9 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -13,7 +13,7 @@ import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck, // project imports import Settings from 'views/settings' import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog' -import APICodeDialog from 'ui-component/dialog/APICodeDialog' +import APICodeDialog from 'views/chatflows/APICodeDialog' // API import chatflowsApi from 'api/chatflows' @@ -107,7 +107,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl title: 'Embed in website or use as API', chatflowid: chatflow.id, chatflowApiKeyId: chatflow.apikeyid, - isFormDataRequired + isFormDataRequired, + chatbotConfig: chatflow.chatbotConfig }) setAPIDialogOpen(true) } diff --git a/packages/ui/src/views/chatbot/index.js b/packages/ui/src/views/chatbot/index.js new file mode 100644 index 00000000000..b33bec2c505 --- /dev/null +++ b/packages/ui/src/views/chatbot/index.js @@ -0,0 +1,59 @@ +import { useEffect, useState } from 'react' +import { baseURL } from 'store/constant' +import axios from 'axios' +import { FullPageChat } from 'flowise-embed-react' + +// ==============================|| Chatbot ||============================== // + +const fetchChatflow = async ({ chatflowId }) => { + const username = localStorage.getItem('username') + const password = localStorage.getItem('password') + + let chatflow = await axios + .get(`${baseURL}/api/v1/chatflows/${chatflowId}`, { auth: username && password ? { username, password } : undefined }) + .then(async function (response) { + return response.data + }) + .catch(function (error) { + console.error(error) + }) + return chatflow +} + +const ChatbotFull = () => { + const URLpath = document.location.pathname.toString().split('/') + const chatflowId = URLpath[URLpath.length - 1] === 'chatbot' ? '' : URLpath[URLpath.length - 1] + + const [chatflow, setChatflow] = useState(null) + const [chatbotTheme, setChatbotTheme] = useState({}) + + useEffect(() => { + ;(async () => { + const fetchData = async () => { + let response = await fetchChatflow({ chatflowId }) + setChatflow(response) + if (response.chatbotConfig) { + try { + setChatbotTheme(JSON.parse(response.chatbotConfig)) + } catch (e) { + console.error(e) + setChatbotTheme({}) + } + } + } + fetchData() + })() + }, [chatflowId]) + + return ( + <> + {!chatflow || chatflow.apikeyid ? ( +

Invalid Chatbot

+ ) : ( + + )} + + ) +} + +export default ChatbotFull diff --git a/packages/ui/src/ui-component/dialog/APICodeDialog.js b/packages/ui/src/views/chatflows/APICodeDialog.js similarity index 73% rename from packages/ui/src/ui-component/dialog/APICodeDialog.js rename to packages/ui/src/views/chatflows/APICodeDialog.js index e64f4bf8667..fea49909e2e 100644 --- a/packages/ui/src/ui-component/dialog/APICodeDialog.js +++ b/packages/ui/src/views/chatflows/APICodeDialog.js @@ -9,6 +9,8 @@ import { CopyBlock, atomOneDark } from 'react-code-blocks' // Project import import { Dropdown } from 'ui-component/dropdown/Dropdown' +import ShareChatbot from './ShareChatbot' +import EmbedChat from './EmbedChat' // Const import { baseURL } from 'store/constant' @@ -19,6 +21,7 @@ import pythonSVG from 'assets/images/python.svg' import javascriptSVG from 'assets/images/javascript.svg' import cURLSVG from 'assets/images/cURL.svg' import EmbedSVG from 'assets/images/embed.svg' +import ShareChatbotSVG from 'assets/images/sharing.png' // API import apiKeyApi from 'api/apikey' @@ -119,77 +122,19 @@ const getConfigExamplesForCurl = (configData, bodyType) => { return finalStr } -const embedCode = (chatflowid) => { - return `` -} - -const embedCodeCustomization = (chatflowid) => { - return `` -} - const APICodeDialog = ({ show, dialogProps, onCancel }) => { const portalElement = document.getElementById('portal') const navigate = useNavigate() const dispatch = useDispatch() - const codes = ['Embed', 'Python', 'JavaScript', 'cURL'] + + const codes = ['Embed', 'Python', 'JavaScript', 'cURL', 'Share Chatbot'] const [value, setValue] = useState(0) const [keyOptions, setKeyOptions] = useState([]) const [apiKeys, setAPIKeys] = useState([]) const [chatflowApiKeyId, setChatflowApiKeyId] = useState('') const [selectedApiKey, setSelectedApiKey] = useState({}) const [checkboxVal, setCheckbox] = useState(false) - const [embedChatCheckboxVal, setEmbedChatCheckbox] = useState(false) + const [chatbotConfig, setChatbotConfig] = useState(null) const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys) const updateChatflowApi = useApi(chatflowsApi.updateChatflow) @@ -203,10 +148,6 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { } } - const onCheckBoxEmbedChatChanged = (newVal) => { - setEmbedChatCheckbox(newVal) - } - const onApiKeySelected = (keyValue) => { if (keyValue === 'addnewkey') { navigate('/apikey') @@ -265,8 +206,6 @@ query({"question": "Hey, how are you?"}).then((response) => { return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\ -X POST \\ -d '{"question": "Hey, how are you?"}'` - } else if (codeLang === 'Embed') { - return embedCode(dialogProps.chatflowid) } return '' } @@ -309,8 +248,6 @@ query({"question": "Hey, how are you?"}).then((response) => { -X POST \\ -d '{"question": "Hey, how are you?"}' \\ -H "Authorization: Bearer ${selectedApiKey?.apiKey}"` - } else if (codeLang === 'Embed') { - return embedCode(dialogProps.chatflowid) } return '' } @@ -318,7 +255,7 @@ query({"question": "Hey, how are you?"}).then((response) => { const getLang = (codeLang) => { if (codeLang === 'Python') { return 'python' - } else if (codeLang === 'JavaScript' || codeLang === 'Embed') { + } else if (codeLang === 'JavaScript') { return 'javascript' } else if (codeLang === 'cURL') { return 'bash' @@ -335,6 +272,8 @@ query({"question": "Hey, how are you?"}).then((response) => { return EmbedSVG } else if (codeLang === 'cURL') { return cURLSVG + } else if (codeLang === 'Share Chatbot') { + return ShareChatbotSVG } return pythonSVG } @@ -552,6 +491,12 @@ query({ setChatflowApiKeyId(dialogProps.chatflowApiKeyId) setSelectedApiKey(getAllAPIKeysApi.data.find((key) => key.id === dialogProps.chatflowApiKeyId)) } + + if (dialogProps.chatbotConfig) { + setChatbotConfig(JSON.parse(dialogProps.chatbotConfig)) + } else { + setChatbotConfig(null) + } } }, [dialogProps, getAllAPIKeysApi.data]) @@ -593,92 +538,71 @@ query({ ))} - {value !== 0 && ( -
- onApiKeySelected(newValue)} - value={dialogProps.chatflowApiKeyId ?? chatflowApiKeyId ?? 'Choose an API key'} - /> -
- )} +
+ onApiKeySelected(newValue)} + value={dialogProps.chatflowApiKeyId ?? chatflowApiKeyId ?? 'Choose an API key'} + /> +
{codes.map((codeLang, index) => ( - {value === 0 && ( + {(codeLang === 'Embed' || codeLang === 'Share Chatbot') && chatflowApiKeyId && ( <> - - Paste this anywhere in the {``} tag of your html file. -

- You can also specify a  - - version - - : {`https://cdn.jsdelivr.net/npm/flowise-embed@/dist/web.js`} -

-
-
+

You cannot use API key while embedding/sharing chatbot.

+

+ Please select "No Authorization" from the dropdown at the top right corner. +

)} - - {value !== 0 && } - {value !== 0 && checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && ( + {codeLang === 'Embed' && !chatflowApiKeyId && } + {codeLang !== 'Embed' && codeLang !== 'Share Chatbot' && ( <> - + + {checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && ( + <> + + + + )} + {getIsChatflowStreamingApi.data?.isStreaming && ( +

+ Read  + + here + +  on how to stream response back to application +

+ )} )} - {value === 0 && ( - - )} - {value === 0 && embedChatCheckboxVal && ( - - )} - {value !== 0 && getIsChatflowStreamingApi.data?.isStreaming && ( -

- Read  - - here - -  on how to stream response back to application -

+ {codeLang === 'Share Chatbot' && !chatflowApiKeyId && ( + )}
))} diff --git a/packages/ui/src/views/chatflows/EmbedChat.js b/packages/ui/src/views/chatflows/EmbedChat.js new file mode 100644 index 00000000000..c6385efbad9 --- /dev/null +++ b/packages/ui/src/views/chatflows/EmbedChat.js @@ -0,0 +1,324 @@ +import { useState } from 'react' +import PropTypes from 'prop-types' + +import { Tabs, Tab, Box } from '@mui/material' +import { CopyBlock, atomOneDark } from 'react-code-blocks' + +// Project import +import { CheckboxInput } from 'ui-component/checkbox/Checkbox' + +// Const +import { baseURL } from 'store/constant' + +function TabPanel(props) { + const { children, value, index, ...other } = props + return ( + + ) +} + +TabPanel.propTypes = { + children: PropTypes.node, + index: PropTypes.number.isRequired, + value: PropTypes.number.isRequired +} + +function a11yProps(index) { + return { + id: `attachment-tab-${index}`, + 'aria-controls': `attachment-tabpanel-${index}` + } +} + +const embedPopupHtmlCode = (chatflowid) => { + return `` +} + +const embedPopupReactCode = (chatflowid) => { + return `import { BubbleChat } from 'flowise-embed-react' + +const App = () => { + return ( + + ); +};` +} + +const embedFullpageHtmlCode = (chatflowid) => { + return ` +` +} + +const embedFullpageReactCode = (chatflowid) => { + return `import { FullPageChat } from "flowise-embed-react" + +const App = () => { + return ( + + ); +};` +} + +const buttonConfig = (isReact = false) => { + return isReact + ? `button: { + backgroundColor: "#3B81F6", + right: 20, + bottom: 20, + size: "medium", + iconColor: "white", + customIconSrc: "https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/svg/google-messages.svg", + }` + : `button: { + backgroundColor: "#3B81F6", + right: 20, + bottom: 20, + size: "medium", + iconColor: "white", + customIconSrc: "https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/svg/google-messages.svg", + }` +} + +const chatwindowConfig = (isReact = false) => { + return isReact + ? `chatWindow: { + welcomeMessage: "Hello! This is custom welcome message", + backgroundColor: "#ffffff", + height: 700, + width: 400, + fontSize: 16, + poweredByTextColor: "#303235", + botMessage: { + backgroundColor: "#f7f8ff", + textColor: "#303235", + showAvatar: true, + avatarSrc: "https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/parroticon.png", + }, + userMessage: { + backgroundColor: "#3B81F6", + textColor: "#ffffff", + showAvatar: true, + avatarSrc: "https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/usericon.png", + }, + textInput: { + placeholder: "Type your question", + backgroundColor: "#ffffff", + textColor: "#303235", + sendButtonColor: "#3B81F6", + } + }` + : `chatWindow: { + welcomeMessage: "Hello! This is custom welcome message", + backgroundColor: "#ffffff", + height: 700, + width: 400, + fontSize: 16, + poweredByTextColor: "#303235", + botMessage: { + backgroundColor: "#f7f8ff", + textColor: "#303235", + showAvatar: true, + avatarSrc: "https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/parroticon.png", + }, + userMessage: { + backgroundColor: "#3B81F6", + textColor: "#ffffff", + showAvatar: true, + avatarSrc: "https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/usericon.png", + }, + textInput: { + placeholder: "Type your question", + backgroundColor: "#ffffff", + textColor: "#303235", + sendButtonColor: "#3B81F6", + } + }` +} + +const embedPopupHtmlCodeCustomization = (chatflowid) => { + return `` +} + +const embedPopupReactCodeCustomization = (chatflowid) => { + return `import { BubbleChat } from 'flowise-embed-react' + +const App = () => { + return ( + + ); +};` +} + +const embedFullpageHtmlCodeCustomization = (chatflowid) => { + return ` +` +} + +const embedFullpageReactCodeCustomization = (chatflowid) => { + return `import { FullPageChat } from "flowise-embed-react" + +const App = () => { + return ( + + ); +};` +} + +const EmbedChat = ({ chatflowid }) => { + const codes = ['Popup Html', 'Fullpage Html', 'Popup React', 'Fullpage React'] + const [value, setValue] = useState(0) + const [embedChatCheckboxVal, setEmbedChatCheckbox] = useState(false) + + const onCheckBoxEmbedChatChanged = (newVal) => { + setEmbedChatCheckbox(newVal) + } + + const handleChange = (event, newValue) => { + setValue(newValue) + } + + const getCode = (codeLang) => { + switch (codeLang) { + case 'Popup Html': + return embedPopupHtmlCode(chatflowid) + case 'Fullpage Html': + return embedFullpageHtmlCode(chatflowid) + case 'Popup React': + return embedPopupReactCode(chatflowid) + case 'Fullpage React': + return embedFullpageReactCode(chatflowid) + default: + return '' + } + } + + const getCodeCustomization = (codeLang) => { + switch (codeLang) { + case 'Popup Html': + return embedPopupHtmlCodeCustomization(chatflowid) + case 'Fullpage Html': + return embedFullpageHtmlCodeCustomization(chatflowid) + case 'Popup React': + return embedPopupReactCodeCustomization(chatflowid) + case 'Fullpage React': + return embedFullpageReactCodeCustomization(chatflowid) + default: + return '' + } + } + + return ( + <> +
+
+ + {codes.map((codeLang, index) => ( + + ))} + +
+
+
+ {codes.map((codeLang, index) => ( + + {(value === 0 || value === 1) && ( + <> + + Paste this anywhere in the {``} tag of your html file. +

+ You can also specify a  + + version + + : {`https://cdn.jsdelivr.net/npm/flowise-embed@/dist/web.js`} +

+
+
+ + )} + + + + + {embedChatCheckboxVal && ( + + )} +
+ ))} + + ) +} + +EmbedChat.propTypes = { + chatflowid: PropTypes.string +} + +export default EmbedChat diff --git a/packages/ui/src/views/chatflows/ShareChatbot.js b/packages/ui/src/views/chatflows/ShareChatbot.js new file mode 100644 index 00000000000..dffecf5b7dc --- /dev/null +++ b/packages/ui/src/views/chatflows/ShareChatbot.js @@ -0,0 +1,420 @@ +import PropTypes from 'prop-types' +import { useState } from 'react' +import { useDispatch } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import { SketchPicker } from 'react-color' + +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' + +// Icons +import { IconX, IconCopy, IconArrowUpRightCircle } from '@tabler/icons' + +// 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 = ({ chatflowid, chatbotConfig }) => { + const dispatch = useDispatch() + const theme = useTheme() + + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [welcomeMessage, setWelcomeMessage] = useState(chatbotConfig?.welcomeMessage ?? '') + 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 [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: {} + } + if (welcomeMessage) obj.welcomeMessage = welcomeMessage + 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 + + 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) => ( + + ) + } + }) + } + } catch (error) { + console.error(error) + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to save Chatbot Configuration: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + } + } + + 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 'welcomeMessage': + setWelcomeMessage(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 + } + } + + const colorField = (color, fieldName, fieldLabel) => { + return ( + +
+ {fieldLabel} + { + setSelectedColorConfig(fieldName) + setSketchPickerColor(color ?? '#ffffff') + setColorAnchorEl(event.currentTarget) + }} + > +
+
+ ) + } + + const booleanField = (value, fieldName, fieldLabel) => { + return ( + +
+ {fieldLabel} + { + onBooleanChanged(event.target.checked, fieldName) + }} + /> +
+
+ ) + } + + const textField = (message, fieldName, fieldLabel, fieldType = 'string', placeholder = '') => { + return ( + +
+ {fieldLabel} + { + onTextChanged(e.target.value, fieldName) + }} + /> +
+
+ ) + } + + return ( + <> + + + {`${baseURL}/chatbot/${chatflowid}`} + + { + navigator.clipboard.writeText(`${baseURL}/chatbot/${chatflowid}`) + setCopyAnchorEl(event.currentTarget) + setTimeout(() => { + handleCloseCopyPopOver() + }, 1500) + }} + > + + + window.open(`${baseURL}/chatbot/${chatflowid}`, '_blank')}> + + + + {textField(welcomeMessage, 'welcomeMessage', 'Welcome Message', 'string', 'Hello! This is custom welcome message')} + {colorField(backgroundColor, 'backgroundColor', 'Background Color')} + {textField(fontSize, 'fontSize', 'Font Size', 'number')} + {colorField(poweredByTextColor, 'poweredByTextColor', 'PoweredBy TextColor')} + + {/*BOT Message*/} + + Bot Message + + {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*/} + + User Message + + {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*/} + + Text Input + + {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')} + + onSave()}> + Save Changes + + + onColorSelected(color.hex)} /> + + + + Copied! + + + + ) +} + +ShareChatbot.propTypes = { + chatflowid: PropTypes.string, + chatbotConfig: PropTypes.object +} + +export default ShareChatbot