Merge branch 'main' into FEATURE/Vision

This commit is contained in:
Henry Heng
2024-02-14 18:06:33 +08:00
committed by GitHub
130 changed files with 6581 additions and 284 deletions
+2 -1
View File
@@ -10,7 +10,8 @@ module.exports = {
}
}
]
}
},
ignoreWarnings: [/Failed to parse source map/] // Ignore warnings about source maps
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "flowise-ui",
"version": "1.4.9",
"version": "1.5.0",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://flowiseai.com",
"author": {
+3 -1
View File
@@ -2,8 +2,10 @@ import client from './client'
const getAllChatflowsMarketplaces = () => client.get('/marketplaces/chatflows')
const getAllToolsMarketplaces = () => client.get('/marketplaces/tools')
const getAllTemplatesFromMarketplaces = () => client.get('/marketplaces/templates')
export default {
getAllChatflowsMarketplaces,
getAllToolsMarketplaces
getAllToolsMarketplaces,
getAllTemplatesFromMarketplaces
}
+3 -3
View File
@@ -1,8 +1,8 @@
import client from './client'
const fetchAllLinks = (url, relativeLinksMethod) =>
client.get(`/fetch-links?url=${encodeURIComponent(url)}&relativeLinksMethod=${relativeLinksMethod}`)
const fetchLinks = (url, relativeLinksMethod, relativeLinksLimit) =>
client.get(`/fetch-links?url=${encodeURIComponent(url)}&relativeLinksMethod=${relativeLinksMethod}&limit=${relativeLinksLimit}`)
export default {
fetchAllLinks
fetchLinks
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@@ -16,7 +16,7 @@ import {
Stack,
Typography
} from '@mui/material'
import { IconTrash } from '@tabler/icons'
import { IconTrash, IconX } from '@tabler/icons'
import PerfectScrollbar from 'react-perfect-scrollbar'
import { BackdropLoader } from 'ui-component/loading/BackdropLoader'
@@ -24,12 +24,23 @@ import { StyledButton } from 'ui-component/button/StyledButton'
import scraperApi from 'api/scraper'
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
import useNotifier from 'utils/useNotifier'
import {
HIDE_CANVAS_DIALOG,
SHOW_CANVAS_DIALOG,
enqueueSnackbar as enqueueSnackbarAction,
closeSnackbar as closeSnackbarAction
} from 'store/actions'
const ManageScrapedLinksDialog = ({ show, dialogProps, onCancel, onSave }) => {
const portalElement = document.getElementById('portal')
const dispatch = useDispatch()
useNotifier()
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
const [loading, setLoading] = useState(false)
const [selectedLinks, setSelectedLinks] = useState([])
const [url, setUrl] = useState('')
@@ -53,9 +64,38 @@ const ManageScrapedLinksDialog = ({ show, dialogProps, onCancel, onSave }) => {
const handleFetchLinks = async () => {
setLoading(true)
const fetchLinksResp = await scraperApi.fetchAllLinks(url, 'webCrawl')
if (fetchLinksResp.data) {
setSelectedLinks(fetchLinksResp.data.links)
try {
const fetchLinksResp = await scraperApi.fetchLinks(url, dialogProps.relativeLinksMethod, dialogProps.limit)
if (fetchLinksResp.data) {
setSelectedLinks(fetchLinksResp.data.links)
enqueueSnackbar({
message: 'Successfully fetched links',
options: {
key: new Date().getTime() + Math.random(),
variant: 'success',
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
} catch (error) {
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
enqueueSnackbar({
message: errorData,
options: {
key: new Date().getTime() + Math.random(),
variant: 'error',
persist: true,
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
setLoading(false)
}
@@ -132,6 +132,35 @@ const NodeInfoDialog = ({ show, dialogProps, onCancel }) => {
</span>
</div>
)}
{dialogProps.data.tags &&
dialogProps.data.tags.length &&
dialogProps.data.tags.map((tag, index) => (
<div
style={{
display: 'flex',
flexDirection: 'row',
width: 'max-content',
borderRadius: 15,
background: '#cae9ff',
padding: 5,
paddingLeft: 10,
paddingRight: 10,
marginTop: 5,
marginLeft: 10,
marginBottom: 5
}}
key={index}
>
<span
style={{
color: '#023e7d',
fontSize: '0.825rem'
}}
>
{tag.toLowerCase()}
</span>
</div>
))}
</div>
</div>
</div>
@@ -0,0 +1,146 @@
import PropTypes from 'prop-types'
import { styled } from '@mui/material/styles'
import Table from '@mui/material/Table'
import TableBody from '@mui/material/TableBody'
import TableCell, { tableCellClasses } from '@mui/material/TableCell'
import TableContainer from '@mui/material/TableContainer'
import TableHead from '@mui/material/TableHead'
import TableRow from '@mui/material/TableRow'
import Paper from '@mui/material/Paper'
import Chip from '@mui/material/Chip'
import { Button, Typography } from '@mui/material'
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white
},
[`&.${tableCellClasses.body}`]: {
fontSize: 14
}
}))
const StyledTableRow = styled(TableRow)(({ theme }) => ({
'&:nth-of-type(odd)': {
backgroundColor: theme.palette.action.hover
},
// hide last border
'&:last-child td, &:last-child th': {
border: 0
}
}))
export const MarketplaceTable = ({ data, filterFunction, filterByBadge, filterByType, filterByFramework, goToCanvas, goToTool }) => {
const openTemplate = (selectedTemplate) => {
if (selectedTemplate.flowData) {
goToCanvas(selectedTemplate)
} else {
goToTool(selectedTemplate)
}
}
return (
<>
<TableContainer style={{ marginTop: '30', border: 1 }} component={Paper}>
<Table sx={{ minWidth: 650 }} size='small' aria-label='a dense table'>
<TableHead>
<TableRow sx={{ marginTop: '10', backgroundColor: 'primary' }}>
<StyledTableCell component='th' scope='row' style={{ width: '15%' }} key='0'>
Name
</StyledTableCell>
<StyledTableCell component='th' scope='row' style={{ width: '5%' }} key='1'>
Type
</StyledTableCell>
<StyledTableCell style={{ width: '35%' }} key='2'>
Description
</StyledTableCell>
<StyledTableCell style={{ width: '35%' }} key='3'>
Nodes
</StyledTableCell>
<StyledTableCell component='th' scope='row' style={{ width: '5%' }} key='4'>
&nbsp;
</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{data
.filter(filterByBadge)
.filter(filterByType)
.filter(filterFunction)
.filter(filterByFramework)
.map((row, index) => (
<StyledTableRow key={index}>
<TableCell key='0'>
<Typography
sx={{ fontSize: '1.2rem', fontWeight: 500, overflowWrap: 'break-word', whiteSpace: 'pre-line' }}
>
<Button onClick={() => openTemplate(row)} sx={{ textAlign: 'left' }}>
{row.templateName || row.name}
</Button>
</Typography>
</TableCell>
<TableCell key='1'>
<Typography>{row.type}</Typography>
</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>
))}
</TableBody>
</Table>
</TableContainer>
</>
)
}
MarketplaceTable.propTypes = {
data: PropTypes.array,
filterFunction: PropTypes.func,
filterByBadge: PropTypes.func,
filterByType: PropTypes.func,
filterByFramework: PropTypes.func,
goToTool: PropTypes.func,
goToCanvas: PropTypes.func
}
+4
View File
@@ -99,6 +99,7 @@ export const initNode = (nodeData, newNodeId) => {
id: `${newNodeId}-output-${nodeData.outputs[j].name}-${baseClasses}`,
name: nodeData.outputs[j].name,
label: nodeData.outputs[j].label,
description: nodeData.outputs[j].description ?? '',
type
}
options.push(newOutputOption)
@@ -107,6 +108,7 @@ export const initNode = (nodeData, newNodeId) => {
name: 'output',
label: 'Output',
type: 'options',
description: nodeData.outputs[0].description ?? '',
options,
default: nodeData.outputs[0].name
}
@@ -116,6 +118,7 @@ export const initNode = (nodeData, newNodeId) => {
id: `${newNodeId}-output-${nodeData.name}-${nodeData.baseClasses.join('|')}`,
name: nodeData.name,
label: nodeData.type,
description: nodeData.description ?? '',
type: nodeData.baseClasses.join(' | ')
}
outputAnchors.push(newOutput)
@@ -277,6 +280,7 @@ export const generateExportFlowData = (flowData) => {
name: node.data.name,
type: node.data.type,
baseClasses: node.data.baseClasses,
tags: node.data.tags,
category: node.data.category,
description: node.data.description,
inputParams: node.data.inputParams,
+92 -8
View File
@@ -22,7 +22,9 @@ import {
Popper,
Stack,
Typography,
Chip
Chip,
Tab,
Tabs
} from '@mui/material'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
@@ -36,12 +38,20 @@ import { StyledFab } from 'ui-component/button/StyledFab'
// icons
import { IconPlus, IconSearch, IconMinus, IconX } from '@tabler/icons'
import LlamaindexPNG from 'assets/images/llamaindex.png'
import LangChainPNG from 'assets/images/langchain.png'
// const
import { baseURL } from 'store/constant'
import { SET_COMPONENT_NODES } from 'store/actions'
// ==============================|| ADD NODES||============================== //
function a11yProps(index) {
return {
id: `attachment-tab-${index}`,
'aria-controls': `attachment-tabpanel-${index}`
}
}
const AddNodes = ({ nodesData, node }) => {
const theme = useTheme()
@@ -52,6 +62,7 @@ const AddNodes = ({ nodesData, node }) => {
const [nodes, setNodes] = useState({})
const [open, setOpen] = useState(false)
const [categoryExpanded, setCategoryExpanded] = useState({})
const [tabValue, setTabValue] = useState(0)
const anchorRef = useRef(null)
const prevOpen = useRef(open)
@@ -86,6 +97,11 @@ const AddNodes = ({ nodesData, node }) => {
}
}
const handleTabChange = (event, newValue) => {
setTabValue(newValue)
filterSearch(searchValue, newValue)
}
const getSearchedNodes = (value) => {
const passed = nodesData.filter((nd) => {
const passesQuery = nd.name.toLowerCase().includes(value.toLowerCase())
@@ -95,23 +111,34 @@ const AddNodes = ({ nodesData, node }) => {
return passed
}
const filterSearch = (value) => {
const filterSearch = (value, newTabValue) => {
setSearchValue(value)
setTimeout(() => {
if (value) {
const returnData = getSearchedNodes(value)
groupByCategory(returnData, true)
groupByCategory(returnData, newTabValue ?? tabValue, true)
scrollTop()
} else if (value === '') {
groupByCategory(nodesData)
groupByCategory(nodesData, newTabValue ?? tabValue)
scrollTop()
}
}, 500)
}
const groupByCategory = (nodes, isFilter) => {
const groupByTags = (nodes, newTabValue = 0) => {
const langchainNodes = nodes.filter((nd) => !nd.tags)
const llmaindexNodes = nodes.filter((nd) => nd.tags && nd.tags.includes('LlamaIndex'))
if (newTabValue === 0) {
return langchainNodes
} else {
return llmaindexNodes
}
}
const groupByCategory = (nodes, newTabValue, isFilter) => {
const taggedNodes = groupByTags(nodes, newTabValue)
const accordianCategories = {}
const result = nodes.reduce(function (r, a) {
const result = taggedNodes.reduce(function (r, a) {
r[a.category] = r[a.category] || []
r[a.category].push(a)
accordianCategories[a.category] = isFilter ? true : false
@@ -244,15 +271,72 @@ const AddNodes = ({ nodesData, node }) => {
'aria-label': 'weight'
}}
/>
<Tabs
sx={{ position: 'relative', minHeight: '50px', height: '50px' }}
variant='fullWidth'
value={tabValue}
onChange={handleTabChange}
aria-label='tabs'
>
{['LangChain', 'LlamaIndex'].map((item, index) => (
<Tab
icon={
<div
style={{
borderRadius: '50%'
}}
>
<img
style={{
width: '25px',
height: '25px',
borderRadius: '50%',
objectFit: 'contain'
}}
src={index === 0 ? LangChainPNG : LlamaindexPNG}
alt={item}
/>
</div>
}
iconPosition='start'
sx={{ minHeight: '50px', height: '50px' }}
key={index}
label={item}
{...a11yProps(index)}
></Tab>
))}
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
borderRadius: 10,
background: 'rgb(254,252,191)',
paddingLeft: 6,
paddingRight: 6,
paddingTop: 1,
paddingBottom: 1,
width: 'max-content',
position: 'absolute',
top: 0,
right: 0,
fontSize: '0.65rem',
fontWeight: 700
}}
>
<span style={{ color: 'rgb(116,66,16)' }}>BETA</span>
</div>
</Tabs>
<Divider />
</Box>
<PerfectScrollbar
containerRef={(el) => {
ps.current = el
}}
style={{ height: '100%', maxHeight: 'calc(100vh - 320px)', overflowX: 'hidden' }}
style={{ height: '100%', maxHeight: 'calc(100vh - 380px)', overflowX: 'hidden' }}
>
<Box sx={{ p: 2 }}>
<Box sx={{ p: 2, pt: 0 }}>
<List
sx={{
width: '100%',
+18 -1
View File
@@ -19,6 +19,7 @@ import NodeInfoDialog from 'ui-component/dialog/NodeInfoDialog'
import { baseURL } from 'store/constant'
import { IconTrash, IconCopy, IconInfoCircle, IconAlertTriangle } from '@tabler/icons'
import { flowContext } from 'store/context/ReactFlowContext'
import LlamaindexPNG from 'assets/images/llamaindex.png'
// ===========================|| CANVAS NODE ||=========================== //
@@ -158,9 +159,25 @@ const CanvasNode = ({ data }) => {
{data.label}
</Typography>
</Box>
<div style={{ flexGrow: 1 }}></div>
{data.tags && data.tags.includes('LlamaIndex') && (
<>
<div
style={{
borderRadius: '50%',
padding: 15
}}
>
<img
style={{ width: '25px', height: '25px', borderRadius: '50%', objectFit: 'contain' }}
src={LlamaindexPNG}
alt='LlamaIndex'
/>
</div>
</>
)}
{warningMessage && (
<>
<div style={{ flexGrow: 1 }}></div>
<Tooltip title={<span style={{ whiteSpace: 'pre-line' }}>{warningMessage}</span>} placement='top'>
<IconButton sx={{ height: 35, width: 35 }}>
<IconAlertTriangle size={35} color='orange' />
@@ -91,9 +91,11 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
}
}
const onManageLinksDialogClicked = (url, selectedLinks) => {
const onManageLinksDialogClicked = (url, selectedLinks, relativeLinksMethod, limit) => {
const dialogProps = {
url,
relativeLinksMethod,
limit,
selectedLinks,
confirmButtonName: 'Save',
cancelButtonName: 'Cancel'
@@ -475,7 +477,9 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
onClick={() =>
onManageLinksDialogClicked(
data.inputs[inputParam.name] ?? inputParam.default ?? '',
data.inputs.selectedLinks
data.inputs.selectedLinks,
data.inputs['relativeLinksMethod'] ?? 'webCrawl',
parseInt(data.inputs['limit']) ?? 0
)
}
>
@@ -13,6 +13,7 @@ import AdditionalParamsDialog from 'ui-component/dialog/AdditionalParamsDialog'
// const
import { baseURL } from 'store/constant'
import LlamaindexPNG from 'assets/images/llamaindex.png'
const CardWrapper = styled(MainCard)(({ theme }) => ({
background: theme.palette.card.main,
@@ -87,6 +88,23 @@ const MarketplaceCanvasNode = ({ data }) => {
{data.label}
</Typography>
</Box>
<div style={{ flexGrow: 1 }}></div>
{data.tags && data.tags.includes('LlamaIndex') && (
<>
<div
style={{
borderRadius: '50%',
padding: 15
}}
>
<img
style={{ width: '25px', height: '25px', borderRadius: '50%', objectFit: 'contain' }}
src={LlamaindexPNG}
alt='LlamaIndex'
/>
</div>
</>
)}
</div>
{(data.inputAnchors.length > 0 || data.inputParams.length > 0) && (
<>
+323 -99
View File
@@ -4,9 +4,26 @@ import { useSelector } from 'react-redux'
import PropTypes from 'prop-types'
// material-ui
import { Grid, Box, Stack, Tabs, Tab, Badge } from '@mui/material'
import {
Grid,
Box,
Stack,
Badge,
Toolbar,
TextField,
InputAdornment,
ButtonGroup,
ToggleButton,
InputLabel,
FormControl,
Select,
OutlinedInput,
Checkbox,
ListItemText,
Button
} from '@mui/material'
import { useTheme } from '@mui/material/styles'
import { IconHierarchy, IconTool } from '@tabler/icons'
import { IconChevronsDown, IconChevronsUp, IconLayoutGrid, IconList, IconSearch } from '@tabler/icons'
// project imports
import MainCard from 'ui-component/cards/MainCard'
@@ -23,6 +40,10 @@ import useApi from 'hooks/useApi'
// const
import { baseURL } from 'store/constant'
import * as React from 'react'
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'
import { MarketplaceTable } from '../../ui-component/table/MarketplaceTable'
import MenuItem from '@mui/material/MenuItem'
function TabPanel(props) {
const { children, value, index, ...other } = props
@@ -45,6 +66,19 @@ TabPanel.propTypes = {
value: PropTypes.number.isRequired
}
const ITEM_HEIGHT = 48
const ITEM_PADDING_TOP = 8
const badges = ['POPULAR', 'NEW']
const types = ['Chatflow', 'Tool']
const framework = ['Langchain', 'LlamaIndex']
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250
}
}
}
// ==============================|| Marketplace ||============================== //
const Marketplace = () => {
@@ -53,16 +87,77 @@ const Marketplace = () => {
const theme = useTheme()
const customization = useSelector((state) => state.customization)
const [isChatflowsLoading, setChatflowsLoading] = useState(true)
const [isToolsLoading, setToolsLoading] = useState(true)
const [isLoading, setLoading] = useState(true)
const [images, setImages] = useState({})
const tabItems = ['Chatflows', 'Tools']
const [value, setValue] = useState(0)
const [showToolDialog, setShowToolDialog] = useState(false)
const [toolDialogProps, setToolDialogProps] = useState({})
const getAllChatflowsMarketplacesApi = useApi(marketplacesApi.getAllChatflowsMarketplaces)
const getAllToolsMarketplacesApi = useApi(marketplacesApi.getAllToolsMarketplaces)
const getAllTemplatesMarketplacesApi = useApi(marketplacesApi.getAllTemplatesFromMarketplaces)
const [view, setView] = React.useState(localStorage.getItem('mpDisplayStyle') || 'card')
const [search, setSearch] = useState('')
const [badgeFilter, setBadgeFilter] = useState([])
const [typeFilter, setTypeFilter] = useState([])
const [frameworkFilter, setFrameworkFilter] = useState([])
const [open, setOpen] = useState(false)
const handleBadgeFilterChange = (event) => {
const {
target: { value }
} = event
setBadgeFilter(
// On autofill we get a stringified value.
typeof value === 'string' ? value.split(',') : value
)
}
const handleTypeFilterChange = (event) => {
const {
target: { value }
} = event
setTypeFilter(
// On autofill we get a stringified value.
typeof value === 'string' ? value.split(',') : value
)
}
const handleFrameworkFilterChange = (event) => {
const {
target: { value }
} = event
setFrameworkFilter(
// On autofill we get a stringified value.
typeof value === 'string' ? value.split(',') : value
)
}
const handleViewChange = (event, nextView) => {
localStorage.setItem('mpDisplayStyle', nextView)
setView(nextView)
}
const onSearchChange = (event) => {
setSearch(event.target.value)
}
function filterFlows(data) {
return (
data.categories?.toLowerCase().indexOf(search.toLowerCase()) > -1 ||
data.templateName.toLowerCase().indexOf(search.toLowerCase()) > -1 ||
(data.description && data.description.toLowerCase().indexOf(search.toLowerCase()) > -1)
)
}
function filterByBadge(data) {
return badgeFilter.length > 0 ? badgeFilter.includes(data.badge) : true
}
function filterByType(data) {
return typeFilter.length > 0 ? typeFilter.includes(data.type) : true
}
function filterByFramework(data) {
return frameworkFilter.length > 0 ? frameworkFilter.includes(data.framework) : true
}
const onUseTemplate = (selectedTool) => {
const dialogProp = {
@@ -90,39 +185,33 @@ const Marketplace = () => {
navigate(`/marketplace/${selectedChatflow.id}`, { state: selectedChatflow })
}
const handleChange = (event, newValue) => {
setValue(newValue)
}
useEffect(() => {
getAllChatflowsMarketplacesApi.request()
getAllToolsMarketplacesApi.request()
getAllTemplatesMarketplacesApi.request()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
setChatflowsLoading(getAllChatflowsMarketplacesApi.loading)
}, [getAllChatflowsMarketplacesApi.loading])
setLoading(getAllTemplatesMarketplacesApi.loading)
}, [getAllTemplatesMarketplacesApi.loading])
useEffect(() => {
setToolsLoading(getAllToolsMarketplacesApi.loading)
}, [getAllToolsMarketplacesApi.loading])
useEffect(() => {
if (getAllChatflowsMarketplacesApi.data) {
if (getAllTemplatesMarketplacesApi.data) {
try {
const chatflows = getAllChatflowsMarketplacesApi.data
const flows = getAllTemplatesMarketplacesApi.data
const images = {}
for (let i = 0; i < chatflows.length; i += 1) {
const flowDataStr = chatflows[i].flowData
const flowData = JSON.parse(flowDataStr)
const nodes = flowData.nodes || []
images[chatflows[i].id] = []
for (let j = 0; j < nodes.length; j += 1) {
const imageSrc = `${baseURL}/api/v1/node-icon/${nodes[j].data.name}`
if (!images[chatflows[i].id].includes(imageSrc)) {
images[chatflows[i].id].push(imageSrc)
for (let i = 0; i < flows.length; i += 1) {
if (flows[i].flowData) {
const flowDataStr = flows[i].flowData
const flowData = JSON.parse(flowDataStr)
const nodes = flowData.nodes || []
images[flows[i].id] = []
for (let j = 0; j < nodes.length; j += 1) {
const imageSrc = `${baseURL}/api/v1/node-icon/${nodes[j].data.name}`
if (!images[flows[i].id].includes(imageSrc)) {
images[flows[i].id].push(imageSrc)
}
}
}
}
@@ -131,80 +220,215 @@ const Marketplace = () => {
console.error(e)
}
}
}, [getAllChatflowsMarketplacesApi.data])
}, [getAllTemplatesMarketplacesApi.data])
return (
<>
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
<Stack flexDirection='row'>
<h1>Marketplace</h1>
</Stack>
<Tabs sx={{ mb: 2 }} variant='fullWidth' value={value} onChange={handleChange} aria-label='tabs'>
{tabItems.map((item, index) => (
<Tab
key={index}
icon={index === 0 ? <IconHierarchy /> : <IconTool />}
iconPosition='start'
label={<span style={{ fontSize: '1.1rem' }}>{item}</span>}
<Box sx={{ flexGrow: 1 }}>
<Toolbar
disableGutters={true}
style={{
margin: 1,
padding: 1,
paddingBottom: 10,
display: 'flex',
justifyContent: 'space-between',
width: '100%'
}}
>
<h1>Marketplace</h1>
<TextField
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>
)
}}
/>
))}
</Tabs>
{tabItems.map((item, index) => (
<TabPanel key={index} value={value} index={index}>
{item === 'Chatflows' && (
<Grid container spacing={gridSpacing}>
{!isChatflowsLoading &&
getAllChatflowsMarketplacesApi.data &&
getAllChatflowsMarketplacesApi.data.map((data, index) => (
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
{data.badge && (
<Badge
sx={{
'& .MuiBadge-badge': {
right: 20
}
}}
badgeContent={data.badge}
color={data.badge === 'POPULAR' ? 'primary' : 'error'}
>
<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 />
</ToggleButton>
<ToggleButton
sx={{ color: theme?.customization?.isDarkMode ? 'white' : 'inherit' }}
variant='contained'
value='list'
title='List View'
>
<IconList />
</ToggleButton>
</ToggleButtonGroup>
</ButtonGroup>
</ButtonGroup>
</Toolbar>
</Box>
{open && (
<Box sx={{ flexGrow: 1, mb: 2 }}>
<Toolbar
disableGutters={true}
style={{
margin: 1,
padding: 1,
paddingBottom: 10,
display: 'flex',
justifyContent: 'flex-start',
width: '100%',
borderBottom: '1px solid'
}}
>
<FormControl sx={{ m: 1, width: 250 }}>
<InputLabel size='small' id='filter-badge-label'>
Tag
</InputLabel>
<Select
labelId='filter-badge-label'
id='filter-badge-checkbox'
size='small'
multiple
value={badgeFilter}
onChange={handleBadgeFilterChange}
input={<OutlinedInput label='Badge' />}
renderValue={(selected) => selected.join(', ')}
MenuProps={MenuProps}
>
{badges.map((name) => (
<MenuItem key={name} value={name}>
<Checkbox checked={badgeFilter.indexOf(name) > -1} />
<ListItemText primary={name} />
</MenuItem>
))}
</Select>
</FormControl>
<FormControl sx={{ m: 1, width: 250 }}>
<InputLabel size='small' id='type-badge-label'>
Type
</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) => (
<MenuItem key={name} value={name}>
<Checkbox checked={typeFilter.indexOf(name) > -1} />
<ListItemText primary={name} />
</MenuItem>
))}
</Select>
</FormControl>
<FormControl sx={{ m: 1, width: 250 }}>
<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}
>
{framework.map((name) => (
<MenuItem key={name} value={name}>
<Checkbox checked={frameworkFilter.indexOf(name) > -1} />
<ListItemText primary={name} />
</MenuItem>
))}
</Select>
</FormControl>
</Toolbar>
</Box>
)}
{!isLoading && (!view || view === 'card') && getAllTemplatesMarketplacesApi.data && (
<>
<Grid container spacing={gridSpacing}>
{getAllTemplatesMarketplacesApi.data
.filter(filterByBadge)
.filter(filterByType)
.filter(filterFlows)
.filter(filterByFramework)
.map((data, index) => (
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
{data.badge && (
<Badge
sx={{
'& .MuiBadge-badge': {
right: 20
}
}}
badgeContent={data.badge}
color={data.badge === 'POPULAR' ? 'primary' : 'error'}
>
{data.type === 'Chatflow' && (
<ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
</Badge>
)}
{!data.badge && (
<ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
)}
</Grid>
))}
</Grid>
)}
{item === 'Tools' && (
<Grid container spacing={gridSpacing}>
{!isToolsLoading &&
getAllToolsMarketplacesApi.data &&
getAllToolsMarketplacesApi.data.map((data, index) => (
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
{data.badge && (
<Badge
sx={{
'& .MuiBadge-badge': {
right: 20
}
}}
badgeContent={data.badge}
color={data.badge === 'POPULAR' ? 'primary' : 'error'}
>
<ItemCard data={data} onClick={() => goToTool(data)} />
</Badge>
)}
{!data.badge && <ItemCard data={data} onClick={() => goToTool(data)} />}
</Grid>
))}
</Grid>
)}
</TabPanel>
))}
{((!isChatflowsLoading && (!getAllChatflowsMarketplacesApi.data || getAllChatflowsMarketplacesApi.data.length === 0)) ||
(!isToolsLoading && (!getAllToolsMarketplacesApi.data || getAllToolsMarketplacesApi.data.length === 0))) && (
)}
{data.type === 'Tool' && <ItemCard data={data} onClick={() => goToTool(data)} />}
</Badge>
)}
{!data.badge && data.type === 'Chatflow' && (
<ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
)}
{!data.badge && data.type === 'Tool' && <ItemCard data={data} onClick={() => goToTool(data)} />}
</Grid>
))}
</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