Feature: add tooltip display on icon hover in chatflows and marketplace page (#4428)

* feat: add tooltip display on icon hover in chatflows and marketplace page

* update list view, remove sticky note images

---------

Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
Anoop P
2025-05-28 02:04:10 +05:30
committed by GitHub
parent 82d60c7d15
commit 7ef0e99eb2
6 changed files with 182 additions and 96 deletions
+57 -41
View File
@@ -3,10 +3,11 @@ 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, useTheme } from '@mui/material' import { Box, Grid, Tooltip, Typography, useTheme } from '@mui/material'
// project imports // project imports
import MainCard from '@/ui-component/cards/MainCard' import MainCard from '@/ui-component/cards/MainCard'
import MoreItemsTooltip from '../tooltip/MoreItemsTooltip'
const CardWrapper = styled(MainCard)(({ theme }) => ({ const CardWrapper = styled(MainCard)(({ theme }) => ({
background: theme.palette.card.main, background: theme.palette.card.main,
@@ -116,48 +117,63 @@ const ItemCard = ({ data, images, icons, onClick }) => {
}} }}
> >
{[ {[
...(images || []).map((img) => ({ type: 'image', src: img })), ...(images || []).map((img) => ({ type: 'image', src: img.imageSrc, label: img.label })),
...(icons || []).map((ic) => ({ type: 'icon', icon: ic.icon, color: ic.color })) ...(icons || []).map((ic) => ({ type: 'icon', icon: ic.icon, color: ic.color, label: ic.name }))
] ]
.slice(0, 3) .slice(0, 3)
.map((item, index) => .map((item, index) => (
item.type === 'image' ? ( <Tooltip key={item.src || index} title={item.label} placement='top'>
<Box {item.type === 'image' ? (
key={item.src} <Box
sx={{ sx={{
width: 30, width: 30,
height: 30, height: 30,
borderRadius: '50%', borderRadius: '50%',
backgroundColor: customization.isDarkMode backgroundColor: customization.isDarkMode
? theme.palette.common.white ? theme.palette.common.white
: theme.palette.grey[300] + 75 : theme.palette.grey[300] + 75
}} }}
> >
<img <img
style={{ width: '100%', height: '100%', padding: 5, objectFit: 'contain' }} style={{ width: '100%', height: '100%', padding: 5, objectFit: 'contain' }}
alt='' alt=''
src={item.src} src={item.src}
/> />
</Box> </Box>
) : ( ) : (
<div <div
key={index} style={{
style={{ width: 30,
width: 30, height: 30,
height: 30, display: 'flex',
display: 'flex', alignItems: 'center',
alignItems: 'center', justifyContent: 'center'
justifyContent: 'center' }}
}} >
> <item.icon size={25} color={item.color} />
<item.icon size={25} color={item.color} /> </div>
</div> )}
) </Tooltip>
)} ))}
{images?.length + (icons?.length || 0) > 3 && (
<Typography sx={{ alignItems: 'center', display: 'flex', fontSize: '.9rem', fontWeight: 200 }}> {(images?.length || 0) + (icons?.length || 0) > 3 && (
+ {images?.length + (icons?.length || 0) - 3} More <MoreItemsTooltip
</Typography> images={[
...(images?.slice(3) || []),
...(icons?.slice(Math.max(0, 3 - (images?.length || 0))) || []).map((ic) => ({ label: ic.name }))
]}
>
<Typography
sx={{
alignItems: 'center',
display: 'flex',
fontSize: '.9rem',
fontWeight: 200
}}
>
+ {(images?.length || 0) + (icons?.length || 0) - 3} More
</Typography>
</MoreItemsTooltip>
)} )}
</Box> </Box>
)} )}
@@ -25,6 +25,8 @@ import FlowListMenu from '../button/FlowListMenu'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { useAuth } from '@/hooks/useAuth' import { useAuth } from '@/hooks/useAuth'
import MoreItemsTooltip from '../tooltip/MoreItemsTooltip'
const StyledTableCell = styled(TableCell)(({ theme }) => ({ const StyledTableCell = styled(TableCell)(({ theme }) => ({
borderColor: theme.palette.grey[900] + 25, borderColor: theme.palette.grey[900] + 25,
@@ -234,64 +236,80 @@ export const FlowListTable = ({
}} }}
> >
{[ {[
...(images[row.id] || []).map((img) => ({ type: 'image', src: img })), ...(images[row.id] || []).map((img) => ({
type: 'image',
src: img.imageSrc,
label: img.label
})),
...(icons[row.id] || []).map((ic) => ({ ...(icons[row.id] || []).map((ic) => ({
type: 'icon', type: 'icon',
icon: ic.icon, icon: ic.icon,
color: ic.color color: ic.color,
title: ic.name
})) }))
] ]
.slice(0, 5) .slice(0, 5)
.map((item, index) => .map((item, index) => (
item.type === 'image' ? ( <Tooltip key={item.imageSrc || index} title={item.label} placement='top'>
<Box {item.type === 'image' ? (
key={item.src} <Box
sx={{ sx={{
width: 30, width: 30,
height: 30, height: 30,
borderRadius: '50%', borderRadius: '50%',
backgroundColor: customization.isDarkMode backgroundColor: customization.isDarkMode
? theme.palette.common.white ? theme.palette.common.white
: theme.palette.grey[300] + 75 : theme.palette.grey[300] + 75
}}
>
<img
style={{
width: '100%',
height: '100%',
padding: 5,
objectFit: 'contain'
}} }}
alt='' >
src={item.src} <img
/> style={{
</Box> width: '100%',
) : ( height: '100%',
<div padding: 5,
key={index} objectFit: 'contain'
style={{ }}
width: 30, alt=''
height: 30, src={item.src}
display: 'flex', />
alignItems: 'center', </Box>
justifyContent: 'center' ) : (
}} <div
> style={{
<item.icon size={25} color={item.color} /> width: 30,
</div> height: 30,
) display: 'flex',
)} alignItems: 'center',
justifyContent: 'center'
}}
>
<item.icon size={25} color={item.color} />
</div>
)}
</Tooltip>
))}
{(images[row.id]?.length || 0) + (icons[row.id]?.length || 0) > 5 && ( {(images[row.id]?.length || 0) + (icons[row.id]?.length || 0) > 5 && (
<Typography <MoreItemsTooltip
sx={{ images={[
alignItems: 'center', ...(images[row.id]?.slice(5) || []),
display: 'flex', ...(
fontSize: '.9rem', icons[row.id]?.slice(Math.max(0, 5 - (images[row.id]?.length || 0))) ||
fontWeight: 200 []
}} ).map((ic) => ({ label: ic.name }))
]}
> >
+ {(images[row.id]?.length || 0) + (icons[row.id]?.length || 0) - 5} More <Typography
</Typography> sx={{
alignItems: 'center',
display: 'flex',
fontSize: '.9rem',
fontWeight: 200
}}
>
+ {(images[row.id]?.length || 0) + (icons[row.id]?.length || 0) - 5} More
</Typography>
</MoreItemsTooltip>
)} )}
</Box> </Box>
)} )}
@@ -0,0 +1,40 @@
import { Tooltip, Typography } from '@mui/material'
import { styled } from '@mui/material/styles'
import PropTypes from 'prop-types'
const StyledOl = styled('ol')(() => ({
paddingLeft: 20,
margin: 0
}))
const StyledLi = styled('li')(() => ({
paddingBottom: 4
}))
const MoreItemsTooltip = ({ images, children }) => {
if (!images || images.length === 0) return children
return (
<Tooltip
title={
<StyledOl>
{images.map((img) => (
<StyledLi key={img.imageSrc || img.label}>
<Typography>{img.label}</Typography>
</StyledLi>
))}
</StyledOl>
}
placement='top'
>
{children}
</Tooltip>
)
}
export default MoreItemsTooltip
MoreItemsTooltip.propTypes = {
images: PropTypes.array,
children: PropTypes.node
}
+6 -2
View File
@@ -117,13 +117,17 @@ const Agentflows = () => {
images[agentflows[i].id] = [] images[agentflows[i].id] = []
icons[agentflows[i].id] = [] icons[agentflows[i].id] = []
for (let j = 0; j < nodes.length; j += 1) { for (let j = 0; j < nodes.length; j += 1) {
if (nodes[j].data.name === 'stickyNote' || nodes[j].data.name === 'stickyNoteAgentflow') continue
const foundIcon = AGENTFLOW_ICONS.find((icon) => icon.name === nodes[j].data.name) const foundIcon = AGENTFLOW_ICONS.find((icon) => icon.name === nodes[j].data.name)
if (foundIcon) { if (foundIcon) {
icons[agentflows[i].id].push(foundIcon) icons[agentflows[i].id].push(foundIcon)
} else { } else {
const imageSrc = `${baseURL}/api/v1/node-icon/${nodes[j].data.name}` const imageSrc = `${baseURL}/api/v1/node-icon/${nodes[j].data.name}`
if (!images[agentflows[i].id].includes(imageSrc)) { if (!images[agentflows[i].id].some((img) => img.imageSrc === imageSrc)) {
images[agentflows[i].id].push(imageSrc) images[agentflows[i].id].push({
imageSrc,
label: nodes[j].data.label
})
} }
} }
} }
+6 -2
View File
@@ -89,9 +89,13 @@ const Chatflows = () => {
const nodes = flowData.nodes || [] const nodes = flowData.nodes || []
images[chatflows[i].id] = [] images[chatflows[i].id] = []
for (let j = 0; j < nodes.length; j += 1) { for (let j = 0; j < nodes.length; j += 1) {
if (nodes[j].data.name === 'stickyNote' || nodes[j].data.name === 'stickyNoteAgentflow') continue
const imageSrc = `${baseURL}/api/v1/node-icon/${nodes[j].data.name}` const imageSrc = `${baseURL}/api/v1/node-icon/${nodes[j].data.name}`
if (!images[chatflows[i].id].includes(imageSrc)) { if (!images[chatflows[i].id].some((img) => img.imageSrc === imageSrc)) {
images[chatflows[i].id].push(imageSrc) images[chatflows[i].id].push({
imageSrc,
label: nodes[j].data.label
})
} }
} }
} }
+6 -2
View File
@@ -375,13 +375,17 @@ const Marketplace = () => {
images[flows[i].id] = [] images[flows[i].id] = []
icons[flows[i].id] = [] icons[flows[i].id] = []
for (let j = 0; j < nodes.length; j += 1) { for (let j = 0; j < nodes.length; j += 1) {
if (nodes[j].data.name === 'stickyNote' || nodes[j].data.name === 'stickyNoteAgentflow') continue
const foundIcon = AGENTFLOW_ICONS.find((icon) => icon.name === nodes[j].data.name) const foundIcon = AGENTFLOW_ICONS.find((icon) => icon.name === nodes[j].data.name)
if (foundIcon) { if (foundIcon) {
icons[flows[i].id].push(foundIcon) icons[flows[i].id].push(foundIcon)
} else { } else {
const imageSrc = `${baseURL}/api/v1/node-icon/${nodes[j].data.name}` const imageSrc = `${baseURL}/api/v1/node-icon/${nodes[j].data.name}`
if (!images[flows[i].id].includes(imageSrc)) { if (!images[flows[i].id].some((img) => img.imageSrc === imageSrc)) {
images[flows[i].id].push(imageSrc) images[flows[i].id].push({
imageSrc,
label: nodes[j].data.name
})
} }
} }
} }