mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-29 07:01:04 +03:00
UI Improvements (#1935)
* Update styles for dashboard page * Fix grid in chatflows and marketplaces pages * Update styles for main routes * Create ViewHeader component and use it in chatflows and marketplace * Use viewheader in all main routes views and make the styles consistent * Update table styles for chatflow and marketplace views * Update table and grid styles in all main routes views * Make backgrounds, borders, and colors everywhere * Apply text ellipsis for titles in cards and tables * Update credentials list dialog styles * Update tools dialog styles * Update styles for inputs and dialogs * Show skeleton loaders for main routes * Apply text ellipsis to chatflow title in canvas page * Update icons for load and export buttons in tools and assistants * Fix issue where table header is shown when number of elements is zero * Add error boundary component to main routes * Capture errors from all requests in main routes * Fix id for add api key and add variable buttons * Fix missing th tag in variables table body
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import { Box, Card, IconButton, Stack, Typography, useTheme } from '@mui/material'
|
||||||
|
import { IconCopy } from '@tabler/icons'
|
||||||
|
|
||||||
|
const ErrorBoundary = ({ error }) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
|
||||||
|
const copyToClipboard = () => {
|
||||||
|
const errorMessage = `Status: ${error.response.status}\n${error.response.data.message}`
|
||||||
|
navigator.clipboard.writeText(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2, padding: '20px', maxWidth: '1280px' }}>
|
||||||
|
<Stack flexDirection='column' sx={{ alignItems: 'center', gap: 3 }}>
|
||||||
|
<Stack flexDirection='column' sx={{ alignItems: 'center', gap: 1 }}>
|
||||||
|
<Typography variant='h2'>Oh snap!</Typography>
|
||||||
|
<Typography variant='h3'>The following error occured when loading this page.</Typography>
|
||||||
|
</Stack>
|
||||||
|
<Card variant='outlined'>
|
||||||
|
<Box sx={{ position: 'relative', px: 2, py: 3 }}>
|
||||||
|
<IconButton
|
||||||
|
onClick={copyToClipboard}
|
||||||
|
size='small'
|
||||||
|
sx={{ position: 'absolute', top: 1, right: 1, color: theme.palette.grey[900] + 25 }}
|
||||||
|
>
|
||||||
|
<IconCopy />
|
||||||
|
</IconButton>
|
||||||
|
<pre style={{ margin: 0 }}>
|
||||||
|
<code>{`Status: ${error.response.status}`}</code>
|
||||||
|
<br />
|
||||||
|
<code>{error.response.data.message}</code>
|
||||||
|
</pre>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
<Typography variant='body1' sx={{ fontSize: '1.1rem', textAlign: 'center', lineHeight: '1.5' }}>
|
||||||
|
Please retry after some time. If the issue persists, reach out to us on our Discord server.
|
||||||
|
<br />
|
||||||
|
Alternatively, you can raise an issue on Github.
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorBoundary.propTypes = {
|
||||||
|
error: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorBoundary
|
||||||
@@ -44,6 +44,7 @@ const NavGroup = ({ item }) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
sx={{ py: '20px' }}
|
||||||
>
|
>
|
||||||
{items}
|
{items}
|
||||||
</List>
|
</List>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { BrowserView, MobileView } from 'react-device-detect'
|
|||||||
// project imports
|
// project imports
|
||||||
import MenuList from './MenuList'
|
import MenuList from './MenuList'
|
||||||
import LogoSection from '../LogoSection'
|
import LogoSection from '../LogoSection'
|
||||||
import { drawerWidth } from '@/store/constant'
|
import { drawerWidth, headerHeight } from '@/store/constant'
|
||||||
|
|
||||||
// ==============================|| SIDEBAR DRAWER ||============================== //
|
// ==============================|| SIDEBAR DRAWER ||============================== //
|
||||||
|
|
||||||
@@ -21,7 +21,12 @@ const Sidebar = ({ drawerOpen, drawerToggle, window }) => {
|
|||||||
|
|
||||||
const drawer = (
|
const drawer = (
|
||||||
<>
|
<>
|
||||||
<Box sx={{ display: { xs: 'block', md: 'none' } }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: { xs: 'block', md: 'none' },
|
||||||
|
height: '80px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Box sx={{ display: 'flex', p: 2, mx: 'auto' }}>
|
<Box sx={{ display: 'flex', p: 2, mx: 'auto' }}>
|
||||||
<LogoSection />
|
<LogoSection />
|
||||||
</Box>
|
</Box>
|
||||||
@@ -30,7 +35,7 @@ const Sidebar = ({ drawerOpen, drawerToggle, window }) => {
|
|||||||
<PerfectScrollbar
|
<PerfectScrollbar
|
||||||
component='div'
|
component='div'
|
||||||
style={{
|
style={{
|
||||||
height: !matchUpMd ? 'calc(100vh - 56px)' : 'calc(100vh - 88px)',
|
height: !matchUpMd ? 'calc(100vh - 56px)' : `calc(100vh - ${headerHeight}px)`,
|
||||||
paddingLeft: '16px',
|
paddingLeft: '16px',
|
||||||
paddingRight: '16px'
|
paddingRight: '16px'
|
||||||
}}
|
}}
|
||||||
@@ -49,7 +54,14 @@ const Sidebar = ({ drawerOpen, drawerToggle, window }) => {
|
|||||||
const container = window !== undefined ? () => window.document.body : undefined
|
const container = window !== undefined ? () => window.document.body : undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box component='nav' sx={{ flexShrink: { md: 0 }, width: matchUpMd ? drawerWidth : 'auto' }} aria-label='mailbox folders'>
|
<Box
|
||||||
|
component='nav'
|
||||||
|
sx={{
|
||||||
|
flexShrink: { md: 0 },
|
||||||
|
width: matchUpMd ? drawerWidth : 'auto'
|
||||||
|
}}
|
||||||
|
aria-label='mailbox folders'
|
||||||
|
>
|
||||||
<Drawer
|
<Drawer
|
||||||
container={container}
|
container={container}
|
||||||
variant={matchUpMd ? 'persistent' : 'temporary'}
|
variant={matchUpMd ? 'persistent' : 'temporary'}
|
||||||
@@ -61,10 +73,11 @@ const Sidebar = ({ drawerOpen, drawerToggle, window }) => {
|
|||||||
width: drawerWidth,
|
width: drawerWidth,
|
||||||
background: theme.palette.background.default,
|
background: theme.palette.background.default,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
borderRight: 'none',
|
|
||||||
[theme.breakpoints.up('md')]: {
|
[theme.breakpoints.up('md')]: {
|
||||||
top: '66px'
|
top: `${headerHeight}px`
|
||||||
}
|
},
|
||||||
|
borderRight: drawerOpen ? '1px solid' : 'none',
|
||||||
|
borderColor: drawerOpen ? theme.palette.primary[200] + 75 : 'transparent'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
ModalProps={{ keepMounted: true }}
|
ModalProps={{ keepMounted: true }}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Box, OutlinedInput, Toolbar, Typography } from '@mui/material'
|
||||||
|
import { useTheme } from '@mui/material/styles'
|
||||||
|
|
||||||
|
// icons
|
||||||
|
import { IconSearch } from '@tabler/icons'
|
||||||
|
|
||||||
|
const ViewHeader = ({ children, filters = null, onSearchChange, search, searchPlaceholder = 'Search', title }) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ flexGrow: 1, py: 1.25, width: '100%' }}>
|
||||||
|
<Toolbar
|
||||||
|
disableGutters={true}
|
||||||
|
sx={{
|
||||||
|
p: 0,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: '2rem',
|
||||||
|
fontWeight: 600
|
||||||
|
}}
|
||||||
|
variant='h1'
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ height: 40, display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
{search && (
|
||||||
|
<OutlinedInput
|
||||||
|
size='small'
|
||||||
|
sx={{
|
||||||
|
width: '280px',
|
||||||
|
height: '100%',
|
||||||
|
display: { xs: 'none', sm: 'flex' },
|
||||||
|
borderRadius: 2,
|
||||||
|
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': {
|
||||||
|
borderRadius: 2
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
variant='outlined'
|
||||||
|
placeholder={searchPlaceholder}
|
||||||
|
onChange={onSearchChange}
|
||||||
|
startAdornment={
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.grey[400],
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
mr: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconSearch style={{ color: 'inherit', width: 16, height: 16 }} />
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
type='search'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{filters}
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</Toolbar>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewHeader.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
filters: PropTypes.node,
|
||||||
|
onSearchChange: PropTypes.func,
|
||||||
|
search: PropTypes.bool,
|
||||||
|
searchPlaceholder: PropTypes.string,
|
||||||
|
title: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ViewHeader
|
||||||
@@ -9,21 +9,23 @@ import { AppBar, Box, CssBaseline, Toolbar, useMediaQuery } from '@mui/material'
|
|||||||
// project imports
|
// project imports
|
||||||
import Header from './Header'
|
import Header from './Header'
|
||||||
import Sidebar from './Sidebar'
|
import Sidebar from './Sidebar'
|
||||||
import { drawerWidth } from '@/store/constant'
|
import { drawerWidth, headerHeight } from '@/store/constant'
|
||||||
import { SET_MENU } from '@/store/actions'
|
import { SET_MENU } from '@/store/actions'
|
||||||
|
|
||||||
// styles
|
// styles
|
||||||
const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({
|
const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({
|
||||||
...theme.typography.mainContent,
|
...theme.typography.mainContent,
|
||||||
...(!open && {
|
...(!open && {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
borderBottomLeftRadius: 0,
|
borderBottomLeftRadius: 0,
|
||||||
borderBottomRightRadius: 0,
|
borderBottomRightRadius: 0,
|
||||||
transition: theme.transitions.create('margin', {
|
transition: theme.transitions.create('all', {
|
||||||
easing: theme.transitions.easing.sharp,
|
easing: theme.transitions.easing.sharp,
|
||||||
duration: theme.transitions.duration.leavingScreen
|
duration: theme.transitions.duration.leavingScreen
|
||||||
}),
|
}),
|
||||||
|
marginRight: 0,
|
||||||
[theme.breakpoints.up('md')]: {
|
[theme.breakpoints.up('md')]: {
|
||||||
marginLeft: -(drawerWidth - 20),
|
marginLeft: -drawerWidth,
|
||||||
width: `calc(100% - ${drawerWidth}px)`
|
width: `calc(100% - ${drawerWidth}px)`
|
||||||
},
|
},
|
||||||
[theme.breakpoints.down('md')]: {
|
[theme.breakpoints.down('md')]: {
|
||||||
@@ -39,20 +41,16 @@ const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
...(open && {
|
...(open && {
|
||||||
transition: theme.transitions.create('margin', {
|
backgroundColor: 'transparent',
|
||||||
|
transition: theme.transitions.create('all', {
|
||||||
easing: theme.transitions.easing.easeOut,
|
easing: theme.transitions.easing.easeOut,
|
||||||
duration: theme.transitions.duration.enteringScreen
|
duration: theme.transitions.duration.enteringScreen
|
||||||
}),
|
}),
|
||||||
marginLeft: 0,
|
marginLeft: 0,
|
||||||
|
marginRight: 0,
|
||||||
borderBottomLeftRadius: 0,
|
borderBottomLeftRadius: 0,
|
||||||
borderBottomRightRadius: 0,
|
borderBottomRightRadius: 0,
|
||||||
width: `calc(100% - ${drawerWidth}px)`,
|
width: `calc(100% - ${drawerWidth}px)`
|
||||||
[theme.breakpoints.down('md')]: {
|
|
||||||
marginLeft: '20px'
|
|
||||||
},
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
marginLeft: '10px'
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -88,7 +86,7 @@ const MainLayout = () => {
|
|||||||
transition: leftDrawerOpened ? theme.transitions.create('width') : 'none'
|
transition: leftDrawerOpened ? theme.transitions.create('width') : 'none'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Toolbar>
|
<Toolbar sx={{ height: `${headerHeight}px`, borderBottom: '1px solid', borderColor: theme.palette.primary[200] + 75 }}>
|
||||||
<Header handleLeftDrawerToggle={handleLeftDrawerToggle} />
|
<Header handleLeftDrawerToggle={handleLeftDrawerToggle} />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
export const gridSpacing = 3
|
export const gridSpacing = 3
|
||||||
export const drawerWidth = 260
|
export const drawerWidth = 260
|
||||||
export const appDrawerWidth = 320
|
export const appDrawerWidth = 320
|
||||||
|
export const headerHeight = 80
|
||||||
export const maxScroll = 100000
|
export const maxScroll = 100000
|
||||||
export const baseURL =
|
export const baseURL =
|
||||||
import.meta.env.PROD === true
|
import.meta.env.PROD === true
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const StyledMenu = styled((props) => (
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
export default function FlowListMenu({ chatflow, setError, updateFlowsApi }) {
|
||||||
const { confirm } = useConfirm()
|
const { confirm } = useConfirm()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
||||||
@@ -153,6 +153,7 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||||||
await updateChatflowApi.request(chatflow.id, updateBody)
|
await updateChatflowApi.request(chatflow.id, updateBody)
|
||||||
await updateFlowsApi.request()
|
await updateFlowsApi.request()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: error.response.data.message,
|
message: error.response.data.message,
|
||||||
options: {
|
options: {
|
||||||
@@ -191,6 +192,7 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||||||
await updateChatflowApi.request(chatflow.id, updateBody)
|
await updateChatflowApi.request(chatflow.id, updateBody)
|
||||||
await updateFlowsApi.request()
|
await updateFlowsApi.request()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: error.response.data.message,
|
message: error.response.data.message,
|
||||||
options: {
|
options: {
|
||||||
@@ -222,6 +224,7 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||||||
await chatflowsApi.deleteChatflow(chatflow.id)
|
await chatflowsApi.deleteChatflow(chatflow.id)
|
||||||
await updateFlowsApi.request()
|
await updateFlowsApi.request()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: error.response.data.message,
|
message: error.response.data.message,
|
||||||
options: {
|
options: {
|
||||||
@@ -370,5 +373,6 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||||||
|
|
||||||
FlowListMenu.propTypes = {
|
FlowListMenu.propTypes = {
|
||||||
chatflow: PropTypes.object,
|
chatflow: PropTypes.object,
|
||||||
|
setError: PropTypes.func,
|
||||||
updateFlowsApi: PropTypes.object
|
updateFlowsApi: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { styled } from '@mui/material/styles'
|
import { styled } from '@mui/material/styles'
|
||||||
import { Box, Grid, Typography } from '@mui/material'
|
import { Box, Grid, Typography, useTheme } from '@mui/material'
|
||||||
|
|
||||||
// project imports
|
// project imports
|
||||||
import MainCard from '@/ui-component/cards/MainCard'
|
import MainCard from '@/ui-component/cards/MainCard'
|
||||||
import SkeletonChatflowCard from '@/ui-component/cards/Skeleton/ChatflowCard'
|
|
||||||
|
|
||||||
const CardWrapper = styled(MainCard)(({ theme }) => ({
|
const CardWrapper = styled(MainCard)(({ theme }) => ({
|
||||||
background: theme.palette.card.main,
|
background: theme.palette.card.main,
|
||||||
@@ -19,101 +19,115 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({
|
|||||||
background: theme.palette.card.hover,
|
background: theme.palette.card.hover,
|
||||||
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 20%)'
|
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 20%)'
|
||||||
},
|
},
|
||||||
|
height: '100%',
|
||||||
|
minHeight: '160px',
|
||||||
maxHeight: '300px',
|
maxHeight: '300px',
|
||||||
maxWidth: '300px',
|
width: '100%',
|
||||||
overflowWrap: 'break-word',
|
overflowWrap: 'break-word',
|
||||||
whiteSpace: 'pre-line'
|
whiteSpace: 'pre-line'
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// ===========================|| CONTRACT CARD ||=========================== //
|
// ===========================|| CONTRACT CARD ||=========================== //
|
||||||
|
|
||||||
const ItemCard = ({ isLoading, data, images, onClick }) => {
|
const ItemCard = ({ data, images, onClick }) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const customization = useSelector((state) => state.customization)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<CardWrapper content={false} onClick={onClick} sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}>
|
||||||
{isLoading ? (
|
<Box sx={{ height: '100%', p: 2.25 }}>
|
||||||
<SkeletonChatflowCard />
|
<Grid container justifyContent='space-between' direction='column' sx={{ height: '100%', gap: 3 }}>
|
||||||
) : (
|
<Box display='flex' flexDirection='column' sx={{ width: '100%' }}>
|
||||||
<CardWrapper border={false} content={false} onClick={onClick}>
|
<div
|
||||||
<Box sx={{ p: 2.25 }}>
|
style={{
|
||||||
<Grid container direction='column'>
|
width: '100%',
|
||||||
<div
|
display: 'flex',
|
||||||
style={{
|
flexDirection: 'row',
|
||||||
display: 'flex',
|
alignItems: 'center',
|
||||||
flexDirection: 'row',
|
overflow: 'hidden'
|
||||||
alignItems: 'center'
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{data.iconSrc && (
|
||||||
{data.iconSrc && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: 35,
|
|
||||||
height: 35,
|
|
||||||
marginRight: 10,
|
|
||||||
borderRadius: '50%',
|
|
||||||
background: `url(${data.iconSrc})`,
|
|
||||||
backgroundSize: 'contain',
|
|
||||||
backgroundRepeat: 'no-repeat',
|
|
||||||
backgroundPosition: 'center center'
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
{!data.iconSrc && data.color && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: 35,
|
|
||||||
height: 35,
|
|
||||||
marginRight: 10,
|
|
||||||
borderRadius: '50%',
|
|
||||||
background: data.color
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
<Typography
|
|
||||||
sx={{ fontSize: '1.5rem', fontWeight: 500, overflowWrap: 'break-word', whiteSpace: 'pre-line' }}
|
|
||||||
>
|
|
||||||
{data.templateName || data.name}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
{data.description && (
|
|
||||||
<span style={{ marginTop: 10, overflowWrap: 'break-word', whiteSpace: 'pre-line' }}>
|
|
||||||
{data.description}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{images && (
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexShrink: 0,
|
||||||
flexWrap: 'wrap',
|
marginRight: 10,
|
||||||
marginTop: 5
|
borderRadius: '50%',
|
||||||
|
background: `url(${data.iconSrc})`,
|
||||||
|
backgroundSize: 'contain',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundPosition: 'center center'
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
{!data.iconSrc && data.color && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
display: 'flex',
|
||||||
|
flexShrink: 0,
|
||||||
|
marginRight: 10,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: data.color
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
display: '-webkit-box',
|
||||||
|
fontSize: '1.25rem',
|
||||||
|
fontWeight: 500,
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.templateName || data.name}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
{data.description && (
|
||||||
|
<span style={{ marginTop: 10, overflowWrap: 'break-word', whiteSpace: 'pre-line' }}>{data.description}</span>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{images && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'start',
|
||||||
|
gap: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{images.slice(0, images.length > 3 ? 3 : images.length).map((img) => (
|
||||||
|
<Box
|
||||||
|
key={img}
|
||||||
|
sx={{
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: customization.isDarkMode
|
||||||
|
? theme.palette.common.white
|
||||||
|
: theme.palette.grey[300] + 75
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{images.map((img) => (
|
<img style={{ width: '100%', height: '100%', padding: 5, objectFit: 'contain' }} alt='' src={img} />
|
||||||
<div
|
</Box>
|
||||||
key={img}
|
))}
|
||||||
style={{
|
{images.length > 3 && (
|
||||||
width: 35,
|
<Typography sx={{ alignItems: 'center', display: 'flex', fontSize: '.9rem', fontWeight: 200 }}>
|
||||||
height: 35,
|
+ {images.length - 3} More
|
||||||
marginRight: 5,
|
</Typography>
|
||||||
borderRadius: '50%',
|
|
||||||
backgroundColor: 'white',
|
|
||||||
marginTop: 5
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
style={{ width: '100%', height: '100%', padding: 5, objectFit: 'contain' }}
|
|
||||||
alt=''
|
|
||||||
src={img}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
</CardWrapper>
|
</Grid>
|
||||||
)}
|
</Box>
|
||||||
</>
|
</CardWrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
|
|||||||
import { forwardRef } from 'react'
|
import { forwardRef } from 'react'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { useTheme } from '@mui/material/styles'
|
|
||||||
import { Card, CardContent, CardHeader, Divider, Typography } from '@mui/material'
|
import { Card, CardContent, CardHeader, Divider, Typography } from '@mui/material'
|
||||||
|
|
||||||
// constant
|
// constant
|
||||||
@@ -14,12 +13,14 @@ const headerSX = {
|
|||||||
|
|
||||||
const MainCard = forwardRef(function MainCard(
|
const MainCard = forwardRef(function MainCard(
|
||||||
{
|
{
|
||||||
border = true,
|
|
||||||
boxShadow,
|
boxShadow,
|
||||||
children,
|
children,
|
||||||
content = true,
|
content = true,
|
||||||
contentClass = '',
|
contentClass = '',
|
||||||
contentSX = {},
|
contentSX = {
|
||||||
|
px: 2,
|
||||||
|
py: 0
|
||||||
|
},
|
||||||
darkTitle,
|
darkTitle,
|
||||||
secondary,
|
secondary,
|
||||||
shadow,
|
shadow,
|
||||||
@@ -29,18 +30,17 @@ const MainCard = forwardRef(function MainCard(
|
|||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) {
|
) {
|
||||||
const theme = useTheme()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...others}
|
{...others}
|
||||||
sx={{
|
sx={{
|
||||||
border: border ? '1px solid' : 'none',
|
background: 'transparent',
|
||||||
borderColor: theme.palette.primary[200] + 75,
|
|
||||||
':hover': {
|
':hover': {
|
||||||
boxShadow: boxShadow ? shadow || '0 2px 14px 0 rgb(32 40 45 / 8%)' : 'inherit'
|
boxShadow: boxShadow ? shadow || '0 2px 14px 0 rgb(32 40 45 / 8%)' : 'inherit'
|
||||||
},
|
},
|
||||||
|
maxWidth: '1280px',
|
||||||
|
mx: 'auto',
|
||||||
...sx
|
...sx
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export const AsyncDropdown = ({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
disableClearable={disableClearable}
|
disableClearable={disableClearable}
|
||||||
size='small'
|
size='small'
|
||||||
sx={{ width: '100%' }}
|
sx={{ width: '100%', height: '52px' }}
|
||||||
open={open}
|
open={open}
|
||||||
onOpen={() => {
|
onOpen={() => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
@@ -148,6 +148,7 @@ export const AsyncDropdown = ({
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
sx={{ height: '100%', '& .MuiInputBase-root': { height: '100%' } }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
renderOption={(props, option) => (
|
renderOption={(props, option) => (
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const Dropdown = ({ name, value, options, onSelect, disabled = false, dis
|
|||||||
let [internalValue, setInternalValue] = useState(value ?? 'choose an option')
|
let [internalValue, setInternalValue] = useState(value ?? 'choose an option')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl sx={{ mt: 1, width: '100%' }} size='small'>
|
<FormControl sx={{ width: '100%', height: '52px' }} size='small'>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
id={name}
|
id={name}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -39,7 +39,9 @@ export const Dropdown = ({ name, value, options, onSelect, disabled = false, dis
|
|||||||
onSelect(value)
|
onSelect(value)
|
||||||
}}
|
}}
|
||||||
PopperComponent={StyledPopper}
|
PopperComponent={StyledPopper}
|
||||||
renderInput={(params) => <TextField {...params} value={internalValue} />}
|
renderInput={(params) => (
|
||||||
|
<TextField {...params} value={internalValue} sx={{ height: '100%', '& .MuiInputBase-root': { height: '100%' } }} />
|
||||||
|
)}
|
||||||
renderOption={(props, option) => (
|
renderOption={(props, option) => (
|
||||||
<Box component='li' {...props}>
|
<Box component='li' {...props}>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
@@ -50,6 +52,7 @@ export const Dropdown = ({ name, value, options, onSelect, disabled = false, dis
|
|||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
sx={{ height: '100%' }}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const MultiDropdown = ({ name, value, options, onSelect, formControlSx =
|
|||||||
let [internalValue, setInternalValue] = useState(value ?? [])
|
let [internalValue, setInternalValue] = useState(value ?? [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl sx={{ mt: 1, width: '100%', ...formControlSx }} size='small'>
|
<FormControl sx={{ width: '100%', height: '52px', ...formControlSx }} size='small'>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
id={name}
|
id={name}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -53,7 +53,9 @@ export const MultiDropdown = ({ name, value, options, onSelect, formControlSx =
|
|||||||
onSelect(value)
|
onSelect(value)
|
||||||
}}
|
}}
|
||||||
PopperComponent={StyledPopper}
|
PopperComponent={StyledPopper}
|
||||||
renderInput={(params) => <TextField {...params} value={internalValue} />}
|
renderInput={(params) => (
|
||||||
|
<TextField {...params} value={internalValue} sx={{ height: '100%', '& .MuiInputBase-root': { height: '100%' } }} />
|
||||||
|
)}
|
||||||
renderOption={(props, option) => (
|
renderOption={(props, option) => (
|
||||||
<Box component='li' {...props}>
|
<Box component='li' {...props}>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
@@ -64,6 +66,7 @@ export const MultiDropdown = ({ name, value, options, onSelect, formControlSx =
|
|||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
sx={{ height: '100%' }}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { DataGrid } from '@mui/x-data-grid'
|
import { DataGrid } from '@mui/x-data-grid'
|
||||||
import { IconPlus } from '@tabler/icons'
|
|
||||||
import { Button } from '@mui/material'
|
|
||||||
|
|
||||||
export const Grid = ({ columns, rows, style, disabled = false, onRowUpdate, addNewRow }) => {
|
export const Grid = ({ columns, rows, style, disabled = false, onRowUpdate }) => {
|
||||||
const handleProcessRowUpdate = (newRow) => {
|
const handleProcessRowUpdate = (newRow) => {
|
||||||
onRowUpdate(newRow)
|
onRowUpdate(newRow)
|
||||||
return newRow
|
return newRow
|
||||||
@@ -11,11 +9,6 @@ export const Grid = ({ columns, rows, style, disabled = false, onRowUpdate, addN
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!disabled && (
|
|
||||||
<Button variant='outlined' onClick={addNewRow} startIcon={<IconPlus />}>
|
|
||||||
Add Item
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{rows && columns && (
|
{rows && columns && (
|
||||||
<div style={{ marginTop: 10, height: 300, width: '100%', ...style }}>
|
<div style={{ marginTop: 10, height: 300, width: '100%', ...style }}>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
@@ -38,6 +31,5 @@ Grid.propTypes = {
|
|||||||
columns: PropTypes.array,
|
columns: PropTypes.array,
|
||||||
style: PropTypes.any,
|
style: PropTypes.any,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
addNewRow: PropTypes.func,
|
|
||||||
onRowUpdate: PropTypes.func
|
onRowUpdate: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,61 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useSelector } from 'react-redux'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { styled } from '@mui/material/styles'
|
import { styled } from '@mui/material/styles'
|
||||||
import Table from '@mui/material/Table'
|
import {
|
||||||
import TableBody from '@mui/material/TableBody'
|
Box,
|
||||||
import TableCell, { tableCellClasses } from '@mui/material/TableCell'
|
Chip,
|
||||||
import TableContainer from '@mui/material/TableContainer'
|
Paper,
|
||||||
import TableHead from '@mui/material/TableHead'
|
Skeleton,
|
||||||
import TableRow from '@mui/material/TableRow'
|
Stack,
|
||||||
import Paper from '@mui/material/Paper'
|
Table,
|
||||||
import Chip from '@mui/material/Chip'
|
TableBody,
|
||||||
import { Button, Stack, Typography } from '@mui/material'
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from '@mui/material'
|
||||||
|
import { tableCellClasses } from '@mui/material/TableCell'
|
||||||
import FlowListMenu from '../button/FlowListMenu'
|
import FlowListMenu from '../button/FlowListMenu'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
||||||
|
borderColor: theme.palette.grey[900] + 25,
|
||||||
|
|
||||||
[`&.${tableCellClasses.head}`]: {
|
[`&.${tableCellClasses.head}`]: {
|
||||||
backgroundColor: theme.palette.common.black,
|
color: theme.palette.grey[900]
|
||||||
color: theme.palette.common.white
|
|
||||||
},
|
},
|
||||||
[`&.${tableCellClasses.body}`]: {
|
[`&.${tableCellClasses.body}`]: {
|
||||||
fontSize: 14
|
fontSize: 14,
|
||||||
|
height: 64
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const StyledTableRow = styled(TableRow)(({ theme }) => ({
|
const StyledTableRow = styled(TableRow)(() => ({
|
||||||
'&:nth-of-type(odd)': {
|
|
||||||
backgroundColor: theme.palette.action.hover
|
|
||||||
},
|
|
||||||
// hide last border
|
// hide last border
|
||||||
'&:last-child td, &:last-child th': {
|
'&:last-child td, &:last-child th': {
|
||||||
border: 0
|
border: 0
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export const FlowListTable = ({ data, images, filterFunction, updateFlowsApi }) => {
|
export const FlowListTable = ({ data, images, isLoading, filterFunction, updateFlowsApi, setError }) => {
|
||||||
const navigate = useNavigate()
|
const theme = useTheme()
|
||||||
const goToCanvas = (selectedChatflow) => {
|
const customization = useSelector((state) => state.customization)
|
||||||
navigate(`/canvas/${selectedChatflow.id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TableContainer style={{ marginTop: '30', border: 1 }} component={Paper}>
|
<TableContainer sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }} component={Paper}>
|
||||||
<Table sx={{ minWidth: 650 }} size='small' aria-label='a dense table'>
|
<Table sx={{ minWidth: 650 }} size='small' aria-label='a dense table'>
|
||||||
<TableHead>
|
<TableHead
|
||||||
<TableRow sx={{ marginTop: '10', backgroundColor: 'primary' }}>
|
sx={{
|
||||||
|
backgroundColor: customization.isDarkMode ? theme.palette.common.black : theme.palette.grey[100],
|
||||||
|
height: 56
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableRow>
|
||||||
<StyledTableCell component='th' scope='row' style={{ width: '20%' }} key='0'>
|
<StyledTableCell component='th' scope='row' style={{ width: '20%' }} key='0'>
|
||||||
Name
|
Name
|
||||||
</StyledTableCell>
|
</StyledTableCell>
|
||||||
@@ -63,82 +74,150 @@ export const FlowListTable = ({ data, images, filterFunction, updateFlowsApi })
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{data.filter(filterFunction).map((row, index) => (
|
{isLoading ? (
|
||||||
<StyledTableRow key={index}>
|
<>
|
||||||
<TableCell key='0'>
|
<StyledTableRow>
|
||||||
<Typography
|
<StyledTableCell>
|
||||||
sx={{ fontSize: '1.2rem', fontWeight: 500, overflowWrap: 'break-word', whiteSpace: 'pre-line' }}
|
<Skeleton variant='text' />
|
||||||
>
|
</StyledTableCell>
|
||||||
<Button onClick={() => goToCanvas(row)} sx={{ textAlign: 'left' }}>
|
<StyledTableCell>
|
||||||
{row.templateName || row.name}
|
<Skeleton variant='text' />
|
||||||
</Button>
|
</StyledTableCell>
|
||||||
</Typography>
|
<StyledTableCell>
|
||||||
</TableCell>
|
<Skeleton variant='text' />
|
||||||
<TableCell key='1'>
|
</StyledTableCell>
|
||||||
<div
|
<StyledTableCell>
|
||||||
style={{
|
<Skeleton variant='text' />
|
||||||
display: 'flex',
|
</StyledTableCell>
|
||||||
flexDirection: 'row',
|
<StyledTableCell>
|
||||||
flexWrap: 'wrap',
|
<Skeleton variant='text' />
|
||||||
marginTop: 5
|
</StyledTableCell>
|
||||||
}}
|
</StyledTableRow>
|
||||||
>
|
<StyledTableRow>
|
||||||
|
<StyledTableCell>
|
||||||
{row.category &&
|
<Skeleton variant='text' />
|
||||||
row.category
|
</StyledTableCell>
|
||||||
.split(';')
|
<StyledTableCell>
|
||||||
.map((tag, index) => (
|
<Skeleton variant='text' />
|
||||||
<Chip key={index} label={tag} style={{ marginRight: 5, marginBottom: 5 }} />
|
</StyledTableCell>
|
||||||
))}
|
<StyledTableCell>
|
||||||
</div>
|
<Skeleton variant='text' />
|
||||||
</TableCell>
|
</StyledTableCell>
|
||||||
<TableCell key='2'>
|
<StyledTableCell>
|
||||||
{images[row.id] && (
|
<Skeleton variant='text' />
|
||||||
<div
|
</StyledTableCell>
|
||||||
style={{
|
<StyledTableCell>
|
||||||
display: 'flex',
|
<Skeleton variant='text' />
|
||||||
flexDirection: 'row',
|
</StyledTableCell>
|
||||||
flexWrap: 'wrap',
|
</StyledTableRow>
|
||||||
marginTop: 5
|
</>
|
||||||
}}
|
) : (
|
||||||
>
|
<>
|
||||||
{images[row.id].slice(0, images[row.id].length > 5 ? 5 : images[row.id].length).map((img) => (
|
{data?.filter(filterFunction).map((row, index) => (
|
||||||
<div
|
<StyledTableRow key={index}>
|
||||||
key={img}
|
<StyledTableCell key='0'>
|
||||||
style={{
|
<Tooltip title={row.templateName || row.name}>
|
||||||
width: 35,
|
<Typography
|
||||||
height: 35,
|
sx={{
|
||||||
marginRight: 5,
|
display: '-webkit-box',
|
||||||
borderRadius: '50%',
|
fontSize: 14,
|
||||||
backgroundColor: 'white',
|
fontWeight: 500,
|
||||||
marginTop: 5
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<Link to={`/canvas/${row.id}`} style={{ color: '#2196f3', textDecoration: 'none' }}>
|
||||||
style={{ width: '100%', height: '100%', padding: 5, objectFit: 'contain' }}
|
{row.templateName || row.name}
|
||||||
alt=''
|
</Link>
|
||||||
src={img}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{images[row.id].length > 5 && (
|
|
||||||
<Typography
|
|
||||||
sx={{ alignItems: 'center', display: 'flex', fontSize: '.8rem', fontWeight: 200 }}
|
|
||||||
>
|
|
||||||
+ {images[row.id].length - 5} More
|
|
||||||
</Typography>
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell key='1'>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
marginTop: 5
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
{row.category &&
|
||||||
|
row.category
|
||||||
|
.split(';')
|
||||||
|
.map((tag, index) => (
|
||||||
|
<Chip key={index} label={tag} style={{ marginRight: 5, marginBottom: 5 }} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell key='2'>
|
||||||
|
{images[row.id] && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'start',
|
||||||
|
gap: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{images[row.id]
|
||||||
|
.slice(0, images[row.id].length > 5 ? 5 : images[row.id].length)
|
||||||
|
.map((img) => (
|
||||||
|
<Box
|
||||||
|
key={img}
|
||||||
|
sx={{
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: customization.isDarkMode
|
||||||
|
? theme.palette.common.white
|
||||||
|
: theme.palette.grey[300] + 75
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
padding: 5,
|
||||||
|
objectFit: 'contain'
|
||||||
|
}}
|
||||||
|
alt=''
|
||||||
|
src={img}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
{images[row.id].length > 5 && (
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
fontSize: '.9rem',
|
||||||
|
fontWeight: 200
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+ {images[row.id].length - 5} More
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</div>
|
</StyledTableCell>
|
||||||
)}
|
<StyledTableCell key='3'>{moment(row.updatedDate).format('MMMM Do, YYYY')}</StyledTableCell>
|
||||||
</TableCell>
|
<StyledTableCell key='4'>
|
||||||
<TableCell key='3'>{moment(row.updatedDate).format('MMMM Do, YYYY')}</TableCell>
|
<Stack
|
||||||
<TableCell key='4'>
|
direction={{ xs: 'column', sm: 'row' }}
|
||||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={1} justifyContent='center' alignItems='center'>
|
spacing={1}
|
||||||
<FlowListMenu chatflow={row} updateFlowsApi={updateFlowsApi} />
|
justifyContent='center'
|
||||||
</Stack>
|
alignItems='center'
|
||||||
</TableCell>
|
>
|
||||||
</StyledTableRow>
|
<FlowListMenu chatflow={row} setError={setError} updateFlowsApi={updateFlowsApi} />
|
||||||
))}
|
</Stack>
|
||||||
|
</StyledTableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
@@ -149,6 +228,8 @@ export const FlowListTable = ({ data, images, filterFunction, updateFlowsApi })
|
|||||||
FlowListTable.propTypes = {
|
FlowListTable.propTypes = {
|
||||||
data: PropTypes.array,
|
data: PropTypes.array,
|
||||||
images: PropTypes.object,
|
images: PropTypes.object,
|
||||||
|
isLoading: PropTypes.bool,
|
||||||
filterFunction: PropTypes.func,
|
filterFunction: PropTypes.func,
|
||||||
updateFlowsApi: PropTypes.object
|
updateFlowsApi: PropTypes.object,
|
||||||
|
setError: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,54 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
import { styled } from '@mui/material/styles'
|
import { styled } from '@mui/material/styles'
|
||||||
import Table from '@mui/material/Table'
|
import { tableCellClasses } from '@mui/material/TableCell'
|
||||||
import TableBody from '@mui/material/TableBody'
|
import {
|
||||||
import TableCell, { tableCellClasses } from '@mui/material/TableCell'
|
Button,
|
||||||
import TableContainer from '@mui/material/TableContainer'
|
Chip,
|
||||||
import TableHead from '@mui/material/TableHead'
|
Paper,
|
||||||
import TableRow from '@mui/material/TableRow'
|
Skeleton,
|
||||||
import Paper from '@mui/material/Paper'
|
Table,
|
||||||
import Chip from '@mui/material/Chip'
|
TableBody,
|
||||||
import { Button, Typography } from '@mui/material'
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from '@mui/material'
|
||||||
|
|
||||||
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
||||||
|
borderColor: theme.palette.grey[900] + 25,
|
||||||
|
|
||||||
[`&.${tableCellClasses.head}`]: {
|
[`&.${tableCellClasses.head}`]: {
|
||||||
backgroundColor: theme.palette.common.black,
|
color: theme.palette.grey[900]
|
||||||
color: theme.palette.common.white
|
|
||||||
},
|
},
|
||||||
[`&.${tableCellClasses.body}`]: {
|
[`&.${tableCellClasses.body}`]: {
|
||||||
fontSize: 14
|
fontSize: 14,
|
||||||
|
height: 64
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const StyledTableRow = styled(TableRow)(({ theme }) => ({
|
const StyledTableRow = styled(TableRow)(() => ({
|
||||||
'&:nth-of-type(odd)': {
|
|
||||||
backgroundColor: theme.palette.action.hover
|
|
||||||
},
|
|
||||||
// hide last border
|
// hide last border
|
||||||
'&:last-child td, &:last-child th': {
|
'&:last-child td, &:last-child th': {
|
||||||
border: 0
|
border: 0
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export const MarketplaceTable = ({ data, filterFunction, filterByBadge, filterByType, filterByFramework, goToCanvas, goToTool }) => {
|
export const MarketplaceTable = ({
|
||||||
|
data,
|
||||||
|
filterFunction,
|
||||||
|
filterByBadge,
|
||||||
|
filterByType,
|
||||||
|
filterByFramework,
|
||||||
|
goToCanvas,
|
||||||
|
goToTool,
|
||||||
|
isLoading
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const customization = useSelector((state) => state.customization)
|
||||||
|
|
||||||
const openTemplate = (selectedTemplate) => {
|
const openTemplate = (selectedTemplate) => {
|
||||||
if (selectedTemplate.flowData) {
|
if (selectedTemplate.flowData) {
|
||||||
goToCanvas(selectedTemplate)
|
goToCanvas(selectedTemplate)
|
||||||
@@ -41,10 +59,15 @@ export const MarketplaceTable = ({ data, filterFunction, filterByBadge, filterBy
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TableContainer style={{ marginTop: '30', border: 1 }} component={Paper}>
|
<TableContainer sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }} component={Paper}>
|
||||||
<Table sx={{ minWidth: 650 }} size='small' aria-label='a dense table'>
|
<Table sx={{ minWidth: 650 }} size='small' aria-label='a dense table'>
|
||||||
<TableHead>
|
<TableHead
|
||||||
<TableRow sx={{ marginTop: '10', backgroundColor: 'primary' }}>
|
sx={{
|
||||||
|
backgroundColor: customization.isDarkMode ? theme.palette.common.black : theme.palette.grey[100],
|
||||||
|
height: 56
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableRow>
|
||||||
<StyledTableCell component='th' scope='row' style={{ width: '15%' }} key='0'>
|
<StyledTableCell component='th' scope='row' style={{ width: '15%' }} key='0'>
|
||||||
Name
|
Name
|
||||||
</StyledTableCell>
|
</StyledTableCell>
|
||||||
@@ -63,71 +86,120 @@ export const MarketplaceTable = ({ data, filterFunction, filterByBadge, filterBy
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{data
|
{isLoading ? (
|
||||||
.filter(filterByBadge)
|
<>
|
||||||
.filter(filterByType)
|
<StyledTableRow>
|
||||||
.filter(filterFunction)
|
<StyledTableCell>
|
||||||
.filter(filterByFramework)
|
<Skeleton variant='text' />
|
||||||
.map((row, index) => (
|
</StyledTableCell>
|
||||||
<StyledTableRow key={index}>
|
<StyledTableCell>
|
||||||
<TableCell key='0'>
|
<Skeleton variant='text' />
|
||||||
<Typography
|
</StyledTableCell>
|
||||||
sx={{ fontSize: '1.2rem', fontWeight: 500, overflowWrap: 'break-word', whiteSpace: 'pre-line' }}
|
<StyledTableCell>
|
||||||
>
|
<Skeleton variant='text' />
|
||||||
<Button onClick={() => openTemplate(row)} sx={{ textAlign: 'left' }}>
|
</StyledTableCell>
|
||||||
{row.templateName || row.name}
|
<StyledTableCell>
|
||||||
</Button>
|
<Skeleton variant='text' />
|
||||||
</Typography>
|
</StyledTableCell>
|
||||||
</TableCell>
|
<StyledTableCell>
|
||||||
<TableCell key='1'>
|
<Skeleton variant='text' />
|
||||||
<Typography>{row.type}</Typography>
|
</StyledTableCell>
|
||||||
</TableCell>
|
|
||||||
<TableCell key='2'>
|
|
||||||
<Typography sx={{ overflowWrap: 'break-word', whiteSpace: 'pre-line' }}>
|
|
||||||
{row.description || ''}
|
|
||||||
</Typography>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell key='3'>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
marginTop: 5
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{row.categories &&
|
|
||||||
row.categories
|
|
||||||
.split(',')
|
|
||||||
.map((tag, index) => (
|
|
||||||
<Chip
|
|
||||||
variant='outlined'
|
|
||||||
key={index}
|
|
||||||
size='small'
|
|
||||||
label={tag.toUpperCase()}
|
|
||||||
style={{ marginRight: 3, marginBottom: 3 }}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell key='4'>
|
|
||||||
<Typography>
|
|
||||||
{row.badge &&
|
|
||||||
row.badge
|
|
||||||
.split(';')
|
|
||||||
.map((tag, index) => (
|
|
||||||
<Chip
|
|
||||||
color={tag === 'POPULAR' ? 'primary' : 'error'}
|
|
||||||
key={index}
|
|
||||||
size='small'
|
|
||||||
label={tag.toUpperCase()}
|
|
||||||
style={{ marginRight: 5, marginBottom: 5 }}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Typography>
|
|
||||||
</TableCell>
|
|
||||||
</StyledTableRow>
|
</StyledTableRow>
|
||||||
))}
|
<StyledTableRow>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{data
|
||||||
|
?.filter(filterByBadge)
|
||||||
|
.filter(filterByType)
|
||||||
|
.filter(filterFunction)
|
||||||
|
.filter(filterByFramework)
|
||||||
|
.map((row, index) => (
|
||||||
|
<StyledTableRow key={index}>
|
||||||
|
<StyledTableCell key='0'>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
display: '-webkit-box',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button onClick={() => openTemplate(row)} sx={{ textAlign: 'left' }}>
|
||||||
|
{row.templateName || row.name}
|
||||||
|
</Button>
|
||||||
|
</Typography>
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell key='1'>
|
||||||
|
<Typography>{row.type}</Typography>
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell key='2'>
|
||||||
|
<Typography sx={{ overflowWrap: 'break-word', whiteSpace: 'pre-line' }}>
|
||||||
|
{row.description || ''}
|
||||||
|
</Typography>
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell key='3'>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
marginTop: 5
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{row.categories &&
|
||||||
|
row.categories
|
||||||
|
.split(',')
|
||||||
|
.map((tag, index) => (
|
||||||
|
<Chip
|
||||||
|
variant='outlined'
|
||||||
|
key={index}
|
||||||
|
size='small'
|
||||||
|
label={tag.toUpperCase()}
|
||||||
|
style={{ marginRight: 3, marginBottom: 3 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell key='4'>
|
||||||
|
<Typography>
|
||||||
|
{row.badge &&
|
||||||
|
row.badge
|
||||||
|
.split(';')
|
||||||
|
.map((tag, index) => (
|
||||||
|
<Chip
|
||||||
|
color={tag === 'POPULAR' ? 'primary' : 'error'}
|
||||||
|
key={index}
|
||||||
|
size='small'
|
||||||
|
label={tag.toUpperCase()}
|
||||||
|
style={{ marginRight: 5, marginBottom: 5 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Typography>
|
||||||
|
</StyledTableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
@@ -142,5 +214,6 @@ MarketplaceTable.propTypes = {
|
|||||||
filterByType: PropTypes.func,
|
filterByType: PropTypes.func,
|
||||||
filterByFramework: PropTypes.func,
|
filterByFramework: PropTypes.func,
|
||||||
goToTool: PropTypes.func,
|
goToTool: PropTypes.func,
|
||||||
goToCanvas: PropTypes.func
|
goToCanvas: PropTypes.func,
|
||||||
|
isLoading: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import parser from 'html-react-parser'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
export const TooltipWithParser = ({ title, style }) => {
|
export const TooltipWithParser = ({ title, sx }) => {
|
||||||
const customization = useSelector((state) => state.customization)
|
const customization = useSelector((state) => state.customization)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={parser(title)} placement='right'>
|
<Tooltip title={parser(title)} placement='right'>
|
||||||
<IconButton sx={{ height: 15, width: 15 }}>
|
<IconButton sx={{ height: 15, width: 15, ml: 2, mt: -0.5 }}>
|
||||||
<Info
|
<Info
|
||||||
style={{
|
sx={{
|
||||||
...style,
|
...sx,
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
color: customization.isDarkMode ? 'white' : 'inherit',
|
color: customization.isDarkMode ? 'white' : 'inherit',
|
||||||
height: 15,
|
height: 15,
|
||||||
@@ -26,5 +26,5 @@ export const TooltipWithParser = ({ title, style }) => {
|
|||||||
|
|
||||||
TooltipWithParser.propTypes = {
|
TooltipWithParser.propTypes = {
|
||||||
title: PropTypes.node,
|
title: PropTypes.node,
|
||||||
style: PropTypes.any
|
sx: PropTypes.any
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import apikeyApi from '@/api/apikey'
|
|||||||
// utils
|
// utils
|
||||||
import useNotifier from '@/utils/useNotifier'
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
|
||||||
const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
|
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
@@ -77,6 +77,7 @@ const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||||||
onConfirm()
|
onConfirm()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to add new API key: ${error.response.data.message}`,
|
message: `Failed to add new API key: ${error.response.data.message}`,
|
||||||
options: {
|
options: {
|
||||||
@@ -113,6 +114,7 @@ const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||||||
onConfirm()
|
onConfirm()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to save API key: ${error.response.data.message}`,
|
message: `Failed to save API key: ${error.response.data.message}`,
|
||||||
options: {
|
options: {
|
||||||
@@ -227,7 +229,8 @@ APIKeyDialog.propTypes = {
|
|||||||
show: PropTypes.bool,
|
show: PropTypes.bool,
|
||||||
dialogProps: PropTypes.object,
|
dialogProps: PropTypes.object,
|
||||||
onCancel: PropTypes.func,
|
onCancel: PropTypes.func,
|
||||||
onConfirm: PropTypes.func
|
onConfirm: PropTypes.func,
|
||||||
|
setError: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default APIKeyDialog
|
export default APIKeyDialog
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Box,
|
Box,
|
||||||
Chip,
|
Chip,
|
||||||
|
Skeleton,
|
||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -17,11 +18,7 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Popover,
|
Popover,
|
||||||
Collapse,
|
Collapse,
|
||||||
Typography,
|
Typography
|
||||||
Toolbar,
|
|
||||||
TextField,
|
|
||||||
InputAdornment,
|
|
||||||
ButtonGroup
|
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import TableCell, { tableCellClasses } from '@mui/material/TableCell'
|
import TableCell, { tableCellClasses } from '@mui/material/TableCell'
|
||||||
import { useTheme, styled } from '@mui/material/styles'
|
import { useTheme, styled } from '@mui/material/styles'
|
||||||
@@ -43,26 +40,24 @@ import useConfirm from '@/hooks/useConfirm'
|
|||||||
import useNotifier from '@/utils/useNotifier'
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import {
|
import { IconTrash, IconEdit, IconCopy, IconChevronsUp, IconChevronsDown, IconX, IconPlus, IconEye, IconEyeOff } from '@tabler/icons'
|
||||||
IconTrash,
|
|
||||||
IconEdit,
|
|
||||||
IconCopy,
|
|
||||||
IconChevronsUp,
|
|
||||||
IconChevronsDown,
|
|
||||||
IconX,
|
|
||||||
IconSearch,
|
|
||||||
IconPlus,
|
|
||||||
IconEye,
|
|
||||||
IconEyeOff
|
|
||||||
} from '@tabler/icons'
|
|
||||||
import APIEmptySVG from '@/assets/images/api_empty.svg'
|
import APIEmptySVG from '@/assets/images/api_empty.svg'
|
||||||
import * as PropTypes from 'prop-types'
|
import * as PropTypes from 'prop-types'
|
||||||
import moment from 'moment/moment'
|
import moment from 'moment/moment'
|
||||||
|
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||||
|
import ErrorBoundary from '@/ErrorBoundary'
|
||||||
|
|
||||||
// ==============================|| APIKey ||============================== //
|
// ==============================|| APIKey ||============================== //
|
||||||
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
||||||
|
borderColor: theme.palette.grey[900] + 25,
|
||||||
|
padding: '6px 16px',
|
||||||
|
|
||||||
[`&.${tableCellClasses.head}`]: {
|
[`&.${tableCellClasses.head}`]: {
|
||||||
backgroundColor: theme.palette.action.hover
|
color: theme.palette.grey[900]
|
||||||
|
},
|
||||||
|
[`&.${tableCellClasses.body}`]: {
|
||||||
|
fontSize: 14,
|
||||||
|
height: 64
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -75,11 +70,15 @@ const StyledTableRow = styled(TableRow)(() => ({
|
|||||||
|
|
||||||
function APIKeyRow(props) {
|
function APIKeyRow(props) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
const theme = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TableRow sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
<TableRow sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||||
<TableCell scope='row'>{props.apiKey.keyName}</TableCell>
|
<StyledTableCell scope='row' style={{ width: '15%' }}>
|
||||||
<TableCell>
|
{props.apiKey.keyName}
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell style={{ width: '40%' }}>
|
||||||
{props.showApiKeys.includes(props.apiKey.apiKey)
|
{props.showApiKeys.includes(props.apiKey.apiKey)
|
||||||
? props.apiKey.apiKey
|
? props.apiKey.apiKey
|
||||||
: `${props.apiKey.apiKey.substring(0, 2)}${'•'.repeat(18)}${props.apiKey.apiKey.substring(
|
: `${props.apiKey.apiKey.substring(0, 2)}${'•'.repeat(18)}${props.apiKey.apiKey.substring(
|
||||||
@@ -108,48 +107,46 @@ function APIKeyRow(props) {
|
|||||||
Copied!
|
Copied!
|
||||||
</Typography>
|
</Typography>
|
||||||
</Popover>
|
</Popover>
|
||||||
</TableCell>
|
</StyledTableCell>
|
||||||
<TableCell>
|
<StyledTableCell>
|
||||||
{props.apiKey.chatFlows.length}{' '}
|
{props.apiKey.chatFlows.length}{' '}
|
||||||
{props.apiKey.chatFlows.length > 0 && (
|
{props.apiKey.chatFlows.length > 0 && (
|
||||||
<IconButton aria-label='expand row' size='small' color='inherit' onClick={() => setOpen(!open)}>
|
<IconButton aria-label='expand row' size='small' color='inherit' onClick={() => setOpen(!open)}>
|
||||||
{props.apiKey.chatFlows.length > 0 && open ? <IconChevronsUp /> : <IconChevronsDown />}
|
{props.apiKey.chatFlows.length > 0 && open ? <IconChevronsUp /> : <IconChevronsDown />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</StyledTableCell>
|
||||||
<TableCell>{props.apiKey.createdAt}</TableCell>
|
<StyledTableCell>{moment(props.apiKey.createdAt).format('MMMM Do, YYYY')}</StyledTableCell>
|
||||||
<TableCell>
|
<StyledTableCell>
|
||||||
<IconButton title='Edit' color='primary' onClick={props.onEditClick}>
|
<IconButton title='Edit' color='primary' onClick={props.onEditClick}>
|
||||||
<IconEdit />
|
<IconEdit />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</TableCell>
|
</StyledTableCell>
|
||||||
<TableCell>
|
<StyledTableCell>
|
||||||
<IconButton title='Delete' color='error' onClick={props.onDeleteClick}>
|
<IconButton title='Delete' color='error' onClick={props.onDeleteClick}>
|
||||||
<IconTrash />
|
<IconTrash />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</TableCell>
|
</StyledTableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
{open && (
|
{open && (
|
||||||
<TableRow sx={{ '& td': { border: 0 } }}>
|
<TableRow sx={{ '& td': { border: 0 } }}>
|
||||||
<TableCell sx={{ pb: 0, pt: 0 }} colSpan={6}>
|
<StyledTableCell sx={{ p: 2 }} colSpan={6}>
|
||||||
<Collapse in={open} timeout='auto' unmountOnExit>
|
<Collapse in={open} timeout='auto' unmountOnExit>
|
||||||
<Box sx={{ mt: 1, mb: 2, borderRadius: '15px', border: '1px solid' }}>
|
<Box sx={{ borderRadius: 2, border: 1, borderColor: theme.palette.grey[900] + 25, overflow: 'hidden' }}>
|
||||||
<Table aria-label='chatflow table'>
|
<Table aria-label='chatflow table'>
|
||||||
<TableHead>
|
<TableHead sx={{ height: 48 }}>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<StyledTableCell sx={{ width: '30%', borderTopLeftRadius: '15px' }}>
|
<StyledTableCell sx={{ width: '30%' }}>Chatflow Name</StyledTableCell>
|
||||||
Chatflow Name
|
|
||||||
</StyledTableCell>
|
|
||||||
<StyledTableCell sx={{ width: '20%' }}>Modified On</StyledTableCell>
|
<StyledTableCell sx={{ width: '20%' }}>Modified On</StyledTableCell>
|
||||||
<StyledTableCell sx={{ width: '50%', borderTopRightRadius: '15px' }}>Category</StyledTableCell>
|
<StyledTableCell sx={{ width: '50%' }}>Category</StyledTableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{props.apiKey.chatFlows.map((flow, index) => (
|
{props.apiKey.chatFlows.map((flow, index) => (
|
||||||
<StyledTableRow key={index}>
|
<TableRow key={index}>
|
||||||
<TableCell>{flow.flowName}</TableCell>
|
<StyledTableCell>{flow.flowName}</StyledTableCell>
|
||||||
<TableCell>{moment(flow.updatedDate).format('DD-MMM-YY')}</TableCell>
|
<StyledTableCell>{moment(flow.updatedDate).format('MMMM Do, YYYY')}</StyledTableCell>
|
||||||
<TableCell>
|
<StyledTableCell>
|
||||||
|
|
||||||
{flow.category &&
|
{flow.category &&
|
||||||
flow.category
|
flow.category
|
||||||
@@ -157,14 +154,14 @@ function APIKeyRow(props) {
|
|||||||
.map((tag, index) => (
|
.map((tag, index) => (
|
||||||
<Chip key={index} label={tag} style={{ marginRight: 5, marginBottom: 5 }} />
|
<Chip key={index} label={tag} style={{ marginRight: 5, marginBottom: 5 }} />
|
||||||
))}
|
))}
|
||||||
</TableCell>
|
</StyledTableCell>
|
||||||
</StyledTableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</TableCell>
|
</StyledTableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -193,6 +190,8 @@ const APIKey = () => {
|
|||||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
|
||||||
|
const [isLoading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
const [showDialog, setShowDialog] = useState(false)
|
const [showDialog, setShowDialog] = useState(false)
|
||||||
const [dialogProps, setDialogProps] = useState({})
|
const [dialogProps, setDialogProps] = useState({})
|
||||||
const [apiKeys, setAPIKeys] = useState([])
|
const [apiKeys, setAPIKeys] = useState([])
|
||||||
@@ -315,111 +314,148 @@ const APIKey = () => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(getAllAPIKeysApi.loading)
|
||||||
|
}, [getAllAPIKeysApi.loading])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getAllAPIKeysApi.data) {
|
if (getAllAPIKeysApi.data) {
|
||||||
setAPIKeys(getAllAPIKeysApi.data)
|
setAPIKeys(getAllAPIKeysApi.data)
|
||||||
}
|
}
|
||||||
}, [getAllAPIKeysApi.data])
|
}, [getAllAPIKeysApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAllAPIKeysApi.error) {
|
||||||
|
setError(getAllAPIKeysApi.error)
|
||||||
|
}
|
||||||
|
}, [getAllAPIKeysApi.error])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
<MainCard>
|
||||||
<Stack flexDirection='row'>
|
{error ? (
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<ErrorBoundary error={error} />
|
||||||
<Toolbar
|
) : (
|
||||||
disableGutters={true}
|
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
||||||
style={{
|
<ViewHeader onSearchChange={onSearchChange} search={true} searchPlaceholder='Search API Keys' title='API Keys'>
|
||||||
margin: 1,
|
<StyledButton
|
||||||
padding: 1,
|
|
||||||
paddingBottom: 10,
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h1>API Keys </h1>
|
|
||||||
<TextField
|
|
||||||
size='small'
|
|
||||||
sx={{ display: { xs: 'none', sm: 'block' }, ml: 3 }}
|
|
||||||
variant='outlined'
|
|
||||||
placeholder='Search key name'
|
|
||||||
onChange={onSearchChange}
|
|
||||||
InputProps={{
|
|
||||||
startAdornment: (
|
|
||||||
<InputAdornment position='start'>
|
|
||||||
<IconSearch />
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Box sx={{ flexGrow: 1 }} />
|
|
||||||
<ButtonGroup
|
|
||||||
sx={{ maxHeight: 40 }}
|
|
||||||
disableElevation
|
|
||||||
variant='contained'
|
variant='contained'
|
||||||
aria-label='outlined primary button group'
|
sx={{ borderRadius: 2, height: '100%' }}
|
||||||
|
onClick={addNew}
|
||||||
|
startIcon={<IconPlus />}
|
||||||
|
id='btn_createApiKey'
|
||||||
>
|
>
|
||||||
<ButtonGroup disableElevation aria-label='outlined primary button group'>
|
Create Key
|
||||||
<StyledButton
|
</StyledButton>
|
||||||
variant='contained'
|
</ViewHeader>
|
||||||
sx={{ color: 'white', mr: 1, height: 37 }}
|
{!isLoading && apiKeys.length <= 0 ? (
|
||||||
onClick={addNew}
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||||
startIcon={<IconPlus />}
|
<Box sx={{ p: 2, height: 'auto' }}>
|
||||||
id='btn_createApiKey'
|
<img
|
||||||
>
|
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
||||||
Create Key
|
src={APIEmptySVG}
|
||||||
</StyledButton>
|
alt='APIEmptySVG'
|
||||||
</ButtonGroup>
|
|
||||||
</ButtonGroup>
|
|
||||||
</Toolbar>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
{apiKeys.length <= 0 && (
|
|
||||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
|
||||||
<Box sx={{ p: 2, height: 'auto' }}>
|
|
||||||
<img style={{ objectFit: 'cover', height: '30vh', width: 'auto' }} src={APIEmptySVG} alt='APIEmptySVG' />
|
|
||||||
</Box>
|
|
||||||
<div>No API Keys Yet</div>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
{apiKeys.length > 0 && (
|
|
||||||
<TableContainer component={Paper}>
|
|
||||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>Key Name</TableCell>
|
|
||||||
<TableCell>API Key</TableCell>
|
|
||||||
<TableCell>Usage</TableCell>
|
|
||||||
<TableCell>Created</TableCell>
|
|
||||||
<TableCell> </TableCell>
|
|
||||||
<TableCell> </TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{apiKeys.filter(filterKeys).map((key, index) => (
|
|
||||||
<APIKeyRow
|
|
||||||
key={index}
|
|
||||||
apiKey={key}
|
|
||||||
showApiKeys={showApiKeys}
|
|
||||||
onCopyClick={(event) => {
|
|
||||||
navigator.clipboard.writeText(key.apiKey)
|
|
||||||
setAnchorEl(event.currentTarget)
|
|
||||||
setTimeout(() => {
|
|
||||||
handleClosePopOver()
|
|
||||||
}, 1500)
|
|
||||||
}}
|
|
||||||
onShowAPIClick={() => onShowApiKeyClick(key.apiKey)}
|
|
||||||
open={openPopOver}
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
onClose={handleClosePopOver}
|
|
||||||
theme={theme}
|
|
||||||
onEditClick={() => edit(key)}
|
|
||||||
onDeleteClick={() => deleteKey(key)}
|
|
||||||
/>
|
/>
|
||||||
))}
|
</Box>
|
||||||
</TableBody>
|
<div>No API Keys Yet</div>
|
||||||
</Table>
|
</Stack>
|
||||||
</TableContainer>
|
) : (
|
||||||
|
<TableContainer
|
||||||
|
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
|
||||||
|
component={Paper}
|
||||||
|
>
|
||||||
|
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||||
|
<TableHead
|
||||||
|
sx={{
|
||||||
|
backgroundColor: customization.isDarkMode
|
||||||
|
? theme.palette.common.black
|
||||||
|
: theme.palette.grey[100],
|
||||||
|
height: 56
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableRow>
|
||||||
|
<StyledTableCell>Key Name</StyledTableCell>
|
||||||
|
<StyledTableCell>API Key</StyledTableCell>
|
||||||
|
<StyledTableCell>Usage</StyledTableCell>
|
||||||
|
<StyledTableCell>Created</StyledTableCell>
|
||||||
|
<StyledTableCell> </StyledTableCell>
|
||||||
|
<StyledTableCell> </StyledTableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{isLoading ? (
|
||||||
|
<>
|
||||||
|
<StyledTableRow>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
<StyledTableRow>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{apiKeys.filter(filterKeys).map((key, index) => (
|
||||||
|
<APIKeyRow
|
||||||
|
key={index}
|
||||||
|
apiKey={key}
|
||||||
|
showApiKeys={showApiKeys}
|
||||||
|
onCopyClick={(event) => {
|
||||||
|
navigator.clipboard.writeText(key.apiKey)
|
||||||
|
setAnchorEl(event.currentTarget)
|
||||||
|
setTimeout(() => {
|
||||||
|
handleClosePopOver()
|
||||||
|
}, 1500)
|
||||||
|
}}
|
||||||
|
onShowAPIClick={() => onShowApiKeyClick(key.apiKey)}
|
||||||
|
open={openPopOver}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
onClose={handleClosePopOver}
|
||||||
|
theme={theme}
|
||||||
|
onEditClick={() => edit(key)}
|
||||||
|
onDeleteClick={() => deleteKey(key)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</MainCard>
|
</MainCard>
|
||||||
<APIKeyDialog
|
<APIKeyDialog
|
||||||
@@ -427,6 +463,7 @@ const APIKey = () => {
|
|||||||
dialogProps={dialogProps}
|
dialogProps={dialogProps}
|
||||||
onCancel={() => setShowDialog(false)}
|
onCancel={() => setShowDialog(false)}
|
||||||
onConfirm={onConfirm}
|
onConfirm={onConfirm}
|
||||||
|
setError={setError}
|
||||||
></APIKeyDialog>
|
></APIKeyDialog>
|
||||||
<ConfirmDialog />
|
<ConfirmDialog />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ const assistantAvailableModels = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
useNotifier()
|
useNotifier()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
@@ -122,6 +122,18 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||||||
}
|
}
|
||||||
}, [getAssistantObjApi.data])
|
}, [getAssistantObjApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAssistantObjApi.error) {
|
||||||
|
syncData(getAssistantObjApi.error)
|
||||||
|
}
|
||||||
|
}, [getAssistantObjApi.error])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getSpecificAssistantApi.error) {
|
||||||
|
syncData(getSpecificAssistantApi.error)
|
||||||
|
}
|
||||||
|
}, [getSpecificAssistantApi.error])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dialogProps.type === 'EDIT' && dialogProps.data) {
|
if (dialogProps.type === 'EDIT' && dialogProps.data) {
|
||||||
// When assistant dialog is opened from Assistants dashboard
|
// When assistant dialog is opened from Assistants dashboard
|
||||||
@@ -235,6 +247,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to add new Assistant: ${error.response.data.message}`,
|
message: `Failed to add new Assistant: ${error.response.data.message}`,
|
||||||
options: {
|
options: {
|
||||||
@@ -288,6 +301,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to save Assistant: ${error.response.data.message}`,
|
message: `Failed to save Assistant: ${error.response.data.message}`,
|
||||||
options: {
|
options: {
|
||||||
@@ -327,6 +341,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to sync Assistant: ${error.response.data.message}`,
|
message: `Failed to sync Assistant: ${error.response.data.message}`,
|
||||||
options: {
|
options: {
|
||||||
@@ -373,6 +388,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||||||
onConfirm()
|
onConfirm()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to delete Assistant: ${error.response.data.message}`,
|
message: `Failed to delete Assistant: ${error.response.data.message}`,
|
||||||
options: {
|
options: {
|
||||||
@@ -403,214 +419,193 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||||||
aria-labelledby='alert-dialog-title'
|
aria-labelledby='alert-dialog-title'
|
||||||
aria-describedby='alert-dialog-description'
|
aria-describedby='alert-dialog-description'
|
||||||
>
|
>
|
||||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
<DialogTitle sx={{ fontSize: '1rem', p: 3, pb: 0 }} id='alert-dialog-title'>
|
||||||
{dialogProps.title}
|
{dialogProps.title}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, maxHeight: '75vh', position: 'relative', px: 3, pb: 3 }}>
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 2 }}>
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
<Box>
|
||||||
<Typography variant='overline'>
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
Assistant Name
|
<Typography variant='overline'>Assistant Name</Typography>
|
||||||
<TooltipWithParser
|
<TooltipWithParser title={'The name of the assistant. The maximum length is 256 characters.'} />
|
||||||
style={{ marginLeft: 10 }}
|
</Stack>
|
||||||
title={'The name of the assistant. The maximum length is 256 characters.'}
|
<OutlinedInput
|
||||||
/>
|
id='assistantName'
|
||||||
</Typography>
|
type='string'
|
||||||
</Stack>
|
fullWidth
|
||||||
<OutlinedInput
|
placeholder='My New Assistant'
|
||||||
id='assistantName'
|
value={assistantName}
|
||||||
type='string'
|
name='assistantName'
|
||||||
fullWidth
|
onChange={(e) => setAssistantName(e.target.value)}
|
||||||
placeholder='My New Assistant'
|
|
||||||
value={assistantName}
|
|
||||||
name='assistantName'
|
|
||||||
onChange={(e) => setAssistantName(e.target.value)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{ p: 2 }}>
|
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
|
||||||
<Typography variant='overline'>
|
|
||||||
Assistant Description
|
|
||||||
<TooltipWithParser
|
|
||||||
style={{ marginLeft: 10 }}
|
|
||||||
title={'The description of the assistant. The maximum length is 512 characters.'}
|
|
||||||
/>
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
<OutlinedInput
|
|
||||||
id='assistantDesc'
|
|
||||||
type='string'
|
|
||||||
fullWidth
|
|
||||||
placeholder='Description of what the Assistant does'
|
|
||||||
multiline={true}
|
|
||||||
rows={3}
|
|
||||||
value={assistantDesc}
|
|
||||||
name='assistantDesc'
|
|
||||||
onChange={(e) => setAssistantDesc(e.target.value)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{ p: 2 }}>
|
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
|
||||||
<Typography variant='overline'>Assistant Icon Src</Typography>
|
|
||||||
</Stack>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
borderRadius: '50%',
|
|
||||||
backgroundColor: 'white'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
padding: 5,
|
|
||||||
borderRadius: '50%',
|
|
||||||
objectFit: 'contain'
|
|
||||||
}}
|
|
||||||
alt={assistantName}
|
|
||||||
src={assistantIcon}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
<OutlinedInput
|
<Box>
|
||||||
id='assistantIcon'
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
type='string'
|
<Typography variant='overline'>Assistant Description</Typography>
|
||||||
fullWidth
|
<TooltipWithParser title={'The description of the assistant. The maximum length is 512 characters.'} />
|
||||||
placeholder={`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`}
|
</Stack>
|
||||||
value={assistantIcon}
|
<OutlinedInput
|
||||||
name='assistantIcon'
|
id='assistantDesc'
|
||||||
onChange={(e) => setAssistantIcon(e.target.value)}
|
type='string'
|
||||||
/>
|
fullWidth
|
||||||
</Box>
|
placeholder='Description of what the Assistant does'
|
||||||
<Box sx={{ p: 2 }}>
|
multiline={true}
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
rows={3}
|
||||||
<Typography variant='overline'>
|
value={assistantDesc}
|
||||||
Assistant Model
|
name='assistantDesc'
|
||||||
<span style={{ color: 'red' }}> *</span>
|
onChange={(e) => setAssistantDesc(e.target.value)}
|
||||||
</Typography>
|
/>
|
||||||
</Stack>
|
</Box>
|
||||||
<Dropdown
|
<Box>
|
||||||
key={assistantModel}
|
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||||
name={assistantModel}
|
<Typography variant='overline'>Assistant Icon Src</Typography>
|
||||||
options={assistantAvailableModels}
|
</Stack>
|
||||||
onSelect={(newValue) => setAssistantModel(newValue)}
|
<div
|
||||||
value={assistantModel ?? 'choose an option'}
|
style={{
|
||||||
/>
|
width: 100,
|
||||||
</Box>
|
height: 100,
|
||||||
<Box sx={{ p: 2 }}>
|
borderRadius: '50%',
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
backgroundColor: 'white'
|
||||||
<Typography variant='overline'>
|
}}
|
||||||
OpenAI Credential
|
>
|
||||||
<span style={{ color: 'red' }}> *</span>
|
<img
|
||||||
</Typography>
|
style={{
|
||||||
</Stack>
|
width: '100%',
|
||||||
<CredentialInputHandler
|
height: '100%',
|
||||||
key={assistantCredential}
|
padding: 5,
|
||||||
data={assistantCredential ? { credential: assistantCredential } : {}}
|
borderRadius: '50%',
|
||||||
inputParam={{
|
objectFit: 'contain'
|
||||||
label: 'Connect Credential',
|
}}
|
||||||
name: 'credential',
|
alt={assistantName}
|
||||||
type: 'credential',
|
src={assistantIcon}
|
||||||
credentialNames: ['openAIApi']
|
/>
|
||||||
}}
|
</div>
|
||||||
onSelect={(newValue) => setAssistantCredential(newValue)}
|
<OutlinedInput
|
||||||
/>
|
id='assistantIcon'
|
||||||
</Box>
|
type='string'
|
||||||
<Box sx={{ p: 2 }}>
|
fullWidth
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
placeholder={`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`}
|
||||||
<Typography variant='overline'>
|
value={assistantIcon}
|
||||||
Assistant Instruction
|
name='assistantIcon'
|
||||||
|
onChange={(e) => setAssistantIcon(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||||
|
<Typography variant='overline'>
|
||||||
|
Assistant Model
|
||||||
|
<span style={{ color: 'red' }}> *</span>
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
<Dropdown
|
||||||
|
key={assistantModel}
|
||||||
|
name={assistantModel}
|
||||||
|
options={assistantAvailableModels}
|
||||||
|
onSelect={(newValue) => setAssistantModel(newValue)}
|
||||||
|
value={assistantModel ?? 'choose an option'}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||||
|
<Typography variant='overline'>
|
||||||
|
OpenAI Credential
|
||||||
|
<span style={{ color: 'red' }}> *</span>
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
<CredentialInputHandler
|
||||||
|
key={assistantCredential}
|
||||||
|
data={assistantCredential ? { credential: assistantCredential } : {}}
|
||||||
|
inputParam={{
|
||||||
|
label: 'Connect Credential',
|
||||||
|
name: 'credential',
|
||||||
|
type: 'credential',
|
||||||
|
credentialNames: ['openAIApi']
|
||||||
|
}}
|
||||||
|
onSelect={(newValue) => setAssistantCredential(newValue)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
|
<Typography variant='overline'>Assistant Instruction</Typography>
|
||||||
<TooltipWithParser
|
<TooltipWithParser
|
||||||
style={{ marginLeft: 10 }}
|
|
||||||
title={'The system instructions that the assistant uses. The maximum length is 32768 characters.'}
|
title={'The system instructions that the assistant uses. The maximum length is 32768 characters.'}
|
||||||
/>
|
/>
|
||||||
</Typography>
|
</Stack>
|
||||||
</Stack>
|
<OutlinedInput
|
||||||
<OutlinedInput
|
id='assistantInstructions'
|
||||||
id='assistantInstructions'
|
type='string'
|
||||||
type='string'
|
fullWidth
|
||||||
fullWidth
|
placeholder='You are a personal math tutor. When asked a question, write and run Python code to answer the question.'
|
||||||
placeholder='You are a personal math tutor. When asked a question, write and run Python code to answer the question.'
|
multiline={true}
|
||||||
multiline={true}
|
rows={3}
|
||||||
rows={3}
|
value={assistantInstructions}
|
||||||
value={assistantInstructions}
|
name='assistantInstructions'
|
||||||
name='assistantInstructions'
|
onChange={(e) => setAssistantInstructions(e.target.value)}
|
||||||
onChange={(e) => setAssistantInstructions(e.target.value)}
|
/>
|
||||||
/>
|
</Box>
|
||||||
</Box>
|
<Box>
|
||||||
<Box sx={{ p: 2 }}>
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
<Typography variant='overline'>Assistant Tools</Typography>
|
||||||
<Typography variant='overline'>
|
<TooltipWithParser title='A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant.' />
|
||||||
Assistant Tools
|
</Stack>
|
||||||
<TooltipWithParser
|
<MultiDropdown
|
||||||
style={{ marginLeft: 10 }}
|
key={JSON.stringify(assistantTools)}
|
||||||
title='A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant.'
|
name={JSON.stringify(assistantTools)}
|
||||||
/>
|
options={[
|
||||||
</Typography>
|
{
|
||||||
</Stack>
|
label: 'Code Interpreter',
|
||||||
<MultiDropdown
|
name: 'code_interpreter'
|
||||||
key={JSON.stringify(assistantTools)}
|
},
|
||||||
name={JSON.stringify(assistantTools)}
|
{
|
||||||
options={[
|
label: 'Retrieval',
|
||||||
{
|
name: 'retrieval'
|
||||||
label: 'Code Interpreter',
|
}
|
||||||
name: 'code_interpreter'
|
]}
|
||||||
},
|
onSelect={(newValue) => (newValue ? setAssistantTools(JSON.parse(newValue)) : setAssistantTools([]))}
|
||||||
{
|
value={assistantTools ?? 'choose an option'}
|
||||||
label: 'Retrieval',
|
/>
|
||||||
name: 'retrieval'
|
</Box>
|
||||||
}
|
<Box>
|
||||||
]}
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
onSelect={(newValue) => (newValue ? setAssistantTools(JSON.parse(newValue)) : setAssistantTools([]))}
|
<Typography variant='overline'>Knowledge Files</Typography>
|
||||||
value={assistantTools ?? 'choose an option'}
|
<TooltipWithParser title='Allow assistant to use the content from uploaded files for retrieval and code interpreter. MAX: 20 files' />
|
||||||
/>
|
</Stack>
|
||||||
</Box>
|
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
<Box sx={{ p: 2 }}>
|
{assistantFiles.map((file, index) => (
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
<div
|
||||||
<Typography variant='overline'>
|
key={index}
|
||||||
Knowledge Files
|
style={{
|
||||||
<TooltipWithParser
|
display: 'flex',
|
||||||
style={{ marginLeft: 10 }}
|
flexDirection: 'row',
|
||||||
title='Allow assistant to use the content from uploaded files for retrieval and code interpreter. MAX: 20 files'
|
alignItems: 'center',
|
||||||
/>
|
width: 'max-content',
|
||||||
</Typography>
|
height: 'max-content',
|
||||||
</Stack>
|
borderRadius: 15,
|
||||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
background: 'rgb(254,252,191)',
|
||||||
{assistantFiles.map((file, index) => (
|
paddingLeft: 15,
|
||||||
<div
|
paddingRight: 15,
|
||||||
key={index}
|
paddingTop: 5,
|
||||||
style={{
|
paddingBottom: 5,
|
||||||
display: 'flex',
|
marginRight: 10
|
||||||
flexDirection: 'row',
|
}}
|
||||||
alignItems: 'center',
|
>
|
||||||
width: 'max-content',
|
<span style={{ color: 'rgb(116,66,16)', marginRight: 10 }}>{file.filename}</span>
|
||||||
height: 'max-content',
|
<IconButton sx={{ height: 15, width: 15, p: 0 }} onClick={() => onFileDeleteClick(file.id)}>
|
||||||
borderRadius: 15,
|
<IconX />
|
||||||
background: 'rgb(254,252,191)',
|
</IconButton>
|
||||||
paddingLeft: 15,
|
</div>
|
||||||
paddingRight: 15,
|
))}
|
||||||
paddingTop: 5,
|
</div>
|
||||||
paddingBottom: 5,
|
<File
|
||||||
marginRight: 10
|
key={uploadAssistantFiles}
|
||||||
}}
|
fileType='*'
|
||||||
>
|
onChange={(newValue) => setUploadAssistantFiles(newValue)}
|
||||||
<span style={{ color: 'rgb(116,66,16)', marginRight: 10 }}>{file.filename}</span>
|
value={uploadAssistantFiles ?? 'Choose a file to upload'}
|
||||||
<IconButton sx={{ height: 15, width: 15, p: 0 }} onClick={() => onFileDeleteClick(file.id)}>
|
/>
|
||||||
<IconX />
|
</Box>
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<File
|
|
||||||
key={uploadAssistantFiles}
|
|
||||||
fileType='*'
|
|
||||||
onChange={(newValue) => setUploadAssistantFiles(newValue)}
|
|
||||||
value={uploadAssistantFiles ?? 'Choose a file to upload'}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions sx={{ p: 3, pt: 0 }}>
|
||||||
{dialogProps.type === 'EDIT' && (
|
{dialogProps.type === 'EDIT' && (
|
||||||
<StyledButton color='secondary' variant='contained' onClick={() => onSyncClick()}>
|
<StyledButton color='secondary' variant='contained' onClick={() => onSyncClick()}>
|
||||||
Sync
|
Sync
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { StyledButton } from '@/ui-component/button/StyledButton'
|
|||||||
import assistantsApi from '@/api/assistants'
|
import assistantsApi from '@/api/assistants'
|
||||||
import useApi from '@/hooks/useApi'
|
import useApi from '@/hooks/useApi'
|
||||||
|
|
||||||
const LoadAssistantDialog = ({ show, dialogProps, onCancel, onAssistantSelected }) => {
|
const LoadAssistantDialog = ({ show, dialogProps, onCancel, onAssistantSelected, setError }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
|
|
||||||
const getAllAvailableAssistantsApi = useApi(assistantsApi.getAllAvailableAssistants)
|
const getAllAvailableAssistantsApi = useApi(assistantsApi.getAllAvailableAssistants)
|
||||||
@@ -39,6 +39,13 @@ const LoadAssistantDialog = ({ show, dialogProps, onCancel, onAssistantSelected
|
|||||||
}
|
}
|
||||||
}, [getAllAvailableAssistantsApi.data])
|
}, [getAllAvailableAssistantsApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAllAvailableAssistantsApi.error) {
|
||||||
|
setError(getAllAvailableAssistantsApi.error)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getAllAvailableAssistantsApi.error])
|
||||||
|
|
||||||
const component = show ? (
|
const component = show ? (
|
||||||
<Dialog
|
<Dialog
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -108,7 +115,8 @@ LoadAssistantDialog.propTypes = {
|
|||||||
show: PropTypes.bool,
|
show: PropTypes.bool,
|
||||||
dialogProps: PropTypes.object,
|
dialogProps: PropTypes.object,
|
||||||
onCancel: PropTypes.func,
|
onCancel: PropTypes.func,
|
||||||
onAssistantSelected: PropTypes.func
|
onAssistantSelected: PropTypes.func,
|
||||||
|
setError: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LoadAssistantDialog
|
export default LoadAssistantDialog
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { Grid, Box, Stack, Button } from '@mui/material'
|
import { Box, Stack, Button, Skeleton } from '@mui/material'
|
||||||
import { useTheme } from '@mui/material/styles'
|
|
||||||
|
|
||||||
// project imports
|
// project imports
|
||||||
import MainCard from '@/ui-component/cards/MainCard'
|
import MainCard from '@/ui-component/cards/MainCard'
|
||||||
@@ -21,16 +19,17 @@ import assistantsApi from '@/api/assistants'
|
|||||||
import useApi from '@/hooks/useApi'
|
import useApi from '@/hooks/useApi'
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
import { IconPlus, IconFileImport } from '@tabler/icons'
|
import { IconPlus, IconFileUpload } from '@tabler/icons'
|
||||||
|
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||||
|
import ErrorBoundary from '@/ErrorBoundary'
|
||||||
|
|
||||||
// ==============================|| CHATFLOWS ||============================== //
|
// ==============================|| CHATFLOWS ||============================== //
|
||||||
|
|
||||||
const Assistants = () => {
|
const Assistants = () => {
|
||||||
const theme = useTheme()
|
|
||||||
const customization = useSelector((state) => state.customization)
|
|
||||||
|
|
||||||
const getAllAssistantsApi = useApi(assistantsApi.getAllAssistants)
|
const getAllAssistantsApi = useApi(assistantsApi.getAllAssistants)
|
||||||
|
|
||||||
|
const [isLoading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
const [showDialog, setShowDialog] = useState(false)
|
const [showDialog, setShowDialog] = useState(false)
|
||||||
const [dialogProps, setDialogProps] = useState({})
|
const [dialogProps, setDialogProps] = useState({})
|
||||||
const [showLoadDialog, setShowLoadDialog] = useState(false)
|
const [showLoadDialog, setShowLoadDialog] = useState(false)
|
||||||
@@ -85,45 +84,75 @@ const Assistants = () => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(getAllAssistantsApi.loading)
|
||||||
|
}, [getAllAssistantsApi.loading])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAllAssistantsApi.error) {
|
||||||
|
setError(getAllAssistantsApi.error)
|
||||||
|
}
|
||||||
|
}, [getAllAssistantsApi.error])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
<MainCard>
|
||||||
<Stack flexDirection='row'>
|
{error ? (
|
||||||
<Grid sx={{ mb: 1.25 }} container direction='row'>
|
<ErrorBoundary error={error} />
|
||||||
<h1>OpenAI Assistants</h1>
|
) : (
|
||||||
<Box sx={{ flexGrow: 1 }} />
|
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
||||||
<Grid item>
|
<ViewHeader title='OpenAI Assistants'>
|
||||||
<Button variant='outlined' sx={{ mr: 2 }} onClick={loadExisting} startIcon={<IconFileImport />}>
|
<Button
|
||||||
|
variant='outlined'
|
||||||
|
onClick={loadExisting}
|
||||||
|
startIcon={<IconFileUpload />}
|
||||||
|
sx={{ borderRadius: 2, height: 40 }}
|
||||||
|
>
|
||||||
Load
|
Load
|
||||||
</Button>
|
</Button>
|
||||||
<StyledButton variant='contained' sx={{ color: 'white' }} onClick={addNew} startIcon={<IconPlus />}>
|
<StyledButton
|
||||||
|
variant='contained'
|
||||||
|
sx={{ borderRadius: 2, height: 40 }}
|
||||||
|
onClick={addNew}
|
||||||
|
startIcon={<IconPlus />}
|
||||||
|
>
|
||||||
Add
|
Add
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
</Grid>
|
</ViewHeader>
|
||||||
</Grid>
|
{isLoading ? (
|
||||||
</Stack>
|
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||||
<Grid container spacing={gridSpacing}>
|
<Skeleton variant='rounded' height={160} />
|
||||||
{!getAllAssistantsApi.loading &&
|
<Skeleton variant='rounded' height={160} />
|
||||||
getAllAssistantsApi.data &&
|
<Skeleton variant='rounded' height={160} />
|
||||||
getAllAssistantsApi.data.map((data, index) => (
|
</Box>
|
||||||
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
|
) : (
|
||||||
<ItemCard
|
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||||
data={{
|
{getAllAssistantsApi.data &&
|
||||||
name: JSON.parse(data.details)?.name,
|
getAllAssistantsApi.data.map((data, index) => (
|
||||||
description: JSON.parse(data.details)?.instructions,
|
<ItemCard
|
||||||
iconSrc: data.iconSrc
|
data={{
|
||||||
}}
|
name: JSON.parse(data.details)?.name,
|
||||||
onClick={() => edit(data)}
|
description: JSON.parse(data.details)?.instructions,
|
||||||
/>
|
iconSrc: data.iconSrc
|
||||||
</Grid>
|
}}
|
||||||
))}
|
key={index}
|
||||||
</Grid>
|
onClick={() => edit(data)}
|
||||||
{!getAllAssistantsApi.loading && (!getAllAssistantsApi.data || getAllAssistantsApi.data.length === 0) && (
|
/>
|
||||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
))}
|
||||||
<Box sx={{ p: 2, height: 'auto' }}>
|
</Box>
|
||||||
<img style={{ objectFit: 'cover', height: '30vh', width: 'auto' }} src={ToolEmptySVG} alt='ToolEmptySVG' />
|
)}
|
||||||
</Box>
|
{!isLoading && (!getAllAssistantsApi.data || getAllAssistantsApi.data.length === 0) && (
|
||||||
<div>No Assistants Added Yet</div>
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||||
|
<Box sx={{ p: 2, height: 'auto' }}>
|
||||||
|
<img
|
||||||
|
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
||||||
|
src={ToolEmptySVG}
|
||||||
|
alt='ToolEmptySVG'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<div>No Assistants Added Yet</div>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</MainCard>
|
</MainCard>
|
||||||
@@ -132,12 +161,14 @@ const Assistants = () => {
|
|||||||
dialogProps={loadDialogProps}
|
dialogProps={loadDialogProps}
|
||||||
onCancel={() => setShowLoadDialog(false)}
|
onCancel={() => setShowLoadDialog(false)}
|
||||||
onAssistantSelected={onAssistantSelected}
|
onAssistantSelected={onAssistantSelected}
|
||||||
|
setError={setError}
|
||||||
></LoadAssistantDialog>
|
></LoadAssistantDialog>
|
||||||
<AssistantDialog
|
<AssistantDialog
|
||||||
show={showDialog}
|
show={showDialog}
|
||||||
dialogProps={dialogProps}
|
dialogProps={dialogProps}
|
||||||
onCancel={() => setShowDialog(false)}
|
onCancel={() => setShowDialog(false)}
|
||||||
onConfirm={onConfirm}
|
onConfirm={onConfirm}
|
||||||
|
setError={setError}
|
||||||
></AssistantDialog>
|
></AssistantDialog>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -192,186 +192,192 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box>
|
<Stack flexDirection='row' justifyContent='space-between' sx={{ width: '100%' }}>
|
||||||
<ButtonBase title='Back' sx={{ borderRadius: '50%' }}>
|
<Stack flexDirection='row' sx={{ width: '100%', maxWidth: '50%' }}>
|
||||||
<Avatar
|
<Box>
|
||||||
variant='rounded'
|
<ButtonBase title='Back' sx={{ borderRadius: '50%' }}>
|
||||||
sx={{
|
<Avatar
|
||||||
...theme.typography.commonAvatar,
|
variant='rounded'
|
||||||
...theme.typography.mediumAvatar,
|
sx={{
|
||||||
transition: 'all .2s ease-in-out',
|
...theme.typography.commonAvatar,
|
||||||
background: theme.palette.secondary.light,
|
...theme.typography.mediumAvatar,
|
||||||
color: theme.palette.secondary.dark,
|
transition: 'all .2s ease-in-out',
|
||||||
'&:hover': {
|
background: theme.palette.secondary.light,
|
||||||
background: theme.palette.secondary.dark,
|
color: theme.palette.secondary.dark,
|
||||||
color: theme.palette.secondary.light
|
'&:hover': {
|
||||||
}
|
background: theme.palette.secondary.dark,
|
||||||
}}
|
color: theme.palette.secondary.light
|
||||||
color='inherit'
|
}
|
||||||
onClick={() =>
|
}}
|
||||||
window.history.state && window.history.state.idx > 0 ? navigate(-1) : navigate('/', { replace: true })
|
color='inherit'
|
||||||
}
|
onClick={() =>
|
||||||
>
|
window.history.state && window.history.state.idx > 0 ? navigate(-1) : navigate('/', { replace: true })
|
||||||
<IconChevronLeft stroke={1.5} size='1.3rem' />
|
}
|
||||||
</Avatar>
|
>
|
||||||
</ButtonBase>
|
<IconChevronLeft stroke={1.5} size='1.3rem' />
|
||||||
</Box>
|
</Avatar>
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
</ButtonBase>
|
||||||
{!isEditingFlowName && (
|
</Box>
|
||||||
<Stack flexDirection='row'>
|
<Box sx={{ width: '100%' }}>
|
||||||
<Typography
|
{!isEditingFlowName ? (
|
||||||
sx={{
|
<Stack flexDirection='row'>
|
||||||
fontSize: '1.5rem',
|
<Typography
|
||||||
fontWeight: 600,
|
|
||||||
ml: 2
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{canvas.isDirty && <strong style={{ color: theme.palette.orange.main }}>*</strong>} {flowName}
|
|
||||||
</Typography>
|
|
||||||
{chatflow?.id && (
|
|
||||||
<ButtonBase title='Edit Name' sx={{ borderRadius: '50%' }}>
|
|
||||||
<Avatar
|
|
||||||
variant='rounded'
|
|
||||||
sx={{
|
sx={{
|
||||||
...theme.typography.commonAvatar,
|
fontSize: '1.5rem',
|
||||||
...theme.typography.mediumAvatar,
|
fontWeight: 600,
|
||||||
transition: 'all .2s ease-in-out',
|
ml: 2,
|
||||||
ml: 1,
|
textOverflow: 'ellipsis',
|
||||||
background: theme.palette.secondary.light,
|
overflow: 'hidden',
|
||||||
color: theme.palette.secondary.dark,
|
whiteSpace: 'nowrap'
|
||||||
'&:hover': {
|
|
||||||
background: theme.palette.secondary.dark,
|
|
||||||
color: theme.palette.secondary.light
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
color='inherit'
|
|
||||||
onClick={() => setEditingFlowName(true)}
|
|
||||||
>
|
>
|
||||||
<IconPencil stroke={1.5} size='1.3rem' />
|
{canvas.isDirty && <strong style={{ color: theme.palette.orange.main }}>*</strong>} {flowName}
|
||||||
</Avatar>
|
</Typography>
|
||||||
</ButtonBase>
|
{chatflow?.id && (
|
||||||
|
<ButtonBase title='Edit Name' sx={{ borderRadius: '50%' }}>
|
||||||
|
<Avatar
|
||||||
|
variant='rounded'
|
||||||
|
sx={{
|
||||||
|
...theme.typography.commonAvatar,
|
||||||
|
...theme.typography.mediumAvatar,
|
||||||
|
transition: 'all .2s ease-in-out',
|
||||||
|
ml: 1,
|
||||||
|
background: theme.palette.secondary.light,
|
||||||
|
color: theme.palette.secondary.dark,
|
||||||
|
'&:hover': {
|
||||||
|
background: theme.palette.secondary.dark,
|
||||||
|
color: theme.palette.secondary.light
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
color='inherit'
|
||||||
|
onClick={() => setEditingFlowName(true)}
|
||||||
|
>
|
||||||
|
<IconPencil stroke={1.5} size='1.3rem' />
|
||||||
|
</Avatar>
|
||||||
|
</ButtonBase>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Stack flexDirection='row' sx={{ width: '100%' }}>
|
||||||
|
<TextField
|
||||||
|
size='small'
|
||||||
|
inputRef={flowNameRef}
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
ml: 2
|
||||||
|
}}
|
||||||
|
defaultValue={flowName}
|
||||||
|
/>
|
||||||
|
<ButtonBase title='Save Name' sx={{ borderRadius: '50%' }}>
|
||||||
|
<Avatar
|
||||||
|
variant='rounded'
|
||||||
|
sx={{
|
||||||
|
...theme.typography.commonAvatar,
|
||||||
|
...theme.typography.mediumAvatar,
|
||||||
|
transition: 'all .2s ease-in-out',
|
||||||
|
background: theme.palette.success.light,
|
||||||
|
color: theme.palette.success.dark,
|
||||||
|
ml: 1,
|
||||||
|
'&:hover': {
|
||||||
|
background: theme.palette.success.dark,
|
||||||
|
color: theme.palette.success.light
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
color='inherit'
|
||||||
|
onClick={submitFlowName}
|
||||||
|
>
|
||||||
|
<IconCheck stroke={1.5} size='1.3rem' />
|
||||||
|
</Avatar>
|
||||||
|
</ButtonBase>
|
||||||
|
<ButtonBase title='Cancel' sx={{ borderRadius: '50%' }}>
|
||||||
|
<Avatar
|
||||||
|
variant='rounded'
|
||||||
|
sx={{
|
||||||
|
...theme.typography.commonAvatar,
|
||||||
|
...theme.typography.mediumAvatar,
|
||||||
|
transition: 'all .2s ease-in-out',
|
||||||
|
background: theme.palette.error.light,
|
||||||
|
color: theme.palette.error.dark,
|
||||||
|
ml: 1,
|
||||||
|
'&:hover': {
|
||||||
|
background: theme.palette.error.dark,
|
||||||
|
color: theme.palette.error.light
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
color='inherit'
|
||||||
|
onClick={() => setEditingFlowName(false)}
|
||||||
|
>
|
||||||
|
<IconX stroke={1.5} size='1.3rem' />
|
||||||
|
</Avatar>
|
||||||
|
</ButtonBase>
|
||||||
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Box>
|
||||||
)}
|
</Stack>
|
||||||
{isEditingFlowName && (
|
<Box>
|
||||||
<Stack flexDirection='row'>
|
{chatflow?.id && (
|
||||||
<TextField
|
<ButtonBase title='API Endpoint' sx={{ borderRadius: '50%', mr: 2 }}>
|
||||||
size='small'
|
|
||||||
inputRef={flowNameRef}
|
|
||||||
sx={{
|
|
||||||
width: '50%',
|
|
||||||
ml: 2
|
|
||||||
}}
|
|
||||||
defaultValue={flowName}
|
|
||||||
/>
|
|
||||||
<ButtonBase title='Save Name' sx={{ borderRadius: '50%' }}>
|
|
||||||
<Avatar
|
<Avatar
|
||||||
variant='rounded'
|
variant='rounded'
|
||||||
sx={{
|
sx={{
|
||||||
...theme.typography.commonAvatar,
|
...theme.typography.commonAvatar,
|
||||||
...theme.typography.mediumAvatar,
|
...theme.typography.mediumAvatar,
|
||||||
transition: 'all .2s ease-in-out',
|
transition: 'all .2s ease-in-out',
|
||||||
background: theme.palette.success.light,
|
background: theme.palette.canvasHeader.deployLight,
|
||||||
color: theme.palette.success.dark,
|
color: theme.palette.canvasHeader.deployDark,
|
||||||
ml: 1,
|
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
background: theme.palette.success.dark,
|
background: theme.palette.canvasHeader.deployDark,
|
||||||
color: theme.palette.success.light
|
color: theme.palette.canvasHeader.deployLight
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
color='inherit'
|
color='inherit'
|
||||||
onClick={submitFlowName}
|
onClick={onAPIDialogClick}
|
||||||
>
|
>
|
||||||
<IconCheck stroke={1.5} size='1.3rem' />
|
<IconCode stroke={1.5} size='1.3rem' />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<ButtonBase title='Cancel' sx={{ borderRadius: '50%' }}>
|
)}
|
||||||
<Avatar
|
<ButtonBase title='Save Chatflow' sx={{ borderRadius: '50%', mr: 2 }}>
|
||||||
variant='rounded'
|
|
||||||
sx={{
|
|
||||||
...theme.typography.commonAvatar,
|
|
||||||
...theme.typography.mediumAvatar,
|
|
||||||
transition: 'all .2s ease-in-out',
|
|
||||||
background: theme.palette.error.light,
|
|
||||||
color: theme.palette.error.dark,
|
|
||||||
ml: 1,
|
|
||||||
'&:hover': {
|
|
||||||
background: theme.palette.error.dark,
|
|
||||||
color: theme.palette.error.light
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
color='inherit'
|
|
||||||
onClick={() => setEditingFlowName(false)}
|
|
||||||
>
|
|
||||||
<IconX stroke={1.5} size='1.3rem' />
|
|
||||||
</Avatar>
|
|
||||||
</ButtonBase>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
{chatflow?.id && (
|
|
||||||
<ButtonBase title='API Endpoint' sx={{ borderRadius: '50%', mr: 2 }}>
|
|
||||||
<Avatar
|
<Avatar
|
||||||
variant='rounded'
|
variant='rounded'
|
||||||
sx={{
|
sx={{
|
||||||
...theme.typography.commonAvatar,
|
...theme.typography.commonAvatar,
|
||||||
...theme.typography.mediumAvatar,
|
...theme.typography.mediumAvatar,
|
||||||
transition: 'all .2s ease-in-out',
|
transition: 'all .2s ease-in-out',
|
||||||
background: theme.palette.canvasHeader.deployLight,
|
background: theme.palette.canvasHeader.saveLight,
|
||||||
color: theme.palette.canvasHeader.deployDark,
|
color: theme.palette.canvasHeader.saveDark,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
background: theme.palette.canvasHeader.deployDark,
|
background: theme.palette.canvasHeader.saveDark,
|
||||||
color: theme.palette.canvasHeader.deployLight
|
color: theme.palette.canvasHeader.saveLight
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
color='inherit'
|
color='inherit'
|
||||||
onClick={onAPIDialogClick}
|
onClick={onSaveChatflowClick}
|
||||||
>
|
>
|
||||||
<IconCode stroke={1.5} size='1.3rem' />
|
<IconDeviceFloppy stroke={1.5} size='1.3rem' />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
)}
|
<ButtonBase ref={settingsRef} title='Settings' sx={{ borderRadius: '50%' }}>
|
||||||
<ButtonBase title='Save Chatflow' sx={{ borderRadius: '50%', mr: 2 }}>
|
<Avatar
|
||||||
<Avatar
|
variant='rounded'
|
||||||
variant='rounded'
|
sx={{
|
||||||
sx={{
|
...theme.typography.commonAvatar,
|
||||||
...theme.typography.commonAvatar,
|
...theme.typography.mediumAvatar,
|
||||||
...theme.typography.mediumAvatar,
|
transition: 'all .2s ease-in-out',
|
||||||
transition: 'all .2s ease-in-out',
|
background: theme.palette.canvasHeader.settingsLight,
|
||||||
background: theme.palette.canvasHeader.saveLight,
|
color: theme.palette.canvasHeader.settingsDark,
|
||||||
color: theme.palette.canvasHeader.saveDark,
|
'&:hover': {
|
||||||
'&:hover': {
|
background: theme.palette.canvasHeader.settingsDark,
|
||||||
background: theme.palette.canvasHeader.saveDark,
|
color: theme.palette.canvasHeader.settingsLight
|
||||||
color: theme.palette.canvasHeader.saveLight
|
}
|
||||||
}
|
}}
|
||||||
}}
|
onClick={() => setSettingsOpen(!isSettingsOpen)}
|
||||||
color='inherit'
|
>
|
||||||
onClick={onSaveChatflowClick}
|
<IconSettings stroke={1.5} size='1.3rem' />
|
||||||
>
|
</Avatar>
|
||||||
<IconDeviceFloppy stroke={1.5} size='1.3rem' />
|
</ButtonBase>
|
||||||
</Avatar>
|
</Box>
|
||||||
</ButtonBase>
|
</Stack>
|
||||||
<ButtonBase ref={settingsRef} title='Settings' sx={{ borderRadius: '50%' }}>
|
|
||||||
<Avatar
|
|
||||||
variant='rounded'
|
|
||||||
sx={{
|
|
||||||
...theme.typography.commonAvatar,
|
|
||||||
...theme.typography.mediumAvatar,
|
|
||||||
transition: 'all .2s ease-in-out',
|
|
||||||
background: theme.palette.canvasHeader.settingsLight,
|
|
||||||
color: theme.palette.canvasHeader.settingsDark,
|
|
||||||
'&:hover': {
|
|
||||||
background: theme.palette.canvasHeader.settingsDark,
|
|
||||||
color: theme.palette.canvasHeader.settingsLight
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onClick={() => setSettingsOpen(!isSettingsOpen)}
|
|
||||||
>
|
|
||||||
<IconSettings stroke={1.5} size='1.3rem' />
|
|
||||||
</Avatar>
|
|
||||||
</ButtonBase>
|
|
||||||
</Box>
|
|
||||||
<Settings
|
<Settings
|
||||||
chatflow={chatflow}
|
chatflow={chatflow}
|
||||||
isSettingsOpen={isSettingsOpen}
|
isSettingsOpen={isSettingsOpen}
|
||||||
|
|||||||
@@ -97,29 +97,26 @@ const CredentialInputHandler = ({ inputParam, data, onSelect, disabled = false }
|
|||||||
{inputParam && (
|
{inputParam && (
|
||||||
<>
|
<>
|
||||||
{inputParam.type === 'credential' && (
|
{inputParam.type === 'credential' && (
|
||||||
<>
|
<div key={reloadTimestamp} style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
<div style={{ marginTop: 10 }} />
|
<AsyncDropdown
|
||||||
<div key={reloadTimestamp} style={{ display: 'flex', flexDirection: 'row' }}>
|
disabled={disabled}
|
||||||
<AsyncDropdown
|
name={inputParam.name}
|
||||||
disabled={disabled}
|
nodeData={data}
|
||||||
name={inputParam.name}
|
value={credentialId ?? 'choose an option'}
|
||||||
nodeData={data}
|
isCreateNewOption={true}
|
||||||
value={credentialId ?? 'choose an option'}
|
credentialNames={inputParam.credentialNames}
|
||||||
isCreateNewOption={true}
|
onSelect={(newValue) => {
|
||||||
credentialNames={inputParam.credentialNames}
|
setCredentialId(newValue)
|
||||||
onSelect={(newValue) => {
|
onSelect(newValue)
|
||||||
setCredentialId(newValue)
|
}}
|
||||||
onSelect(newValue)
|
onCreateNew={() => addAsyncOption(inputParam.name)}
|
||||||
}}
|
/>
|
||||||
onCreateNew={() => addAsyncOption(inputParam.name)}
|
{credentialId && (
|
||||||
/>
|
<IconButton title='Edit' color='primary' size='small' onClick={() => editCredential(credentialId)}>
|
||||||
{credentialId && (
|
<IconEdit />
|
||||||
<IconButton title='Edit' color='primary' size='small' onClick={() => editCredential(credentialId)}>
|
</IconButton>
|
||||||
<IconEdit />
|
)}
|
||||||
</IconButton>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { Grid, Box, Stack, Toolbar, ToggleButton, ButtonGroup, InputAdornment, TextField } from '@mui/material'
|
import { Box, Skeleton, Stack, ToggleButton } from '@mui/material'
|
||||||
import { useTheme } from '@mui/material/styles'
|
import { useTheme } from '@mui/material/styles'
|
||||||
|
|
||||||
// project imports
|
// project imports
|
||||||
@@ -24,20 +23,22 @@ import useApi from '@/hooks/useApi'
|
|||||||
import { baseURL } from '@/store/constant'
|
import { baseURL } from '@/store/constant'
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
import { IconPlus, IconSearch, IconLayoutGrid, IconList } from '@tabler/icons'
|
import { IconPlus, IconLayoutGrid, IconList } from '@tabler/icons'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'
|
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'
|
||||||
import { FlowListTable } from '@/ui-component/table/FlowListTable'
|
import { FlowListTable } from '@/ui-component/table/FlowListTable'
|
||||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||||
|
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||||
|
import ErrorBoundary from '@/ErrorBoundary'
|
||||||
|
|
||||||
// ==============================|| CHATFLOWS ||============================== //
|
// ==============================|| CHATFLOWS ||============================== //
|
||||||
|
|
||||||
const Chatflows = () => {
|
const Chatflows = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const customization = useSelector((state) => state.customization)
|
|
||||||
|
|
||||||
const [isLoading, setLoading] = useState(true)
|
const [isLoading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
const [images, setImages] = useState({})
|
const [images, setImages] = useState({})
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [loginDialogOpen, setLoginDialogOpen] = useState(false)
|
const [loginDialogOpen, setLoginDialogOpen] = useState(false)
|
||||||
@@ -91,6 +92,8 @@ const Chatflows = () => {
|
|||||||
confirmButtonName: 'Login'
|
confirmButtonName: 'Login'
|
||||||
})
|
})
|
||||||
setLoginDialogOpen(true)
|
setLoginDialogOpen(true)
|
||||||
|
} else {
|
||||||
|
setError(getAllChatflowsApi.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [getAllChatflowsApi.error])
|
}, [getAllChatflowsApi.error])
|
||||||
@@ -124,94 +127,89 @@ const Chatflows = () => {
|
|||||||
}, [getAllChatflowsApi.data])
|
}, [getAllChatflowsApi.data])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
<MainCard>
|
||||||
<Stack flexDirection='column'>
|
{error ? (
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<ErrorBoundary error={error} />
|
||||||
<Toolbar
|
) : (
|
||||||
disableGutters={true}
|
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
||||||
style={{
|
<ViewHeader onSearchChange={onSearchChange} search={true} searchPlaceholder='Search Name or Category' title='Chatflows'>
|
||||||
margin: 1,
|
<ToggleButtonGroup
|
||||||
padding: 1,
|
sx={{ borderRadius: 2, maxHeight: 40 }}
|
||||||
paddingBottom: 10,
|
value={view}
|
||||||
display: 'flex',
|
color='primary'
|
||||||
justifyContent: 'space-between',
|
exclusive
|
||||||
width: '100%'
|
onChange={handleChange}
|
||||||
}}
|
>
|
||||||
>
|
<ToggleButton
|
||||||
<h1>Chatflows</h1>
|
sx={{
|
||||||
<TextField
|
borderColor: theme.palette.grey[900] + 25,
|
||||||
size='small'
|
borderRadius: 2,
|
||||||
sx={{ display: { xs: 'none', sm: 'block' }, ml: 3 }}
|
color: theme?.customization?.isDarkMode ? 'white' : 'inherit'
|
||||||
variant='outlined'
|
}}
|
||||||
placeholder='Search name or category'
|
variant='contained'
|
||||||
onChange={onSearchChange}
|
value='card'
|
||||||
InputProps={{
|
title='Card View'
|
||||||
startAdornment: (
|
>
|
||||||
<InputAdornment position='start'>
|
<IconLayoutGrid />
|
||||||
<IconSearch />
|
</ToggleButton>
|
||||||
</InputAdornment>
|
<ToggleButton
|
||||||
)
|
sx={{
|
||||||
}}
|
borderColor: theme.palette.grey[900] + 25,
|
||||||
|
borderRadius: 2,
|
||||||
|
color: theme?.customization?.isDarkMode ? 'white' : 'inherit'
|
||||||
|
}}
|
||||||
|
variant='contained'
|
||||||
|
value='list'
|
||||||
|
title='List View'
|
||||||
|
>
|
||||||
|
<IconList />
|
||||||
|
</ToggleButton>
|
||||||
|
</ToggleButtonGroup>
|
||||||
|
<StyledButton variant='contained' onClick={addNew} startIcon={<IconPlus />} sx={{ borderRadius: 2, height: 40 }}>
|
||||||
|
Add New
|
||||||
|
</StyledButton>
|
||||||
|
</ViewHeader>
|
||||||
|
{!view || view === 'card' ? (
|
||||||
|
<>
|
||||||
|
{isLoading && !getAllChatflowsApi.data ? (
|
||||||
|
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||||
|
<Skeleton variant='rounded' height={160} />
|
||||||
|
<Skeleton variant='rounded' height={160} />
|
||||||
|
<Skeleton variant='rounded' height={160} />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||||
|
{getAllChatflowsApi.data?.filter(filterFlows).map((data, index) => (
|
||||||
|
<ItemCard key={index} onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<FlowListTable
|
||||||
|
data={getAllChatflowsApi.data}
|
||||||
|
images={images}
|
||||||
|
isLoading={isLoading}
|
||||||
|
filterFunction={filterFlows}
|
||||||
|
updateFlowsApi={getAllChatflowsApi}
|
||||||
|
setError={setError}
|
||||||
/>
|
/>
|
||||||
<Box sx={{ flexGrow: 1 }} />
|
)}
|
||||||
<ButtonGroup sx={{ maxHeight: 40 }} disableElevation variant='contained' aria-label='outlined primary button group'>
|
{!isLoading && (!getAllChatflowsApi.data || getAllChatflowsApi.data.length === 0) && (
|
||||||
<ButtonGroup disableElevation variant='contained' aria-label='outlined primary button group'>
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||||
<ToggleButtonGroup sx={{ maxHeight: 40 }} value={view} color='primary' exclusive onChange={handleChange}>
|
<Box sx={{ p: 2, height: 'auto' }}>
|
||||||
<ToggleButton
|
<img
|
||||||
sx={{ color: theme?.customization?.isDarkMode ? 'white' : 'inherit' }}
|
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
||||||
variant='contained'
|
src={WorkflowEmptySVG}
|
||||||
value='card'
|
alt='WorkflowEmptySVG'
|
||||||
title='Card View'
|
/>
|
||||||
>
|
</Box>
|
||||||
<IconLayoutGrid />
|
<div>No Chatflows Yet</div>
|
||||||
</ToggleButton>
|
</Stack>
|
||||||
<ToggleButton
|
)}
|
||||||
sx={{ color: theme?.customization?.isDarkMode ? 'white' : 'inherit' }}
|
|
||||||
variant='contained'
|
|
||||||
value='list'
|
|
||||||
title='List View'
|
|
||||||
>
|
|
||||||
<IconList />
|
|
||||||
</ToggleButton>
|
|
||||||
</ToggleButtonGroup>
|
|
||||||
</ButtonGroup>
|
|
||||||
<Box sx={{ width: 5 }} />
|
|
||||||
<ButtonGroup disableElevation aria-label='outlined primary button group'>
|
|
||||||
<StyledButton variant='contained' onClick={addNew} startIcon={<IconPlus />}>
|
|
||||||
Add New
|
|
||||||
</StyledButton>
|
|
||||||
</ButtonGroup>
|
|
||||||
</ButtonGroup>
|
|
||||||
</Toolbar>
|
|
||||||
</Box>
|
|
||||||
{!isLoading && (!view || view === 'card') && getAllChatflowsApi.data && (
|
|
||||||
<Grid container spacing={gridSpacing}>
|
|
||||||
{getAllChatflowsApi.data.filter(filterFlows).map((data, index) => (
|
|
||||||
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
|
|
||||||
<ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
{!isLoading && view === 'list' && getAllChatflowsApi.data && (
|
|
||||||
<FlowListTable
|
|
||||||
sx={{ mt: 20 }}
|
|
||||||
data={getAllChatflowsApi.data}
|
|
||||||
images={images}
|
|
||||||
filterFunction={filterFlows}
|
|
||||||
updateFlowsApi={getAllChatflowsApi}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{!isLoading && (!getAllChatflowsApi.data || getAllChatflowsApi.data.length === 0) && (
|
|
||||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
|
||||||
<Box sx={{ p: 2, height: 'auto' }}>
|
|
||||||
<img style={{ objectFit: 'cover', height: '30vh', width: 'auto' }} src={WorkflowEmptySVG} alt='WorkflowEmptySVG' />
|
|
||||||
</Box>
|
|
||||||
<div>No Chatflows Yet</div>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<LoginDialog show={loginDialogOpen} dialogProps={loginDialogProps} onConfirm={onLoginClick} />
|
<LoginDialog show={loginDialogOpen} dialogProps={loginDialogProps} onConfirm={onLoginClick} />
|
||||||
<ConfirmDialog />
|
<ConfirmDialog />
|
||||||
</MainCard>
|
</MainCard>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import useNotifier from '@/utils/useNotifier'
|
|||||||
import { baseURL, REDACTED_CREDENTIAL_VALUE } from '@/store/constant'
|
import { baseURL, REDACTED_CREDENTIAL_VALUE } from '@/store/constant'
|
||||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||||
|
|
||||||
const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
@@ -70,6 +70,20 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) =>
|
|||||||
}
|
}
|
||||||
}, [getSpecificComponentCredentialApi.data])
|
}, [getSpecificComponentCredentialApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getSpecificCredentialApi.error) {
|
||||||
|
setError(getSpecificCredentialApi.error)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getSpecificCredentialApi.error])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getSpecificComponentCredentialApi.error) {
|
||||||
|
setError(getSpecificComponentCredentialApi.error)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getSpecificComponentCredentialApi.error])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dialogProps.type === 'EDIT' && dialogProps.data) {
|
if (dialogProps.type === 'EDIT' && dialogProps.data) {
|
||||||
// When credential dialog is opened from Credentials dashboard
|
// When credential dialog is opened from Credentials dashboard
|
||||||
@@ -118,6 +132,7 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) =>
|
|||||||
onConfirm(createResp.data.id)
|
onConfirm(createResp.data.id)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to add new Credential: ${error.response.data.message}`,
|
message: `Failed to add new Credential: ${error.response.data.message}`,
|
||||||
options: {
|
options: {
|
||||||
@@ -167,6 +182,7 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) =>
|
|||||||
onConfirm(saveResp.data.id)
|
onConfirm(saveResp.data.id)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to save Credential: ${error.response.data.message}`,
|
message: `Failed to save Credential: ${error.response.data.message}`,
|
||||||
options: {
|
options: {
|
||||||
@@ -284,7 +300,8 @@ AddEditCredentialDialog.propTypes = {
|
|||||||
show: PropTypes.bool,
|
show: PropTypes.bool,
|
||||||
dialogProps: PropTypes.object,
|
dialogProps: PropTypes.object,
|
||||||
onCancel: PropTypes.func,
|
onCancel: PropTypes.func,
|
||||||
onConfirm: PropTypes.func
|
onConfirm: PropTypes.func,
|
||||||
|
setError: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AddEditCredentialDialog
|
export default AddEditCredentialDialog
|
||||||
|
|||||||
@@ -2,19 +2,7 @@ import { useState, useEffect } from 'react'
|
|||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import {
|
import { List, ListItemButton, Dialog, DialogContent, DialogTitle, Box, OutlinedInput, InputAdornment, Typography } from '@mui/material'
|
||||||
List,
|
|
||||||
ListItemButton,
|
|
||||||
ListItem,
|
|
||||||
ListItemAvatar,
|
|
||||||
ListItemText,
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogTitle,
|
|
||||||
Box,
|
|
||||||
OutlinedInput,
|
|
||||||
InputAdornment
|
|
||||||
} from '@mui/material'
|
|
||||||
import { useTheme } from '@mui/material/styles'
|
import { useTheme } from '@mui/material/styles'
|
||||||
import { IconSearch, IconX } from '@tabler/icons'
|
import { IconSearch, IconX } from '@tabler/icons'
|
||||||
|
|
||||||
@@ -58,17 +46,27 @@ const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelecte
|
|||||||
const component = show ? (
|
const component = show ? (
|
||||||
<Dialog
|
<Dialog
|
||||||
fullWidth
|
fullWidth
|
||||||
maxWidth='xs'
|
maxWidth='md'
|
||||||
open={show}
|
open={show}
|
||||||
onClose={onCancel}
|
onClose={onCancel}
|
||||||
aria-labelledby='alert-dialog-title'
|
aria-labelledby='alert-dialog-title'
|
||||||
aria-describedby='alert-dialog-description'
|
aria-describedby='alert-dialog-description'
|
||||||
>
|
>
|
||||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
<DialogTitle sx={{ fontSize: '1rem', p: 3, pb: 0 }} id='alert-dialog-title'>
|
||||||
{dialogProps.title}
|
{dialogProps.title}
|
||||||
<Box sx={{ p: 2 }}>
|
</DialogTitle>
|
||||||
|
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, maxHeight: '75vh', position: 'relative', px: 3, pb: 3 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: customization.isDarkMode ? theme.palette.background.darkPaper : theme.palette.background.paper,
|
||||||
|
pt: 2,
|
||||||
|
position: 'sticky',
|
||||||
|
top: 0,
|
||||||
|
zIndex: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
<OutlinedInput
|
<OutlinedInput
|
||||||
sx={{ width: '100%', pr: 2, pl: 2, my: 2 }}
|
sx={{ width: '100%', pr: 2, pl: 2, position: 'sticky' }}
|
||||||
id='input-search-credential'
|
id='input-search-credential'
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
onChange={(e) => filterSearch(e.target.value)}
|
onChange={(e) => filterSearch(e.target.value)}
|
||||||
@@ -106,60 +104,59 @@ const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelecte
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<List
|
<List
|
||||||
sx={{
|
sx={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||||
|
gap: 2,
|
||||||
py: 0,
|
py: 0,
|
||||||
|
zIndex: 9,
|
||||||
borderRadius: '10px',
|
borderRadius: '10px',
|
||||||
[theme.breakpoints.down('md')]: {
|
[theme.breakpoints.down('md')]: {
|
||||||
maxWidth: 370
|
maxWidth: 370
|
||||||
},
|
|
||||||
'& .MuiListItemSecondaryAction-root': {
|
|
||||||
top: 22
|
|
||||||
},
|
|
||||||
'& .MuiDivider-root': {
|
|
||||||
my: 0
|
|
||||||
},
|
|
||||||
'& .list-container': {
|
|
||||||
pl: 7
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{[...componentsCredentials].map((componentCredential) => (
|
{[...componentsCredentials].map((componentCredential) => (
|
||||||
<div key={componentCredential.name}>
|
<ListItemButton
|
||||||
<ListItemButton
|
alignItems='center'
|
||||||
onClick={() => onCredentialSelected(componentCredential)}
|
key={componentCredential.name}
|
||||||
sx={{ p: 0, borderRadius: `${customization.borderRadius}px` }}
|
onClick={() => onCredentialSelected(componentCredential)}
|
||||||
|
sx={{
|
||||||
|
border: 1,
|
||||||
|
borderColor: theme.palette.grey[900] + 25,
|
||||||
|
borderRadius: 2,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'start',
|
||||||
|
textAlign: 'left',
|
||||||
|
gap: 1,
|
||||||
|
p: 2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ListItem alignItems='center'>
|
<img
|
||||||
<ListItemAvatar>
|
style={{
|
||||||
<div
|
width: '100%',
|
||||||
style={{
|
height: '100%',
|
||||||
width: 50,
|
padding: 7,
|
||||||
height: 50,
|
borderRadius: '50%',
|
||||||
borderRadius: '50%',
|
objectFit: 'contain'
|
||||||
backgroundColor: 'white'
|
}}
|
||||||
}}
|
alt={componentCredential.name}
|
||||||
>
|
src={`${baseURL}/api/v1/components-credentials-icon/${componentCredential.name}`}
|
||||||
<img
|
/>
|
||||||
style={{
|
</div>
|
||||||
width: '100%',
|
<Typography>{componentCredential.label}</Typography>
|
||||||
height: '100%',
|
</ListItemButton>
|
||||||
padding: 7,
|
|
||||||
borderRadius: '50%',
|
|
||||||
objectFit: 'contain'
|
|
||||||
}}
|
|
||||||
alt={componentCredential.name}
|
|
||||||
src={`${baseURL}/api/v1/components-credentials-icon/${componentCredential.name}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ListItemAvatar>
|
|
||||||
<ListItemText sx={{ ml: 1 }} primary={componentCredential.label} />
|
|
||||||
</ListItem>
|
|
||||||
</ListItemButton>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackba
|
|||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
|
import { styled } from '@mui/material/styles'
|
||||||
|
import { tableCellClasses } from '@mui/material/TableCell'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Box,
|
Box,
|
||||||
|
Skeleton,
|
||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -16,12 +19,8 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
Paper,
|
Paper,
|
||||||
IconButton,
|
IconButton,
|
||||||
Toolbar,
|
useTheme
|
||||||
TextField,
|
|
||||||
InputAdornment,
|
|
||||||
ButtonGroup
|
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useTheme } from '@mui/material/styles'
|
|
||||||
|
|
||||||
// project imports
|
// project imports
|
||||||
import MainCard from '@/ui-component/cards/MainCard'
|
import MainCard from '@/ui-component/cards/MainCard'
|
||||||
@@ -41,25 +40,48 @@ import useConfirm from '@/hooks/useConfirm'
|
|||||||
import useNotifier from '@/utils/useNotifier'
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import { IconTrash, IconEdit, IconX, IconPlus, IconSearch } from '@tabler/icons'
|
import { IconTrash, IconEdit, IconX, IconPlus } from '@tabler/icons'
|
||||||
import CredentialEmptySVG from '@/assets/images/credential_empty.svg'
|
import CredentialEmptySVG from '@/assets/images/credential_empty.svg'
|
||||||
|
|
||||||
// const
|
// const
|
||||||
import { baseURL } from '@/store/constant'
|
import { baseURL } from '@/store/constant'
|
||||||
import { SET_COMPONENT_CREDENTIALS } from '@/store/actions'
|
import { SET_COMPONENT_CREDENTIALS } from '@/store/actions'
|
||||||
|
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||||
|
import ErrorBoundary from '@/ErrorBoundary'
|
||||||
|
|
||||||
|
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
||||||
|
borderColor: theme.palette.grey[900] + 25,
|
||||||
|
padding: '6px 16px',
|
||||||
|
|
||||||
|
[`&.${tableCellClasses.head}`]: {
|
||||||
|
color: theme.palette.grey[900]
|
||||||
|
},
|
||||||
|
[`&.${tableCellClasses.body}`]: {
|
||||||
|
fontSize: 14,
|
||||||
|
height: 64
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const StyledTableRow = styled(TableRow)(() => ({
|
||||||
|
// hide last border
|
||||||
|
'&:last-child td, &:last-child th': {
|
||||||
|
border: 0
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
// ==============================|| Credentials ||============================== //
|
// ==============================|| Credentials ||============================== //
|
||||||
|
|
||||||
const Credentials = () => {
|
const Credentials = () => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const customization = useSelector((state) => state.customization)
|
const customization = useSelector((state) => state.customization)
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
useNotifier()
|
useNotifier()
|
||||||
|
|
||||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
|
||||||
|
const [isLoading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
const [showCredentialListDialog, setShowCredentialListDialog] = useState(false)
|
const [showCredentialListDialog, setShowCredentialListDialog] = useState(false)
|
||||||
const [credentialListDialogProps, setCredentialListDialogProps] = useState({})
|
const [credentialListDialogProps, setCredentialListDialogProps] = useState({})
|
||||||
const [showSpecificCredentialDialog, setShowSpecificCredentialDialog] = useState(false)
|
const [showSpecificCredentialDialog, setShowSpecificCredentialDialog] = useState(false)
|
||||||
@@ -174,12 +196,22 @@ const Credentials = () => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(getAllCredentialsApi.loading)
|
||||||
|
}, [getAllCredentialsApi.loading])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getAllCredentialsApi.data) {
|
if (getAllCredentialsApi.data) {
|
||||||
setCredentials(getAllCredentialsApi.data)
|
setCredentials(getAllCredentialsApi.data)
|
||||||
}
|
}
|
||||||
}, [getAllCredentialsApi.data])
|
}, [getAllCredentialsApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAllCredentialsApi.error) {
|
||||||
|
setError(getAllCredentialsApi.error)
|
||||||
|
}
|
||||||
|
}, [getAllCredentialsApi.error])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getAllComponentsCredentialsApi.data) {
|
if (getAllComponentsCredentialsApi.data) {
|
||||||
setComponentsCredentials(getAllComponentsCredentialsApi.data)
|
setComponentsCredentials(getAllComponentsCredentialsApi.data)
|
||||||
@@ -189,131 +221,164 @@ const Credentials = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
<MainCard>
|
||||||
<Stack flexDirection='row'>
|
{error ? (
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<ErrorBoundary error={error} />
|
||||||
<Toolbar
|
) : (
|
||||||
disableGutters={true}
|
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
||||||
style={{
|
<ViewHeader
|
||||||
margin: 1,
|
onSearchChange={onSearchChange}
|
||||||
padding: 1,
|
search={true}
|
||||||
paddingBottom: 10,
|
searchPlaceholder='Search Credentials'
|
||||||
display: 'flex',
|
title='Credentials'
|
||||||
justifyContent: 'space-between',
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<h1>Credentials </h1>
|
<StyledButton
|
||||||
<TextField
|
|
||||||
size='small'
|
|
||||||
sx={{ display: { xs: 'none', sm: 'block' }, ml: 3 }}
|
|
||||||
variant='outlined'
|
|
||||||
placeholder='Search credential name'
|
|
||||||
onChange={onSearchChange}
|
|
||||||
InputProps={{
|
|
||||||
startAdornment: (
|
|
||||||
<InputAdornment position='start'>
|
|
||||||
<IconSearch />
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Box sx={{ flexGrow: 1 }} />
|
|
||||||
<ButtonGroup
|
|
||||||
sx={{ maxHeight: 40 }}
|
|
||||||
disableElevation
|
|
||||||
variant='contained'
|
variant='contained'
|
||||||
aria-label='outlined primary button group'
|
sx={{ borderRadius: 2, height: '100%' }}
|
||||||
|
onClick={listCredential}
|
||||||
|
startIcon={<IconPlus />}
|
||||||
>
|
>
|
||||||
<ButtonGroup disableElevation aria-label='outlined primary button group'>
|
Add Credential
|
||||||
<StyledButton
|
</StyledButton>
|
||||||
variant='contained'
|
</ViewHeader>
|
||||||
sx={{ color: 'white', mr: 1, height: 37 }}
|
{!isLoading && credentials.length <= 0 ? (
|
||||||
onClick={listCredential}
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||||
startIcon={<IconPlus />}
|
<Box sx={{ p: 2, height: 'auto' }}>
|
||||||
|
<img
|
||||||
|
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
||||||
|
src={CredentialEmptySVG}
|
||||||
|
alt='CredentialEmptySVG'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<div>No Credentials Yet</div>
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<TableContainer
|
||||||
|
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
|
||||||
|
component={Paper}
|
||||||
|
>
|
||||||
|
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||||
|
<TableHead
|
||||||
|
sx={{
|
||||||
|
backgroundColor: customization.isDarkMode
|
||||||
|
? theme.palette.common.black
|
||||||
|
: theme.palette.grey[100],
|
||||||
|
height: 56
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Add Credential
|
<TableRow>
|
||||||
</StyledButton>
|
<StyledTableCell>Name</StyledTableCell>
|
||||||
</ButtonGroup>
|
<StyledTableCell>Last Updated</StyledTableCell>
|
||||||
</ButtonGroup>
|
<StyledTableCell>Created</StyledTableCell>
|
||||||
</Toolbar>
|
<StyledTableCell> </StyledTableCell>
|
||||||
</Box>
|
<StyledTableCell> </StyledTableCell>
|
||||||
</Stack>
|
</TableRow>
|
||||||
{credentials.length <= 0 && (
|
</TableHead>
|
||||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
<TableBody>
|
||||||
<Box sx={{ p: 2, height: 'auto' }}>
|
{isLoading ? (
|
||||||
<img
|
<>
|
||||||
style={{ objectFit: 'cover', height: '30vh', width: 'auto' }}
|
<StyledTableRow>
|
||||||
src={CredentialEmptySVG}
|
<StyledTableCell>
|
||||||
alt='CredentialEmptySVG'
|
<Skeleton variant='text' />
|
||||||
/>
|
</StyledTableCell>
|
||||||
</Box>
|
<StyledTableCell>
|
||||||
<div>No Credentials Yet</div>
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
<StyledTableRow>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{credentials.filter(filterCredentials).map((credential, index) => (
|
||||||
|
<StyledTableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||||
|
<StyledTableCell scope='row'>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: customization.isDarkMode
|
||||||
|
? theme.palette.common.white
|
||||||
|
: theme.palette.grey[300] + 75
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
padding: 5,
|
||||||
|
objectFit: 'contain'
|
||||||
|
}}
|
||||||
|
alt={credential.credentialName}
|
||||||
|
src={`${baseURL}/api/v1/components-credentials-icon/${credential.credentialName}`}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{credential.name}
|
||||||
|
</Box>
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
{moment(credential.updatedDate).format('MMMM Do, YYYY')}
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
{moment(credential.createdDate).format('MMMM Do, YYYY')}
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<IconButton title='Edit' color='primary' onClick={() => edit(credential)}>
|
||||||
|
<IconEdit />
|
||||||
|
</IconButton>
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<IconButton
|
||||||
|
title='Delete'
|
||||||
|
color='error'
|
||||||
|
onClick={() => deleteCredential(credential)}
|
||||||
|
>
|
||||||
|
<IconTrash />
|
||||||
|
</IconButton>
|
||||||
|
</StyledTableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{credentials.length > 0 && (
|
|
||||||
<TableContainer component={Paper}>
|
|
||||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>Name</TableCell>
|
|
||||||
<TableCell>Last Updated</TableCell>
|
|
||||||
<TableCell>Created</TableCell>
|
|
||||||
<TableCell> </TableCell>
|
|
||||||
<TableCell> </TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{credentials.filter(filterCredentials).map((credential, index) => (
|
|
||||||
<TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
|
||||||
<TableCell component='th' scope='row'>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: 25,
|
|
||||||
height: 25,
|
|
||||||
marginRight: 10,
|
|
||||||
borderRadius: '50%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
borderRadius: '50%',
|
|
||||||
objectFit: 'contain'
|
|
||||||
}}
|
|
||||||
alt={credential.credentialName}
|
|
||||||
src={`${baseURL}/api/v1/components-credentials-icon/${credential.credentialName}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{credential.name}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{moment(credential.updatedDate).format('DD-MMM-YY')}</TableCell>
|
|
||||||
<TableCell>{moment(credential.createdDate).format('DD-MMM-YY')}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<IconButton title='Edit' color='primary' onClick={() => edit(credential)}>
|
|
||||||
<IconEdit />
|
|
||||||
</IconButton>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<IconButton title='Delete' color='error' onClick={() => deleteCredential(credential)}>
|
|
||||||
<IconTrash />
|
|
||||||
</IconButton>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
)}
|
|
||||||
</MainCard>
|
</MainCard>
|
||||||
<CredentialListDialog
|
<CredentialListDialog
|
||||||
show={showCredentialListDialog}
|
show={showCredentialListDialog}
|
||||||
@@ -326,6 +391,7 @@ const Credentials = () => {
|
|||||||
dialogProps={specificCredentialDialogProps}
|
dialogProps={specificCredentialDialogProps}
|
||||||
onCancel={() => setShowSpecificCredentialDialog(false)}
|
onCancel={() => setShowSpecificCredentialDialog(false)}
|
||||||
onConfirm={onConfirm}
|
onConfirm={onConfirm}
|
||||||
|
setError={setError}
|
||||||
></AddEditCredentialDialog>
|
></AddEditCredentialDialog>
|
||||||
<ConfirmDialog />
|
<ConfirmDialog />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import {
|
import {
|
||||||
Grid,
|
|
||||||
Box,
|
Box,
|
||||||
Stack,
|
Stack,
|
||||||
Badge,
|
Badge,
|
||||||
Toolbar,
|
|
||||||
TextField,
|
|
||||||
InputAdornment,
|
|
||||||
ButtonGroup,
|
|
||||||
ToggleButton,
|
ToggleButton,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -21,10 +15,10 @@ import {
|
|||||||
OutlinedInput,
|
OutlinedInput,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
Button
|
Skeleton
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useTheme } from '@mui/material/styles'
|
import { useTheme } from '@mui/material/styles'
|
||||||
import { IconChevronsDown, IconChevronsUp, IconLayoutGrid, IconList, IconSearch } from '@tabler/icons'
|
import { IconLayoutGrid, IconList } from '@tabler/icons'
|
||||||
|
|
||||||
// project imports
|
// project imports
|
||||||
import MainCard from '@/ui-component/cards/MainCard'
|
import MainCard from '@/ui-component/cards/MainCard'
|
||||||
@@ -44,6 +38,8 @@ import { baseURL } from '@/store/constant'
|
|||||||
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'
|
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'
|
||||||
import { MarketplaceTable } from '@/ui-component/table/MarketplaceTable'
|
import { MarketplaceTable } from '@/ui-component/table/MarketplaceTable'
|
||||||
import MenuItem from '@mui/material/MenuItem'
|
import MenuItem from '@mui/material/MenuItem'
|
||||||
|
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||||
|
import ErrorBoundary from '@/ErrorBoundary'
|
||||||
|
|
||||||
function TabPanel(props) {
|
function TabPanel(props) {
|
||||||
const { children, value, index, ...other } = props
|
const { children, value, index, ...other } = props
|
||||||
@@ -66,28 +62,30 @@ TabPanel.propTypes = {
|
|||||||
value: PropTypes.number.isRequired
|
value: PropTypes.number.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
const ITEM_HEIGHT = 48
|
|
||||||
const ITEM_PADDING_TOP = 8
|
|
||||||
const badges = ['POPULAR', 'NEW']
|
const badges = ['POPULAR', 'NEW']
|
||||||
const types = ['Chatflow', 'Tool']
|
const types = ['Chatflow', 'Tool']
|
||||||
const framework = ['Langchain', 'LlamaIndex']
|
const framework = ['Langchain', 'LlamaIndex']
|
||||||
const MenuProps = {
|
const MenuProps = {
|
||||||
PaperProps: {
|
PaperProps: {
|
||||||
style: {
|
style: {
|
||||||
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
|
width: 160
|
||||||
width: 250
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const SelectStyles = {
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': {
|
||||||
|
borderRadius: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
// ==============================|| Marketplace ||============================== //
|
// ==============================|| Marketplace ||============================== //
|
||||||
|
|
||||||
const Marketplace = () => {
|
const Marketplace = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const customization = useSelector((state) => state.customization)
|
|
||||||
|
|
||||||
const [isLoading, setLoading] = useState(true)
|
const [isLoading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
const [images, setImages] = useState({})
|
const [images, setImages] = useState({})
|
||||||
|
|
||||||
const [showToolDialog, setShowToolDialog] = useState(false)
|
const [showToolDialog, setShowToolDialog] = useState(false)
|
||||||
@@ -101,7 +99,6 @@ const Marketplace = () => {
|
|||||||
const [badgeFilter, setBadgeFilter] = useState([])
|
const [badgeFilter, setBadgeFilter] = useState([])
|
||||||
const [typeFilter, setTypeFilter] = useState([])
|
const [typeFilter, setTypeFilter] = useState([])
|
||||||
const [frameworkFilter, setFrameworkFilter] = useState([])
|
const [frameworkFilter, setFrameworkFilter] = useState([])
|
||||||
const [open, setOpen] = useState(false)
|
|
||||||
const handleBadgeFilterChange = (event) => {
|
const handleBadgeFilterChange = (event) => {
|
||||||
const {
|
const {
|
||||||
target: { value }
|
target: { value }
|
||||||
@@ -223,222 +220,248 @@ const Marketplace = () => {
|
|||||||
}
|
}
|
||||||
}, [getAllTemplatesMarketplacesApi.data])
|
}, [getAllTemplatesMarketplacesApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAllTemplatesMarketplacesApi.error) {
|
||||||
|
setError(getAllTemplatesMarketplacesApi.error)
|
||||||
|
}
|
||||||
|
}, [getAllTemplatesMarketplacesApi.error])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
<MainCard>
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
{error ? (
|
||||||
<Toolbar
|
<ErrorBoundary error={error} />
|
||||||
disableGutters={true}
|
) : (
|
||||||
style={{
|
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
||||||
margin: 1,
|
<ViewHeader
|
||||||
padding: 1,
|
filters={
|
||||||
paddingBottom: 10,
|
<>
|
||||||
display: 'flex',
|
<FormControl
|
||||||
justifyContent: 'space-between',
|
sx={{
|
||||||
width: '100%'
|
borderRadius: 2,
|
||||||
}}
|
display: 'flex',
|
||||||
>
|
flexDirection: 'column',
|
||||||
<h1>Marketplace</h1>
|
justifyContent: 'end',
|
||||||
<TextField
|
minWidth: 120
|
||||||
size='small'
|
}}
|
||||||
id='search-filter-textbox'
|
|
||||||
sx={{ display: { xs: 'none', sm: 'block' }, ml: 3 }}
|
|
||||||
variant='outlined'
|
|
||||||
fullWidth='true'
|
|
||||||
placeholder='Search name or description or node name'
|
|
||||||
onChange={onSearchChange}
|
|
||||||
InputProps={{
|
|
||||||
startAdornment: (
|
|
||||||
<InputAdornment position='start'>
|
|
||||||
<IconSearch />
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
sx={{ width: '220px', ml: 3, mr: 5 }}
|
|
||||||
variant='outlined'
|
|
||||||
onClick={() => setOpen(!open)}
|
|
||||||
startIcon={open ? <IconChevronsUp /> : <IconChevronsDown />}
|
|
||||||
>
|
|
||||||
{open ? 'Hide Filters' : 'Show Filters'}
|
|
||||||
</Button>
|
|
||||||
<Box sx={{ flexGrow: 1 }} />
|
|
||||||
<ButtonGroup sx={{ maxHeight: 40 }} disableElevation variant='contained' aria-label='outlined primary button group'>
|
|
||||||
<ButtonGroup disableElevation variant='contained' aria-label='outlined primary button group'>
|
|
||||||
<ToggleButtonGroup
|
|
||||||
sx={{ maxHeight: 40 }}
|
|
||||||
value={view}
|
|
||||||
color='primary'
|
|
||||||
exclusive
|
|
||||||
onChange={handleViewChange}
|
|
||||||
>
|
|
||||||
<ToggleButton
|
|
||||||
sx={{ color: theme?.customization?.isDarkMode ? 'white' : 'inherit' }}
|
|
||||||
variant='contained'
|
|
||||||
value='card'
|
|
||||||
title='Card View'
|
|
||||||
>
|
>
|
||||||
<IconLayoutGrid />
|
<InputLabel size='small' id='filter-badge-label'>
|
||||||
</ToggleButton>
|
Tag
|
||||||
<ToggleButton
|
</InputLabel>
|
||||||
sx={{ color: theme?.customization?.isDarkMode ? 'white' : 'inherit' }}
|
<Select
|
||||||
variant='contained'
|
labelId='filter-badge-label'
|
||||||
value='list'
|
id='filter-badge-checkbox'
|
||||||
title='List View'
|
size='small'
|
||||||
|
multiple
|
||||||
|
value={badgeFilter}
|
||||||
|
onChange={handleBadgeFilterChange}
|
||||||
|
input={<OutlinedInput label='Badge' />}
|
||||||
|
renderValue={(selected) => selected.join(', ')}
|
||||||
|
MenuProps={MenuProps}
|
||||||
|
sx={SelectStyles}
|
||||||
|
>
|
||||||
|
{badges.map((name) => (
|
||||||
|
<MenuItem
|
||||||
|
key={name}
|
||||||
|
value={name}
|
||||||
|
sx={{ display: 'flex', alignItems: 'center', gap: 1, p: 1 }}
|
||||||
|
>
|
||||||
|
<Checkbox checked={badgeFilter.indexOf(name) > -1} sx={{ p: 0 }} />
|
||||||
|
<ListItemText primary={name} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl
|
||||||
|
sx={{
|
||||||
|
borderRadius: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'end',
|
||||||
|
minWidth: 120
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<IconList />
|
<InputLabel size='small' id='type-badge-label'>
|
||||||
</ToggleButton>
|
Type
|
||||||
</ToggleButtonGroup>
|
</InputLabel>
|
||||||
</ButtonGroup>
|
<Select
|
||||||
</ButtonGroup>
|
size='small'
|
||||||
</Toolbar>
|
labelId='type-badge-label'
|
||||||
</Box>
|
id='type-badge-checkbox'
|
||||||
{open && (
|
multiple
|
||||||
<Box sx={{ flexGrow: 1, mb: 2 }}>
|
value={typeFilter}
|
||||||
<Toolbar
|
onChange={handleTypeFilterChange}
|
||||||
disableGutters={true}
|
input={<OutlinedInput label='Badge' />}
|
||||||
style={{
|
renderValue={(selected) => selected.join(', ')}
|
||||||
margin: 1,
|
MenuProps={MenuProps}
|
||||||
padding: 1,
|
sx={SelectStyles}
|
||||||
paddingBottom: 10,
|
>
|
||||||
display: 'flex',
|
{types.map((name) => (
|
||||||
justifyContent: 'flex-start',
|
<MenuItem
|
||||||
width: '100%',
|
key={name}
|
||||||
borderBottom: '1px solid'
|
value={name}
|
||||||
}}
|
sx={{ display: 'flex', alignItems: 'center', gap: 1, p: 1 }}
|
||||||
|
>
|
||||||
|
<Checkbox checked={typeFilter.indexOf(name) > -1} sx={{ p: 0 }} />
|
||||||
|
<ListItemText primary={name} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl
|
||||||
|
sx={{
|
||||||
|
borderRadius: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'end',
|
||||||
|
minWidth: 120
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InputLabel size='small' id='type-fw-label'>
|
||||||
|
Framework
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
size='small'
|
||||||
|
labelId='type-fw-label'
|
||||||
|
id='type-fw-checkbox'
|
||||||
|
multiple
|
||||||
|
value={frameworkFilter}
|
||||||
|
onChange={handleFrameworkFilterChange}
|
||||||
|
input={<OutlinedInput label='Badge' />}
|
||||||
|
renderValue={(selected) => selected.join(', ')}
|
||||||
|
MenuProps={MenuProps}
|
||||||
|
sx={SelectStyles}
|
||||||
|
>
|
||||||
|
{framework.map((name) => (
|
||||||
|
<MenuItem
|
||||||
|
key={name}
|
||||||
|
value={name}
|
||||||
|
sx={{ display: 'flex', alignItems: 'center', gap: 1, p: 1 }}
|
||||||
|
>
|
||||||
|
<Checkbox checked={frameworkFilter.indexOf(name) > -1} sx={{ p: 0 }} />
|
||||||
|
<ListItemText primary={name} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
onSearchChange={onSearchChange}
|
||||||
|
search={true}
|
||||||
|
searchPlaceholder='Search Name/Description/Node'
|
||||||
|
title='Marketplace'
|
||||||
>
|
>
|
||||||
<FormControl sx={{ m: 1, width: 250 }}>
|
<ToggleButtonGroup
|
||||||
<InputLabel size='small' id='filter-badge-label'>
|
sx={{ borderRadius: 2, height: '100%' }}
|
||||||
Tag
|
value={view}
|
||||||
</InputLabel>
|
color='primary'
|
||||||
<Select
|
exclusive
|
||||||
labelId='filter-badge-label'
|
onChange={handleViewChange}
|
||||||
id='filter-badge-checkbox'
|
>
|
||||||
size='small'
|
<ToggleButton
|
||||||
multiple
|
sx={{
|
||||||
value={badgeFilter}
|
borderColor: theme.palette.grey[900] + 25,
|
||||||
onChange={handleBadgeFilterChange}
|
borderRadius: 2,
|
||||||
input={<OutlinedInput label='Badge' />}
|
color: theme?.customization?.isDarkMode ? 'white' : 'inherit'
|
||||||
renderValue={(selected) => selected.join(', ')}
|
}}
|
||||||
MenuProps={MenuProps}
|
variant='contained'
|
||||||
|
value='card'
|
||||||
|
title='Card View'
|
||||||
>
|
>
|
||||||
{badges.map((name) => (
|
<IconLayoutGrid />
|
||||||
<MenuItem key={name} value={name}>
|
</ToggleButton>
|
||||||
<Checkbox checked={badgeFilter.indexOf(name) > -1} />
|
<ToggleButton
|
||||||
<ListItemText primary={name} />
|
sx={{
|
||||||
</MenuItem>
|
borderColor: theme.palette.grey[900] + 25,
|
||||||
))}
|
borderRadius: 2,
|
||||||
</Select>
|
color: theme?.customization?.isDarkMode ? 'white' : 'inherit'
|
||||||
</FormControl>
|
}}
|
||||||
<FormControl sx={{ m: 1, width: 250 }}>
|
variant='contained'
|
||||||
<InputLabel size='small' id='type-badge-label'>
|
value='list'
|
||||||
Type
|
title='List View'
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
size='small'
|
|
||||||
labelId='type-badge-label'
|
|
||||||
id='type-badge-checkbox'
|
|
||||||
multiple
|
|
||||||
value={typeFilter}
|
|
||||||
onChange={handleTypeFilterChange}
|
|
||||||
input={<OutlinedInput label='Badge' />}
|
|
||||||
renderValue={(selected) => selected.join(', ')}
|
|
||||||
MenuProps={MenuProps}
|
|
||||||
>
|
>
|
||||||
{types.map((name) => (
|
<IconList />
|
||||||
<MenuItem key={name} value={name}>
|
</ToggleButton>
|
||||||
<Checkbox checked={typeFilter.indexOf(name) > -1} />
|
</ToggleButtonGroup>
|
||||||
<ListItemText primary={name} />
|
</ViewHeader>
|
||||||
</MenuItem>
|
{!view || view === 'card' ? (
|
||||||
))}
|
<>
|
||||||
</Select>
|
{isLoading ? (
|
||||||
</FormControl>
|
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||||
<FormControl sx={{ m: 1, width: 250 }}>
|
<Skeleton variant='rounded' height={160} />
|
||||||
<InputLabel size='small' id='type-fw-label'>
|
<Skeleton variant='rounded' height={160} />
|
||||||
Framework
|
<Skeleton variant='rounded' height={160} />
|
||||||
</InputLabel>
|
</Box>
|
||||||
<Select
|
) : (
|
||||||
size='small'
|
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||||
labelId='type-fw-label'
|
{getAllTemplatesMarketplacesApi.data
|
||||||
id='type-fw-checkbox'
|
?.filter(filterByBadge)
|
||||||
multiple
|
.filter(filterByType)
|
||||||
value={frameworkFilter}
|
.filter(filterFlows)
|
||||||
onChange={handleFrameworkFilterChange}
|
.filter(filterByFramework)
|
||||||
input={<OutlinedInput label='Badge' />}
|
.map((data, index) => (
|
||||||
renderValue={(selected) => selected.join(', ')}
|
<Box key={index}>
|
||||||
MenuProps={MenuProps}
|
{data.badge && (
|
||||||
>
|
<Badge
|
||||||
{framework.map((name) => (
|
sx={{
|
||||||
<MenuItem key={name} value={name}>
|
width: '100%',
|
||||||
<Checkbox checked={frameworkFilter.indexOf(name) > -1} />
|
height: '100%',
|
||||||
<ListItemText primary={name} />
|
'& .MuiBadge-badge': {
|
||||||
</MenuItem>
|
right: 20
|
||||||
))}
|
}
|
||||||
</Select>
|
}}
|
||||||
</FormControl>
|
badgeContent={data.badge}
|
||||||
</Toolbar>
|
color={data.badge === 'POPULAR' ? 'primary' : 'error'}
|
||||||
</Box>
|
>
|
||||||
)}
|
{data.type === 'Chatflow' && (
|
||||||
|
<ItemCard
|
||||||
{!isLoading && (!view || view === 'card') && getAllTemplatesMarketplacesApi.data && (
|
onClick={() => goToCanvas(data)}
|
||||||
<>
|
data={data}
|
||||||
<Grid container spacing={gridSpacing}>
|
images={images[data.id]}
|
||||||
{getAllTemplatesMarketplacesApi.data
|
/>
|
||||||
.filter(filterByBadge)
|
)}
|
||||||
.filter(filterByType)
|
{data.type === 'Tool' && (
|
||||||
.filter(filterFlows)
|
<ItemCard data={data} onClick={() => goToTool(data)} />
|
||||||
.filter(filterByFramework)
|
)}
|
||||||
.map((data, index) => (
|
</Badge>
|
||||||
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
|
)}
|
||||||
{data.badge && (
|
{!data.badge && data.type === 'Chatflow' && (
|
||||||
<Badge
|
<ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
|
||||||
sx={{
|
)}
|
||||||
'& .MuiBadge-badge': {
|
{!data.badge && data.type === 'Tool' && (
|
||||||
right: 20
|
<ItemCard data={data} onClick={() => goToTool(data)} />
|
||||||
}
|
)}
|
||||||
}}
|
</Box>
|
||||||
badgeContent={data.badge}
|
))}
|
||||||
color={data.badge === 'POPULAR' ? 'primary' : 'error'}
|
</Box>
|
||||||
>
|
)}
|
||||||
{data.type === 'Chatflow' && (
|
</>
|
||||||
<ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
|
) : (
|
||||||
)}
|
<MarketplaceTable
|
||||||
{data.type === 'Tool' && <ItemCard data={data} onClick={() => goToTool(data)} />}
|
data={getAllTemplatesMarketplacesApi.data}
|
||||||
</Badge>
|
filterFunction={filterFlows}
|
||||||
)}
|
filterByType={filterByType}
|
||||||
{!data.badge && data.type === 'Chatflow' && (
|
filterByBadge={filterByBadge}
|
||||||
<ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
|
filterByFramework={filterByFramework}
|
||||||
)}
|
goToTool={goToTool}
|
||||||
{!data.badge && data.type === 'Tool' && <ItemCard data={data} onClick={() => goToTool(data)} />}
|
goToCanvas={goToCanvas}
|
||||||
</Grid>
|
isLoading={isLoading}
|
||||||
))}
|
setError={setError}
|
||||||
</Grid>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{!isLoading && view === 'list' && getAllTemplatesMarketplacesApi.data && (
|
|
||||||
<MarketplaceTable
|
|
||||||
sx={{ mt: 20 }}
|
|
||||||
data={getAllTemplatesMarketplacesApi.data}
|
|
||||||
filterFunction={filterFlows}
|
|
||||||
filterByType={filterByType}
|
|
||||||
filterByBadge={filterByBadge}
|
|
||||||
filterByFramework={filterByFramework}
|
|
||||||
goToTool={goToTool}
|
|
||||||
goToCanvas={goToCanvas}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isLoading && (!getAllTemplatesMarketplacesApi.data || getAllTemplatesMarketplacesApi.data.length === 0) && (
|
|
||||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
|
||||||
<Box sx={{ p: 2, height: 'auto' }}>
|
|
||||||
<img
|
|
||||||
style={{ objectFit: 'cover', height: '30vh', width: 'auto' }}
|
|
||||||
src={WorkflowEmptySVG}
|
|
||||||
alt='WorkflowEmptySVG'
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
)}
|
||||||
<div>No Marketplace Yet</div>
|
|
||||||
|
{!isLoading && (!getAllTemplatesMarketplacesApi.data || getAllTemplatesMarketplacesApi.data.length === 0) && (
|
||||||
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||||
|
<Box sx={{ p: 2, height: 'auto' }}>
|
||||||
|
<img
|
||||||
|
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
||||||
|
src={WorkflowEmptySVG}
|
||||||
|
alt='WorkflowEmptySVG'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<div>No Marketplace Yet</div>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</MainCard>
|
</MainCard>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useDispatch, useSelector } from 'react-redux'
|
|||||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions'
|
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
|
|
||||||
import { Box, Typography, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, OutlinedInput } from '@mui/material'
|
import { Box, Button, Typography, Dialog, DialogActions, DialogContent, DialogTitle, Stack, OutlinedInput } from '@mui/material'
|
||||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||||
import { Grid } from '@/ui-component/grid/Grid'
|
import { Grid } from '@/ui-component/grid/Grid'
|
||||||
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
||||||
@@ -16,7 +16,7 @@ import { CodeEditor } from '@/ui-component/editor/CodeEditor'
|
|||||||
import HowToUseFunctionDialog from './HowToUseFunctionDialog'
|
import HowToUseFunctionDialog from './HowToUseFunctionDialog'
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import { IconX, IconFileExport } from '@tabler/icons'
|
import { IconX, IconFileDownload, IconPlus } from '@tabler/icons'
|
||||||
|
|
||||||
// API
|
// API
|
||||||
import toolsApi from '@/api/tools'
|
import toolsApi from '@/api/tools'
|
||||||
@@ -55,7 +55,7 @@ try {
|
|||||||
return '';
|
return '';
|
||||||
}`
|
}`
|
||||||
|
|
||||||
const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) => {
|
const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, setError }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
|
|
||||||
const customization = useSelector((state) => state.customization)
|
const customization = useSelector((state) => state.customization)
|
||||||
@@ -160,6 +160,13 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) =
|
|||||||
}
|
}
|
||||||
}, [getSpecificToolApi.data])
|
}, [getSpecificToolApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getSpecificToolApi.error) {
|
||||||
|
setError(getSpecificToolApi.error)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getSpecificToolApi.error])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dialogProps.type === 'EDIT' && dialogProps.data) {
|
if (dialogProps.type === 'EDIT' && dialogProps.data) {
|
||||||
// When tool dialog is opened from Tools dashboard
|
// When tool dialog is opened from Tools dashboard
|
||||||
@@ -383,128 +390,122 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) =
|
|||||||
aria-labelledby='alert-dialog-title'
|
aria-labelledby='alert-dialog-title'
|
||||||
aria-describedby='alert-dialog-description'
|
aria-describedby='alert-dialog-description'
|
||||||
>
|
>
|
||||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
<DialogTitle sx={{ fontSize: '1rem', p: 3, pb: 0 }} id='alert-dialog-title'>
|
||||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
|
||||||
{dialogProps.title}
|
{dialogProps.title}
|
||||||
<div style={{ flex: 1 }} />
|
|
||||||
{dialogProps.type === 'EDIT' && (
|
{dialogProps.type === 'EDIT' && (
|
||||||
<Button variant='outlined' onClick={() => exportTool()} startIcon={<IconFileExport />}>
|
<Button variant='outlined' onClick={() => exportTool()} startIcon={<IconFileDownload />}>
|
||||||
Export
|
Export
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<Box sx={{ p: 2 }}>
|
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
|
||||||
<Typography variant='overline'>
|
|
||||||
Tool Name
|
|
||||||
<span style={{ color: 'red' }}> *</span>
|
|
||||||
<TooltipWithParser
|
|
||||||
style={{ marginLeft: 10 }}
|
|
||||||
title={'Tool name must be small capital letter with underscore. Ex: my_tool'}
|
|
||||||
/>
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
<OutlinedInput
|
|
||||||
id='toolName'
|
|
||||||
type='string'
|
|
||||||
fullWidth
|
|
||||||
disabled={dialogProps.type === 'TEMPLATE'}
|
|
||||||
placeholder='My New Tool'
|
|
||||||
value={toolName}
|
|
||||||
name='toolName'
|
|
||||||
onChange={(e) => setToolName(e.target.value)}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ p: 2 }}>
|
</DialogTitle>
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, maxHeight: '75vh', position: 'relative', px: 3, pb: 3 }}>
|
||||||
<Typography variant='overline'>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 2 }}>
|
||||||
Tool description
|
<Box>
|
||||||
<span style={{ color: 'red' }}> *</span>
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
|
<Typography variant='overline'>
|
||||||
|
Tool Name
|
||||||
|
<span style={{ color: 'red' }}> *</span>
|
||||||
|
</Typography>
|
||||||
|
<TooltipWithParser title={'Tool name must be small capital letter with underscore. Ex: my_tool'} />
|
||||||
|
</Stack>
|
||||||
|
<OutlinedInput
|
||||||
|
id='toolName'
|
||||||
|
type='string'
|
||||||
|
fullWidth
|
||||||
|
disabled={dialogProps.type === 'TEMPLATE'}
|
||||||
|
placeholder='My New Tool'
|
||||||
|
value={toolName}
|
||||||
|
name='toolName'
|
||||||
|
onChange={(e) => setToolName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
|
<Typography variant='overline'>
|
||||||
|
Tool description
|
||||||
|
<span style={{ color: 'red' }}> *</span>
|
||||||
|
</Typography>
|
||||||
<TooltipWithParser
|
<TooltipWithParser
|
||||||
style={{ marginLeft: 10 }}
|
|
||||||
title={'Description of what the tool does. This is for ChatGPT to determine when to use this tool.'}
|
title={'Description of what the tool does. This is for ChatGPT to determine when to use this tool.'}
|
||||||
/>
|
/>
|
||||||
</Typography>
|
</Stack>
|
||||||
</Stack>
|
<OutlinedInput
|
||||||
<OutlinedInput
|
id='toolDesc'
|
||||||
id='toolDesc'
|
type='string'
|
||||||
type='string'
|
fullWidth
|
||||||
fullWidth
|
disabled={dialogProps.type === 'TEMPLATE'}
|
||||||
disabled={dialogProps.type === 'TEMPLATE'}
|
placeholder='Description of what the tool does. This is for ChatGPT to determine when to use this tool.'
|
||||||
placeholder='Description of what the tool does. This is for ChatGPT to determine when to use this tool.'
|
multiline={true}
|
||||||
multiline={true}
|
rows={3}
|
||||||
rows={3}
|
value={toolDesc}
|
||||||
value={toolDesc}
|
name='toolDesc'
|
||||||
name='toolDesc'
|
onChange={(e) => setToolDesc(e.target.value)}
|
||||||
onChange={(e) => setToolDesc(e.target.value)}
|
/>
|
||||||
/>
|
</Box>
|
||||||
</Box>
|
<Box>
|
||||||
<Box sx={{ p: 2 }}>
|
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
<Typography variant='overline'>Tool Icon Source</Typography>
|
||||||
<Typography variant='overline'>Tool Icon Src</Typography>
|
</Stack>
|
||||||
</Stack>
|
<OutlinedInput
|
||||||
<OutlinedInput
|
id='toolIcon'
|
||||||
id='toolIcon'
|
type='string'
|
||||||
type='string'
|
fullWidth
|
||||||
fullWidth
|
disabled={dialogProps.type === 'TEMPLATE'}
|
||||||
disabled={dialogProps.type === 'TEMPLATE'}
|
placeholder='https://raw.githubusercontent.com/gilbarbara/logos/main/logos/airtable.svg'
|
||||||
placeholder='https://raw.githubusercontent.com/gilbarbara/logos/main/logos/airtable.svg'
|
value={toolIcon}
|
||||||
value={toolIcon}
|
name='toolIcon'
|
||||||
name='toolIcon'
|
onChange={(e) => setToolIcon(e.target.value)}
|
||||||
onChange={(e) => setToolIcon(e.target.value)}
|
/>
|
||||||
/>
|
</Box>
|
||||||
</Box>
|
<Box>
|
||||||
<Box sx={{ p: 2 }}>
|
<Stack sx={{ position: 'relative', justifyContent: 'space-between' }} direction='row'>
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
<Typography variant='overline'>
|
<Typography variant='overline'>Output Schema</Typography>
|
||||||
Output Schema
|
<TooltipWithParser title={'What should be the output response in JSON format?'} />
|
||||||
<TooltipWithParser style={{ marginLeft: 10 }} title={'What should be the output response in JSON format?'} />
|
</Stack>
|
||||||
</Typography>
|
{dialogProps.type !== 'TEMPLATE' && (
|
||||||
</Stack>
|
<Button variant='outlined' onClick={addNewRow} startIcon={<IconPlus />}>
|
||||||
<Grid
|
Add Item
|
||||||
columns={columns}
|
</Button>
|
||||||
rows={toolSchema}
|
)}
|
||||||
disabled={dialogProps.type === 'TEMPLATE'}
|
</Stack>
|
||||||
addNewRow={addNewRow}
|
<Grid columns={columns} rows={toolSchema} disabled={dialogProps.type === 'TEMPLATE'} onRowUpdate={onRowUpdate} />
|
||||||
onRowUpdate={onRowUpdate}
|
</Box>
|
||||||
/>
|
<Box>
|
||||||
</Box>
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<Box sx={{ p: 2 }}>
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
<Typography variant='overline'>Javascript Function</Typography>
|
||||||
<Typography variant='overline'>
|
<TooltipWithParser title='Function to execute when tool is being used. You can use properties specified in Output Schema as variables. For example, if the property is <code>userid</code>, you can use as <code>$userid</code>. Return value must be a string. You can also override the code from API by following this <a target="_blank" href="https://docs.flowiseai.com/tools/custom-tool#override-function-from-api">guide</a>' />
|
||||||
Javascript Function
|
</Stack>
|
||||||
<TooltipWithParser
|
<Stack direction='row'>
|
||||||
style={{ marginLeft: 10 }}
|
<Button
|
||||||
title='Function to execute when tool is being used. You can use properties specified in Output Schema as variables. For example, if the property is <code>userid</code>, you can use as <code>$userid</code>. Return value must be a string. You can also override the code from API by following this <a target="_blank" href="https://docs.flowiseai.com/tools/custom-tool#override-function-from-api">guide</a>'
|
style={{ marginBottom: 10, marginRight: 10 }}
|
||||||
/>
|
color='secondary'
|
||||||
</Typography>
|
variant='text'
|
||||||
</Stack>
|
onClick={() => setShowHowToDialog(true)}
|
||||||
<Button
|
>
|
||||||
style={{ marginBottom: 10, marginRight: 10 }}
|
How to use Function
|
||||||
color='secondary'
|
</Button>
|
||||||
variant='outlined'
|
{dialogProps.type !== 'TEMPLATE' && (
|
||||||
onClick={() => setShowHowToDialog(true)}
|
<Button style={{ marginBottom: 10 }} variant='outlined' onClick={() => setToolFunc(exampleAPIFunc)}>
|
||||||
>
|
See Example
|
||||||
How to use Function
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
{dialogProps.type !== 'TEMPLATE' && (
|
</Stack>
|
||||||
<Button style={{ marginBottom: 10 }} variant='outlined' onClick={() => setToolFunc(exampleAPIFunc)}>
|
</Box>
|
||||||
See Example
|
<CodeEditor
|
||||||
</Button>
|
disabled={dialogProps.type === 'TEMPLATE'}
|
||||||
)}
|
value={toolFunc}
|
||||||
<CodeEditor
|
theme={customization.isDarkMode ? 'dark' : 'light'}
|
||||||
disabled={dialogProps.type === 'TEMPLATE'}
|
lang={'js'}
|
||||||
value={toolFunc}
|
onValueChange={(code) => setToolFunc(code)}
|
||||||
height='calc(100vh - 220px)'
|
/>
|
||||||
theme={customization.isDarkMode ? 'dark' : 'light'}
|
</Box>
|
||||||
lang={'js'}
|
|
||||||
onValueChange={(code) => setToolFunc(code)}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions sx={{ p: 3 }}>
|
||||||
{dialogProps.type === 'EDIT' && (
|
{dialogProps.type === 'EDIT' && (
|
||||||
<StyledButton color='error' variant='contained' onClick={() => deleteTool()}>
|
<StyledButton color='error' variant='contained' onClick={() => deleteTool()}>
|
||||||
Delete
|
Delete
|
||||||
@@ -538,7 +539,8 @@ ToolDialog.propTypes = {
|
|||||||
dialogProps: PropTypes.object,
|
dialogProps: PropTypes.object,
|
||||||
onUseTemplate: PropTypes.func,
|
onUseTemplate: PropTypes.func,
|
||||||
onCancel: PropTypes.func,
|
onCancel: PropTypes.func,
|
||||||
onConfirm: PropTypes.func
|
onConfirm: PropTypes.func,
|
||||||
|
setError: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ToolDialog
|
export default ToolDialog
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { useEffect, useState, useRef } from 'react'
|
import { useEffect, useState, useRef } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { Grid, Box, Stack, Button } from '@mui/material'
|
import { Box, Stack, Button, ButtonGroup, Skeleton } from '@mui/material'
|
||||||
import { useTheme } from '@mui/material/styles'
|
|
||||||
|
|
||||||
// project imports
|
// project imports
|
||||||
import MainCard from '@/ui-component/cards/MainCard'
|
import MainCard from '@/ui-component/cards/MainCard'
|
||||||
@@ -20,16 +18,17 @@ import toolsApi from '@/api/tools'
|
|||||||
import useApi from '@/hooks/useApi'
|
import useApi from '@/hooks/useApi'
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
import { IconPlus, IconFileImport } from '@tabler/icons'
|
import { IconPlus, IconFileUpload } from '@tabler/icons'
|
||||||
|
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||||
|
import ErrorBoundary from '@/ErrorBoundary'
|
||||||
|
|
||||||
// ==============================|| CHATFLOWS ||============================== //
|
// ==============================|| CHATFLOWS ||============================== //
|
||||||
|
|
||||||
const Tools = () => {
|
const Tools = () => {
|
||||||
const theme = useTheme()
|
|
||||||
const customization = useSelector((state) => state.customization)
|
|
||||||
|
|
||||||
const getAllToolsApi = useApi(toolsApi.getAllTools)
|
const getAllToolsApi = useApi(toolsApi.getAllTools)
|
||||||
|
|
||||||
|
const [isLoading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
const [showDialog, setShowDialog] = useState(false)
|
const [showDialog, setShowDialog] = useState(false)
|
||||||
const [dialogProps, setDialogProps] = useState({})
|
const [dialogProps, setDialogProps] = useState({})
|
||||||
|
|
||||||
@@ -101,44 +100,79 @@ const Tools = () => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(getAllToolsApi.loading)
|
||||||
|
}, [getAllToolsApi.loading])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAllToolsApi.error) {
|
||||||
|
setError(getAllToolsApi.error)
|
||||||
|
}
|
||||||
|
}, [getAllToolsApi.error])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
<MainCard>
|
||||||
<Stack flexDirection='row'>
|
{error ? (
|
||||||
<h1>Tools</h1>
|
<ErrorBoundary error={error} />
|
||||||
<Grid sx={{ mb: 1.25 }} container direction='row'>
|
) : (
|
||||||
<Box sx={{ flexGrow: 1 }} />
|
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
||||||
<Grid item>
|
<ViewHeader title='Tools'>
|
||||||
<Button
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
variant='outlined'
|
<Button
|
||||||
sx={{ mr: 2 }}
|
variant='outlined'
|
||||||
onClick={() => inputRef.current.click()}
|
onClick={() => inputRef.current.click()}
|
||||||
startIcon={<IconFileImport />}
|
startIcon={<IconFileUpload />}
|
||||||
>
|
sx={{ borderRadius: 2, height: 40 }}
|
||||||
Load
|
>
|
||||||
</Button>
|
Load
|
||||||
<input ref={inputRef} type='file' hidden accept='.json' onChange={(e) => handleFileUpload(e)} />
|
</Button>
|
||||||
<StyledButton variant='contained' sx={{ color: 'white' }} onClick={addNew} startIcon={<IconPlus />}>
|
<input
|
||||||
Create
|
style={{ display: 'none' }}
|
||||||
</StyledButton>
|
ref={inputRef}
|
||||||
</Grid>
|
type='file'
|
||||||
</Grid>
|
hidden
|
||||||
</Stack>
|
accept='.json'
|
||||||
<Grid container spacing={gridSpacing}>
|
onChange={(e) => handleFileUpload(e)}
|
||||||
{!getAllToolsApi.loading &&
|
/>
|
||||||
getAllToolsApi.data &&
|
</Box>
|
||||||
getAllToolsApi.data.map((data, index) => (
|
<ButtonGroup disableElevation aria-label='outlined primary button group'>
|
||||||
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
|
<StyledButton
|
||||||
<ItemCard data={data} onClick={() => edit(data)} />
|
variant='contained'
|
||||||
</Grid>
|
onClick={addNew}
|
||||||
))}
|
startIcon={<IconPlus />}
|
||||||
</Grid>
|
sx={{ borderRadius: 2, height: 40 }}
|
||||||
{!getAllToolsApi.loading && (!getAllToolsApi.data || getAllToolsApi.data.length === 0) && (
|
>
|
||||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
Create
|
||||||
<Box sx={{ p: 2, height: 'auto' }}>
|
</StyledButton>
|
||||||
<img style={{ objectFit: 'cover', height: '30vh', width: 'auto' }} src={ToolEmptySVG} alt='ToolEmptySVG' />
|
</ButtonGroup>
|
||||||
</Box>
|
</ViewHeader>
|
||||||
<div>No Tools Created Yet</div>
|
{isLoading ? (
|
||||||
|
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||||
|
<Skeleton variant='rounded' height={160} />
|
||||||
|
<Skeleton variant='rounded' height={160} />
|
||||||
|
<Skeleton variant='rounded' height={160} />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||||
|
{getAllToolsApi.data &&
|
||||||
|
getAllToolsApi.data.map((data, index) => (
|
||||||
|
<ItemCard data={data} key={index} onClick={() => edit(data)} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{!isLoading && (!getAllToolsApi.data || getAllToolsApi.data.length === 0) && (
|
||||||
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||||
|
<Box sx={{ p: 2, height: 'auto' }}>
|
||||||
|
<img
|
||||||
|
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
||||||
|
src={ToolEmptySVG}
|
||||||
|
alt='ToolEmptySVG'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<div>No Tools Created Yet</div>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</MainCard>
|
</MainCard>
|
||||||
@@ -147,6 +181,7 @@ const Tools = () => {
|
|||||||
dialogProps={dialogProps}
|
dialogProps={dialogProps}
|
||||||
onCancel={() => setShowDialog(false)}
|
onCancel={() => setShowDialog(false)}
|
||||||
onConfirm={onConfirm}
|
onConfirm={onConfirm}
|
||||||
|
setError={setError}
|
||||||
></ToolDialog>
|
></ToolDialog>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const variableTypes = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
@@ -111,6 +111,7 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||||||
onConfirm(createResp.data.id)
|
onConfirm(createResp.data.id)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
setError(err)
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to add new Variable: ${error.response.data.message}`,
|
message: `Failed to add new Variable: ${error.response.data.message}`,
|
||||||
options: {
|
options: {
|
||||||
@@ -153,6 +154,7 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||||||
onConfirm(saveResp.data.id)
|
onConfirm(saveResp.data.id)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setError(err)
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to save Variable: ${error.response.data.message}`,
|
message: `Failed to save Variable: ${error.response.data.message}`,
|
||||||
options: {
|
options: {
|
||||||
@@ -281,7 +283,8 @@ AddEditVariableDialog.propTypes = {
|
|||||||
show: PropTypes.bool,
|
show: PropTypes.bool,
|
||||||
dialogProps: PropTypes.object,
|
dialogProps: PropTypes.object,
|
||||||
onCancel: PropTypes.func,
|
onCancel: PropTypes.func,
|
||||||
onConfirm: PropTypes.func
|
onConfirm: PropTypes.func,
|
||||||
|
setError: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AddEditVariableDialog
|
export default AddEditVariableDialog
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackba
|
|||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
|
import { styled } from '@mui/material/styles'
|
||||||
|
import { tableCellClasses } from '@mui/material/TableCell'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Box,
|
Box,
|
||||||
|
Skeleton,
|
||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -16,13 +19,9 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
Paper,
|
Paper,
|
||||||
IconButton,
|
IconButton,
|
||||||
Toolbar,
|
Chip,
|
||||||
TextField,
|
useTheme
|
||||||
InputAdornment,
|
|
||||||
ButtonGroup,
|
|
||||||
Chip
|
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useTheme } from '@mui/material/styles'
|
|
||||||
|
|
||||||
// project imports
|
// project imports
|
||||||
import MainCard from '@/ui-component/cards/MainCard'
|
import MainCard from '@/ui-component/cards/MainCard'
|
||||||
@@ -40,25 +39,47 @@ import useConfirm from '@/hooks/useConfirm'
|
|||||||
import useNotifier from '@/utils/useNotifier'
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import { IconTrash, IconEdit, IconX, IconPlus, IconSearch, IconVariable } from '@tabler/icons'
|
import { IconTrash, IconEdit, IconX, IconPlus, IconVariable } from '@tabler/icons'
|
||||||
import VariablesEmptySVG from '@/assets/images/variables_empty.svg'
|
import VariablesEmptySVG from '@/assets/images/variables_empty.svg'
|
||||||
|
|
||||||
// const
|
// const
|
||||||
import AddEditVariableDialog from './AddEditVariableDialog'
|
import AddEditVariableDialog from './AddEditVariableDialog'
|
||||||
import HowToUseVariablesDialog from './HowToUseVariablesDialog'
|
import HowToUseVariablesDialog from './HowToUseVariablesDialog'
|
||||||
|
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||||
|
import ErrorBoundary from '@/ErrorBoundary'
|
||||||
|
|
||||||
|
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
||||||
|
borderColor: theme.palette.grey[900] + 25,
|
||||||
|
|
||||||
|
[`&.${tableCellClasses.head}`]: {
|
||||||
|
color: theme.palette.grey[900]
|
||||||
|
},
|
||||||
|
[`&.${tableCellClasses.body}`]: {
|
||||||
|
fontSize: 14,
|
||||||
|
height: 64
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const StyledTableRow = styled(TableRow)(() => ({
|
||||||
|
// hide last border
|
||||||
|
'&:last-child td, &:last-child th': {
|
||||||
|
border: 0
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
// ==============================|| Credentials ||============================== //
|
// ==============================|| Credentials ||============================== //
|
||||||
|
|
||||||
const Variables = () => {
|
const Variables = () => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const customization = useSelector((state) => state.customization)
|
const customization = useSelector((state) => state.customization)
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
useNotifier()
|
useNotifier()
|
||||||
|
|
||||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
|
||||||
|
const [isLoading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
const [showVariableDialog, setShowVariableDialog] = useState(false)
|
const [showVariableDialog, setShowVariableDialog] = useState(false)
|
||||||
const [variableDialogProps, setVariableDialogProps] = useState({})
|
const [variableDialogProps, setVariableDialogProps] = useState({})
|
||||||
const [variables, setVariables] = useState([])
|
const [variables, setVariables] = useState([])
|
||||||
@@ -154,6 +175,16 @@ const Variables = () => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(getAllVariables.loading)
|
||||||
|
}, [getAllVariables.loading])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAllVariables.error) {
|
||||||
|
setError(getAllVariables.error)
|
||||||
|
}
|
||||||
|
}, [getAllVariables.error])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getAllVariables.data) {
|
if (getAllVariables.data) {
|
||||||
setVariables(getAllVariables.data)
|
setVariables(getAllVariables.data)
|
||||||
@@ -162,149 +193,187 @@ const Variables = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
<MainCard>
|
||||||
<Stack flexDirection='row'>
|
{error ? (
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<ErrorBoundary error={error} />
|
||||||
<Toolbar
|
) : (
|
||||||
disableGutters={true}
|
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
||||||
style={{
|
<ViewHeader onSearchChange={onSearchChange} search={true} searchPlaceholder='Search Variables' title='Variables'>
|
||||||
margin: 1,
|
<Button variant='outlined' sx={{ borderRadius: 2, height: '100%' }} onClick={() => setShowHowToDialog(true)}>
|
||||||
padding: 1,
|
|
||||||
paddingBottom: 10,
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h1>Variables </h1>
|
|
||||||
<TextField
|
|
||||||
size='small'
|
|
||||||
sx={{ display: { xs: 'none', sm: 'block' }, ml: 3 }}
|
|
||||||
variant='outlined'
|
|
||||||
placeholder='Search variable name'
|
|
||||||
onChange={onSearchChange}
|
|
||||||
InputProps={{
|
|
||||||
startAdornment: (
|
|
||||||
<InputAdornment position='start'>
|
|
||||||
<IconSearch />
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Box sx={{ flexGrow: 1 }} />
|
|
||||||
<Button variant='outlined' sx={{ mr: 2 }} onClick={() => setShowHowToDialog(true)}>
|
|
||||||
How To Use
|
How To Use
|
||||||
</Button>
|
</Button>
|
||||||
<ButtonGroup
|
<StyledButton
|
||||||
sx={{ maxHeight: 40 }}
|
|
||||||
disableElevation
|
|
||||||
variant='contained'
|
variant='contained'
|
||||||
aria-label='outlined primary button group'
|
sx={{ borderRadius: 2, height: '100%' }}
|
||||||
|
onClick={addNew}
|
||||||
|
startIcon={<IconPlus />}
|
||||||
|
id='btn_createVariable'
|
||||||
>
|
>
|
||||||
<ButtonGroup disableElevation aria-label='outlined primary button group'>
|
Add Variable
|
||||||
<StyledButton
|
</StyledButton>
|
||||||
variant='contained'
|
</ViewHeader>
|
||||||
sx={{ color: 'white', mr: 1, height: 37 }}
|
{!isLoading && variables.length === 0 ? (
|
||||||
onClick={addNew}
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||||
startIcon={<IconPlus />}
|
<Box sx={{ p: 2, height: 'auto' }}>
|
||||||
id='btn_createVariable'
|
<img
|
||||||
|
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
||||||
|
src={VariablesEmptySVG}
|
||||||
|
alt='VariablesEmptySVG'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<div>No Variables Yet</div>
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<TableContainer
|
||||||
|
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
|
||||||
|
component={Paper}
|
||||||
|
>
|
||||||
|
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||||
|
<TableHead
|
||||||
|
sx={{
|
||||||
|
backgroundColor: customization.isDarkMode
|
||||||
|
? theme.palette.common.black
|
||||||
|
: theme.palette.grey[100],
|
||||||
|
height: 56
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Add Variable
|
<TableRow>
|
||||||
</StyledButton>
|
<StyledTableCell>Name</StyledTableCell>
|
||||||
</ButtonGroup>
|
<StyledTableCell>Value</StyledTableCell>
|
||||||
</ButtonGroup>
|
<StyledTableCell>Type</StyledTableCell>
|
||||||
</Toolbar>
|
<StyledTableCell>Last Updated</StyledTableCell>
|
||||||
</Box>
|
<StyledTableCell>Created</StyledTableCell>
|
||||||
</Stack>
|
<StyledTableCell> </StyledTableCell>
|
||||||
{variables.length === 0 && (
|
<StyledTableCell> </StyledTableCell>
|
||||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
</TableRow>
|
||||||
<Box sx={{ p: 2, height: 'auto' }}>
|
</TableHead>
|
||||||
<img
|
<TableBody>
|
||||||
style={{ objectFit: 'cover', height: '30vh', width: 'auto' }}
|
{isLoading ? (
|
||||||
src={VariablesEmptySVG}
|
<>
|
||||||
alt='VariablesEmptySVG'
|
<StyledTableRow>
|
||||||
/>
|
<StyledTableCell>
|
||||||
</Box>
|
<Skeleton variant='text' />
|
||||||
<div>No Variables Yet</div>
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
<StyledTableRow>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{variables.filter(filterVariables).map((variable, index) => (
|
||||||
|
<StyledTableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||||
|
<StyledTableCell component='th' scope='row'>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 25,
|
||||||
|
height: 25,
|
||||||
|
marginRight: 10,
|
||||||
|
borderRadius: '50%'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconVariable
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
borderRadius: '50%',
|
||||||
|
objectFit: 'contain'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{variable.name}
|
||||||
|
</div>
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>{variable.value}</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<Chip
|
||||||
|
color={variable.type === 'static' ? 'info' : 'secondary'}
|
||||||
|
size='small'
|
||||||
|
label={variable.type}
|
||||||
|
/>
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
{moment(variable.updatedDate).format('MMMM Do, YYYY')}
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
{moment(variable.createdDate).format('MMMM Do, YYYY')}
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<IconButton title='Edit' color='primary' onClick={() => edit(variable)}>
|
||||||
|
<IconEdit />
|
||||||
|
</IconButton>
|
||||||
|
</StyledTableCell>
|
||||||
|
<StyledTableCell>
|
||||||
|
<IconButton
|
||||||
|
title='Delete'
|
||||||
|
color='error'
|
||||||
|
onClick={() => deleteVariable(variable)}
|
||||||
|
>
|
||||||
|
<IconTrash />
|
||||||
|
</IconButton>
|
||||||
|
</StyledTableCell>
|
||||||
|
</StyledTableRow>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{variables.length > 0 && (
|
|
||||||
<TableContainer component={Paper}>
|
|
||||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>Name</TableCell>
|
|
||||||
<TableCell>Value</TableCell>
|
|
||||||
<TableCell>Type</TableCell>
|
|
||||||
<TableCell>Last Updated</TableCell>
|
|
||||||
<TableCell>Created</TableCell>
|
|
||||||
<TableCell> </TableCell>
|
|
||||||
<TableCell> </TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{variables.filter(filterVariables).map((variable, index) => (
|
|
||||||
<TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
|
||||||
<TableCell component='th' scope='row'>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: 25,
|
|
||||||
height: 25,
|
|
||||||
marginRight: 10,
|
|
||||||
borderRadius: '50%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconVariable
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
borderRadius: '50%',
|
|
||||||
objectFit: 'contain'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{variable.name}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{variable.value}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Chip
|
|
||||||
color={variable.type === 'static' ? 'info' : 'secondary'}
|
|
||||||
size='small'
|
|
||||||
label={variable.type}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{moment(variable.updatedDate).format('DD-MMM-YY')}</TableCell>
|
|
||||||
<TableCell>{moment(variable.createdDate).format('DD-MMM-YY')}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<IconButton title='Edit' color='primary' onClick={() => edit(variable)}>
|
|
||||||
<IconEdit />
|
|
||||||
</IconButton>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<IconButton title='Delete' color='error' onClick={() => deleteVariable(variable)}>
|
|
||||||
<IconTrash />
|
|
||||||
</IconButton>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
)}
|
|
||||||
</MainCard>
|
</MainCard>
|
||||||
<AddEditVariableDialog
|
<AddEditVariableDialog
|
||||||
show={showVariableDialog}
|
show={showVariableDialog}
|
||||||
dialogProps={variableDialogProps}
|
dialogProps={variableDialogProps}
|
||||||
onCancel={() => setShowVariableDialog(false)}
|
onCancel={() => setShowVariableDialog(false)}
|
||||||
onConfirm={onConfirm}
|
onConfirm={onConfirm}
|
||||||
|
setError={setError}
|
||||||
></AddEditVariableDialog>
|
></AddEditVariableDialog>
|
||||||
<HowToUseVariablesDialog show={showHowToDialog} onCancel={() => setShowHowToDialog(false)}></HowToUseVariablesDialog>
|
<HowToUseVariablesDialog show={showHowToDialog} onCancel={() => setShowHowToDialog(false)}></HowToUseVariablesDialog>
|
||||||
<ConfirmDialog />
|
<ConfirmDialog />
|
||||||
|
|||||||
Reference in New Issue
Block a user