add tools marketplace

This commit is contained in:
Henry 2023-07-01 23:59:51 +01:00
parent 4cadd7aa37
commit c8ba8e2aee
42 changed files with 444 additions and 69 deletions

View File

@ -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}"
}

View File

@ -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}"
}

View File

@ -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}"
}

View File

@ -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}"
}

View File

@ -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}"
}

View File

@ -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}"
}

View File

@ -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}"
}

View File

@ -30,6 +30,7 @@ export interface ITool {
name: string name: string
description: string description: string
color: string color: string
iconSrc?: string
schema?: string schema?: string
func?: string func?: string
updatedDate: Date updatedDate: Date

View File

@ -16,6 +16,9 @@ export class Tool implements ITool {
@Column() @Column()
color: string color: string
@Column({ nullable: true })
iconSrc?: string
@Column({ nullable: true }) @Column({ nullable: true })
schema?: string schema?: string

View File

@ -462,12 +462,12 @@ export class App {
// ---------------------------------------- // ----------------------------------------
// Get all chatflows for marketplaces // Get all chatflows for marketplaces
this.app.get('/api/v1/marketplaces', async (req: Request, res: Response) => { this.app.get('/api/v1/marketplaces/chatflows', async (req: Request, res: Response) => {
const marketplaceDir = path.join(__dirname, '..', 'marketplaces') const marketplaceDir = path.join(__dirname, '..', 'marketplaces', 'chatflows')
const jsonsInDir = fs.readdirSync(marketplaceDir).filter((file) => path.extname(file) === '.json') const jsonsInDir = fs.readdirSync(marketplaceDir).filter((file) => path.extname(file) === '.json')
const templates: any[] = [] const templates: any[] = []
jsonsInDir.forEach((file, index) => { 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 fileData = fs.readFileSync(filePath)
const fileDataObj = JSON.parse(fileData.toString()) const fileDataObj = JSON.parse(fileData.toString())
const template = { const template = {
@ -481,6 +481,25 @@ export class App {
return res.json(templates) 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 // API Keys
// ---------------------------------------- // ----------------------------------------

View File

@ -1,13 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Flowise - LangchainJS UI</title> <title>Flowise - Low-code LLM apps builder</title>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<!-- Meta Tags--> <!-- Meta Tags-->
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#2296f3" /> <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="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="keywords" content="react, material-ui, reactjs, reactjs, workflow automation, web3, web2, blockchain" />
<meta name="author" content="CodedThemes" /> <meta name="author" content="CodedThemes" />
@ -17,13 +17,13 @@
<meta property="og:url" content="https://flowiseai.com/" /> <meta property="og:url" content="https://flowiseai.com/" />
<meta property="og:site_name" content="flowiseai.com" /> <meta property="og:site_name" content="flowiseai.com" />
<meta property="article:publisher" content="https://www.facebook.com/codedthemes" /> <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: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" /> <meta property="og:image" content="https://flowiseai.com/og-image/og-facebook.png" />
<!-- Twitter --> <!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" /> <meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://flowiseai.com" /> <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: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 property="twitter:image" content="https://flowiseai.com/og-image/og-twitter.png" />
<meta name="twitter:creator" content="@codedthemes" /> <meta name="twitter:creator" content="@codedthemes" />

View File

@ -1,7 +1,9 @@
import client from './client' import client from './client'
const getAllMarketplaces = () => client.get('/marketplaces') const getAllChatflowsMarketplaces = () => client.get('/marketplaces/chatflows')
const getAllToolsMarketplaces = () => client.get('/marketplaces/tools')
export default { export default {
getAllMarketplaces getAllChatflowsMarketplaces,
getAllToolsMarketplaces
} }

View File

@ -136,6 +136,9 @@ export default function componentStyleOverrides(theme) {
'&::placeholder': { '&::placeholder': {
color: theme.darkTextSecondary, color: theme.darkTextSecondary,
fontSize: '0.875rem' fontSize: '0.875rem'
},
'&.Mui-disabled': {
WebkitTextFillColor: theme?.customization?.isDarkMode ? theme.colors?.grey500 : theme.darkTextSecondary
} }
} }
} }

View File

@ -27,7 +27,7 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({
// ===========================|| CONTRACT CARD ||=========================== // // ===========================|| CONTRACT CARD ||=========================== //
const ItemCard = ({ isLoading, data, images, color, onClick }) => { const ItemCard = ({ isLoading, data, images, onClick }) => {
return ( return (
<> <>
{isLoading ? ( {isLoading ? (
@ -43,21 +43,35 @@ const ItemCard = ({ isLoading, data, images, color, onClick }) => {
alignItems: 'center' alignItems: 'center'
}} }}
> >
{color && ( {data.iconSrc && (
<div <div
style={{ style={{
width: 35, width: 35,
height: 35, height: 35,
marginRight: 10, marginRight: 10,
borderRadius: '50%', 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> ></div>
)} )}
<Typography <Typography
sx={{ fontSize: '1.5rem', fontWeight: 500, overflowWrap: 'break-word', whiteSpace: 'pre-line' }} sx={{ fontSize: '1.5rem', fontWeight: 500, overflowWrap: 'break-word', whiteSpace: 'pre-line' }}
> >
{data.name} {data.templateName || data.name}
</Typography> </Typography>
</div> </div>
{data.description && ( {data.description && (
@ -107,7 +121,6 @@ ItemCard.propTypes = {
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
data: PropTypes.object, data: PropTypes.object,
images: PropTypes.array, images: PropTypes.array,
color: PropTypes.string,
onClick: PropTypes.func onClick: PropTypes.func
} }

View File

@ -3,7 +3,7 @@ import { DataGrid } from '@mui/x-data-grid'
import { IconPlus } from '@tabler/icons' import { IconPlus } from '@tabler/icons'
import { Button } from '@mui/material' 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) => { const handleProcessRowUpdate = (newRow) => {
onRowUpdate(newRow) onRowUpdate(newRow)
return newRow return newRow
@ -11,13 +11,18 @@ export const Grid = ({ columns, rows, style, onRowUpdate, addNewRow }) => {
return ( return (
<> <>
{!disabled && (
<Button variant='outlined' onClick={addNewRow} startIcon={<IconPlus />}> <Button variant='outlined' onClick={addNewRow} startIcon={<IconPlus />}>
Add Item Add Item
</Button> </Button>
)}
{rows && columns && ( {rows && columns && (
<div style={{ marginTop: 10, height: 300, width: '100%', ...style }}> <div style={{ marginTop: 10, height: 300, width: '100%', ...style }}>
<DataGrid <DataGrid
processRowUpdate={handleProcessRowUpdate} processRowUpdate={handleProcessRowUpdate}
isCellEditable={() => {
return !disabled
}}
onProcessRowUpdateError={(error) => console.error(error)} onProcessRowUpdateError={(error) => console.error(error)}
rows={rows} rows={rows}
columns={columns} columns={columns}
@ -32,6 +37,7 @@ Grid.propTypes = {
rows: PropTypes.array, rows: PropTypes.array,
columns: PropTypes.array, columns: PropTypes.array,
style: PropTypes.any, style: PropTypes.any,
disabled: PropTypes.bool,
addNewRow: PropTypes.func, addNewRow: PropTypes.func,
onRowUpdate: PropTypes.func onRowUpdate: PropTypes.func
} }

View File

@ -1,16 +1,19 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import PropTypes from 'prop-types'
// material-ui // 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 { useTheme } from '@mui/material/styles'
import { IconHierarchy, IconTool } from '@tabler/icons'
// project imports // project imports
import MainCard from 'ui-component/cards/MainCard' import MainCard from 'ui-component/cards/MainCard'
import ItemCard from 'ui-component/cards/ItemCard' import ItemCard from 'ui-component/cards/ItemCard'
import { gridSpacing } from 'store/constant' import { gridSpacing } from 'store/constant'
import WorkflowEmptySVG from 'assets/images/workflow_empty.svg' import WorkflowEmptySVG from 'assets/images/workflow_empty.svg'
import ToolDialog from 'views/tools/ToolDialog'
// API // API
import marketplacesApi from 'api/marketplaces' import marketplacesApi from 'api/marketplaces'
@ -21,6 +24,27 @@ import useApi from 'hooks/useApi'
// const // const
import { baseURL } from 'store/constant' 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 ||============================== // // ==============================|| Marketplace ||============================== //
const Marketplace = () => { const Marketplace = () => {
@ -29,29 +53,66 @@ const Marketplace = () => {
const theme = useTheme() const theme = useTheme()
const customization = useSelector((state) => state.customization) 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 [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) => { const goToCanvas = (selectedChatflow) => {
navigate(`/marketplace/${selectedChatflow.id}`, { state: selectedChatflow }) navigate(`/marketplace/${selectedChatflow.id}`, { state: selectedChatflow })
} }
const handleChange = (event, newValue) => {
setValue(newValue)
}
useEffect(() => { useEffect(() => {
getAllMarketplacesApi.request() getAllChatflowsMarketplacesApi.request()
getAllToolsMarketplacesApi.request()
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
useEffect(() => { useEffect(() => {
setLoading(getAllMarketplacesApi.loading) setChatflowsLoading(getAllChatflowsMarketplacesApi.loading)
}, [getAllMarketplacesApi.loading]) }, [getAllChatflowsMarketplacesApi.loading])
useEffect(() => { useEffect(() => {
if (getAllMarketplacesApi.data) { setToolsLoading(getAllToolsMarketplacesApi.loading)
}, [getAllToolsMarketplacesApi.loading])
useEffect(() => {
if (getAllChatflowsMarketplacesApi.data) {
try { try {
const chatflows = getAllMarketplacesApi.data const chatflows = getAllChatflowsMarketplacesApi.data
const images = {} const images = {}
for (let i = 0; i < chatflows.length; i += 1) { for (let i = 0; i < chatflows.length; i += 1) {
const flowDataStr = chatflows[i].flowData const flowDataStr = chatflows[i].flowData
@ -70,31 +131,83 @@ const Marketplace = () => {
console.error(e) console.error(e)
} }
} }
}, [getAllMarketplacesApi.data]) }, [getAllChatflowsMarketplacesApi.data])
return ( return (
<>
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}> <MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
<Stack flexDirection='row'> <Stack flexDirection='row'>
<h1>Marketplace</h1> <h1>Marketplace</h1>
</Stack> </Stack>
<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}> <Grid container spacing={gridSpacing}>
{!isLoading && {!isChatflowsLoading &&
getAllMarketplacesApi.data && getAllChatflowsMarketplacesApi.data &&
getAllMarketplacesApi.data.map((data, index) => ( getAllChatflowsMarketplacesApi.data.map((data, index) => (
<Grid key={index} item lg={3} md={4} sm={6} xs={12}> <Grid key={index} item lg={3} md={4} sm={6} xs={12}>
<ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} /> <ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
</Grid> </Grid>
))} ))}
</Grid> </Grid>
{!isLoading && (!getAllMarketplacesApi.data || getAllMarketplacesApi.data.length === 0) && ( )}
{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'> <Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
<Box sx={{ p: 2, height: 'auto' }}> <Box sx={{ p: 2, height: 'auto' }}>
<img style={{ objectFit: 'cover', height: '30vh', width: 'auto' }} src={WorkflowEmptySVG} alt='WorkflowEmptySVG' /> <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> </Box>
<div>No Marketplace Yet</div> <div>No Marketplace Yet</div>
</Stack> </Stack>
)} )}
</MainCard> </MainCard>
<ToolDialog
show={showToolDialog}
dialogProps={toolDialogProps}
onCancel={() => setShowToolDialog(false)}
onConfirm={() => setShowToolDialog(false)}
onUseTemplate={(tool) => onUseTemplate(tool)}
></ToolDialog>
</>
) )
} }

View File

@ -17,7 +17,7 @@ import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor'
import { useTheme } from '@mui/material/styles' import { useTheme } from '@mui/material/styles'
// Icons // Icons
import { IconX } from '@tabler/icons' import { IconX, IconFileExport } from '@tabler/icons'
// API // API
import toolsApi from 'api/tools' import toolsApi from 'api/tools'
@ -53,7 +53,7 @@ try {
return ''; return '';
}` }`
const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) => {
const portalElement = document.getElementById('portal') const portalElement = document.getElementById('portal')
const theme = useTheme() const theme = useTheme()
@ -73,6 +73,7 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
const [toolId, setToolId] = useState('') const [toolId, setToolId] = useState('')
const [toolName, setToolName] = useState('') const [toolName, setToolName] = useState('')
const [toolDesc, setToolDesc] = useState('') const [toolDesc, setToolDesc] = useState('')
const [toolIcon, setToolIcon] = useState('')
const [toolSchema, setToolSchema] = useState([]) const [toolSchema, setToolSchema] = useState([])
const [toolFunc, setToolFunc] = useState('') const [toolFunc, setToolFunc] = useState('')
@ -167,18 +168,39 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
useEffect(() => { useEffect(() => {
if (dialogProps.type === 'EDIT' && dialogProps.data) { if (dialogProps.type === 'EDIT' && dialogProps.data) {
// When tool dialog is opened from Tools dashboard
setToolId(dialogProps.data.id) setToolId(dialogProps.data.id)
setToolName(dialogProps.data.name) setToolName(dialogProps.data.name)
setToolDesc(dialogProps.data.description) setToolDesc(dialogProps.data.description)
setToolIcon(dialogProps.data.iconSrc)
setToolSchema(formatSchema(dialogProps.data.schema)) setToolSchema(formatSchema(dialogProps.data.schema))
if (dialogProps.data.func) setToolFunc(dialogProps.data.func) if (dialogProps.data.func) setToolFunc(dialogProps.data.func)
else setToolFunc('') else setToolFunc('')
} else if (dialogProps.type === 'EDIT' && dialogProps.toolId) { } else if (dialogProps.type === 'EDIT' && dialogProps.toolId) {
// When tool dialog is opened from CustomTool node in canvas
getSpecificToolApi.request(dialogProps.toolId) 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') { } else if (dialogProps.type === 'ADD') {
// When tool dialog is to add a new tool
setToolId('') setToolId('')
setToolName('') setToolName('')
setToolDesc('') setToolDesc('')
setToolIcon('')
setToolSchema([]) setToolSchema([])
setToolFunc('') setToolFunc('')
} }
@ -186,6 +208,47 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [dialogProps]) }, [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 () => { const addNewTool = async () => {
try { try {
const obj = { const obj = {
@ -193,7 +256,8 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
description: toolDesc, description: toolDesc,
color: generateRandomGradient(), color: generateRandomGradient(),
schema: JSON.stringify(toolSchema), schema: JSON.stringify(toolSchema),
func: toolFunc func: toolFunc,
iconSrc: toolIcon
} }
const createResp = await toolsApi.createNewTool(obj) const createResp = await toolsApi.createNewTool(obj)
if (createResp.data) { if (createResp.data) {
@ -236,7 +300,8 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
name: toolName, name: toolName,
description: toolDesc, description: toolDesc,
schema: JSON.stringify(toolSchema), schema: JSON.stringify(toolSchema),
func: toolFunc func: toolFunc,
iconSrc: toolIcon
}) })
if (saveResp.data) { if (saveResp.data) {
enqueueSnackbar({ enqueueSnackbar({
@ -330,7 +395,15 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
aria-describedby='alert-dialog-description' aria-describedby='alert-dialog-description'
> >
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'> <DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
<div style={{ display: 'flex', flexDirection: 'row' }}>
{dialogProps.title} {dialogProps.title}
<div style={{ flex: 1 }} />
{dialogProps.type === 'EDIT' && (
<Button variant='outlined' onClick={() => exportTool()} startIcon={<IconFileExport />}>
Export
</Button>
)}
</div>
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<Box sx={{ p: 2 }}> <Box sx={{ p: 2 }}>
@ -338,12 +411,17 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
<Typography variant='overline'> <Typography variant='overline'>
Tool Name Tool Name
<span style={{ color: 'red' }}>&nbsp;*</span> <span style={{ color: 'red' }}>&nbsp;*</span>
<TooltipWithParser
style={{ marginLeft: 10 }}
title={'Tool name must be small capital letter with underscore. Ex: my_tool'}
/>
</Typography> </Typography>
</Stack> </Stack>
<OutlinedInput <OutlinedInput
id='toolName' id='toolName'
type='string' type='string'
fullWidth fullWidth
disabled={dialogProps.type === 'TEMPLATE'}
placeholder='My New Tool' placeholder='My New Tool'
value={toolName} value={toolName}
name='toolName' name='toolName'
@ -355,12 +433,17 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
<Typography variant='overline'> <Typography variant='overline'>
Tool description Tool description
<span style={{ color: 'red' }}>&nbsp;*</span> <span style={{ color: 'red' }}>&nbsp;*</span>
<TooltipWithParser
style={{ marginLeft: 10 }}
title={'Description of what the tool does. This is for ChatGPT to determine when to use this tool.'}
/>
</Typography> </Typography>
</Stack> </Stack>
<OutlinedInput <OutlinedInput
id='toolDesc' id='toolDesc'
type='string' type='string'
fullWidth fullWidth
disabled={dialogProps.type === 'TEMPLATE'}
placeholder='Description of what the tool does. This is for ChatGPT to determine when to use this tool.' placeholder='Description of what the tool does. This is for ChatGPT to determine when to use this tool.'
multiline={true} multiline={true}
rows={3} rows={3}
@ -369,6 +452,21 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
onChange={(e) => setToolDesc(e.target.value)} onChange={(e) => setToolDesc(e.target.value)}
/> />
</Box> </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 }}> <Box sx={{ p: 2 }}>
<Stack sx={{ position: 'relative' }} direction='row'> <Stack sx={{ position: 'relative' }} direction='row'>
<Typography variant='overline'> <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?'} /> <TooltipWithParser style={{ marginLeft: 10 }} title={'What should be the output response in JSON format?'} />
</Typography> </Typography>
</Stack> </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>
<Box sx={{ p: 2 }}> <Box sx={{ p: 2 }}>
<Stack sx={{ position: 'relative' }} direction='row'> <Stack sx={{ position: 'relative' }} direction='row'>
@ -388,12 +492,15 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
/> />
</Typography> </Typography>
</Stack> </Stack>
{dialogProps.type !== 'TEMPLATE' && (
<Button style={{ marginBottom: 10 }} variant='outlined' onClick={() => setToolFunc(exampleAPIFunc)}> <Button style={{ marginBottom: 10 }} variant='outlined' onClick={() => setToolFunc(exampleAPIFunc)}>
See Example See Example
</Button> </Button>
)}
{customization.isDarkMode ? ( {customization.isDarkMode ? (
<DarkCodeEditor <DarkCodeEditor
value={toolFunc} value={toolFunc}
disabled={dialogProps.type === 'TEMPLATE'}
onValueChange={(code) => setToolFunc(code)} onValueChange={(code) => setToolFunc(code)}
style={{ style={{
fontSize: '0.875rem', fontSize: '0.875rem',
@ -405,6 +512,7 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
) : ( ) : (
<LightCodeEditor <LightCodeEditor
value={toolFunc} value={toolFunc}
disabled={dialogProps.type === 'TEMPLATE'}
onValueChange={(code) => setToolFunc(code)} onValueChange={(code) => setToolFunc(code)}
style={{ style={{
fontSize: '0.875rem', fontSize: '0.875rem',
@ -423,13 +531,20 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
Delete Delete
</StyledButton> </StyledButton>
)} )}
{dialogProps.type === 'TEMPLATE' && (
<StyledButton color='secondary' variant='contained' onClick={useToolTemplate}>
Use Template
</StyledButton>
)}
{dialogProps.type !== 'TEMPLATE' && (
<StyledButton <StyledButton
disabled={!(toolName && toolDesc)} disabled={!(toolName && toolDesc)}
variant='contained' variant='contained'
onClick={() => (dialogProps.type === 'ADD' ? addNewTool() : saveTool())} onClick={() => (dialogProps.type === 'ADD' || dialogProps.type === 'IMPORT' ? addNewTool() : saveTool())}
> >
{dialogProps.confirmButtonName} {dialogProps.confirmButtonName}
</StyledButton> </StyledButton>
)}
</DialogActions> </DialogActions>
<ConfirmDialog /> <ConfirmDialog />
</Dialog> </Dialog>
@ -441,6 +556,7 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
ToolDialog.propTypes = { ToolDialog.propTypes = {
show: PropTypes.bool, show: PropTypes.bool,
dialogProps: PropTypes.object, dialogProps: PropTypes.object,
onUseTemplate: PropTypes.func,
onCancel: PropTypes.func, onCancel: PropTypes.func,
onConfirm: PropTypes.func onConfirm: PropTypes.func
} }

View File

@ -1,8 +1,8 @@
import { useEffect, useState } from 'react' import { useEffect, useState, useRef } from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
// material-ui // material-ui
import { Grid, Box, Stack } from '@mui/material' import { Grid, Box, Stack, Button } from '@mui/material'
import { useTheme } from '@mui/material/styles' import { useTheme } from '@mui/material/styles'
// project imports // project imports
@ -20,7 +20,7 @@ import toolsApi from 'api/tools'
import useApi from 'hooks/useApi' import useApi from 'hooks/useApi'
// icons // icons
import { IconPlus } from '@tabler/icons' import { IconPlus, IconFileImport } from '@tabler/icons'
// ==============================|| CHATFLOWS ||============================== // // ==============================|| CHATFLOWS ||============================== //
@ -33,6 +33,40 @@ const Tools = () => {
const [showDialog, setShowDialog] = useState(false) const [showDialog, setShowDialog] = useState(false)
const [dialogProps, setDialogProps] = useState({}) 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 addNew = () => {
const dialogProp = { const dialogProp = {
title: 'Add New Tool', title: 'Add New Tool',
@ -75,8 +109,17 @@ const Tools = () => {
<Grid sx={{ mb: 1.25 }} container direction='row'> <Grid sx={{ mb: 1.25 }} container direction='row'>
<Box sx={{ flexGrow: 1 }} /> <Box sx={{ flexGrow: 1 }} />
<Grid item> <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 />}> <StyledButton variant='contained' sx={{ color: 'white' }} onClick={addNew} startIcon={<IconPlus />}>
Create New Create
</StyledButton> </StyledButton>
</Grid> </Grid>
</Grid> </Grid>
@ -86,7 +129,7 @@ const Tools = () => {
getAllToolsApi.data && getAllToolsApi.data &&
getAllToolsApi.data.map((data, index) => ( getAllToolsApi.data.map((data, index) => (
<Grid key={index} item lg={3} md={4} sm={6} xs={12}> <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>
))} ))}
</Grid> </Grid>