From 0f464fddb8c787334385320db768f991fbc6bb2d Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 17 Nov 2023 07:22:08 +0530 Subject: [PATCH 01/11] Conversation Starters: Initial commit --- .../cards/StarterConversationCard.js | 49 ++++++++++++++++ .../ui/src/views/chatflows/Configuration.js | 57 ++++++++++++++----- .../ui/src/views/chatmessage/ChatMessage.js | 15 ++++- 3 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 packages/ui/src/ui-component/cards/StarterConversationCard.js diff --git a/packages/ui/src/ui-component/cards/StarterConversationCard.js b/packages/ui/src/ui-component/cards/StarterConversationCard.js new file mode 100644 index 00000000..946c4c11 --- /dev/null +++ b/packages/ui/src/ui-component/cards/StarterConversationCard.js @@ -0,0 +1,49 @@ +import Chip from '@mui/material/Chip' +import Box from '@mui/material/Box' +import PropTypes from 'prop-types' +import { MenuItem, Select } from '@mui/material' + +const StarterConversationCard = ({ isGrid, chipsData, onChipClick }) => { + if (isGrid) { + const chipStyle = { + margin: '5px', + width: 'calc(50% - 10px)' + } + + return ( + + {chipsData.map((chipLabel, index) => ( + onChipClick(chipLabel)} /> + ))} + + ) + } else { + return ( + + ) + } +} + +StarterConversationCard.propTypes = { + isGrid: PropTypes.bool, + chipsData: PropTypes.arrayOf(PropTypes.string), + onChipClick: PropTypes.func +} + +export default StarterConversationCard diff --git a/packages/ui/src/views/chatflows/Configuration.js b/packages/ui/src/views/chatflows/Configuration.js index d569020b..9dc74090 100644 --- a/packages/ui/src/views/chatflows/Configuration.js +++ b/packages/ui/src/views/chatflows/Configuration.js @@ -4,6 +4,10 @@ import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackba import PropTypes from 'prop-types' import { Box, Typography, Button, OutlinedInput } from '@mui/material' +import Accordion from '@mui/material/Accordion' +import AccordionSummary from '@mui/material/AccordionSummary' +import AccordionDetails from '@mui/material/AccordionDetails' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' // Project import import { StyledButton } from 'ui-component/button/StyledButton' @@ -32,6 +36,10 @@ const Configuration = () => { const [limitMax, setLimitMax] = useState(apiConfig?.rateLimit?.limitMax ?? '') const [limitDuration, setLimitDuration] = useState(apiConfig?.rateLimit?.limitDuration ?? '') const [limitMsg, setLimitMsg] = useState(apiConfig?.rateLimit?.limitMsg ?? '') + const [prompt1, setPrompt1] = useState(apiConfig?.prompt?.prompt1 ?? '') + const [prompt2, setPrompt2] = useState(apiConfig?.prompt?.prompt2 ?? '') + const [prompt3, setPrompt3] = useState(apiConfig?.prompt?.prompt3 ?? '') + const [prompt4, setPrompt4] = useState(apiConfig?.prompt?.prompt4 ?? '') const formatObj = () => { const obj = { @@ -111,7 +119,7 @@ const Configuration = () => { return (
- {fieldLabel} + {fieldLabel && {fieldLabel}} { return ( <> - {/*Rate Limit*/} - - Rate Limit{' '} - Rate Limit Setup Guide to set up Rate Limit correctly in your hosting environment.' - } - /> - - {textField(limitMax, 'limitMax', 'Message Limit per Duration', 'number')} - {textField(limitDuration, 'limitDuration', 'Duration in Second', 'number')} - {textField(limitMsg, 'limitMsg', 'Limit Message', 'string')} + + } aria-controls='panel1a-content' id='panel1a-header'> + + Rate Limit{' '} + Rate Limit Setup Guide to set up Rate Limit correctly in your hosting environment.' + } + /> + + + + + {/*Rate Limit*/} + {textField(limitMax, 'limitMax', 'Message Limit per Duration', 'number')} + {textField(limitDuration, 'limitDuration', 'Duration in Second', 'number')} + {textField(limitMsg, 'limitMsg', 'Limit Message', 'string')} + + + + + } aria-controls='panel2a-content' id='panel2a-header'> + Conversation Starters + + + + {textField(prompt1, 'prompt1', 'Starter Prompts', 'string')} + {textField(prompt2, 'prompt2', '', 'string')} + {textField(prompt3, 'prompt3', '', 'string')} + {textField(prompt4, 'prompt4', '', 'string')} + + + onSave()}> Save Changes diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 0cf5695b..d047a3aa 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -32,6 +32,7 @@ import { baseURL, maxScroll } from 'store/constant' import robotPNG from 'assets/images/robot.png' import userPNG from 'assets/images/account.png' import { isValidURL, removeDuplicateURL } from 'utils/genericHelper' +import StarterConversationCard from '../../ui-component/cards/StarterConversationCard' export const ChatMessage = ({ open, chatflowid, isDialog }) => { const theme = useTheme() @@ -103,9 +104,14 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { }, 100) } + const handlePromptClick = async (prompt) => { + setUserInput(prompt) + await handleSubmit() + } + // Handle form submission const handleSubmit = async (e) => { - e.preventDefault() + if (e) e.preventDefault() if (userInput.trim() === '') { return @@ -369,6 +375,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
+ {messages && messages.length === 1 && ( + + )} Date: Tue, 21 Nov 2023 17:15:39 +0530 Subject: [PATCH 02/11] Conversation Starters: Initial Implementation --- .../server/src/database/entities/ChatFlow.ts | 3 + ...00565042576-AddStarterPromptsToChatFlow.ts | 12 ++ .../src/database/migrations/mysql/index.ts | 4 +- ...00565042576-AddStarterPromptsToChatFlow.ts | 11 + .../src/database/migrations/postgres/index.ts | 4 +- ...00565042576-AddStarterPromptsToChatFlow.ts | 11 + .../src/database/migrations/sqlite/index.ts | 4 +- packages/ui/src/menu-items/settings.js | 11 +- .../cards/StarterConversationCard.css | 16 ++ .../cards/StarterConversationCard.js | 53 ++--- .../dialog/ConversationStarterDialog.js | 188 ++++++++++++++++++ packages/ui/src/views/canvas/CanvasHeader.js | 14 ++ .../ui/src/views/chatflows/Configuration.js | 57 ++---- .../ui/src/views/chatmessage/ChatMessage.js | 24 ++- 14 files changed, 319 insertions(+), 93 deletions(-) create mode 100644 packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts create mode 100644 packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts create mode 100644 packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts create mode 100644 packages/ui/src/ui-component/cards/StarterConversationCard.css create mode 100644 packages/ui/src/ui-component/dialog/ConversationStarterDialog.js diff --git a/packages/server/src/database/entities/ChatFlow.ts b/packages/server/src/database/entities/ChatFlow.ts index 376a100b..626f743a 100644 --- a/packages/server/src/database/entities/ChatFlow.ts +++ b/packages/server/src/database/entities/ChatFlow.ts @@ -28,6 +28,9 @@ export class ChatFlow implements IChatFlow { @Column({ nullable: true, type: 'text' }) apiConfig?: string + @Column({ nullable: true, type: 'text' }) + starterPrompt?: string + @Column({ nullable: true, type: 'text' }) analytic?: string diff --git a/packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts b/packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts new file mode 100644 index 00000000..1f7a3c54 --- /dev/null +++ b/packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts @@ -0,0 +1,12 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddStarterPrompt1700565042576 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const columnExists = await queryRunner.hasColumn('chat_flow', 'starterPrompt') + if (!columnExists) queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`starterPrompt\` TEXT;`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`chat_flow\` DROP COLUMN \`starterPrompt\`;`) + } +} diff --git a/packages/server/src/database/migrations/mysql/index.ts b/packages/server/src/database/migrations/mysql/index.ts index 4b7b8a95..f38a9cfe 100644 --- a/packages/server/src/database/migrations/mysql/index.ts +++ b/packages/server/src/database/migrations/mysql/index.ts @@ -8,6 +8,7 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694658767766 } from './1694658767766-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' +import { AddStarterPrompt1700565042576 } from './1700565042576-AddStarterPromptsToChatFlow' export const mysqlMigrations = [ Init1693840429259, @@ -19,5 +20,6 @@ export const mysqlMigrations = [ AddAnalytic1694432361423, AddChatHistory1694658767766, AddAssistantEntity1699325775451, - AddUsedToolsToChatMessage1699481607341 + AddUsedToolsToChatMessage1699481607341, + AddStarterPrompt1700565042576 ] diff --git a/packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts b/packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts new file mode 100644 index 00000000..ac2694b9 --- /dev/null +++ b/packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts @@ -0,0 +1,11 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddStarterPrompt1700565042576 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN IF NOT EXISTS "starterPrompt" TEXT;`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_flow" DROP COLUMN "starterPrompt";`) + } +} diff --git a/packages/server/src/database/migrations/postgres/index.ts b/packages/server/src/database/migrations/postgres/index.ts index 75562c0b..e9018265 100644 --- a/packages/server/src/database/migrations/postgres/index.ts +++ b/packages/server/src/database/migrations/postgres/index.ts @@ -8,6 +8,7 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694658756136 } from './1694658756136-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' +import { AddStarterPrompt1700565042576 } from './1700565042576-AddStarterPromptsToChatFlow' export const postgresMigrations = [ Init1693891895163, @@ -19,5 +20,6 @@ export const postgresMigrations = [ AddAnalytic1694432361423, AddChatHistory1694658756136, AddAssistantEntity1699325775451, - AddUsedToolsToChatMessage1699481607341 + AddUsedToolsToChatMessage1699481607341, + AddStarterPrompt1700565042576 ] diff --git a/packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts b/packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts new file mode 100644 index 00000000..3d180797 --- /dev/null +++ b/packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts @@ -0,0 +1,11 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddStarterPrompt1700565042576 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN "starterPrompt" TEXT;`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_flow" DROP COLUMN "starterPrompt";`) + } +} diff --git a/packages/server/src/database/migrations/sqlite/index.ts b/packages/server/src/database/migrations/sqlite/index.ts index 4a14fc40..414d755e 100644 --- a/packages/server/src/database/migrations/sqlite/index.ts +++ b/packages/server/src/database/migrations/sqlite/index.ts @@ -8,6 +8,7 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694657778173 } from './1694657778173-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' +import { AddStarterPrompt1700565042576 } from './1700565042576-AddStarterPromptsToChatFlow' export const sqliteMigrations = [ Init1693835579790, @@ -19,5 +20,6 @@ export const sqliteMigrations = [ AddAnalytic1694432361423, AddChatHistory1694657778173, AddAssistantEntity1699325775451, - AddUsedToolsToChatMessage1699481607341 + AddUsedToolsToChatMessage1699481607341, + AddStarterPrompt1700565042576 ] diff --git a/packages/ui/src/menu-items/settings.js b/packages/ui/src/menu-items/settings.js index 307bd0bd..9224b78f 100644 --- a/packages/ui/src/menu-items/settings.js +++ b/packages/ui/src/menu-items/settings.js @@ -1,8 +1,8 @@ // assets -import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage } from '@tabler/icons' +import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage, IconPictureInPictureOff } from '@tabler/icons' // constant -const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage } +const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage, IconPictureInPictureOff } // ==============================|| SETTINGS MENU ITEMS ||============================== // @@ -11,6 +11,13 @@ const settings = { title: '', type: 'group', children: [ + { + id: 'conversationStarters', + title: 'Set Starter Prompts', + type: 'item', + url: '', + icon: icons.IconPictureInPictureOff + }, { id: 'viewMessages', title: 'View Messages', diff --git a/packages/ui/src/ui-component/cards/StarterConversationCard.css b/packages/ui/src/ui-component/cards/StarterConversationCard.css new file mode 100644 index 00000000..8eb3b0ad --- /dev/null +++ b/packages/ui/src/ui-component/cards/StarterConversationCard.css @@ -0,0 +1,16 @@ +.button-container { + display: flex; + overflow-x: auto; + -webkit-overflow-scrolling: touch; /* For momentum scroll on mobile devices */ + scrollbar-width: none; /* For Firefox */ +} + +/*.button-container::-webkit-scrollbar {*/ +/* display: none; !* For Chrome, Safari, and Opera *!*/ +/*}*/ + +.button { + flex: 0 0 auto; /* Don't grow, don't shrink, base width on content */ + margin: 5px; /* Adjust as needed for spacing between buttons */ + /* Add additional button styling here */ +} diff --git a/packages/ui/src/ui-component/cards/StarterConversationCard.js b/packages/ui/src/ui-component/cards/StarterConversationCard.js index 946c4c11..aa50f266 100644 --- a/packages/ui/src/ui-component/cards/StarterConversationCard.js +++ b/packages/ui/src/ui-component/cards/StarterConversationCard.js @@ -1,49 +1,24 @@ -import Chip from '@mui/material/Chip' import Box from '@mui/material/Box' import PropTypes from 'prop-types' -import { MenuItem, Select } from '@mui/material' +import { Button } from '@mui/material' +import './StarterConversationCard.css' -const StarterConversationCard = ({ isGrid, chipsData, onChipClick }) => { - if (isGrid) { - const chipStyle = { - margin: '5px', - width: 'calc(50% - 10px)' - } - - return ( - - {chipsData.map((chipLabel, index) => ( - onChipClick(chipLabel)} /> - ))} - - ) - } else { - return ( - - ) - } +const StarterConversationCard = ({ isGrid, starterPrompts, onPromptClick }) => { + return ( + + {starterPrompts.map((sp, index) => ( + + ))} + + ) } StarterConversationCard.propTypes = { isGrid: PropTypes.bool, - chipsData: PropTypes.arrayOf(PropTypes.string), - onChipClick: PropTypes.func + starterPrompts: PropTypes.arrayOf(PropTypes.string), + onPromptClick: PropTypes.func } export default StarterConversationCard diff --git a/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js b/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js new file mode 100644 index 00000000..1139f21e --- /dev/null +++ b/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js @@ -0,0 +1,188 @@ +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 { Button, IconButton, Dialog, DialogContent, OutlinedInput, DialogTitle, DialogActions, Box, List, Divider } from '@mui/material' +import { IconX, IconTrash, IconPlus } from '@tabler/icons' + +// Project import +import { StyledButton } from 'ui-component/button/StyledButton' + +// store +import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' +import useNotifier from 'utils/useNotifier' + +// API +import chatflowsApi from 'api/chatflows' + +const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { + const portalElement = document.getElementById('portal') + const dispatch = useDispatch() + + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [inputFields, setInputFields] = useState([ + { + prompt: '' + } + ]) + + const 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 { + const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { + starterPrompt: JSON.stringify(inputFields) + }) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Conversation Starter Prompts Saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + 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 Conversation Starter Prompts: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + } + } + + useEffect(() => { + if (dialogProps.chatflow && dialogProps.chatflow.starterPrompt) { + try { + setInputFields(JSON.parse(dialogProps.chatflow.starterPrompt)) + } catch (e) { + setInputFields([ + { + prompt: '' + } + ]) + console.error(e) + } + } + + return () => {} + }, [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 ? ( + + + Set Conversation Starter Prompts + + + + + {inputFields.map((data, index) => { + return ( +
+ + handleChange(index, e)} + value={data.prompt} + name='prompt' + /> + + + {inputFields.length !== 1 && ( + + + + )} + + + {index === inputFields.length - 1 && ( + + + + )} + +
+ ) + })} +
+
+
+ + + + Cancel + + + Save + + +
+ ) : null + + return createPortal(component, portalElement) +} + +ConversationStarterDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func +} + +export default ConversationStarterDialog diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index 56365ba8..d931ee26 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -16,6 +16,7 @@ import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog' import APICodeDialog from 'views/chatflows/APICodeDialog' import AnalyseFlowDialog from 'ui-component/dialog/AnalyseFlowDialog' import ViewMessagesDialog from 'ui-component/dialog/ViewMessagesDialog' +import ConversationStarterDialog from 'ui-component/dialog/ConversationStarterDialog' // API import chatflowsApi from 'api/chatflows' @@ -45,6 +46,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl const [apiDialogProps, setAPIDialogProps] = useState({}) const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false) const [analyseDialogProps, setAnalyseDialogProps] = useState({}) + const [conversationStartersDialogOpen, setConversationStartersDialogOpen] = useState(false) + const [conversationStartersDialogProps, setConversationStartersDialogProps] = useState({}) const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false) const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({}) @@ -56,6 +59,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl if (setting === 'deleteChatflow') { handleDeleteFlow() + } else if (setting === 'conversationStarters') { + setConversationStartersDialogProps({ + title: 'Set Conversation Starters - ' + chatflow.name, + chatflow: chatflow + }) + setConversationStartersDialogOpen(true) } else if (setting === 'analyseChatflow') { setAnalyseDialogProps({ title: 'Analyse Chatflow', @@ -376,6 +385,11 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl /> setAPIDialogOpen(false)} /> setAnalyseDialogOpen(false)} /> + setConversationStartersDialogOpen(false)} + /> { const [limitMax, setLimitMax] = useState(apiConfig?.rateLimit?.limitMax ?? '') const [limitDuration, setLimitDuration] = useState(apiConfig?.rateLimit?.limitDuration ?? '') const [limitMsg, setLimitMsg] = useState(apiConfig?.rateLimit?.limitMsg ?? '') - const [prompt1, setPrompt1] = useState(apiConfig?.prompt?.prompt1 ?? '') - const [prompt2, setPrompt2] = useState(apiConfig?.prompt?.prompt2 ?? '') - const [prompt3, setPrompt3] = useState(apiConfig?.prompt?.prompt3 ?? '') - const [prompt4, setPrompt4] = useState(apiConfig?.prompt?.prompt4 ?? '') const formatObj = () => { const obj = { @@ -119,7 +111,7 @@ const Configuration = () => { return (
- {fieldLabel && {fieldLabel}} + {fieldLabel} { return ( <> - - } aria-controls='panel1a-content' id='panel1a-header'> - - Rate Limit{' '} - Rate Limit Setup Guide to set up Rate Limit correctly in your hosting environment.' - } - /> - - - - - {/*Rate Limit*/} - {textField(limitMax, 'limitMax', 'Message Limit per Duration', 'number')} - {textField(limitDuration, 'limitDuration', 'Duration in Second', 'number')} - {textField(limitMsg, 'limitMsg', 'Limit Message', 'string')} - - - + {/*Rate Limit*/} + + Rate Limit{' '} + Rate Limit Setup Guide to set up Rate Limit correctly in your hosting environment.' + } + /> + + {textField(limitMax, 'limitMax', 'Message Limit per Duration', 'number')} + {textField(limitDuration, 'limitDuration', 'Duration in Second', 'number')} + {textField(limitMsg, 'limitMsg', 'Limit Message', 'string')} - - } aria-controls='panel2a-content' id='panel2a-header'> - Conversation Starters - - - - {textField(prompt1, 'prompt1', 'Starter Prompts', 'string')} - {textField(prompt2, 'prompt2', '', 'string')} - {textField(prompt3, 'prompt3', '', 'string')} - {textField(prompt4, 'prompt4', '', 'string')} - - - onSave()}> Save Changes diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index d047a3aa..8e9b753b 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -57,6 +57,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { const inputRef = useRef(null) const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow) const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming) + const getChatflowConfig = useApi(chatflowsApi.getSpecificChatflow) + + const [starterPrompts, setStarterPrompts] = useState([]) const onSourceDialogClick = (data, title) => { setSourceDialogProps({ data, title }) @@ -104,14 +107,14 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { }, 100) } - const handlePromptClick = async (prompt) => { + const handlePromptClick = async (prompt, e) => { setUserInput(prompt) - await handleSubmit() + await handleSubmit(e) } // Handle form submission const handleSubmit = async (e) => { - if (e) e.preventDefault() + e.preventDefault() if (userInput.trim() === '') { return @@ -202,10 +205,18 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { if (getIsChatflowStreamingApi.data) { setIsChatFlowAvailableToStream(getIsChatflowStreamingApi.data?.isStreaming ?? false) } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [getIsChatflowStreamingApi.data]) + useEffect(() => { + if (getChatflowConfig.data) { + if (getChatflowConfig.data?.starterPrompt && JSON.parse(getChatflowConfig.data?.starterPrompt)) { + setStarterPrompts(JSON.parse(getChatflowConfig.data?.starterPrompt)) + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getChatflowConfig.data]) + // Auto scroll chat to bottom useEffect(() => { scrollToBottom() @@ -224,6 +235,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { if (open && chatflowid) { getChatmessageApi.request(chatflowid) getIsChatflowStreamingApi.request(chatflowid) + getChatflowConfig.request(chatflowid) scrollToBottom() socket = socketIOClient(baseURL) @@ -377,8 +389,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { {messages && messages.length === 1 && ( )} From a27da2375ba8519287c4a11f8b0a3b522ca420a0 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Thu, 23 Nov 2023 10:30:18 +0530 Subject: [PATCH 03/11] Conversation Starters: - Updates to use the chatbot config for persistence. --- packages/server/src/database/entities/ChatFlow.ts | 3 --- .../1700565042576-AddStarterPromptsToChatFlow.ts | 12 ------------ .../server/src/database/migrations/mysql/index.ts | 4 +--- .../1700565042576-AddStarterPromptsToChatFlow.ts | 11 ----------- .../server/src/database/migrations/postgres/index.ts | 4 +--- .../1700565042576-AddStarterPromptsToChatFlow.ts | 11 ----------- .../server/src/database/migrations/sqlite/index.ts | 4 +--- packages/ui/src/menu-items/settings.js | 2 +- .../ui-component/dialog/ConversationStarterDialog.js | 10 ++++++---- packages/ui/src/views/canvas/CanvasHeader.js | 2 +- packages/ui/src/views/chatmessage/ChatMessage.js | 7 +++++-- 11 files changed, 16 insertions(+), 54 deletions(-) delete mode 100644 packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts delete mode 100644 packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts delete mode 100644 packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts diff --git a/packages/server/src/database/entities/ChatFlow.ts b/packages/server/src/database/entities/ChatFlow.ts index 626f743a..376a100b 100644 --- a/packages/server/src/database/entities/ChatFlow.ts +++ b/packages/server/src/database/entities/ChatFlow.ts @@ -28,9 +28,6 @@ export class ChatFlow implements IChatFlow { @Column({ nullable: true, type: 'text' }) apiConfig?: string - @Column({ nullable: true, type: 'text' }) - starterPrompt?: string - @Column({ nullable: true, type: 'text' }) analytic?: string diff --git a/packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts b/packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts deleted file mode 100644 index 1f7a3c54..00000000 --- a/packages/server/src/database/migrations/mysql/1700565042576-AddStarterPromptsToChatFlow.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm' - -export class AddStarterPrompt1700565042576 implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - const columnExists = await queryRunner.hasColumn('chat_flow', 'starterPrompt') - if (!columnExists) queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`starterPrompt\` TEXT;`) - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`chat_flow\` DROP COLUMN \`starterPrompt\`;`) - } -} diff --git a/packages/server/src/database/migrations/mysql/index.ts b/packages/server/src/database/migrations/mysql/index.ts index f38a9cfe..4b7b8a95 100644 --- a/packages/server/src/database/migrations/mysql/index.ts +++ b/packages/server/src/database/migrations/mysql/index.ts @@ -8,7 +8,6 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694658767766 } from './1694658767766-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' -import { AddStarterPrompt1700565042576 } from './1700565042576-AddStarterPromptsToChatFlow' export const mysqlMigrations = [ Init1693840429259, @@ -20,6 +19,5 @@ export const mysqlMigrations = [ AddAnalytic1694432361423, AddChatHistory1694658767766, AddAssistantEntity1699325775451, - AddUsedToolsToChatMessage1699481607341, - AddStarterPrompt1700565042576 + AddUsedToolsToChatMessage1699481607341 ] diff --git a/packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts b/packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts deleted file mode 100644 index ac2694b9..00000000 --- a/packages/server/src/database/migrations/postgres/1700565042576-AddStarterPromptsToChatFlow.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm' - -export class AddStarterPrompt1700565042576 implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN IF NOT EXISTS "starterPrompt" TEXT;`) - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "chat_flow" DROP COLUMN "starterPrompt";`) - } -} diff --git a/packages/server/src/database/migrations/postgres/index.ts b/packages/server/src/database/migrations/postgres/index.ts index e9018265..75562c0b 100644 --- a/packages/server/src/database/migrations/postgres/index.ts +++ b/packages/server/src/database/migrations/postgres/index.ts @@ -8,7 +8,6 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694658756136 } from './1694658756136-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' -import { AddStarterPrompt1700565042576 } from './1700565042576-AddStarterPromptsToChatFlow' export const postgresMigrations = [ Init1693891895163, @@ -20,6 +19,5 @@ export const postgresMigrations = [ AddAnalytic1694432361423, AddChatHistory1694658756136, AddAssistantEntity1699325775451, - AddUsedToolsToChatMessage1699481607341, - AddStarterPrompt1700565042576 + AddUsedToolsToChatMessage1699481607341 ] diff --git a/packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts b/packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts deleted file mode 100644 index 3d180797..00000000 --- a/packages/server/src/database/migrations/sqlite/1700565042576-AddStarterPromptsToChatFlow.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm' - -export class AddStarterPrompt1700565042576 implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN "starterPrompt" TEXT;`) - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "chat_flow" DROP COLUMN "starterPrompt";`) - } -} diff --git a/packages/server/src/database/migrations/sqlite/index.ts b/packages/server/src/database/migrations/sqlite/index.ts index 414d755e..4a14fc40 100644 --- a/packages/server/src/database/migrations/sqlite/index.ts +++ b/packages/server/src/database/migrations/sqlite/index.ts @@ -8,7 +8,6 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694657778173 } from './1694657778173-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' -import { AddStarterPrompt1700565042576 } from './1700565042576-AddStarterPromptsToChatFlow' export const sqliteMigrations = [ Init1693835579790, @@ -20,6 +19,5 @@ export const sqliteMigrations = [ AddAnalytic1694432361423, AddChatHistory1694657778173, AddAssistantEntity1699325775451, - AddUsedToolsToChatMessage1699481607341, - AddStarterPrompt1700565042576 + AddUsedToolsToChatMessage1699481607341 ] diff --git a/packages/ui/src/menu-items/settings.js b/packages/ui/src/menu-items/settings.js index 9224b78f..1e0f58dd 100644 --- a/packages/ui/src/menu-items/settings.js +++ b/packages/ui/src/menu-items/settings.js @@ -13,7 +13,7 @@ const settings = { children: [ { id: 'conversationStarters', - title: 'Set Starter Prompts', + title: 'Starter Prompts', type: 'item', url: '', icon: icons.IconPictureInPictureOff diff --git a/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js b/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js index 1139f21e..979a4526 100644 --- a/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js +++ b/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js @@ -57,7 +57,9 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { const onSave = async () => { try { const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { - starterPrompt: JSON.stringify(inputFields) + chatbotConfig: { + starterPrompts: JSON.stringify(inputFields) + } }) if (saveResp.data) { enqueueSnackbar({ @@ -94,9 +96,9 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { } useEffect(() => { - if (dialogProps.chatflow && dialogProps.chatflow.starterPrompt) { + if (dialogProps.chatflow && dialogProps.chatbotConfig.starterPrompts) { try { - setInputFields(JSON.parse(dialogProps.chatflow.starterPrompt)) + setInputFields(JSON.parse(dialogProps.chatbotConfig.starterPrompts)) } catch (e) { setInputFields([ { @@ -126,7 +128,7 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { aria-describedby='alert-dialog-description' > - Set Conversation Starter Prompts + {dialogProps.title || 'Conversation Starter Prompts'} diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index d931ee26..b08eb6ab 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -61,7 +61,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl handleDeleteFlow() } else if (setting === 'conversationStarters') { setConversationStartersDialogProps({ - title: 'Set Conversation Starters - ' + chatflow.name, + title: 'Starter Prompts - ' + chatflow.name, chatflow: chatflow }) setConversationStartersDialogOpen(true) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 8e9b753b..e006ba49 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -210,8 +210,11 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { useEffect(() => { if (getChatflowConfig.data) { - if (getChatflowConfig.data?.starterPrompt && JSON.parse(getChatflowConfig.data?.starterPrompt)) { - setStarterPrompts(JSON.parse(getChatflowConfig.data?.starterPrompt)) + if ( + getChatflowConfig.data?.chatbotConfig?.starterPrompts && + JSON.parse(getChatflowConfig.data?.chatbotConfig?.starterPrompts) + ) { + setStarterPrompts(JSON.parse(getChatflowConfig.data?.chatbotConfig?.starterPrompts)) } } // eslint-disable-next-line react-hooks/exhaustive-deps From 6881231ea40c73018199b4450e7566b333e81fb5 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Thu, 23 Nov 2023 22:38:45 +0530 Subject: [PATCH 04/11] Conversation Starter: Setup of prompts from the chatflow dashboard and other refactorings. --- .../src/ui-component/button/FlowListMenu.js | 22 +++++ ...rsationCard.css => StarterPromptsCard.css} | 0 ...versationCard.js => StarterPromptsCard.js} | 8 +- ...arterDialog.js => StarterPromptsDialog.js} | 86 +++++++++++++------ packages/ui/src/views/canvas/CanvasHeader.js | 4 +- .../ui/src/views/chatmessage/ChatMessage.css | 2 +- .../ui/src/views/chatmessage/ChatMessage.js | 24 +++--- 7 files changed, 104 insertions(+), 42 deletions(-) rename packages/ui/src/ui-component/cards/{StarterConversationCard.css => StarterPromptsCard.css} (100%) rename packages/ui/src/ui-component/cards/{StarterConversationCard.js => StarterPromptsCard.js} (75%) rename packages/ui/src/ui-component/dialog/{ConversationStarterDialog.js => StarterPromptsDialog.js} (66%) diff --git a/packages/ui/src/ui-component/button/FlowListMenu.js b/packages/ui/src/ui-component/button/FlowListMenu.js index b242d2cb..3f94c400 100644 --- a/packages/ui/src/ui-component/button/FlowListMenu.js +++ b/packages/ui/src/ui-component/button/FlowListMenu.js @@ -11,6 +11,7 @@ import FileCopyIcon from '@mui/icons-material/FileCopy' import FileDownloadIcon from '@mui/icons-material/Downloading' import FileDeleteIcon from '@mui/icons-material/Delete' import FileCategoryIcon from '@mui/icons-material/Category' +import PictureInPictureAltIcon from '@mui/icons-material/PictureInPictureAlt' import Button from '@mui/material/Button' import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' import { IconX } from '@tabler/icons' @@ -28,6 +29,7 @@ import TagDialog from '../dialog/TagDialog' import { generateExportFlowData } from '../../utils/genericHelper' import useNotifier from '../../utils/useNotifier' +import StarterPromptsDialog from '../dialog/StarterPromptsDialog' const StyledMenu = styled((props) => ( { setAnchorEl(event.currentTarget) @@ -93,6 +97,15 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) { setFlowDialogOpen(true) } + const handleFlowStarterPrompts = () => { + setAnchorEl(null) + setConversationStartersDialogProps({ + title: 'Starter Prompts - ' + chatflow.name, + chatflow: chatflow + }) + setConversationStartersDialogOpen(true) + } + const saveFlowRename = async (chatflowName) => { const updateBody = { name: chatflowName, @@ -254,6 +267,10 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) { Export + + + Starter Prompts + Update Category @@ -281,6 +298,11 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) { onClose={() => setCategoryDialogOpen(false)} onSubmit={saveFlowCategory} /> + setConversationStartersDialogOpen(false)} + />
) } diff --git a/packages/ui/src/ui-component/cards/StarterConversationCard.css b/packages/ui/src/ui-component/cards/StarterPromptsCard.css similarity index 100% rename from packages/ui/src/ui-component/cards/StarterConversationCard.css rename to packages/ui/src/ui-component/cards/StarterPromptsCard.css diff --git a/packages/ui/src/ui-component/cards/StarterConversationCard.js b/packages/ui/src/ui-component/cards/StarterPromptsCard.js similarity index 75% rename from packages/ui/src/ui-component/cards/StarterConversationCard.js rename to packages/ui/src/ui-component/cards/StarterPromptsCard.js index aa50f266..caf8a219 100644 --- a/packages/ui/src/ui-component/cards/StarterConversationCard.js +++ b/packages/ui/src/ui-component/cards/StarterPromptsCard.js @@ -1,9 +1,9 @@ import Box from '@mui/material/Box' import PropTypes from 'prop-types' import { Button } from '@mui/material' -import './StarterConversationCard.css' +import './StarterPromptsCard.css' -const StarterConversationCard = ({ isGrid, starterPrompts, onPromptClick }) => { +const StarterPromptsCard = ({ isGrid, starterPrompts, onPromptClick }) => { return ( {starterPrompts.map((sp, index) => ( @@ -15,10 +15,10 @@ const StarterConversationCard = ({ isGrid, starterPrompts, onPromptClick }) => { ) } -StarterConversationCard.propTypes = { +StarterPromptsCard.propTypes = { isGrid: PropTypes.bool, starterPrompts: PropTypes.arrayOf(PropTypes.string), onPromptClick: PropTypes.func } -export default StarterConversationCard +export default StarterPromptsCard diff --git a/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js similarity index 66% rename from packages/ui/src/ui-component/dialog/ConversationStarterDialog.js rename to packages/ui/src/ui-component/dialog/StarterPromptsDialog.js index 979a4526..7752e32f 100644 --- a/packages/ui/src/ui-component/dialog/ConversationStarterDialog.js +++ b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js @@ -5,7 +5,18 @@ import PropTypes from 'prop-types' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions' // material-ui -import { Button, IconButton, Dialog, DialogContent, OutlinedInput, DialogTitle, DialogActions, Box, List, Divider } from '@mui/material' +import { + Button, + IconButton, + Dialog, + DialogContent, + OutlinedInput, + DialogTitle, + DialogActions, + Box, + List, + InputAdornment +} from '@mui/material' import { IconX, IconTrash, IconPlus } from '@tabler/icons' // Project import @@ -18,7 +29,7 @@ import useNotifier from 'utils/useNotifier' // API import chatflowsApi from 'api/chatflows' -const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { +const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -56,10 +67,13 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { const onSave = async () => { try { - const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { - chatbotConfig: { - starterPrompts: JSON.stringify(inputFields) + let value = { + starterPrompts: { + ...inputFields } + } + const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { + chatbotConfig: JSON.stringify(value) }) if (saveResp.data) { enqueueSnackbar({ @@ -96,16 +110,30 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { } useEffect(() => { - if (dialogProps.chatflow && dialogProps.chatbotConfig.starterPrompts) { + if (dialogProps.chatflow && dialogProps.chatflow.chatbotConfig) { try { - setInputFields(JSON.parse(dialogProps.chatbotConfig.starterPrompts)) + let chatbotConfig = JSON.parse(dialogProps.chatflow.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: '' } ]) - console.error(e) } } @@ -131,28 +159,34 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { {dialogProps.title || 'Conversation Starter Prompts'} - + :not(style)': { m: 1 }, pt: 2 }}> {inputFields.map((data, index) => { return ( -
- +
+ handleChange(index, e)} + size='small' value={data.prompt} name='prompt' + endAdornment={ + + + + + + } /> - - {inputFields.length !== 1 && ( - - - - )} - {index === inputFields.length - 1 && ( @@ -160,6 +194,13 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { )} + {/**/} + {/* {inputFields.length !== 1 && (*/} + {/* */} + {/* */} + {/* */} + {/* )}*/} + {/**/}
) })} @@ -167,10 +208,7 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => {
- - - Cancel - + Save @@ -181,10 +219,10 @@ const ConversationStarterDialog = ({ show, dialogProps, onCancel }) => { return createPortal(component, portalElement) } -ConversationStarterDialog.propTypes = { +StarterPromptsDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, onCancel: PropTypes.func } -export default ConversationStarterDialog +export default StarterPromptsDialog diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index b08eb6ab..2c3bdfe4 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -16,7 +16,7 @@ import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog' import APICodeDialog from 'views/chatflows/APICodeDialog' import AnalyseFlowDialog from 'ui-component/dialog/AnalyseFlowDialog' import ViewMessagesDialog from 'ui-component/dialog/ViewMessagesDialog' -import ConversationStarterDialog from 'ui-component/dialog/ConversationStarterDialog' +import StarterPromptsDialog from 'ui-component/dialog/StarterPromptsDialog' // API import chatflowsApi from 'api/chatflows' @@ -385,7 +385,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl /> setAPIDialogOpen(false)} /> setAnalyseDialogOpen(false)} /> - setConversationStartersDialogOpen(false)} diff --git a/packages/ui/src/views/chatmessage/ChatMessage.css b/packages/ui/src/views/chatmessage/ChatMessage.css index 2298fee6..2c627988 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.css +++ b/packages/ui/src/views/chatmessage/ChatMessage.css @@ -118,7 +118,7 @@ .cloud { width: 400px; - height: calc(100vh - 260px); + height: calc(90vh - 260px); border-radius: 0.5rem; display: flex; justify-content: center; diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 81ba9512..962ecb36 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -33,7 +33,7 @@ import { baseURL, maxScroll } from 'store/constant' import robotPNG from 'assets/images/robot.png' import userPNG from 'assets/images/account.png' import { isValidURL, removeDuplicateURL } from 'utils/genericHelper' -import StarterConversationCard from '../../ui-component/cards/StarterConversationCard' +import StarterPromptsCard from '../../ui-component/cards/StarterPromptsCard' export const ChatMessage = ({ open, chatflowid, isDialog }) => { const theme = useTheme() @@ -238,11 +238,17 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { useEffect(() => { if (getChatflowConfig.data) { - if ( - getChatflowConfig.data?.chatbotConfig?.starterPrompts && - JSON.parse(getChatflowConfig.data?.chatbotConfig?.starterPrompts) - ) { - setStarterPrompts(JSON.parse(getChatflowConfig.data?.chatbotConfig?.starterPrompts)) + if (getChatflowConfig.data?.chatbotConfig && JSON.parse(getChatflowConfig.data?.chatbotConfig)) { + let config = JSON.parse(getChatflowConfig.data?.chatbotConfig) + if (config.starterPrompts) { + let inputFields = [] + Object.getOwnPropertyNames(config.starterPrompts).forEach((key) => { + if (config.starterPrompts[key]) { + inputFields.push(config.starterPrompts[key]) + } + }) + setStarterPrompts(inputFields) + } } } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -439,11 +445,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
{messages && messages.length === 1 && ( - + )} Date: Fri, 24 Nov 2023 11:20:35 +0530 Subject: [PATCH 05/11] Conversation Starter: Changes to ensure that the chatbotConfig is not overwritten between the starter prompts and share chatbot dialogs --- .../ui/src/ui-component/button/FlowListMenu.js | 6 ++++++ .../ui-component/dialog/StarterPromptsDialog.js | 15 +++++++++++---- packages/ui/src/views/chatflows/ShareChatbot.js | 2 ++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/ui-component/button/FlowListMenu.js b/packages/ui/src/ui-component/button/FlowListMenu.js index 3f94c400..2f5bdd5d 100644 --- a/packages/ui/src/ui-component/button/FlowListMenu.js +++ b/packages/ui/src/ui-component/button/FlowListMenu.js @@ -106,6 +106,11 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) { setConversationStartersDialogOpen(true) } + const saveFlowStarterPrompts = async () => { + setConversationStartersDialogOpen(false) + await updateFlowsApi.request() + } + const saveFlowRename = async (chatflowName) => { const updateBody = { name: chatflowName, @@ -301,6 +306,7 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) { setConversationStartersDialogOpen(false)} />
diff --git a/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js index 7752e32f..286399a6 100644 --- a/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js +++ b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js @@ -29,7 +29,7 @@ import useNotifier from 'utils/useNotifier' // API import chatflowsApi from 'api/chatflows' -const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { +const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm = undefined }) => { const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -44,6 +44,8 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { } ]) + const [chatbotConfig, setChatbotConfig] = useState({}) + const addInputField = () => { setInputFields([ ...inputFields, @@ -72,8 +74,9 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { ...inputFields } } + chatbotConfig.starterPrompts = value.starterPrompts const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { - chatbotConfig: JSON.stringify(value) + chatbotConfig: JSON.stringify(chatbotConfig) }) if (saveResp.data) { enqueueSnackbar({ @@ -90,7 +93,9 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { }) dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) } - onCancel() + if (onConfirm) { + onConfirm() + } } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ @@ -113,6 +118,7 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { 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) => { @@ -222,7 +228,8 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel }) => { StarterPromptsDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, - onCancel: PropTypes.func + onCancel: PropTypes.func, + onConfirm: PropTypes.func } export default StarterPromptsDialog diff --git a/packages/ui/src/views/chatflows/ShareChatbot.js b/packages/ui/src/views/chatflows/ShareChatbot.js index dc6c0621..0bf5fc39 100644 --- a/packages/ui/src/views/chatflows/ShareChatbot.js +++ b/packages/ui/src/views/chatflows/ShareChatbot.js @@ -135,6 +135,8 @@ const ShareChatbot = ({ isSessionMemory }) => { if (isSessionMemory) obj.overrideConfig.generateNewSession = generateNewSession + if (chatbotConfig?.starterPrompts) obj.starterPrompts = chatbotConfig.starterPrompts + return obj } From b62a506edb38af04769086b27ff9fd44fd81988a Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 29 Nov 2023 15:04:24 +0000 Subject: [PATCH 06/11] fix where prompt deleted index is not passed correctly --- .../ui-component/cards/StarterPromptsCard.css | 5 -- .../dialog/StarterPromptsDialog.js | 63 ++++++++++++------- packages/ui/src/views/canvas/CanvasHeader.js | 1 + .../ui/src/views/chatmessage/ChatMessage.css | 2 +- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/packages/ui/src/ui-component/cards/StarterPromptsCard.css b/packages/ui/src/ui-component/cards/StarterPromptsCard.css index 8eb3b0ad..2b8df226 100644 --- a/packages/ui/src/ui-component/cards/StarterPromptsCard.css +++ b/packages/ui/src/ui-component/cards/StarterPromptsCard.css @@ -5,12 +5,7 @@ scrollbar-width: none; /* For Firefox */ } -/*.button-container::-webkit-scrollbar {*/ -/* display: none; !* For Chrome, Safari, and Opera *!*/ -/*}*/ - .button { flex: 0 0 auto; /* Don't grow, don't shrink, base width on content */ margin: 5px; /* Adjust as needed for spacing between buttons */ - /* Add additional button styling here */ } diff --git a/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js index 286399a6..a9916841 100644 --- a/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js +++ b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js @@ -17,7 +17,7 @@ import { List, InputAdornment } from '@mui/material' -import { IconX, IconTrash, IconPlus } from '@tabler/icons' +import { IconX, IconTrash, IconPlus, IconBulb } from '@tabler/icons' // Project import import { StyledButton } from 'ui-component/button/StyledButton' @@ -29,7 +29,7 @@ import useNotifier from 'utils/useNotifier' // API import chatflowsApi from 'api/chatflows' -const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm = undefined }) => { +const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -93,9 +93,7 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm = undefin }) dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) } - if (onConfirm) { - onConfirm() - } + onConfirm() } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ @@ -165,12 +163,34 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm = undefin {dialogProps.title || 'Conversation Starter Prompts'} +
+
+ + + Starter prompts will only be shown when there is no messages on the chat yet + +
+
:not(style)': { m: 1 }, pt: 2 }}> {inputFields.map((data, index) => { return (
- + - - - + {inputFields.length > 1 && ( + removeInputFields(index)} + edge='end' + > + + + )} } /> - + {index === inputFields.length - 1 && ( - + )} - {/**/} - {/* {inputFields.length !== 1 && (*/} - {/* */} - {/* */} - {/* */} - {/* )}*/} - {/**/}
) })} diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index 2c3bdfe4..85408cd8 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -388,6 +388,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl setConversationStartersDialogOpen(false)} onCancel={() => setConversationStartersDialogOpen(false)} /> Date: Wed, 29 Nov 2023 16:05:59 +0000 Subject: [PATCH 07/11] update chat message layout and fix when first start prompt clicked but not sent --- .../ui-component/cards/StarterPromptsCard.css | 3 ++ .../ui-component/cards/StarterPromptsCard.js | 8 ++--- .../ui/src/views/chatmessage/ChatMessage.js | 31 ++++++++++++------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/packages/ui/src/ui-component/cards/StarterPromptsCard.css b/packages/ui/src/ui-component/cards/StarterPromptsCard.css index 2b8df226..85c2d415 100644 --- a/packages/ui/src/ui-component/cards/StarterPromptsCard.css +++ b/packages/ui/src/ui-component/cards/StarterPromptsCard.css @@ -1,4 +1,7 @@ .button-container { + position: absolute; + bottom: 0; + z-index: 1000; display: flex; overflow-x: auto; -webkit-overflow-scrolling: touch; /* For momentum scroll on mobile devices */ diff --git a/packages/ui/src/ui-component/cards/StarterPromptsCard.js b/packages/ui/src/ui-component/cards/StarterPromptsCard.js index caf8a219..3abd8378 100644 --- a/packages/ui/src/ui-component/cards/StarterPromptsCard.js +++ b/packages/ui/src/ui-component/cards/StarterPromptsCard.js @@ -1,15 +1,13 @@ import Box from '@mui/material/Box' import PropTypes from 'prop-types' -import { Button } from '@mui/material' +import { Chip } from '@mui/material' import './StarterPromptsCard.css' const StarterPromptsCard = ({ isGrid, starterPrompts, onPromptClick }) => { return ( - + {starterPrompts.map((sp, index) => ( - + onPromptClick(sp.prompt, e)} /> ))} ) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 33ec50d7..b52ff2da 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -108,26 +108,30 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { }, 100) } - const handlePromptClick = async (prompt, e) => { - setUserInput(prompt) - await handleSubmit(e) + const handlePromptClick = async (promptStarterInput) => { + setUserInput(promptStarterInput) + handleSubmit(undefined, promptStarterInput) } // Handle form submission - const handleSubmit = async (e) => { - e.preventDefault() + const handleSubmit = async (e, promptStarterInput) => { + if (e) e.preventDefault() - if (userInput.trim() === '') { + if (!promptStarterInput && userInput.trim() === '') { return } + let input = userInput + + if (promptStarterInput !== undefined && promptStarterInput.trim() !== '') input = promptStarterInput + setLoading(true) - setMessages((prevMessages) => [...prevMessages, { message: userInput, type: 'userMessage' }]) + setMessages((prevMessages) => [...prevMessages, { message: input, type: 'userMessage' }]) // Send user question and history to API try { const params = { - question: userInput, + question: input, history: messages.filter((msg) => msg.message !== 'Hi there! How can I help?'), chatId } @@ -439,13 +443,16 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { })}
- + +
+ {messages && messages.length === 1 && ( + + )} + +
- {messages && messages.length === 1 && ( - - )} Date: Wed, 29 Nov 2023 16:17:54 +0000 Subject: [PATCH 08/11] update wording --- packages/ui/src/ui-component/dialog/StarterPromptsDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js index a9916841..78866b4c 100644 --- a/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js +++ b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.js @@ -181,7 +181,7 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm }) => { > - Starter prompts will only be shown when there is no messages on the chat yet + Starter prompts will only be shown when there is no messages on the chat
From f51c1d5b7a53cd877b000786d221298ed1766187 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 11 Dec 2023 20:35:30 +0000 Subject: [PATCH 09/11] check for array query parameter --- packages/server/src/utils/XSS.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/server/src/utils/XSS.ts b/packages/server/src/utils/XSS.ts index 3e96e6c8..5d8b81e9 100644 --- a/packages/server/src/utils/XSS.ts +++ b/packages/server/src/utils/XSS.ts @@ -6,8 +6,15 @@ export function sanitizeMiddleware(req: Request, res: Response, next: NextFuncti const decodedURI = decodeURI(req.url) req.url = sanitizeHtml(decodedURI) for (let p in req.query) { - req.query[p] = sanitizeHtml(req.query[p] as string) + if (Array.isArray(req.query[p])) { + const sanitizedQ = [] + for (const q of req.query[p] as string[]) { + sanitizedQ.push(sanitizeHtml(q)) + } + req.query[p] = sanitizedQ + } else { + req.query[p] = sanitizeHtml(req.query[p] as string) + } } - next() } From 8e1ef2d5337f8140e8789830b7c5844ee854cc24 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 12 Dec 2023 12:44:42 +0000 Subject: [PATCH 10/11] fix sanitized & --- packages/server/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 2ab454ad..2d40f32e 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -200,7 +200,7 @@ export class App { // Get component credential via name this.app.get('/api/v1/components-credentials/:name', (req: Request, res: Response) => { - if (!req.params.name.includes('&')) { + if (!req.params.name.includes('&')) { if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, req.params.name)) { return res.json(this.nodesPool.componentCredentials[req.params.name]) } else { @@ -208,7 +208,7 @@ export class App { } } else { const returnResponse = [] - for (const name of req.params.name.split('&')) { + for (const name of req.params.name.split('&')) { if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, name)) { returnResponse.push(this.nodesPool.componentCredentials[name]) } else { From 2110e1146b5d7ca024411f5ec26be3dcee540972 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 12 Dec 2023 23:14:52 +0000 Subject: [PATCH 11/11] add a public endpoint to retrieve chatbotconfig --- packages/server/src/index.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 2d40f32e..fb4a5f5a 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -138,6 +138,7 @@ export class App { '/api/v1/verify/apikey/', '/api/v1/chatflows/apikey/', '/api/v1/public-chatflows', + '/api/v1/public-chatbotConfig', '/api/v1/prediction/', '/api/v1/vector/upsert/', '/api/v1/node-icon/', @@ -328,6 +329,23 @@ export class App { return res.status(404).send(`Chatflow ${req.params.id} not found`) }) + // Get specific chatflow chatbotConfig via id (PUBLIC endpoint, used to retrieve config for embedded chat) + // Safe as public endpoint as chatbotConfig doesn't contain sensitive credential + this.app.get('/api/v1/public-chatbotConfig/:id', async (req: Request, res: Response) => { + const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ + id: req.params.id + }) + if (chatflow && chatflow.chatbotConfig) { + try { + const parsedConfig = JSON.parse(chatflow.chatbotConfig) + return res.json(parsedConfig) + } catch (e) { + return res.status(500).send(`Error parsing Chatbot Config for Chatflow ${req.params.id}`) + } + } + return res.status(404).send(`Chatbot Config for Chatflow ${req.params.id} not found`) + }) + // Save chatflow this.app.post('/api/v1/chatflows', async (req: Request, res: Response) => { const body = req.body