diff --git a/packages/ui/src/menu-items/settings.js b/packages/ui/src/menu-items/settings.js index 3e43d0257..9b83aaa47 100644 --- a/packages/ui/src/menu-items/settings.js +++ b/packages/ui/src/menu-items/settings.js @@ -1,14 +1,5 @@ // assets -import { - IconTrash, - IconFileUpload, - IconFileExport, - IconCopy, - IconSearch, - IconMessage, - IconPictureInPictureOff, - IconAdjustmentsHorizontal -} from '@tabler/icons' +import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconMessage, IconAdjustmentsHorizontal } from '@tabler/icons' // constant const icons = { @@ -16,9 +7,7 @@ const icons = { IconFileUpload, IconFileExport, IconCopy, - IconSearch, IconMessage, - IconPictureInPictureOff, IconAdjustmentsHorizontal } @@ -29,13 +18,6 @@ const settings = { title: '', type: 'group', children: [ - { - id: 'conversationStarters', - title: 'Starter Prompts', - type: 'item', - url: '', - icon: icons.IconPictureInPictureOff - }, { id: 'viewMessages', title: 'View Messages', @@ -71,13 +53,6 @@ const settings = { url: '', icon: icons.IconFileExport }, - { - id: 'analyseChatflow', - title: 'Analyse Chatflow', - type: 'item', - url: '', - icon: icons.IconSearch - }, { id: 'deleteChatflow', title: 'Delete Chatflow', diff --git a/packages/ui/src/ui-component/dialog/AllowedDomainsDialog.js b/packages/ui/src/ui-component/dialog/AllowedDomains.js similarity index 100% rename from packages/ui/src/ui-component/dialog/AllowedDomainsDialog.js rename to packages/ui/src/ui-component/dialog/AllowedDomains.js diff --git a/packages/ui/src/ui-component/dialog/AnalyseFlow.js b/packages/ui/src/ui-component/dialog/AnalyseFlow.js new file mode 100644 index 000000000..8bebea316 --- /dev/null +++ b/packages/ui/src/ui-component/dialog/AnalyseFlow.js @@ -0,0 +1,325 @@ +import { useDispatch } from 'react-redux' +import { useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions' + +// material-ui +import { + Typography, + Box, + Button, + Accordion, + AccordionSummary, + AccordionDetails, + ListItem, + ListItemAvatar, + ListItemText +} from '@mui/material' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' +import { IconX } from '@tabler/icons' + +// Project import +import CredentialInputHandler from 'views/canvas/CredentialInputHandler' +import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' +import { SwitchInput } from 'ui-component/switch/Switch' +import { Input } from 'ui-component/input/Input' +import { StyledButton } from 'ui-component/button/StyledButton' +import langsmithPNG from 'assets/images/langchain.png' +import langfuseSVG from 'assets/images/langfuse.svg' +import lunarySVG from 'assets/images/lunary.svg' + +// store +import useNotifier from 'utils/useNotifier' + +// API +import chatflowsApi from 'api/chatflows' + +const analyticProviders = [ + { + label: 'LangSmith', + name: 'langSmith', + icon: langsmithPNG, + url: 'https://smith.langchain.com', + inputs: [ + { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['langsmithApi'] + }, + { + label: 'Project Name', + name: 'projectName', + type: 'string', + optional: true, + description: 'If not provided, default will be used', + placeholder: 'default' + }, + { + label: 'On/Off', + name: 'status', + type: 'boolean', + optional: true + } + ] + }, + { + label: 'LangFuse', + name: 'langFuse', + icon: langfuseSVG, + url: 'https://langfuse.com', + inputs: [ + { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['langfuseApi'] + }, + { + label: 'Release', + name: 'release', + type: 'string', + optional: true, + description: 'The release number/hash of the application to provide analytics grouped by release' + }, + { + label: 'On/Off', + name: 'status', + type: 'boolean', + optional: true + } + ] + }, + { + label: 'Lunary', + name: 'lunary', + icon: lunarySVG, + url: 'https://lunary.ai', + inputs: [ + { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['lunaryApi'] + }, + { + label: 'On/Off', + name: 'status', + type: 'boolean', + optional: true + } + ] + } +] + +const AnalyseFlow = ({ dialogProps, onCancel }) => { + const dispatch = useDispatch() + + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [analytic, setAnalytic] = useState({}) + const [providerExpanded, setProviderExpanded] = useState({}) + + const onSave = async () => { + try { + const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { + analytic: JSON.stringify(analytic) + }) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Analytic Configuration Saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) + } + onCancel() + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to save Analytic Configuration: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + } + } + + const setValue = (value, providerName, inputParamName) => { + let newVal = {} + if (!Object.prototype.hasOwnProperty.call(analytic, providerName)) { + newVal = { ...analytic, [providerName]: {} } + } else { + newVal = { ...analytic } + } + + newVal[providerName][inputParamName] = value + setAnalytic(newVal) + } + + const handleAccordionChange = (providerName) => (event, isExpanded) => { + const accordianProviders = { ...providerExpanded } + accordianProviders[providerName] = isExpanded + setProviderExpanded(accordianProviders) + } + + useEffect(() => { + if (dialogProps.chatflow && dialogProps.chatflow.analytic) { + try { + setAnalytic(JSON.parse(dialogProps.chatflow.analytic)) + } catch (e) { + setAnalytic({}) + console.error(e) + } + } + + return () => { + setAnalytic({}) + setProviderExpanded({}) + } + }, [dialogProps]) + + return ( + <> + {analyticProviders.map((provider, index) => ( + + } aria-controls={provider.name} id={provider.name}> + + +
+ AI +
+
+ + {provider.url} + + } + /> + {analytic[provider.name] && analytic[provider.name].status && ( +
+
+ ON +
+ )} + + + + {provider.inputs.map((inputParam, index) => ( + +
+ + {inputParam.label} + {!inputParam.optional &&  *} + {inputParam.description && ( + + )} + +
+ {providerExpanded[provider.name] && inputParam.type === 'credential' && ( + setValue(newValue, provider.name, 'credentialId')} + /> + )} + {providerExpanded[provider.name] && inputParam.type === 'boolean' && ( + setValue(newValue, provider.name, inputParam.name)} + value={ + analytic[provider.name] ? analytic[provider.name][inputParam.name] : inputParam.default ?? false + } + /> + )} + {providerExpanded[provider.name] && + (inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && ( + setValue(newValue, provider.name, inputParam.name)} + value={ + analytic[provider.name] + ? analytic[provider.name][inputParam.name] + : inputParam.default ?? '' + } + /> + )} +
+ ))} +
+ + ))} + + Save + + + ) +} + +AnalyseFlow.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func +} + +export default AnalyseFlow diff --git a/packages/ui/src/ui-component/dialog/AnalyseFlowDialog.js b/packages/ui/src/ui-component/dialog/AnalyseFlowDialog.js deleted file mode 100644 index a326db9a5..000000000 --- a/packages/ui/src/ui-component/dialog/AnalyseFlowDialog.js +++ /dev/null @@ -1,358 +0,0 @@ -import { createPortal } from 'react-dom' -import { useDispatch } from 'react-redux' -import { useState, useEffect } from 'react' -import PropTypes from 'prop-types' -import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions' - -// material-ui -import { - Typography, - Box, - Button, - Dialog, - DialogContent, - DialogTitle, - DialogActions, - Accordion, - AccordionSummary, - AccordionDetails, - ListItem, - ListItemAvatar, - ListItemText -} from '@mui/material' -import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import { IconX } from '@tabler/icons' - -// Project import -import CredentialInputHandler from 'views/canvas/CredentialInputHandler' -import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' -import { SwitchInput } from 'ui-component/switch/Switch' -import { Input } from 'ui-component/input/Input' -import { StyledButton } from 'ui-component/button/StyledButton' -import langsmithPNG from 'assets/images/langchain.png' -import langfuseSVG from 'assets/images/langfuse.svg' -import lunarySVG from 'assets/images/lunary.svg' - -// store -import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' -import useNotifier from 'utils/useNotifier' - -// API -import chatflowsApi from 'api/chatflows' - -const analyticProviders = [ - { - label: 'LangSmith', - name: 'langSmith', - icon: langsmithPNG, - url: 'https://smith.langchain.com', - inputs: [ - { - label: 'Connect Credential', - name: 'credential', - type: 'credential', - credentialNames: ['langsmithApi'] - }, - { - label: 'Project Name', - name: 'projectName', - type: 'string', - optional: true, - description: 'If not provided, default will be used', - placeholder: 'default' - }, - { - label: 'On/Off', - name: 'status', - type: 'boolean', - optional: true - } - ] - }, - { - label: 'LangFuse', - name: 'langFuse', - icon: langfuseSVG, - url: 'https://langfuse.com', - inputs: [ - { - label: 'Connect Credential', - name: 'credential', - type: 'credential', - credentialNames: ['langfuseApi'] - }, - { - label: 'Release', - name: 'release', - type: 'string', - optional: true, - description: 'The release number/hash of the application to provide analytics grouped by release' - }, - { - label: 'On/Off', - name: 'status', - type: 'boolean', - optional: true - } - ] - }, - { - label: 'Lunary', - name: 'lunary', - icon: lunarySVG, - url: 'https://lunary.ai', - inputs: [ - { - label: 'Connect Credential', - name: 'credential', - type: 'credential', - credentialNames: ['lunaryApi'] - }, - { - label: 'On/Off', - name: 'status', - type: 'boolean', - optional: true - } - ] - } -] - -const AnalyseFlowDialog = ({ show, dialogProps, onCancel }) => { - const portalElement = document.getElementById('portal') - const dispatch = useDispatch() - - useNotifier() - - const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) - const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) - - const [analytic, setAnalytic] = useState({}) - const [providerExpanded, setProviderExpanded] = useState({}) - - const onSave = async () => { - try { - const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { - analytic: JSON.stringify(analytic) - }) - if (saveResp.data) { - enqueueSnackbar({ - message: 'Analytic Configuration Saved', - options: { - key: new Date().getTime() + Math.random(), - variant: 'success', - action: (key) => ( - - ) - } - }) - dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) - } - onCancel() - } catch (error) { - const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` - enqueueSnackbar({ - message: `Failed to save Analytic Configuration: ${errorData}`, - options: { - key: new Date().getTime() + Math.random(), - variant: 'error', - persist: true, - action: (key) => ( - - ) - } - }) - } - } - - const setValue = (value, providerName, inputParamName) => { - let newVal = {} - if (!Object.prototype.hasOwnProperty.call(analytic, providerName)) { - newVal = { ...analytic, [providerName]: {} } - } else { - newVal = { ...analytic } - } - - newVal[providerName][inputParamName] = value - setAnalytic(newVal) - } - - const handleAccordionChange = (providerName) => (event, isExpanded) => { - const accordianProviders = { ...providerExpanded } - accordianProviders[providerName] = isExpanded - setProviderExpanded(accordianProviders) - } - - useEffect(() => { - if (dialogProps.chatflow && dialogProps.chatflow.analytic) { - try { - setAnalytic(JSON.parse(dialogProps.chatflow.analytic)) - } catch (e) { - setAnalytic({}) - console.error(e) - } - } - - return () => { - setAnalytic({}) - setProviderExpanded({}) - } - }, [dialogProps]) - - useEffect(() => { - if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) - else dispatch({ type: HIDE_CANVAS_DIALOG }) - return () => dispatch({ type: HIDE_CANVAS_DIALOG }) - }, [show, dispatch]) - - const component = show ? ( - - - Analyse Chatflow - - - {analyticProviders.map((provider, index) => ( - - } aria-controls={provider.name} id={provider.name}> - - -
- AI -
-
- - {provider.url} - - } - /> - {analytic[provider.name] && analytic[provider.name].status && ( -
-
- ON -
- )} - - - - {provider.inputs.map((inputParam, index) => ( - -
- - {inputParam.label} - {!inputParam.optional &&  *} - {inputParam.description && ( - - )} - -
- {providerExpanded[provider.name] && inputParam.type === 'credential' && ( - setValue(newValue, provider.name, 'credentialId')} - /> - )} - {providerExpanded[provider.name] && inputParam.type === 'boolean' && ( - setValue(newValue, provider.name, inputParam.name)} - value={ - analytic[provider.name] - ? analytic[provider.name][inputParam.name] - : inputParam.default ?? false - } - /> - )} - {providerExpanded[provider.name] && - (inputParam.type === 'string' || - inputParam.type === 'password' || - inputParam.type === 'number') && ( - setValue(newValue, provider.name, inputParam.name)} - value={ - analytic[provider.name] - ? analytic[provider.name][inputParam.name] - : inputParam.default ?? '' - } - /> - )} -
- ))} -
- - ))} - - - - Save - - -
- ) : null - - return createPortal(component, portalElement) -} - -AnalyseFlowDialog.propTypes = { - show: PropTypes.bool, - dialogProps: PropTypes.object, - onCancel: PropTypes.func -} - -export default AnalyseFlowDialog diff --git a/packages/ui/src/ui-component/dialog/ChatFeedbackDialog.js b/packages/ui/src/ui-component/dialog/ChatFeedback.js similarity index 97% rename from packages/ui/src/ui-component/dialog/ChatFeedbackDialog.js rename to packages/ui/src/ui-component/dialog/ChatFeedback.js index 493f1031b..2ea2db38f 100644 --- a/packages/ui/src/ui-component/dialog/ChatFeedbackDialog.js +++ b/packages/ui/src/ui-component/dialog/ChatFeedback.js @@ -93,7 +93,7 @@ const ChatFeedback = ({ dialogProps }) => { - + Save diff --git a/packages/ui/src/ui-component/dialog/ChatflowConfigurationDialog.js b/packages/ui/src/ui-component/dialog/ChatflowConfigurationDialog.js index 13ca4a15e..1c30aa0c0 100644 --- a/packages/ui/src/ui-component/dialog/ChatflowConfigurationDialog.js +++ b/packages/ui/src/ui-component/dialog/ChatflowConfigurationDialog.js @@ -2,16 +2,22 @@ import PropTypes from 'prop-types' import { useState } from 'react' import { createPortal } from 'react-dom' import { Box, Dialog, DialogContent, DialogTitle, Tabs, Tab } from '@mui/material' -import SpeechToText from './SpeechToTextDialog' +import SpeechToText from './SpeechToText' import Configuration from 'views/chatflows/Configuration' -import AllowedDomains from './AllowedDomainsDialog' -import ChatFeedback from './ChatFeedbackDialog' +import AllowedDomains from './AllowedDomains' +import ChatFeedback from './ChatFeedback' +import AnalyseFlow from './AnalyseFlow' +import StarterPrompts from './StarterPrompts' const CHATFLOW_CONFIGURATION_TABS = [ { label: 'Rate Limiting', id: 'rateLimiting' }, + { + label: 'Starter Prompts', + id: 'conversationStarters' + }, { label: 'Speech to Text', id: 'speechToText' @@ -23,6 +29,10 @@ const CHATFLOW_CONFIGURATION_TABS = [ { label: 'Allowed Domains', id: 'allowedDomains' + }, + { + label: 'Analyse Chatflow', + id: 'analyseChatflow' } ] @@ -34,7 +44,7 @@ function TabPanel(props) { hidden={value !== index} id={`chatflow-config-tabpanel-${index}`} aria-labelledby={`chatflow-config-tab-${index}`} - style={{ paddingTop: '1rem' }} + style={{ width: '100%', paddingTop: '1rem' }} {...other} > {value === index && {children}} @@ -73,22 +83,28 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => { setTabValue(value)} aria-label='tabs' > {CHATFLOW_CONFIGURATION_TABS.map((item, index) => ( - + ))} {CHATFLOW_CONFIGURATION_TABS.map((item, index) => ( {item.id === 'rateLimiting' && } + {item.id === 'conversationStarters' ? : null} {item.id === 'speechToText' ? : null} {item.id === 'chatFeedback' ? : null} {item.id === 'allowedDomains' ? : null} + {item.id === 'analyseChatflow' ? : null} ))} diff --git a/packages/ui/src/ui-component/dialog/SpeechToTextDialog.js b/packages/ui/src/ui-component/dialog/SpeechToText.js similarity index 98% rename from packages/ui/src/ui-component/dialog/SpeechToTextDialog.js rename to packages/ui/src/ui-component/dialog/SpeechToText.js index 7199bf8c7..eb5e5af64 100644 --- a/packages/ui/src/ui-component/dialog/SpeechToTextDialog.js +++ b/packages/ui/src/ui-component/dialog/SpeechToText.js @@ -183,8 +183,10 @@ const SpeechToText = ({ dialogProps }) => { return ( <> - - Speech To Text Providers + + + Providers +