({
+ 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
+ }
+}))
+
+export const ToolsTable = ({ data, isLoading, onSelect }) => {
+ const theme = useTheme()
+ const customization = useSelector((state) => state.customization)
+
+ return (
+ <>
+
+
+
+
+
+ Name
+
+ Description
+
+
+
+
+
+
+ {isLoading ? (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ ) : (
+ <>
+ {data?.map((row, index) => (
+
+
+
+
+
+
+
+
+
+ {row.description || ''}
+
+
+
+
+ ))}
+ >
+ )}
+
+
+
+ >
+ )
+}
+
+ToolsTable.propTypes = {
+ data: PropTypes.array,
+ isLoading: PropTypes.bool,
+ onSelect: PropTypes.func
+}
diff --git a/packages/ui/src/views/apikey/UploadJSONFileDialog.jsx b/packages/ui/src/views/apikey/UploadJSONFileDialog.jsx
new file mode 100644
index 00000000000..4d39d895a43
--- /dev/null
+++ b/packages/ui/src/views/apikey/UploadJSONFileDialog.jsx
@@ -0,0 +1,186 @@
+import { createPortal } from 'react-dom'
+import PropTypes from 'prop-types'
+import { useState, useEffect } from 'react'
+import { useDispatch } from 'react-redux'
+import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions'
+
+// Material
+import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Box, Typography, Stack } from '@mui/material'
+
+// Project imports
+import { StyledButton } from '@/ui-component/button/StyledButton'
+import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog'
+import { File } from '@/ui-component/file/File'
+
+// Icons
+import { IconFileUpload, IconX } from '@tabler/icons-react'
+
+// API
+import apikeyAPI from '@/api/apikey'
+
+// utils
+import useNotifier from '@/utils/useNotifier'
+
+// const
+import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
+import { Dropdown } from '@/ui-component/dropdown/Dropdown'
+
+const importModes = [
+ {
+ label: 'Add & Overwrite',
+ name: 'overwriteIfExist',
+ description: 'Add keys and overwrite existing keys with the same name'
+ },
+ {
+ label: 'Add & Ignore',
+ name: 'ignoreIfExist',
+ description: 'Add keys and ignore existing keys with the same name'
+ },
+ {
+ label: 'Add & Verify',
+ name: 'errorIfExist',
+ description: 'Add Keys and throw error if key with same name exists'
+ },
+ {
+ label: 'Replace All',
+ name: 'replaceAll',
+ description: 'Replace all keys with the imported keys'
+ }
+]
+
+const UploadJSONFileDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
+ const portalElement = document.getElementById('portal')
+
+ const dispatch = useDispatch()
+
+ // ==============================|| Snackbar ||============================== //
+
+ useNotifier()
+
+ const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
+ const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
+
+ const [selectedFile, setSelectedFile] = useState()
+ const [importMode, setImportMode] = useState('overwrite')
+
+ useEffect(() => {
+ return () => {
+ setSelectedFile()
+ }
+ }, [dialogProps])
+
+ useEffect(() => {
+ if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
+ else dispatch({ type: HIDE_CANVAS_DIALOG })
+ return () => dispatch({ type: HIDE_CANVAS_DIALOG })
+ }, [show, dispatch])
+
+ const importKeys = async () => {
+ try {
+ const obj = {
+ importMode: importMode,
+ jsonFile: selectedFile
+ }
+ const createResp = await apikeyAPI.importAPI(obj)
+ if (createResp.data) {
+ enqueueSnackbar({
+ message: 'Imported keys successfully!',
+ options: {
+ key: new Date().getTime() + Math.random(),
+ variant: 'success',
+ action: (key) => (
+
+ )
+ }
+ })
+ onConfirm(createResp.data.id)
+ }
+ } catch (error) {
+ enqueueSnackbar({
+ message: `Failed to import keys: ${
+ typeof error.response.data === 'object' ? error.response.data.message : error.response.data
+ }`,
+ options: {
+ key: new Date().getTime() + Math.random(),
+ variant: 'error',
+ persist: true,
+ action: (key) => (
+
+ )
+ }
+ })
+ onCancel()
+ }
+ }
+
+ const component = show ? (
+
+ ) : null
+
+ return createPortal(component, portalElement)
+}
+
+UploadJSONFileDialog.propTypes = {
+ show: PropTypes.bool,
+ dialogProps: PropTypes.object,
+ onCancel: PropTypes.func,
+ onConfirm: PropTypes.func
+}
+
+export default UploadJSONFileDialog
diff --git a/packages/ui/src/views/apikey/index.jsx b/packages/ui/src/views/apikey/index.jsx
index d0c3bd5665e..56bc3ecfedd 100644
--- a/packages/ui/src/views/apikey/index.jsx
+++ b/packages/ui/src/views/apikey/index.jsx
@@ -44,8 +44,20 @@ import useConfirm from '@/hooks/useConfirm'
import useNotifier from '@/utils/useNotifier'
// Icons
-import { IconTrash, IconEdit, IconCopy, IconChevronsUp, IconChevronsDown, IconX, IconPlus, IconEye, IconEyeOff } from '@tabler/icons-react'
+import {
+ IconTrash,
+ IconEdit,
+ IconCopy,
+ IconChevronsUp,
+ IconChevronsDown,
+ IconX,
+ IconPlus,
+ IconEye,
+ IconEyeOff,
+ IconFileUpload
+} from '@tabler/icons-react'
import APIEmptySVG from '@/assets/images/api_empty.svg'
+import UploadJSONFileDialog from '@/views/apikey/UploadJSONFileDialog'
// ==============================|| APIKey ||============================== //
@@ -200,6 +212,9 @@ const APIKey = () => {
const [showApiKeys, setShowApiKeys] = useState([])
const openPopOver = Boolean(anchorEl)
+ const [showUploadDialog, setShowUploadDialog] = useState(false)
+ const [uploadDialogProps, setUploadDialogProps] = useState({})
+
const [search, setSearch] = useState('')
const onSearchChange = (event) => {
setSearch(event.target.value)
@@ -254,6 +269,17 @@ const APIKey = () => {
setShowDialog(true)
}
+ const uploadDialog = () => {
+ const dialogProp = {
+ type: 'ADD',
+ cancelButtonName: 'Cancel',
+ confirmButtonName: 'Upload',
+ data: {}
+ }
+ setUploadDialogProps(dialogProp)
+ setShowUploadDialog(true)
+ }
+
const deleteKey = async (key) => {
const confirmPayload = {
title: `Delete`,
@@ -308,6 +334,7 @@ const APIKey = () => {
const onConfirm = () => {
setShowDialog(false)
+ setShowUploadDialog(false)
getAllAPIKeysApi.request()
}
@@ -341,6 +368,15 @@ const APIKey = () => {
) : (
+ }
+ id='btn_importApiKeys'
+ >
+ Import
+
{
onConfirm={onConfirm}
setError={setError}
>
+ {showUploadDialog && (
+ setShowUploadDialog(false)}
+ onConfirm={onConfirm}
+ >
+ )}
>
)
diff --git a/packages/ui/src/views/canvas/AddNodes.jsx b/packages/ui/src/views/canvas/AddNodes.jsx
index 5d15d98de20..08ba0cdd59d 100644
--- a/packages/ui/src/views/canvas/AddNodes.jsx
+++ b/packages/ui/src/views/canvas/AddNodes.jsx
@@ -296,6 +296,8 @@ const AddNodes = ({ nodesData, node, isAgentCanvas }) => {
Add Nodes
{
- {node.label}
-
- {node.badge && (
-
+
+ {node.label}
+
+ {node.badge && (
+
+ )}
+
+ {node.author && (
+
+ >
+ By {node.author}
+
)}
-
+ >
}
secondary={node.description}
/>
diff --git a/packages/ui/src/views/tools/index.jsx b/packages/ui/src/views/tools/index.jsx
index 358f1e487f1..5840935a55c 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, Button, ButtonGroup, Skeleton } from '@mui/material'
+import { Box, Stack, Button, ButtonGroup, Skeleton, ToggleButtonGroup, ToggleButton } from '@mui/material'
// project imports
import MainCard from '@/ui-component/cards/MainCard'
@@ -10,6 +10,7 @@ import { gridSpacing } from '@/store/constant'
import ToolEmptySVG from '@/assets/images/tools_empty.svg'
import { StyledButton } from '@/ui-component/button/StyledButton'
import ToolDialog from './ToolDialog'
+import { ToolsTable } from '@/ui-component/table/ToolsListTable'
// API
import toolsApi from '@/api/tools'
@@ -18,22 +19,31 @@ import toolsApi from '@/api/tools'
import useApi from '@/hooks/useApi'
// icons
-import { IconPlus, IconFileUpload } from '@tabler/icons-react'
+import { IconPlus, IconFileUpload, IconLayoutGrid, IconList } from '@tabler/icons-react'
import ViewHeader from '@/layout/MainLayout/ViewHeader'
import ErrorBoundary from '@/ErrorBoundary'
+import { useTheme } from '@mui/material/styles'
// ==============================|| CHATFLOWS ||============================== //
const Tools = () => {
+ const theme = useTheme()
const getAllToolsApi = useApi(toolsApi.getAllTools)
const [isLoading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [showDialog, setShowDialog] = useState(false)
const [dialogProps, setDialogProps] = useState({})
+ const [view, setView] = useState(localStorage.getItem('toolsDisplayStyle') || 'card')
const inputRef = useRef(null)
+ const handleChange = (event, nextView) => {
+ if (nextView === null) return
+ localStorage.setItem('toolsDisplayStyle', nextView)
+ setView(nextView)
+ }
+
const onUploadFile = (file) => {
try {
const dialogProp = {
@@ -118,6 +128,38 @@ const Tools = () => {
) : (