diff --git a/packages/ui/src/ui-component/cards/ItemCard.jsx b/packages/ui/src/ui-component/cards/ItemCard.jsx
index fa113ee22a1..c40746e1e70 100644
--- a/packages/ui/src/ui-component/cards/ItemCard.jsx
+++ b/packages/ui/src/ui-component/cards/ItemCard.jsx
@@ -1,3 +1,4 @@
+import { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { useSelector } from 'react-redux'
@@ -29,9 +30,36 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({
whiteSpace: 'pre-line'
}))
+const IconAvatar = ({ iconSrc, fallbackIconSrc }) => {
+ const [imgSrc, setImgSrc] = useState(iconSrc || fallbackIconSrc)
+
+ useEffect(() => {
+ setImgSrc(iconSrc || fallbackIconSrc)
+ }, [iconSrc, fallbackIconSrc])
+
+ if (!imgSrc) return null
+
+ return (
+
setImgSrc(fallbackIconSrc || '')}
+ style={{
+ width: 35,
+ height: 35,
+ display: 'flex',
+ flexShrink: 0,
+ marginRight: 10,
+ borderRadius: '50%',
+ objectFit: 'contain'
+ }}
+ />
+ )
+}
+
// ===========================|| CONTRACT CARD ||=========================== //
-const ItemCard = ({ data, images, icons, scheduleStatus, onClick }) => {
+const ItemCard = ({ data, images, icons, onClick, fallbackIconSrc }) => {
const theme = useTheme()
const customization = useSelector((state) => state.customization)
@@ -49,23 +77,27 @@ const ItemCard = ({ data, images, icons, scheduleStatus, onClick }) => {
overflow: 'hidden'
}}
>
- {data.iconSrc && (
-
+ {fallbackIconSrc ? (
+
+ ) : (
+ data.iconSrc && (
+
+ )
)}
- {!data.iconSrc && data.color && (
+ {!fallbackIconSrc && !data.iconSrc && data.color && (
({
borderColor: theme.palette.grey[900] + 25,
@@ -35,6 +37,31 @@ const StyledTableRow = styled(TableRow)(() => ({
}
}))
+const ToolIconAvatar = ({ iconSrc }) => {
+ const [imgSrc, setImgSrc] = useState(iconSrc || toolSVG)
+
+ useEffect(() => {
+ setImgSrc(iconSrc || toolSVG)
+ }, [iconSrc])
+
+ return (
+

setImgSrc(toolSVG)}
+ style={{
+ width: 35,
+ height: 35,
+ display: 'flex',
+ flexShrink: 0,
+ marginRight: 10,
+ borderRadius: '50%',
+ objectFit: 'contain'
+ }}
+ />
+ )
+}
+
export const ToolsTable = ({ data, isLoading, onSelect }) => {
const theme = useTheme()
const customization = useSelector((state) => state.customization)
@@ -90,20 +117,7 @@ export const ToolsTable = ({ data, isLoading, onSelect }) => {
{data?.map((row, index) => (
-
+
{
+ const trimmedIconSource = iconSource?.trim()
+ if (!trimmedIconSource) return ''
+
+ try {
+ const parsedUrl = new URL(trimmedIconSource)
+ if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
+ return 'Tool Icon Source must be a valid HTTP/HTTPS URL'
+ }
+ return ''
+ } catch {
+ return 'Tool Icon Source must be a valid HTTP/HTTPS URL'
+ }
+ }
+
+ const validateIconAndNotify = () => {
+ const iconError = validateToolIconUrl(toolIcon)
+ setToolIconError(iconError)
+
+ if (iconError) {
+ enqueueSnackbar({
+ message: iconError,
+ options: {
+ key: new Date().getTime() + Math.random(),
+ variant: 'error',
+ action: (key) => (
+
+ )
+ }
+ })
+ return false
+ }
+
+ return true
+ }
+
const deleteItem = useCallback(
(id) => () => {
setTimeout(() => {
@@ -177,6 +216,8 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
setToolId(getSpecificToolApi.data.id)
setToolName(getSpecificToolApi.data.name)
setToolDesc(getSpecificToolApi.data.description)
+ setToolIcon(getSpecificToolApi.data.iconSrc || '')
+ setToolIconError('')
setToolSchema(formatDataGridRows(getSpecificToolApi.data.schema))
if (getSpecificToolApi.data.func) setToolFunc(getSpecificToolApi.data.func)
else setToolFunc('')
@@ -196,7 +237,8 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
setToolId(dialogProps.data.id)
setToolName(dialogProps.data.name)
setToolDesc(dialogProps.data.description)
- setToolIcon(dialogProps.data.iconSrc)
+ setToolIcon(dialogProps.data.iconSrc || '')
+ setToolIconError('')
setToolSchema(formatDataGridRows(dialogProps.data.schema))
if (dialogProps.data.func) setToolFunc(dialogProps.data.func)
else setToolFunc('')
@@ -207,7 +249,8 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
// When tool dialog is to import existing tool
setToolName(dialogProps.data.name)
setToolDesc(dialogProps.data.description)
- setToolIcon(dialogProps.data.iconSrc)
+ setToolIcon(dialogProps.data.iconSrc || '')
+ setToolIconError('')
setToolSchema(formatDataGridRows(dialogProps.data.schema))
if (dialogProps.data.func) setToolFunc(dialogProps.data.func)
else setToolFunc('')
@@ -215,7 +258,8 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
// When tool dialog is a template
setToolName(dialogProps.data.name)
setToolDesc(dialogProps.data.description)
- setToolIcon(dialogProps.data.iconSrc)
+ setToolIcon(dialogProps.data.iconSrc || '')
+ setToolIconError('')
setToolSchema(formatDataGridRows(dialogProps.data.schema))
if (dialogProps.data.func) setToolFunc(dialogProps.data.func)
else setToolFunc('')
@@ -225,6 +269,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
setToolName('')
setToolDesc('')
setToolIcon('')
+ setToolIconError('')
setToolSchema([])
setToolFunc('')
}
@@ -277,6 +322,8 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
}
const addNewTool = async () => {
+ if (!validateIconAndNotify()) return
+
try {
const obj = {
name: toolName,
@@ -323,6 +370,8 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
}
const saveTool = async () => {
+ if (!validateIconAndNotify()) return
+
try {
const saveResp = await toolsApi.updateTool(toolId, {
name: toolName,
@@ -513,8 +562,23 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
placeholder='https://raw.githubusercontent.com/gilbarbara/logos/main/logos/airtable.svg'
value={toolIcon}
name='toolIcon'
- onChange={(e) => setToolIcon(e.target.value)}
+ error={!!toolIconError}
+ onBlur={() => setToolIconError(validateToolIconUrl(toolIcon))}
+ onChange={(e) => {
+ const iconSource = e.target.value
+ setToolIcon(iconSource)
+ setToolIconError(validateToolIconUrl(iconSource))
+ }}
/>
+ {toolIconError ? (
+
+ {toolIconError}
+
+ ) : (
+
+ Optional. Leave empty to use the default tool icon.
+
+ )}
@@ -583,7 +647,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
{dialogProps.type !== 'TEMPLATE' && (
(dialogProps.type === 'ADD' || dialogProps.type === 'IMPORT' ? addNewTool() : saveTool())}
>
diff --git a/packages/ui/src/views/tools/index.jsx b/packages/ui/src/views/tools/index.jsx
index 0c15ee2b474..bc8427fcf9c 100644
--- a/packages/ui/src/views/tools/index.jsx
+++ b/packages/ui/src/views/tools/index.jsx
@@ -1,7 +1,7 @@
import { useEffect, useState, useRef } from 'react'
// material-ui
-import { Box, Stack, ButtonGroup, Skeleton, ToggleButtonGroup, ToggleButton, Tabs, Tab } from '@mui/material'
+import { Box, Stack, ButtonGroup, Skeleton, ToggleButtonGroup, ToggleButton } from '@mui/material'
import { useTheme } from '@mui/material/styles'
// project imports
@@ -29,6 +29,7 @@ import { gridSpacing } from '@/store/constant'
// icons
import { IconPlus, IconFileUpload, IconLayoutGrid, IconList } from '@tabler/icons-react'
import ToolEmptySVG from '@/assets/images/tools_empty.svg'
+import toolSVG from '@/assets/images/tool.svg'
// ==============================|| TOOLS ||============================== //
@@ -413,14 +414,108 @@ const Tools = () => {
borderColor: 'divider'
}}
>
- setTabValue(newValue)} aria-label='tools tabs'>
-
-
-
- {tabValue === 0 ? renderCustomToolsToolbar() : renderMcpServersToolbar()}
+
+
+
+
+
+
+
+
+
+ inputRef.current.click()}
+ startIcon={}
+ sx={{ borderRadius: 2, height: 40 }}
+ >
+ Load
+
+ handleFileUpload(e)}
+ />
+
+
+ }
+ sx={{ borderRadius: 2, height: 40 }}
+ >
+ Create
+
+
- {tabValue === 0 && renderCustomToolsTab()}
- {tabValue === 1 && renderMcpServersTab()}
+ {isLoading && (
+
+
+
+
+
+ )}
+ {!isLoading && total > 0 && (
+ <>
+ {!view || view === 'card' ? (
+
+ {getAllToolsApi.data?.data?.filter(filterTools).map((data, index) => (
+ edit(data)} fallbackIconSrc={toolSVG} />
+ ))}
+
+ ) : (
+
+ )}
+ {/* Pagination and Page Size Controls */}
+
+ >
+ )}
+ {!isLoading && total === 0 && (
+
+
+
+
+ No Tools Created Yet
+
+ )}
)}