Flowise/packages/ui/src/views/tools/index.jsx

285 lines
11 KiB
JavaScript

import { useEffect, useState, useRef } from 'react'
// material-ui
import { Box, Stack, ButtonGroup, Skeleton, ToggleButtonGroup, ToggleButton } from '@mui/material'
import { useTheme } from '@mui/material/styles'
// project imports
import MainCard from '@/ui-component/cards/MainCard'
import ItemCard from '@/ui-component/cards/ItemCard'
import ToolDialog from './ToolDialog'
import ViewHeader from '@/layout/MainLayout/ViewHeader'
import ErrorBoundary from '@/ErrorBoundary'
import { ToolsTable } from '@/ui-component/table/ToolsListTable'
import { PermissionButton, StyledPermissionButton } from '@/ui-component/button/RBACButtons'
import TablePagination, { DEFAULT_ITEMS_PER_PAGE } from '@/ui-component/pagination/TablePagination'
// API
import toolsApi from '@/api/tools'
// Hooks
import useApi from '@/hooks/useApi'
import { useError } from '@/store/context/ErrorContext'
import { gridSpacing } from '@/store/constant'
// icons
import { IconPlus, IconFileUpload, IconLayoutGrid, IconList } from '@tabler/icons-react'
import ToolEmptySVG from '@/assets/images/tools_empty.svg'
// ==============================|| TOOLS ||============================== //
const Tools = () => {
const theme = useTheme()
const getAllToolsApi = useApi(toolsApi.getAllTools)
const { error, setError } = useError()
const [isLoading, setLoading] = useState(true)
const [showDialog, setShowDialog] = useState(false)
const [dialogProps, setDialogProps] = useState({})
const [view, setView] = useState(localStorage.getItem('toolsDisplayStyle') || 'card')
const inputRef = useRef(null)
/* Table Pagination */
const [currentPage, setCurrentPage] = useState(1)
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
const [total, setTotal] = useState(0)
const onChange = (page, pageLimit) => {
setCurrentPage(page)
setPageLimit(pageLimit)
refresh(page, pageLimit)
}
const refresh = (page, limit) => {
const params = {
page: page || currentPage,
limit: limit || pageLimit
}
getAllToolsApi.request(params)
}
const handleChange = (event, nextView) => {
if (nextView === null) return
localStorage.setItem('toolsDisplayStyle', nextView)
setView(nextView)
}
const onUploadFile = (file) => {
try {
const dialogProp = {
title: 'Add New Tool',
type: 'IMPORT',
cancelButtonName: 'Cancel',
confirmButtonName: 'Save',
data: JSON.parse(file)
}
setDialogProps(dialogProp)
setShowDialog(true)
} catch (e) {
console.error(e)
}
}
const handleFileUpload = (e) => {
if (!e.target.files) return
const file = e.target.files[0]
const reader = new FileReader()
reader.onload = (evt) => {
if (!evt?.target?.result) {
return
}
const { result } = evt.target
onUploadFile(result)
}
reader.readAsText(file)
}
const addNew = () => {
const dialogProp = {
title: 'Add New Tool',
type: 'ADD',
cancelButtonName: 'Cancel',
confirmButtonName: 'Add'
}
setDialogProps(dialogProp)
setShowDialog(true)
}
const edit = (selectedTool) => {
const dialogProp = {
title: 'Edit Tool',
type: 'EDIT',
cancelButtonName: 'Cancel',
confirmButtonName: 'Save',
data: selectedTool
}
setDialogProps(dialogProp)
setShowDialog(true)
}
const onConfirm = () => {
setShowDialog(false)
refresh(currentPage, pageLimit)
}
const [search, setSearch] = useState('')
const onSearchChange = (event) => {
setSearch(event.target.value)
}
function filterTools(data) {
return (
data.name.toLowerCase().indexOf(search.toLowerCase()) > -1 || data.description.toLowerCase().indexOf(search.toLowerCase()) > -1
)
}
useEffect(() => {
refresh(currentPage, pageLimit)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
setLoading(getAllToolsApi.loading)
}, [getAllToolsApi.loading])
useEffect(() => {
if (getAllToolsApi.data) {
setTotal(getAllToolsApi.data.total)
}
}, [getAllToolsApi.data])
return (
<>
<MainCard>
{error ? (
<ErrorBoundary error={error} />
) : (
<Stack flexDirection='column' sx={{ gap: 3 }}>
<ViewHeader
onSearchChange={onSearchChange}
search={true}
searchPlaceholder='Search Tools'
title='Tools'
description='External functions or APIs the agent can use to take action'
>
<ToggleButtonGroup
sx={{ borderRadius: 2, maxHeight: 40 }}
value={view}
color='primary'
disabled={total === 0}
exclusive
onChange={handleChange}
>
<ToggleButton
sx={{
borderColor: theme.palette.grey[900] + 25,
borderRadius: 2,
color: theme?.customization?.isDarkMode ? 'white' : 'inherit'
}}
variant='contained'
value='card'
title='Card View'
>
<IconLayoutGrid />
</ToggleButton>
<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>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<PermissionButton
permissionId={'tools:create'}
variant='outlined'
onClick={() => inputRef.current.click()}
startIcon={<IconFileUpload />}
sx={{ borderRadius: 2, height: 40 }}
>
Load
</PermissionButton>
<input
style={{ display: 'none' }}
ref={inputRef}
type='file'
hidden
accept='.json'
onChange={(e) => handleFileUpload(e)}
/>
</Box>
<ButtonGroup disableElevation aria-label='outlined primary button group'>
<StyledPermissionButton
permissionId={'tools:create'}
variant='contained'
onClick={addNew}
startIcon={<IconPlus />}
sx={{ borderRadius: 2, height: 40 }}
>
Create
</StyledPermissionButton>
</ButtonGroup>
</ViewHeader>
{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>
)}
{!isLoading && total > 0 && (
<>
{!view || view === 'card' ? (
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
{getAllToolsApi.data?.data?.filter(filterTools).map((data, index) => (
<ItemCard data={data} key={index} onClick={() => edit(data)} />
))}
</Box>
) : (
<ToolsTable
data={getAllToolsApi.data?.data?.filter(filterTools) || []}
isLoading={isLoading}
onSelect={edit}
/>
)}
{/* Pagination and Page Size Controls */}
<TablePagination currentPage={currentPage} limit={pageLimit} total={total} onChange={onChange} />
</>
)}
{!isLoading && total === 0 && (
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
<Box sx={{ p: 2, height: 'auto' }}>
<img
style={{ objectFit: 'cover', height: '20vh', width: 'auto' }}
src={ToolEmptySVG}
alt='ToolEmptySVG'
/>
</Box>
<div>No Tools Created Yet</div>
</Stack>
)}
</Stack>
)}
</MainCard>
<ToolDialog
show={showDialog}
dialogProps={dialogProps}
onCancel={() => setShowDialog(false)}
onConfirm={onConfirm}
setError={setError}
></ToolDialog>
</>
)
}
export default Tools