add tools marketplace
This commit is contained in:
parent
4cadd7aa37
commit
c8ba8e2aee
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "add_contact_hubspot",
|
||||
"description": "Add new contact to Hubspot",
|
||||
"color": "linear-gradient(rgb(85,198,123), rgb(0,230,99))",
|
||||
"iconSrc": "https://cdn.worldvectorlogo.com/logos/hubspot-1.svg",
|
||||
"schema": "[{\"id\":1,\"property\":\"email\",\"description\":\"email address of contact\",\"type\":\"string\",\"required\":true},{\"id\":2,\"property\":\"firstname\",\"description\":\"first name of contact\",\"type\":\"string\",\"required\":false},{\"id\":3,\"property\":\"lastname\",\"description\":\"last name of contact\",\"type\":\"string\",\"required\":false}]",
|
||||
"func": "const fetch = require('node-fetch');\nconst url = 'https://api.hubapi.com/crm/v3/objects/contacts'\nconst token = 'YOUR-TOKEN';\n\nconst body = {\n\t\"properties\": {\n\t \"email\": $email\n\t}\n};\n\nif ($firstname) body.properties.firstname = $firstname;\nif ($lastname) body.properties.lastname = $lastname;\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t 'Authorization': `Bearer ${token}`,\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}"
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "add_airtable",
|
||||
"description": "Add column1, column2 to Airtable",
|
||||
"color": "linear-gradient(rgb(125,71,222), rgb(128,102,23))",
|
||||
"iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/airtable.svg",
|
||||
"schema": "[{\"id\":0,\"property\":\"column1\",\"description\":\"this is column1\",\"type\":\"string\",\"required\":true},{\"id\":1,\"property\":\"column2\",\"description\":\"this is column2\",\"type\":\"string\",\"required\":true}]",
|
||||
"func": "const fetch = require('node-fetch');\nconst baseId = 'YOUR-BASE-ID';\nconst tableId = 'YOUR-TABLE-ID';\nconst token = 'YOUR-TOKEN';\n\nconst body = {\n\t\"records\": [\n\t\t{\n\t\t\t\"fields\": {\n\t\t\t\t\"column1\": $column1,\n\t\t\t\t\"column2\": $column2,\n\t\t\t}\n\t\t}\n\t]\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t\t'Authorization': `Bearer ${token}`,\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\nconst url = `https://api.airtable.com/v0/${baseId}/${tableId}`\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}"
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "get_stock_movers",
|
||||
"description": "Get the stocks that has biggest price/volume moves, e.g. actives, gainers, losers, etc.",
|
||||
"iconSrc": "https://rapidapi.com/cdn/images?url=https://rapidapi-prod-apis.s3.amazonaws.com/9c/e743343bdd41edad39a3fdffd5b974/016c33699f51603ae6fe4420c439124b.png",
|
||||
"color": "linear-gradient(rgb(191,202,167), rgb(143,202,246))",
|
||||
"schema": "[]",
|
||||
"func": "const fetch = require('node-fetch');\nconst url = 'https://morning-star.p.rapidapi.com/market/v2/get-movers';\nconst options = {\n\tmethod: 'GET',\n\theaders: {\n\t\t'X-RapidAPI-Key': 'YOUR-API-KEY',\n\t\t'X-RapidAPI-Host': 'morning-star.p.rapidapi.com'\n\t}\n};\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst result = await response.text();\n\tconsole.log(result);\n\treturn result;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}"
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "send_message_to_discord_channel",
|
||||
"description": "Send message to Discord channel",
|
||||
"color": "linear-gradient(rgb(155,190,84), rgb(176,69,245))",
|
||||
"iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/discord-icon.svg",
|
||||
"schema": "[{\"id\":1,\"property\":\"content\",\"description\":\"message to send\",\"type\":\"string\",\"required\":true}]",
|
||||
"func": "const fetch = require('node-fetch');\nconst webhookUrl = 'YOUR-WEBHOOK-URL'\n\nconst body = {\n\t\"content\": $content\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\nconst url = `${webhookUrl}?wait=true`\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}"
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "send_message_to_slack_channel",
|
||||
"description": "Send message to Slack channel",
|
||||
"color": "linear-gradient(rgb(155,190,84), rgb(176,69,245))",
|
||||
"iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/slack-icon.svg",
|
||||
"schema": "[{\"id\":1,\"property\":\"text\",\"description\":\"message to send\",\"type\":\"string\",\"required\":true}]",
|
||||
"func": "const fetch = require('node-fetch');\nconst webhookUrl = 'YOUR-WEBHOOK-URL'\n\nconst body = {\n\t\"text\": $text\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\nconst url = `${webhookUrl}`\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}"
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "send_message_to_teams_channel",
|
||||
"description": "Send message to Teams channel",
|
||||
"color": "linear-gradient(rgb(155,190,84), rgb(176,69,245))",
|
||||
"iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/microsoft-teams.svg",
|
||||
"schema": "[{\"id\":1,\"property\":\"content\",\"description\":\"message to send\",\"type\":\"string\",\"required\":true}]",
|
||||
"func": "const fetch = require('node-fetch');\nconst webhookUrl = 'YOUR-WEBHOOK-URL'\n\nconst body = {\n\t\"content\": $content\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\nconst url = `${webhookUrl}?wait=true`\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}"
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "sendgrid_email",
|
||||
"description": "Send email using SendGrid",
|
||||
"color": "linear-gradient(rgb(230,108,70), rgb(222,4,98))",
|
||||
"iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/sendgrid-icon.svg",
|
||||
"schema": "[{\"id\":0,\"property\":\"fromEmail\",\"description\":\"Email address used to send the message\",\"type\":\"string\",\"required\":true},{\"id\":1,\"property\":\"toEmail \",\"description\":\"The intended recipient's email address\",\"type\":\"string\",\"required\":true},{\"id\":2,\"property\":\"subject\",\"description\":\"The subject of email\",\"type\":\"string\",\"required\":true},{\"id\":3,\"property\":\"content\",\"description\":\"Content of email\",\"type\":\"string\",\"required\":true}]",
|
||||
"func": "const fetch = require('node-fetch');\nconst url = 'https://api.sendgrid.com/v3/mail/send';\nconst api_key = 'YOUR-API-KEY';\n\nconst body = {\n \"personalizations\": [\n {\n \"to\": [{ \"email\": $toEmail }]\n }\n ],\n\t\"from\": {\n\t \"email\": $fromEmail\n\t},\n\t\"subject\": $subject,\n\t\"content\": [\n\t {\n\t \"type\": 'text/plain',\n\t \"value\": $content\n\t }\n\t]\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t 'Authorization': `Bearer ${api_key}`,\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}"
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ export interface ITool {
|
|||
name: string
|
||||
description: string
|
||||
color: string
|
||||
iconSrc?: string
|
||||
schema?: string
|
||||
func?: string
|
||||
updatedDate: Date
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ export class Tool implements ITool {
|
|||
@Column()
|
||||
color: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
iconSrc?: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
schema?: string
|
||||
|
||||
|
|
|
|||
|
|
@ -462,12 +462,12 @@ export class App {
|
|||
// ----------------------------------------
|
||||
|
||||
// Get all chatflows for marketplaces
|
||||
this.app.get('/api/v1/marketplaces', async (req: Request, res: Response) => {
|
||||
const marketplaceDir = path.join(__dirname, '..', 'marketplaces')
|
||||
this.app.get('/api/v1/marketplaces/chatflows', async (req: Request, res: Response) => {
|
||||
const marketplaceDir = path.join(__dirname, '..', 'marketplaces', 'chatflows')
|
||||
const jsonsInDir = fs.readdirSync(marketplaceDir).filter((file) => path.extname(file) === '.json')
|
||||
const templates: any[] = []
|
||||
jsonsInDir.forEach((file, index) => {
|
||||
const filePath = path.join(__dirname, '..', 'marketplaces', file)
|
||||
const filePath = path.join(__dirname, '..', 'marketplaces', 'chatflows', file)
|
||||
const fileData = fs.readFileSync(filePath)
|
||||
const fileDataObj = JSON.parse(fileData.toString())
|
||||
const template = {
|
||||
|
|
@ -481,6 +481,25 @@ export class App {
|
|||
return res.json(templates)
|
||||
})
|
||||
|
||||
// Get all tools for marketplaces
|
||||
this.app.get('/api/v1/marketplaces/tools', async (req: Request, res: Response) => {
|
||||
const marketplaceDir = path.join(__dirname, '..', 'marketplaces', 'tools')
|
||||
const jsonsInDir = fs.readdirSync(marketplaceDir).filter((file) => path.extname(file) === '.json')
|
||||
const templates: any[] = []
|
||||
jsonsInDir.forEach((file, index) => {
|
||||
const filePath = path.join(__dirname, '..', 'marketplaces', 'tools', file)
|
||||
const fileData = fs.readFileSync(filePath)
|
||||
const fileDataObj = JSON.parse(fileData.toString())
|
||||
const template = {
|
||||
...fileDataObj,
|
||||
id: index,
|
||||
templateName: file.split('.json')[0]
|
||||
}
|
||||
templates.push(template)
|
||||
})
|
||||
return res.json(templates)
|
||||
})
|
||||
|
||||
// ----------------------------------------
|
||||
// API Keys
|
||||
// ----------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Flowise - LangchainJS UI</title>
|
||||
<title>Flowise - Low-code LLM apps builder</title>
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<!-- Meta Tags-->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#2296f3" />
|
||||
<meta name="title" content="Flowise - LangchainJS UI" />
|
||||
<meta name="title" content="Flowise - Low-code LLM apps builder" />
|
||||
<meta name="description" content="Flowise helps you to better integrate Web3 with existing Web2 applications" />
|
||||
<meta name="keywords" content="react, material-ui, reactjs, reactjs, workflow automation, web3, web2, blockchain" />
|
||||
<meta name="author" content="CodedThemes" />
|
||||
|
|
@ -17,13 +17,13 @@
|
|||
<meta property="og:url" content="https://flowiseai.com/" />
|
||||
<meta property="og:site_name" content="flowiseai.com" />
|
||||
<meta property="article:publisher" content="https://www.facebook.com/codedthemes" />
|
||||
<meta property="og:title" content="Flowise - LangchainJS UI" />
|
||||
<meta property="og:title" content="Flowise - Low-code LLM apps builder" />
|
||||
<meta property="og:description" content="Flowise helps you to better build LLM flows using Langchain in simple GUI" />
|
||||
<meta property="og:image" content="https://flowiseai.com/og-image/og-facebook.png" />
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://flowiseai.com" />
|
||||
<meta property="twitter:title" content="Flowise - LangchainJS UI" />
|
||||
<meta property="twitter:title" content="Flowise - Low-code LLM apps builder" />
|
||||
<meta property="twitter:description" content="Flowise helps you to better build LLM flows using Langchain in simple GUI" />
|
||||
<meta property="twitter:image" content="https://flowiseai.com/og-image/og-twitter.png" />
|
||||
<meta name="twitter:creator" content="@codedthemes" />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import client from './client'
|
||||
|
||||
const getAllMarketplaces = () => client.get('/marketplaces')
|
||||
const getAllChatflowsMarketplaces = () => client.get('/marketplaces/chatflows')
|
||||
const getAllToolsMarketplaces = () => client.get('/marketplaces/tools')
|
||||
|
||||
export default {
|
||||
getAllMarketplaces
|
||||
getAllChatflowsMarketplaces,
|
||||
getAllToolsMarketplaces
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,6 +136,9 @@ export default function componentStyleOverrides(theme) {
|
|||
'&::placeholder': {
|
||||
color: theme.darkTextSecondary,
|
||||
fontSize: '0.875rem'
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
WebkitTextFillColor: theme?.customization?.isDarkMode ? theme.colors?.grey500 : theme.darkTextSecondary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({
|
|||
|
||||
// ===========================|| CONTRACT CARD ||=========================== //
|
||||
|
||||
const ItemCard = ({ isLoading, data, images, color, onClick }) => {
|
||||
const ItemCard = ({ isLoading, data, images, onClick }) => {
|
||||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
|
|
@ -43,21 +43,35 @@ const ItemCard = ({ isLoading, data, images, color, onClick }) => {
|
|||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
{color && (
|
||||
{data.iconSrc && (
|
||||
<div
|
||||
style={{
|
||||
width: 35,
|
||||
height: 35,
|
||||
marginRight: 10,
|
||||
borderRadius: '50%',
|
||||
background: color
|
||||
background: `url(${data.iconSrc})`,
|
||||
backgroundSize: 'contain',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center center'
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
{!data.iconSrc && data.color && (
|
||||
<div
|
||||
style={{
|
||||
width: 35,
|
||||
height: 35,
|
||||
marginRight: 10,
|
||||
borderRadius: '50%',
|
||||
background: data.color
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
<Typography
|
||||
sx={{ fontSize: '1.5rem', fontWeight: 500, overflowWrap: 'break-word', whiteSpace: 'pre-line' }}
|
||||
>
|
||||
{data.name}
|
||||
{data.templateName || data.name}
|
||||
</Typography>
|
||||
</div>
|
||||
{data.description && (
|
||||
|
|
@ -107,7 +121,6 @@ ItemCard.propTypes = {
|
|||
isLoading: PropTypes.bool,
|
||||
data: PropTypes.object,
|
||||
images: PropTypes.array,
|
||||
color: PropTypes.string,
|
||||
onClick: PropTypes.func
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { DataGrid } from '@mui/x-data-grid'
|
|||
import { IconPlus } from '@tabler/icons'
|
||||
import { Button } from '@mui/material'
|
||||
|
||||
export const Grid = ({ columns, rows, style, onRowUpdate, addNewRow }) => {
|
||||
export const Grid = ({ columns, rows, style, disabled = false, onRowUpdate, addNewRow }) => {
|
||||
const handleProcessRowUpdate = (newRow) => {
|
||||
onRowUpdate(newRow)
|
||||
return newRow
|
||||
|
|
@ -11,13 +11,18 @@ export const Grid = ({ columns, rows, style, onRowUpdate, addNewRow }) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Button variant='outlined' onClick={addNewRow} startIcon={<IconPlus />}>
|
||||
Add Item
|
||||
</Button>
|
||||
{!disabled && (
|
||||
<Button variant='outlined' onClick={addNewRow} startIcon={<IconPlus />}>
|
||||
Add Item
|
||||
</Button>
|
||||
)}
|
||||
{rows && columns && (
|
||||
<div style={{ marginTop: 10, height: 300, width: '100%', ...style }}>
|
||||
<DataGrid
|
||||
processRowUpdate={handleProcessRowUpdate}
|
||||
isCellEditable={() => {
|
||||
return !disabled
|
||||
}}
|
||||
onProcessRowUpdateError={(error) => console.error(error)}
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
|
|
@ -32,6 +37,7 @@ Grid.propTypes = {
|
|||
rows: PropTypes.array,
|
||||
columns: PropTypes.array,
|
||||
style: PropTypes.any,
|
||||
disabled: PropTypes.bool,
|
||||
addNewRow: PropTypes.func,
|
||||
onRowUpdate: PropTypes.func
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
// material-ui
|
||||
import { Grid, Box, Stack } from '@mui/material'
|
||||
import { Grid, Box, Stack, Tabs, Tab } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import { IconHierarchy, IconTool } from '@tabler/icons'
|
||||
|
||||
// project imports
|
||||
import MainCard from 'ui-component/cards/MainCard'
|
||||
import ItemCard from 'ui-component/cards/ItemCard'
|
||||
import { gridSpacing } from 'store/constant'
|
||||
import WorkflowEmptySVG from 'assets/images/workflow_empty.svg'
|
||||
import ToolDialog from 'views/tools/ToolDialog'
|
||||
|
||||
// API
|
||||
import marketplacesApi from 'api/marketplaces'
|
||||
|
|
@ -21,6 +24,27 @@ import useApi from 'hooks/useApi'
|
|||
// const
|
||||
import { baseURL } from 'store/constant'
|
||||
|
||||
function TabPanel(props) {
|
||||
const { children, value, index, ...other } = props
|
||||
return (
|
||||
<div
|
||||
role='tabpanel'
|
||||
hidden={value !== index}
|
||||
id={`attachment-tabpanel-${index}`}
|
||||
aria-labelledby={`attachment-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && <Box sx={{ p: 1 }}>{children}</Box>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
TabPanel.propTypes = {
|
||||
children: PropTypes.node,
|
||||
index: PropTypes.number.isRequired,
|
||||
value: PropTypes.number.isRequired
|
||||
}
|
||||
|
||||
// ==============================|| Marketplace ||============================== //
|
||||
|
||||
const Marketplace = () => {
|
||||
|
|
@ -29,29 +53,66 @@ const Marketplace = () => {
|
|||
const theme = useTheme()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
|
||||
const [isLoading, setLoading] = useState(true)
|
||||
const [isChatflowsLoading, setChatflowsLoading] = useState(true)
|
||||
const [isToolsLoading, setToolsLoading] = 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 getAllMarketplacesApi = useApi(marketplacesApi.getAllMarketplaces)
|
||||
const getAllChatflowsMarketplacesApi = useApi(marketplacesApi.getAllChatflowsMarketplaces)
|
||||
const getAllToolsMarketplacesApi = useApi(marketplacesApi.getAllToolsMarketplaces)
|
||||
|
||||
const onUseTemplate = (selectedTool) => {
|
||||
const dialogProp = {
|
||||
title: 'Add New Tool',
|
||||
type: 'IMPORT',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Add',
|
||||
data: selectedTool
|
||||
}
|
||||
setToolDialogProps(dialogProp)
|
||||
setShowToolDialog(true)
|
||||
}
|
||||
|
||||
const goToTool = (selectedTool) => {
|
||||
const dialogProp = {
|
||||
title: selectedTool.templateName,
|
||||
type: 'TEMPLATE',
|
||||
data: selectedTool
|
||||
}
|
||||
setToolDialogProps(dialogProp)
|
||||
setShowToolDialog(true)
|
||||
}
|
||||
|
||||
const goToCanvas = (selectedChatflow) => {
|
||||
navigate(`/marketplace/${selectedChatflow.id}`, { state: selectedChatflow })
|
||||
}
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
setValue(newValue)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllMarketplacesApi.request()
|
||||
getAllChatflowsMarketplacesApi.request()
|
||||
getAllToolsMarketplacesApi.request()
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(getAllMarketplacesApi.loading)
|
||||
}, [getAllMarketplacesApi.loading])
|
||||
setChatflowsLoading(getAllChatflowsMarketplacesApi.loading)
|
||||
}, [getAllChatflowsMarketplacesApi.loading])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllMarketplacesApi.data) {
|
||||
setToolsLoading(getAllToolsMarketplacesApi.loading)
|
||||
}, [getAllToolsMarketplacesApi.loading])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllChatflowsMarketplacesApi.data) {
|
||||
try {
|
||||
const chatflows = getAllMarketplacesApi.data
|
||||
const chatflows = getAllChatflowsMarketplacesApi.data
|
||||
const images = {}
|
||||
for (let i = 0; i < chatflows.length; i += 1) {
|
||||
const flowDataStr = chatflows[i].flowData
|
||||
|
|
@ -70,31 +131,83 @@ const Marketplace = () => {
|
|||
console.error(e)
|
||||
}
|
||||
}
|
||||
}, [getAllMarketplacesApi.data])
|
||||
}, [getAllChatflowsMarketplacesApi.data])
|
||||
|
||||
return (
|
||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
||||
<Stack flexDirection='row'>
|
||||
<h1>Marketplace</h1>
|
||||
</Stack>
|
||||
<Grid container spacing={gridSpacing}>
|
||||
{!isLoading &&
|
||||
getAllMarketplacesApi.data &&
|
||||
getAllMarketplacesApi.data.map((data, index) => (
|
||||
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
|
||||
<ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
{!isLoading && (!getAllMarketplacesApi.data || getAllMarketplacesApi.data.length === 0) && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img style={{ objectFit: 'cover', height: '30vh', width: 'auto' }} src={WorkflowEmptySVG} alt='WorkflowEmptySVG' />
|
||||
</Box>
|
||||
<div>No Marketplace Yet</div>
|
||||
<>
|
||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
||||
<Stack flexDirection='row'>
|
||||
<h1>Marketplace</h1>
|
||||
</Stack>
|
||||
)}
|
||||
</MainCard>
|
||||
<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>}
|
||||
/>
|
||||
))}
|
||||
</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}>
|
||||
<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}>
|
||||
<ItemCard data={data} onClick={() => goToTool(data)} />
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</TabPanel>
|
||||
))}
|
||||
{!isChatflowsLoading && (!getAllChatflowsMarketplacesApi.data || getAllChatflowsMarketplacesApi.data.length === 0) && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '30vh', width: 'auto' }}
|
||||
src={WorkflowEmptySVG}
|
||||
alt='WorkflowEmptySVG'
|
||||
/>
|
||||
</Box>
|
||||
<div>No Marketplace Yet</div>
|
||||
</Stack>
|
||||
)}
|
||||
{!isToolsLoading && (!getAllToolsMarketplacesApi.data || getAllToolsMarketplacesApi.data.length === 0) && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '30vh', width: 'auto' }}
|
||||
src={WorkflowEmptySVG}
|
||||
alt='WorkflowEmptySVG'
|
||||
/>
|
||||
</Box>
|
||||
<div>No Marketplace Yet</div>
|
||||
</Stack>
|
||||
)}
|
||||
</MainCard>
|
||||
<ToolDialog
|
||||
show={showToolDialog}
|
||||
dialogProps={toolDialogProps}
|
||||
onCancel={() => setShowToolDialog(false)}
|
||||
onConfirm={() => setShowToolDialog(false)}
|
||||
onUseTemplate={(tool) => onUseTemplate(tool)}
|
||||
></ToolDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor'
|
|||
import { useTheme } from '@mui/material/styles'
|
||||
|
||||
// Icons
|
||||
import { IconX } from '@tabler/icons'
|
||||
import { IconX, IconFileExport } from '@tabler/icons'
|
||||
|
||||
// API
|
||||
import toolsApi from 'api/tools'
|
||||
|
|
@ -53,7 +53,7 @@ try {
|
|||
return '';
|
||||
}`
|
||||
|
||||
const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const theme = useTheme()
|
||||
|
||||
|
|
@ -73,6 +73,7 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
const [toolId, setToolId] = useState('')
|
||||
const [toolName, setToolName] = useState('')
|
||||
const [toolDesc, setToolDesc] = useState('')
|
||||
const [toolIcon, setToolIcon] = useState('')
|
||||
const [toolSchema, setToolSchema] = useState([])
|
||||
const [toolFunc, setToolFunc] = useState('')
|
||||
|
||||
|
|
@ -167,18 +168,39 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
|
||||
useEffect(() => {
|
||||
if (dialogProps.type === 'EDIT' && dialogProps.data) {
|
||||
// When tool dialog is opened from Tools dashboard
|
||||
setToolId(dialogProps.data.id)
|
||||
setToolName(dialogProps.data.name)
|
||||
setToolDesc(dialogProps.data.description)
|
||||
setToolIcon(dialogProps.data.iconSrc)
|
||||
setToolSchema(formatSchema(dialogProps.data.schema))
|
||||
if (dialogProps.data.func) setToolFunc(dialogProps.data.func)
|
||||
else setToolFunc('')
|
||||
} else if (dialogProps.type === 'EDIT' && dialogProps.toolId) {
|
||||
// When tool dialog is opened from CustomTool node in canvas
|
||||
getSpecificToolApi.request(dialogProps.toolId)
|
||||
} else if (dialogProps.type === 'IMPORT' && dialogProps.data) {
|
||||
// When tool dialog is to import existing tool
|
||||
setToolName(dialogProps.data.name)
|
||||
setToolDesc(dialogProps.data.description)
|
||||
setToolIcon(dialogProps.data.iconSrc)
|
||||
setToolSchema(formatSchema(dialogProps.data.schema))
|
||||
if (dialogProps.data.func) setToolFunc(dialogProps.data.func)
|
||||
else setToolFunc('')
|
||||
} else if (dialogProps.type === 'TEMPLATE' && dialogProps.data) {
|
||||
// When tool dialog is a template
|
||||
setToolName(dialogProps.data.name)
|
||||
setToolDesc(dialogProps.data.description)
|
||||
setToolIcon(dialogProps.data.iconSrc)
|
||||
setToolSchema(formatSchema(dialogProps.data.schema))
|
||||
if (dialogProps.data.func) setToolFunc(dialogProps.data.func)
|
||||
else setToolFunc('')
|
||||
} else if (dialogProps.type === 'ADD') {
|
||||
// When tool dialog is to add a new tool
|
||||
setToolId('')
|
||||
setToolName('')
|
||||
setToolDesc('')
|
||||
setToolIcon('')
|
||||
setToolSchema([])
|
||||
setToolFunc('')
|
||||
}
|
||||
|
|
@ -186,6 +208,47 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dialogProps])
|
||||
|
||||
const useToolTemplate = () => {
|
||||
onUseTemplate(dialogProps.data)
|
||||
}
|
||||
|
||||
const exportTool = async () => {
|
||||
try {
|
||||
const toolResp = await toolsApi.getSpecificTool(toolId)
|
||||
if (toolResp.data) {
|
||||
const toolData = toolResp.data
|
||||
delete toolData.id
|
||||
delete toolData.createdDate
|
||||
delete toolData.updatedDate
|
||||
let dataStr = JSON.stringify(toolData)
|
||||
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
|
||||
let exportFileDefaultName = `${toolName}-CustomTool.json`
|
||||
|
||||
let linkElement = document.createElement('a')
|
||||
linkElement.setAttribute('href', dataUri)
|
||||
linkElement.setAttribute('download', exportFileDefaultName)
|
||||
linkElement.click()
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to export Tool: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
const addNewTool = async () => {
|
||||
try {
|
||||
const obj = {
|
||||
|
|
@ -193,7 +256,8 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
description: toolDesc,
|
||||
color: generateRandomGradient(),
|
||||
schema: JSON.stringify(toolSchema),
|
||||
func: toolFunc
|
||||
func: toolFunc,
|
||||
iconSrc: toolIcon
|
||||
}
|
||||
const createResp = await toolsApi.createNewTool(obj)
|
||||
if (createResp.data) {
|
||||
|
|
@ -236,7 +300,8 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
name: toolName,
|
||||
description: toolDesc,
|
||||
schema: JSON.stringify(toolSchema),
|
||||
func: toolFunc
|
||||
func: toolFunc,
|
||||
iconSrc: toolIcon
|
||||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
|
|
@ -330,7 +395,15 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
{dialogProps.title}
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
{dialogProps.title}
|
||||
<div style={{ flex: 1 }} />
|
||||
{dialogProps.type === 'EDIT' && (
|
||||
<Button variant='outlined' onClick={() => exportTool()} startIcon={<IconFileExport />}>
|
||||
Export
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ p: 2 }}>
|
||||
|
|
@ -338,12 +411,17 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
<Typography variant='overline'>
|
||||
Tool Name
|
||||
<span style={{ color: 'red' }}> *</span>
|
||||
<TooltipWithParser
|
||||
style={{ marginLeft: 10 }}
|
||||
title={'Tool name must be small capital letter with underscore. Ex: my_tool'}
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='toolName'
|
||||
type='string'
|
||||
fullWidth
|
||||
disabled={dialogProps.type === 'TEMPLATE'}
|
||||
placeholder='My New Tool'
|
||||
value={toolName}
|
||||
name='toolName'
|
||||
|
|
@ -355,12 +433,17 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
<Typography variant='overline'>
|
||||
Tool description
|
||||
<span style={{ color: 'red' }}> *</span>
|
||||
<TooltipWithParser
|
||||
style={{ marginLeft: 10 }}
|
||||
title={'Description of what the tool does. This is for ChatGPT to determine when to use this tool.'}
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='toolDesc'
|
||||
type='string'
|
||||
fullWidth
|
||||
disabled={dialogProps.type === 'TEMPLATE'}
|
||||
placeholder='Description of what the tool does. This is for ChatGPT to determine when to use this tool.'
|
||||
multiline={true}
|
||||
rows={3}
|
||||
|
|
@ -369,6 +452,21 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
onChange={(e) => setToolDesc(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>Tool Icon Src</Typography>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='toolIcon'
|
||||
type='string'
|
||||
fullWidth
|
||||
disabled={dialogProps.type === 'TEMPLATE'}
|
||||
placeholder='https://raw.githubusercontent.com/gilbarbara/logos/main/logos/airtable.svg'
|
||||
value={toolIcon}
|
||||
name='toolIcon'
|
||||
onChange={(e) => setToolIcon(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
|
|
@ -376,7 +474,13 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
<TooltipWithParser style={{ marginLeft: 10 }} title={'What should be the output response in JSON format?'} />
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Grid columns={columns} rows={toolSchema} addNewRow={addNewRow} onRowUpdate={onRowUpdate} />
|
||||
<Grid
|
||||
columns={columns}
|
||||
rows={toolSchema}
|
||||
disabled={dialogProps.type === 'TEMPLATE'}
|
||||
addNewRow={addNewRow}
|
||||
onRowUpdate={onRowUpdate}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
|
|
@ -388,12 +492,15 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Button style={{ marginBottom: 10 }} variant='outlined' onClick={() => setToolFunc(exampleAPIFunc)}>
|
||||
See Example
|
||||
</Button>
|
||||
{dialogProps.type !== 'TEMPLATE' && (
|
||||
<Button style={{ marginBottom: 10 }} variant='outlined' onClick={() => setToolFunc(exampleAPIFunc)}>
|
||||
See Example
|
||||
</Button>
|
||||
)}
|
||||
{customization.isDarkMode ? (
|
||||
<DarkCodeEditor
|
||||
value={toolFunc}
|
||||
disabled={dialogProps.type === 'TEMPLATE'}
|
||||
onValueChange={(code) => setToolFunc(code)}
|
||||
style={{
|
||||
fontSize: '0.875rem',
|
||||
|
|
@ -405,6 +512,7 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
) : (
|
||||
<LightCodeEditor
|
||||
value={toolFunc}
|
||||
disabled={dialogProps.type === 'TEMPLATE'}
|
||||
onValueChange={(code) => setToolFunc(code)}
|
||||
style={{
|
||||
fontSize: '0.875rem',
|
||||
|
|
@ -423,13 +531,20 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
Delete
|
||||
</StyledButton>
|
||||
)}
|
||||
<StyledButton
|
||||
disabled={!(toolName && toolDesc)}
|
||||
variant='contained'
|
||||
onClick={() => (dialogProps.type === 'ADD' ? addNewTool() : saveTool())}
|
||||
>
|
||||
{dialogProps.confirmButtonName}
|
||||
</StyledButton>
|
||||
{dialogProps.type === 'TEMPLATE' && (
|
||||
<StyledButton color='secondary' variant='contained' onClick={useToolTemplate}>
|
||||
Use Template
|
||||
</StyledButton>
|
||||
)}
|
||||
{dialogProps.type !== 'TEMPLATE' && (
|
||||
<StyledButton
|
||||
disabled={!(toolName && toolDesc)}
|
||||
variant='contained'
|
||||
onClick={() => (dialogProps.type === 'ADD' || dialogProps.type === 'IMPORT' ? addNewTool() : saveTool())}
|
||||
>
|
||||
{dialogProps.confirmButtonName}
|
||||
</StyledButton>
|
||||
)}
|
||||
</DialogActions>
|
||||
<ConfirmDialog />
|
||||
</Dialog>
|
||||
|
|
@ -441,6 +556,7 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
|||
ToolDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onUseTemplate: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
onConfirm: PropTypes.func
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useState, useRef } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
// material-ui
|
||||
import { Grid, Box, Stack } from '@mui/material'
|
||||
import { Grid, Box, Stack, Button } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
|
||||
// project imports
|
||||
|
|
@ -20,7 +20,7 @@ import toolsApi from 'api/tools'
|
|||
import useApi from 'hooks/useApi'
|
||||
|
||||
// icons
|
||||
import { IconPlus } from '@tabler/icons'
|
||||
import { IconPlus, IconFileImport } from '@tabler/icons'
|
||||
|
||||
// ==============================|| CHATFLOWS ||============================== //
|
||||
|
||||
|
|
@ -33,6 +33,40 @@ const Tools = () => {
|
|||
const [showDialog, setShowDialog] = useState(false)
|
||||
const [dialogProps, setDialogProps] = useState({})
|
||||
|
||||
const inputRef = useRef(null)
|
||||
|
||||
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',
|
||||
|
|
@ -75,8 +109,17 @@ const Tools = () => {
|
|||
<Grid sx={{ mb: 1.25 }} container direction='row'>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Grid item>
|
||||
<Button
|
||||
variant='outlined'
|
||||
sx={{ mr: 2 }}
|
||||
onClick={() => inputRef.current.click()}
|
||||
startIcon={<IconFileImport />}
|
||||
>
|
||||
Load
|
||||
</Button>
|
||||
<input ref={inputRef} type='file' hidden accept='.json' onChange={(e) => handleFileUpload(e)} />
|
||||
<StyledButton variant='contained' sx={{ color: 'white' }} onClick={addNew} startIcon={<IconPlus />}>
|
||||
Create New
|
||||
Create
|
||||
</StyledButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
|
@ -86,7 +129,7 @@ const Tools = () => {
|
|||
getAllToolsApi.data &&
|
||||
getAllToolsApi.data.map((data, index) => (
|
||||
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
|
||||
<ItemCard data={data} color={data.color} onClick={() => edit(data)} />
|
||||
<ItemCard data={data} onClick={() => edit(data)} />
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
|
|
|
|||
Loading…
Reference in New Issue