diff --git a/packages/server/marketplaces/API Agent.json b/packages/server/marketplaces/chatflows/API Agent.json similarity index 100% rename from packages/server/marketplaces/API Agent.json rename to packages/server/marketplaces/chatflows/API Agent.json diff --git a/packages/server/marketplaces/Antonym.json b/packages/server/marketplaces/chatflows/Antonym.json similarity index 100% rename from packages/server/marketplaces/Antonym.json rename to packages/server/marketplaces/chatflows/Antonym.json diff --git a/packages/server/marketplaces/AutoGPT.json b/packages/server/marketplaces/chatflows/AutoGPT.json similarity index 100% rename from packages/server/marketplaces/AutoGPT.json rename to packages/server/marketplaces/chatflows/AutoGPT.json diff --git a/packages/server/marketplaces/BabyAGI.json b/packages/server/marketplaces/chatflows/BabyAGI.json similarity index 100% rename from packages/server/marketplaces/BabyAGI.json rename to packages/server/marketplaces/chatflows/BabyAGI.json diff --git a/packages/server/marketplaces/ChatGPTPlugin.json b/packages/server/marketplaces/chatflows/ChatGPTPlugin.json similarity index 100% rename from packages/server/marketplaces/ChatGPTPlugin.json rename to packages/server/marketplaces/chatflows/ChatGPTPlugin.json diff --git a/packages/server/marketplaces/Conversational Agent.json b/packages/server/marketplaces/chatflows/Conversational Agent.json similarity index 100% rename from packages/server/marketplaces/Conversational Agent.json rename to packages/server/marketplaces/chatflows/Conversational Agent.json diff --git a/packages/server/marketplaces/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json similarity index 100% rename from packages/server/marketplaces/Conversational Retrieval QA Chain.json rename to packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json diff --git a/packages/server/marketplaces/Github Repo QnA.json b/packages/server/marketplaces/chatflows/Github Repo QnA.json similarity index 100% rename from packages/server/marketplaces/Github Repo QnA.json rename to packages/server/marketplaces/chatflows/Github Repo QnA.json diff --git a/packages/server/marketplaces/HuggingFace LLM Chain.json b/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json similarity index 100% rename from packages/server/marketplaces/HuggingFace LLM Chain.json rename to packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json diff --git a/packages/server/marketplaces/Local QnA.json b/packages/server/marketplaces/chatflows/Local QnA.json similarity index 100% rename from packages/server/marketplaces/Local QnA.json rename to packages/server/marketplaces/chatflows/Local QnA.json diff --git a/packages/server/marketplaces/MRKLAgent.json b/packages/server/marketplaces/chatflows/MRKLAgent.json similarity index 100% rename from packages/server/marketplaces/MRKLAgent.json rename to packages/server/marketplaces/chatflows/MRKLAgent.json diff --git a/packages/server/marketplaces/Metadata Filter Load.json b/packages/server/marketplaces/chatflows/Metadata Filter Load.json similarity index 100% rename from packages/server/marketplaces/Metadata Filter Load.json rename to packages/server/marketplaces/chatflows/Metadata Filter Load.json diff --git a/packages/server/marketplaces/Metadata Filter Upsert.json b/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json similarity index 100% rename from packages/server/marketplaces/Metadata Filter Upsert.json rename to packages/server/marketplaces/chatflows/Metadata Filter Upsert.json diff --git a/packages/server/marketplaces/Multi Prompt Chain.json b/packages/server/marketplaces/chatflows/Multi Prompt Chain.json similarity index 100% rename from packages/server/marketplaces/Multi Prompt Chain.json rename to packages/server/marketplaces/chatflows/Multi Prompt Chain.json diff --git a/packages/server/marketplaces/Multi Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json similarity index 100% rename from packages/server/marketplaces/Multi Retrieval QA Chain.json rename to packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json diff --git a/packages/server/marketplaces/Multiple VectorDB.json b/packages/server/marketplaces/chatflows/Multiple VectorDB.json similarity index 100% rename from packages/server/marketplaces/Multiple VectorDB.json rename to packages/server/marketplaces/chatflows/Multiple VectorDB.json diff --git a/packages/server/marketplaces/OpenAI Agent.json b/packages/server/marketplaces/chatflows/OpenAI Agent.json similarity index 100% rename from packages/server/marketplaces/OpenAI Agent.json rename to packages/server/marketplaces/chatflows/OpenAI Agent.json diff --git a/packages/server/marketplaces/Prompt Chaining.json b/packages/server/marketplaces/chatflows/Prompt Chaining.json similarity index 100% rename from packages/server/marketplaces/Prompt Chaining.json rename to packages/server/marketplaces/chatflows/Prompt Chaining.json diff --git a/packages/server/marketplaces/SQL DB Chain.json b/packages/server/marketplaces/chatflows/SQL DB Chain.json similarity index 100% rename from packages/server/marketplaces/SQL DB Chain.json rename to packages/server/marketplaces/chatflows/SQL DB Chain.json diff --git a/packages/server/marketplaces/Simple Conversation Chain.json b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json similarity index 100% rename from packages/server/marketplaces/Simple Conversation Chain.json rename to packages/server/marketplaces/chatflows/Simple Conversation Chain.json diff --git a/packages/server/marketplaces/Simple LLM Chain.json b/packages/server/marketplaces/chatflows/Simple LLM Chain.json similarity index 100% rename from packages/server/marketplaces/Simple LLM Chain.json rename to packages/server/marketplaces/chatflows/Simple LLM Chain.json diff --git a/packages/server/marketplaces/Translator.json b/packages/server/marketplaces/chatflows/Translator.json similarity index 100% rename from packages/server/marketplaces/Translator.json rename to packages/server/marketplaces/chatflows/Translator.json diff --git a/packages/server/marketplaces/WebBrowser.json b/packages/server/marketplaces/chatflows/WebBrowser.json similarity index 100% rename from packages/server/marketplaces/WebBrowser.json rename to packages/server/marketplaces/chatflows/WebBrowser.json diff --git a/packages/server/marketplaces/Zapier NLA.json b/packages/server/marketplaces/chatflows/Zapier NLA.json similarity index 100% rename from packages/server/marketplaces/Zapier NLA.json rename to packages/server/marketplaces/chatflows/Zapier NLA.json diff --git a/packages/server/marketplaces/tools/Add Hubspot Contact.json b/packages/server/marketplaces/tools/Add Hubspot Contact.json new file mode 100644 index 00000000000..584df4c3340 --- /dev/null +++ b/packages/server/marketplaces/tools/Add Hubspot Contact.json @@ -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}" +} diff --git a/packages/server/marketplaces/tools/Create Airtable Record.json b/packages/server/marketplaces/tools/Create Airtable Record.json new file mode 100644 index 00000000000..c52c9199c58 --- /dev/null +++ b/packages/server/marketplaces/tools/Create Airtable Record.json @@ -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}" +} diff --git a/packages/server/marketplaces/tools/Get Stock Mover.json b/packages/server/marketplaces/tools/Get Stock Mover.json new file mode 100644 index 00000000000..9108cc50379 --- /dev/null +++ b/packages/server/marketplaces/tools/Get Stock Mover.json @@ -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}" +} diff --git a/packages/server/marketplaces/tools/Send Discord Message.json b/packages/server/marketplaces/tools/Send Discord Message.json new file mode 100644 index 00000000000..bbfaaa9053d --- /dev/null +++ b/packages/server/marketplaces/tools/Send Discord Message.json @@ -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}" +} diff --git a/packages/server/marketplaces/tools/Send Slack Message.json b/packages/server/marketplaces/tools/Send Slack Message.json new file mode 100644 index 00000000000..f15d40505c9 --- /dev/null +++ b/packages/server/marketplaces/tools/Send Slack Message.json @@ -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}" +} diff --git a/packages/server/marketplaces/tools/Send Teams Message.json b/packages/server/marketplaces/tools/Send Teams Message.json new file mode 100644 index 00000000000..1af8111b583 --- /dev/null +++ b/packages/server/marketplaces/tools/Send Teams Message.json @@ -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}" +} diff --git a/packages/server/marketplaces/tools/SendGrid Email.json b/packages/server/marketplaces/tools/SendGrid Email.json new file mode 100644 index 00000000000..18f6dad8de4 --- /dev/null +++ b/packages/server/marketplaces/tools/SendGrid Email.json @@ -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}" +} diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 9c47405c74e..b4783f7639b 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -30,6 +30,7 @@ export interface ITool { name: string description: string color: string + iconSrc?: string schema?: string func?: string updatedDate: Date diff --git a/packages/server/src/entity/Tool.ts b/packages/server/src/entity/Tool.ts index 307e8d23cdb..222fd766620 100644 --- a/packages/server/src/entity/Tool.ts +++ b/packages/server/src/entity/Tool.ts @@ -16,6 +16,9 @@ export class Tool implements ITool { @Column() color: string + @Column({ nullable: true }) + iconSrc?: string + @Column({ nullable: true }) schema?: string diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 15762a23c4f..cd4978a069d 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -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 // ---------------------------------------- diff --git a/packages/ui/public/index.html b/packages/ui/public/index.html index 270cc805809..b4ec9ea105a 100644 --- a/packages/ui/public/index.html +++ b/packages/ui/public/index.html @@ -1,13 +1,13 @@ - Flowise - LangchainJS UI + Flowise - Low-code LLM apps builder - + @@ -17,13 +17,13 @@ - + - + diff --git a/packages/ui/src/api/marketplaces.js b/packages/ui/src/api/marketplaces.js index 6906fb4e465..3fd4ae87203 100644 --- a/packages/ui/src/api/marketplaces.js +++ b/packages/ui/src/api/marketplaces.js @@ -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 } diff --git a/packages/ui/src/themes/compStyleOverride.js b/packages/ui/src/themes/compStyleOverride.js index b7ebc8b21c3..c04cc3f17f4 100644 --- a/packages/ui/src/themes/compStyleOverride.js +++ b/packages/ui/src/themes/compStyleOverride.js @@ -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 } } } diff --git a/packages/ui/src/ui-component/cards/ItemCard.js b/packages/ui/src/ui-component/cards/ItemCard.js index 345a88d58c3..1e8789d76d7 100644 --- a/packages/ui/src/ui-component/cards/ItemCard.js +++ b/packages/ui/src/ui-component/cards/ItemCard.js @@ -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 && (
+ )} + {!data.iconSrc && data.color && ( +
)} - {data.name} + {data.templateName || data.name} {data.description && ( @@ -107,7 +121,6 @@ ItemCard.propTypes = { isLoading: PropTypes.bool, data: PropTypes.object, images: PropTypes.array, - color: PropTypes.string, onClick: PropTypes.func } diff --git a/packages/ui/src/ui-component/grid/Grid.js b/packages/ui/src/ui-component/grid/Grid.js index 2049f56c6a9..0670d69bd1e 100644 --- a/packages/ui/src/ui-component/grid/Grid.js +++ b/packages/ui/src/ui-component/grid/Grid.js @@ -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 ( <> - + {!disabled && ( + + )} {rows && columns && (
{ + 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 } diff --git a/packages/ui/src/views/marketplaces/index.js b/packages/ui/src/views/marketplaces/index.js index ba9eb3d63b0..a7836161660 100644 --- a/packages/ui/src/views/marketplaces/index.js +++ b/packages/ui/src/views/marketplaces/index.js @@ -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 ( + + ) +} + +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 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 getAllMarketplacesApi = useApi(marketplacesApi.getAllMarketplaces) + 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 ( - - -

Marketplace

-
- - {!isLoading && - getAllMarketplacesApi.data && - getAllMarketplacesApi.data.map((data, index) => ( - - goToCanvas(data)} data={data} images={images[data.id]} /> - - ))} - - {!isLoading && (!getAllMarketplacesApi.data || getAllMarketplacesApi.data.length === 0) && ( - - - WorkflowEmptySVG - -
No Marketplace Yet
+ <> + + +

Marketplace

- )} -
+ + {tabItems.map((item, index) => ( + : } + iconPosition='start' + label={{item}} + /> + ))} + + {tabItems.map((item, index) => ( + + {item === 'Chatflows' && ( + + {!isChatflowsLoading && + getAllChatflowsMarketplacesApi.data && + getAllChatflowsMarketplacesApi.data.map((data, index) => ( + + goToCanvas(data)} data={data} images={images[data.id]} /> + + ))} + + )} + {item === 'Tools' && ( + + {!isToolsLoading && + getAllToolsMarketplacesApi.data && + getAllToolsMarketplacesApi.data.map((data, index) => ( + + goToTool(data)} /> + + ))} + + )} + + ))} + {!isChatflowsLoading && (!getAllChatflowsMarketplacesApi.data || getAllChatflowsMarketplacesApi.data.length === 0) && ( + + + WorkflowEmptySVG + +
No Marketplace Yet
+
+ )} + {!isToolsLoading && (!getAllToolsMarketplacesApi.data || getAllToolsMarketplacesApi.data.length === 0) && ( + + + WorkflowEmptySVG + +
No Marketplace Yet
+
+ )} +
+ setShowToolDialog(false)} + onConfirm={() => setShowToolDialog(false)} + onUseTemplate={(tool) => onUseTemplate(tool)} + > + ) } diff --git a/packages/ui/src/views/tools/ToolDialog.js b/packages/ui/src/views/tools/ToolDialog.js index bd5af355e26..77ef770dc1a 100644 --- a/packages/ui/src/views/tools/ToolDialog.js +++ b/packages/ui/src/views/tools/ToolDialog.js @@ -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) => ( + + ) + } + }) + 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' > - {dialogProps.title} +
+ {dialogProps.title} +
+ {dialogProps.type === 'EDIT' && ( + + )} +
@@ -338,12 +411,17 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { Tool Name  * + { Tool description  * + { onChange={(e) => setToolDesc(e.target.value)} /> + + + Tool Icon Src + + setToolIcon(e.target.value)} + /> + @@ -376,7 +474,13 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { - + @@ -388,12 +492,15 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { /> - + {dialogProps.type !== 'TEMPLATE' && ( + + )} {customization.isDarkMode ? ( setToolFunc(code)} style={{ fontSize: '0.875rem', @@ -405,6 +512,7 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { ) : ( setToolFunc(code)} style={{ fontSize: '0.875rem', @@ -423,13 +531,20 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { Delete )} - (dialogProps.type === 'ADD' ? addNewTool() : saveTool())} - > - {dialogProps.confirmButtonName} - + {dialogProps.type === 'TEMPLATE' && ( + + Use Template + + )} + {dialogProps.type !== 'TEMPLATE' && ( + (dialogProps.type === 'ADD' || dialogProps.type === 'IMPORT' ? addNewTool() : saveTool())} + > + {dialogProps.confirmButtonName} + + )} @@ -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 } diff --git a/packages/ui/src/views/tools/index.js b/packages/ui/src/views/tools/index.js index efe9e69d769..c97ec660968 100644 --- a/packages/ui/src/views/tools/index.js +++ b/packages/ui/src/views/tools/index.js @@ -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 = () => { + + handleFileUpload(e)} /> }> - Create New + Create @@ -86,7 +129,7 @@ const Tools = () => { getAllToolsApi.data && getAllToolsApi.data.map((data, index) => ( - edit(data)} /> + edit(data)} /> ))}