From 7141401e265a7dfa8261970c430470683697eeeb Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 29 Jun 2023 23:47:20 +0100 Subject: [PATCH] fix bug where share chatflow is not able to open on any other browser --- packages/server/.env.example | 1 + packages/server/src/Interface.ts | 10 +- packages/server/src/entity/ChatFlow.ts | 8 +- packages/server/src/entity/ChatMessage.ts | 2 +- packages/server/src/entity/Tool.ts | 4 +- packages/server/src/index.ts | 12 +- packages/server/src/utils/index.ts | 2 +- packages/ui/src/api/chatflows.js | 3 + packages/ui/src/views/canvas/CanvasHeader.js | 8 +- packages/ui/src/views/canvas/index.js | 2 +- packages/ui/src/views/chatbot/index.js | 122 ++++++++++++------ .../ui/src/views/chatflows/APICodeDialog.js | 11 +- .../ui/src/views/chatflows/ShareChatbot.js | 71 ++++++++-- 13 files changed, 183 insertions(+), 73 deletions(-) diff --git a/packages/server/.env.example b/packages/server/.env.example index 3d524e5c..80fbc3be 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -3,4 +3,5 @@ PORT=3000 # FLOWISE_PASSWORD=1234 # DEBUG=true # DATABASE_PATH=/your_database_path/.flowise +# APIKEY_PATH=/your_api_key_path/.flowise # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 9473638f..9c47405c 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -9,10 +9,10 @@ export interface IChatFlow { id: string name: string flowData: string - apikeyid: string - deployed: boolean + isPublic: boolean updatedDate: Date createdDate: Date + apikeyid?: string chatbotConfig?: string } @@ -22,7 +22,7 @@ export interface IChatMessage { content: string chatflowid: string createdDate: Date - sourceDocuments: string + sourceDocuments?: string } export interface ITool { @@ -30,8 +30,8 @@ export interface ITool { name: string description: string color: string - schema: string - func: string + schema?: string + func?: string updatedDate: Date createdDate: Date } diff --git a/packages/server/src/entity/ChatFlow.ts b/packages/server/src/entity/ChatFlow.ts index 910272ad..400e0517 100644 --- a/packages/server/src/entity/ChatFlow.ts +++ b/packages/server/src/entity/ChatFlow.ts @@ -13,11 +13,11 @@ export class ChatFlow implements IChatFlow { @Column() flowData: string - @Column({ nullable: true }) - apikeyid: string - @Column() - deployed: boolean + isPublic: boolean + + @Column({ nullable: true }) + apikeyid?: string @Column({ nullable: true }) chatbotConfig?: string diff --git a/packages/server/src/entity/ChatMessage.ts b/packages/server/src/entity/ChatMessage.ts index 236dc5f9..3e4e41d2 100644 --- a/packages/server/src/entity/ChatMessage.ts +++ b/packages/server/src/entity/ChatMessage.ts @@ -18,7 +18,7 @@ export class ChatMessage implements IChatMessage { content: string @Column({ nullable: true }) - sourceDocuments: string + sourceDocuments?: string @CreateDateColumn() createdDate: Date diff --git a/packages/server/src/entity/Tool.ts b/packages/server/src/entity/Tool.ts index d547374c..307e8d23 100644 --- a/packages/server/src/entity/Tool.ts +++ b/packages/server/src/entity/Tool.ts @@ -17,10 +17,10 @@ export class Tool implements ITool { color: string @Column({ nullable: true }) - schema: string + schema?: string @Column({ nullable: true }) - func: string + func?: string @CreateDateColumn() createdDate: Date diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 65bfef23..e13934b2 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -92,7 +92,7 @@ export class App { const basicAuthMiddleware = basicAuth({ users: { [username]: password } }) - const whitelistURLs = ['/api/v1/prediction/', '/api/v1/node-icon/', '/api/v1/chatflows-streaming'] + const whitelistURLs = ['/api/v1/public-chatflows', '/api/v1/prediction/', '/api/v1/node-icon/', '/api/v1/chatflows-streaming'] this.app.use((req, res, next) => { if (req.url.includes('/api/v1/')) { whitelistURLs.some((url) => req.url.includes(url)) ? next() : basicAuthMiddleware(req, res, next) @@ -186,6 +186,16 @@ export class App { return res.status(404).send(`Chatflow ${req.params.id} not found`) }) + // Get specific chatflow via id (PUBLIC endpoint, used when sharing chatbot link) + this.app.get('/api/v1/public-chatflows/:id', async (req: Request, res: Response) => { + const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ + id: req.params.id + }) + if (chatflow && chatflow.isPublic) return res.json(chatflow) + else if (chatflow && !chatflow.isPublic) return res.status(401).send(`Unauthorized`) + return res.status(404).send(`Chatflow ${req.params.id} not found`) + }) + // Save chatflow this.app.post('/api/v1/chatflows', async (req: Request, res: Response) => { const body = req.body diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index e3005c7b..3601c77d 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -463,7 +463,7 @@ export const isSameOverrideConfig = ( * @returns {string} */ export const getAPIKeyPath = (): string => { - return path.join(__dirname, '..', '..', 'api.json') + return process.env.APIKEY_PATH ? path.join(process.env.APIKEY_PATH, 'api.json') : path.join(__dirname, '..', '..', 'api.json') } /** diff --git a/packages/ui/src/api/chatflows.js b/packages/ui/src/api/chatflows.js index 1cd1ebb0..8810b5a5 100644 --- a/packages/ui/src/api/chatflows.js +++ b/packages/ui/src/api/chatflows.js @@ -4,6 +4,8 @@ const getAllChatflows = () => client.get('/chatflows') const getSpecificChatflow = (id) => client.get(`/chatflows/${id}`) +const getSpecificChatflowFromPublicEndpoint = (id) => client.get(`/public-chatflows/${id}`) + const createNewChatflow = (body) => client.post(`/chatflows`, body) const updateChatflow = (id, body) => client.put(`/chatflows/${id}`, body) @@ -15,6 +17,7 @@ const getIsChatflowStreaming = (id) => client.get(`/chatflows-streaming/${id}`) export default { getAllChatflows, getSpecificChatflow, + getSpecificChatflowFromPublicEndpoint, createNewChatflow, updateChatflow, deleteChatflow, diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index 521aa9d3..1c1e5212 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types' import { useNavigate } from 'react-router-dom' -import { useSelector } from 'react-redux' +import { useSelector, useDispatch } from 'react-redux' import { useEffect, useRef, useState } from 'react' // material-ui @@ -24,11 +24,13 @@ import useApi from 'hooks/useApi' // utils import { generateExportFlowData } from 'utils/genericHelper' import { uiBaseURL } from 'store/constant' +import { SET_CHATFLOW } from 'store/actions' // ==============================|| CANVAS HEADER ||============================== // const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFlow }) => { const theme = useTheme() + const dispatch = useDispatch() const navigate = useNavigate() const flowNameRef = useRef() const settingsRef = useRef() @@ -107,8 +109,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl title: 'Embed in website or use as API', chatflowid: chatflow.id, chatflowApiKeyId: chatflow.apikeyid, - isFormDataRequired, - chatbotConfig: chatflow.chatbotConfig + isFormDataRequired }) setAPIDialogOpen(true) } @@ -126,6 +127,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl useEffect(() => { if (updateChatflowApi.data) { setFlowName(updateChatflowApi.data.name) + dispatch({ type: SET_CHATFLOW, chatflow: updateChatflowApi.data }) } setEditingFlowName(false) diff --git a/packages/ui/src/views/canvas/index.js b/packages/ui/src/views/canvas/index.js index 2d71f03a..03098963 100644 --- a/packages/ui/src/views/canvas/index.js +++ b/packages/ui/src/views/canvas/index.js @@ -201,7 +201,7 @@ const Canvas = () => { if (!chatflow.id) { const newChatflowBody = { name: chatflowName, - deployed: false, + isPublic: false, flowData } createNewChatflowApi.request(newChatflowBody) diff --git a/packages/ui/src/views/chatbot/index.js b/packages/ui/src/views/chatbot/index.js index b33bec2c..f29c35ee 100644 --- a/packages/ui/src/views/chatbot/index.js +++ b/packages/ui/src/views/chatbot/index.js @@ -1,57 +1,107 @@ import { useEffect, useState } from 'react' -import { baseURL } from 'store/constant' -import axios from 'axios' import { FullPageChat } from 'flowise-embed-react' +import { useNavigate } from 'react-router-dom' + +// Project import +import LoginDialog from 'ui-component/dialog/LoginDialog' + +// API +import chatflowsApi from 'api/chatflows' + +// Hooks +import useApi from 'hooks/useApi' + +//Const +import { baseURL } from 'store/constant' // ==============================|| 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 navigate = useNavigate() const [chatflow, setChatflow] = useState(null) const [chatbotTheme, setChatbotTheme] = useState({}) + const [loginDialogOpen, setLoginDialogOpen] = useState(false) + const [loginDialogProps, setLoginDialogProps] = useState({}) + const [isLoading, setLoading] = useState(true) + + const getSpecificChatflowFromPublicApi = useApi(chatflowsApi.getSpecificChatflowFromPublicEndpoint) + const getSpecificChatflowApi = useApi(chatflowsApi.getSpecificChatflow) + + const onLoginClick = (username, password) => { + localStorage.setItem('username', username) + localStorage.setItem('password', password) + navigate(0) + } 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({}) - } + getSpecificChatflowFromPublicApi.request(chatflowId) + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (getSpecificChatflowFromPublicApi.error) { + if (getSpecificChatflowFromPublicApi.error?.response?.status === 401) { + if (localStorage.getItem('username') && localStorage.getItem('password')) { + getSpecificChatflowApi.request(chatflowId) + } else { + setLoginDialogProps({ + title: 'Login', + confirmButtonName: 'Login' + }) + setLoginDialogOpen(true) } } - fetchData() - })() - }, [chatflowId]) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getSpecificChatflowFromPublicApi.error]) + + useEffect(() => { + if (getSpecificChatflowApi.error) { + if (getSpecificChatflowApi.error?.response?.status === 401) { + setLoginDialogProps({ + title: 'Login', + confirmButtonName: 'Login' + }) + setLoginDialogOpen(true) + } + } + }, [getSpecificChatflowApi.error]) + + useEffect(() => { + if (getSpecificChatflowFromPublicApi.data || getSpecificChatflowApi.data) { + const chatflowData = getSpecificChatflowFromPublicApi.data || getSpecificChatflowApi.data + setChatflow(chatflowData) + if (chatflowData.chatbotConfig) { + try { + setChatbotTheme(JSON.parse(chatflowData.chatbotConfig)) + } catch (e) { + console.error(e) + setChatbotTheme({}) + } + } + } + }, [getSpecificChatflowFromPublicApi.data, getSpecificChatflowApi.data]) + + useEffect(() => { + setLoading(getSpecificChatflowFromPublicApi.loading || getSpecificChatflowApi.loading) + }, [getSpecificChatflowFromPublicApi.loading, getSpecificChatflowApi.loading]) return ( <> - {!chatflow || chatflow.apikeyid ? ( -

Invalid Chatbot

- ) : ( - - )} + {!isLoading ? ( + <> + {!chatflow || chatflow.apikeyid ? ( +

Invalid Chatbot

+ ) : ( + + )} + + + ) : null} ) } diff --git a/packages/ui/src/views/chatflows/APICodeDialog.js b/packages/ui/src/views/chatflows/APICodeDialog.js index fea49909..5e32c1d4 100644 --- a/packages/ui/src/views/chatflows/APICodeDialog.js +++ b/packages/ui/src/views/chatflows/APICodeDialog.js @@ -134,7 +134,6 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { const [chatflowApiKeyId, setChatflowApiKeyId] = useState('') const [selectedApiKey, setSelectedApiKey] = useState({}) const [checkboxVal, setCheckbox] = useState(false) - const [chatbotConfig, setChatbotConfig] = useState(null) const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys) const updateChatflowApi = useApi(chatflowsApi.updateChatflow) @@ -491,12 +490,6 @@ 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]) @@ -601,9 +594,7 @@ query({ )} )} - {codeLang === 'Share Chatbot' && !chatflowApiKeyId && ( - - )} + {codeLang === 'Share Chatbot' && !chatflowApiKeyId && } ))} diff --git a/packages/ui/src/views/chatflows/ShareChatbot.js b/packages/ui/src/views/chatflows/ShareChatbot.js index dffecf5b..51e12e54 100644 --- a/packages/ui/src/views/chatflows/ShareChatbot.js +++ b/packages/ui/src/views/chatflows/ShareChatbot.js @@ -1,7 +1,6 @@ -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 { useDispatch, useSelector } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions' import { SketchPicker } from 'react-color' import { Box, Typography, Button, Switch, OutlinedInput, Popover, Stack, IconButton } from '@mui/material' @@ -9,6 +8,7 @@ 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' @@ -41,15 +41,20 @@ const defaultConfig = { } } -const ShareChatbot = ({ chatflowid, chatbotConfig }) => { +const ShareChatbot = () => { 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 [welcomeMessage, setWelcomeMessage] = useState(chatbotConfig?.welcomeMessage ?? '') const [backgroundColor, setBackgroundColor] = useState(chatbotConfig?.backgroundColor ?? defaultConfig.backgroundColor) const [fontSize, setFontSize] = useState(chatbotConfig?.fontSize ?? defaultConfig.fontSize) @@ -141,6 +146,44 @@ const ShareChatbot = ({ chatflowid, chatbotConfig }) => { ) } }) + dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) + } + } 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 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) => ( + + ) + } + }) + dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) } } catch (error) { console.error(error) @@ -328,6 +371,21 @@ const ShareChatbot = ({ chatflowid, chatbotConfig }) => { window.open(`${baseURL}/chatbot/${chatflowid}`, '_blank')}> +
+
+ { + setChatflowIsPublic(event.target.checked) + onSwitchChange(event.target.checked) + }} + /> + Make Public + +
{textField(welcomeMessage, 'welcomeMessage', 'Welcome Message', 'string', 'Hello! This is custom welcome message')} {colorField(backgroundColor, 'backgroundColor', 'Background Color')} @@ -412,9 +470,4 @@ const ShareChatbot = ({ chatflowid, chatbotConfig }) => { ) } -ShareChatbot.propTypes = { - chatflowid: PropTypes.string, - chatbotConfig: PropTypes.object -} - export default ShareChatbot