diff --git a/package.json b/package.json index 0ff762841..61ea436b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.13", + "version": "1.2.14", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts new file mode 100644 index 000000000..b13680443 --- /dev/null +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -0,0 +1,100 @@ +import { ICommonObject, INode, INodeData, INodeParams, getBaseClasses } from '../../../src' +import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' +import { BufferMemory } from 'langchain/memory' + +class DynamoDb_Memory implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'DynamoDB Memory' + this.name = 'DynamoDbMemory' + this.icon = 'dynamodb.svg' + this.category = 'Memory' + this.description = 'Stores the conversation in dynamo db table' + this.baseClasses = [this.type, ...getBaseClasses(BufferMemory)] + this.inputs = [ + { + label: 'Table Name', + name: 'tableName', + type: 'string' + }, + { + label: 'Partition Key', + name: 'partitionKey', + type: 'string' + }, + { + label: 'Session ID', + name: 'sessionId', + type: 'string', + description: 'if empty, chatId will be used automatically', + default: '', + additionalParams: true, + optional: true + }, + { + label: 'Region', + name: 'region', + type: 'string', + description: 'The aws region in which table is located', + placeholder: 'us-east-1' + }, + { + label: 'Access Key', + name: 'accessKey', + type: 'password' + }, + { + label: 'Secret Access Key', + name: 'secretAccessKey', + type: 'password' + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history' + } + ] + } + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const tableName = nodeData.inputs?.tableName as string + const partitionKey = nodeData.inputs?.partitionKey as string + const sessionId = nodeData.inputs?.sessionId as string + const region = nodeData.inputs?.region as string + const accessKey = nodeData.inputs?.accessKey as string + const secretAccessKey = nodeData.inputs?.secretAccessKey as string + const memoryKey = nodeData.inputs?.memoryKey as string + + const chatId = options.chatId + + const dynamoDb = new DynamoDBChatMessageHistory({ + tableName, + partitionKey, + sessionId: sessionId ? sessionId : chatId, + config: { + region, + credentials: { + accessKeyId: accessKey, + secretAccessKey + } + } + }) + + const memory = new BufferMemory({ + memoryKey, + chatHistory: dynamoDb, + returnMessages: true + }) + return memory + } +} + +module.exports = { nodeClass: DynamoDb_Memory } diff --git a/packages/components/nodes/memory/DynamoDb/dynamodb.svg b/packages/components/nodes/memory/DynamoDb/dynamodb.svg new file mode 100644 index 000000000..f2798350a --- /dev/null +++ b/packages/components/nodes/memory/DynamoDb/dynamodb.svg @@ -0,0 +1,18 @@ + + + + Icon-Architecture/16/Arch_Amazon-DynamoDB_16 + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index e332181d7..2b4e51c26 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -36,7 +36,8 @@ class RedisBackedChatMemory_Memory implements INode { type: 'string', description: 'if empty, chatId will be used automatically', default: '', - additionalParams: true + additionalParams: true, + optional: true }, { label: 'Session Timeouts', diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 1fb6d9ff6..2e7ba001b 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -41,7 +41,8 @@ class ZepMemory_Memory implements INode { type: 'string', description: 'if empty, chatId will be used automatically', default: '', - additionalParams: true + additionalParams: true, + optional: true }, { label: 'Auto Summary Template', diff --git a/packages/components/package.json b/packages/components/package.json index ed48fb657..a5f03e10e 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.14", + "version": "1.2.15", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", @@ -16,6 +16,7 @@ }, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { + "@aws-sdk/client-dynamodb": "^3.360.0", "@dqbd/tiktoken": "^1.0.7", "@getzep/zep-js": "^0.3.1", "@huggingface/inference": "1", 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 000000000..584df4c33 --- /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 000000000..c52c9199c --- /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 000000000..9108cc503 --- /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 000000000..bbfaaa905 --- /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 000000000..f15d40505 --- /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 000000000..1af8111b5 --- /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 000000000..18f6dad8d --- /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/package.json b/packages/server/package.json index 05abd6c93..44b7e9911 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.13", + "version": "1.2.14", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 9c47405c7..0c6304906 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -9,9 +9,10 @@ export interface IChatFlow { id: string name: string flowData: string - isPublic: boolean updatedDate: Date createdDate: Date + deployed?: boolean + isPublic?: boolean apikeyid?: string chatbotConfig?: string } @@ -30,6 +31,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/ChatFlow.ts b/packages/server/src/entity/ChatFlow.ts index 400e05170..e1d212cf6 100644 --- a/packages/server/src/entity/ChatFlow.ts +++ b/packages/server/src/entity/ChatFlow.ts @@ -13,8 +13,11 @@ export class ChatFlow implements IChatFlow { @Column() flowData: string - @Column() - isPublic: boolean + @Column({ nullable: true }) + deployed?: boolean + + @Column({ nullable: true }) + isPublic?: boolean @Column({ nullable: true }) apikeyid?: string diff --git a/packages/server/src/entity/Tool.ts b/packages/server/src/entity/Tool.ts index 307e8d23c..222fd7666 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 c7206154f..280d870bd 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -467,12 +467,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 = { @@ -486,6 +486,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/package.json b/packages/ui/package.json index ba1483b4d..2cadc89d9 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.12", + "version": "1.2.13", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { diff --git a/packages/ui/public/index.html b/packages/ui/public/index.html index 270cc8058..b4ec9ea10 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 6906fb4e4..3fd4ae872 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 b7ebc8b21..c04cc3f17 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 345a88d58..1e8789d76 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 2049f56c6..0670d69bd 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/canvas/index.js b/packages/ui/src/views/canvas/index.js index 03098963c..1c6d610f8 100644 --- a/packages/ui/src/views/canvas/index.js +++ b/packages/ui/src/views/canvas/index.js @@ -201,6 +201,7 @@ const Canvas = () => { if (!chatflow.id) { const newChatflowBody = { name: chatflowName, + deployed: false, isPublic: false, flowData } diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 5021cd9b3..52ff4bb9b 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -163,7 +163,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { // Prevent blank submissions and allow for multiline input const handleEnter = (e) => { - if (e.key === 'Enter' && userInput) { + // Check if IME composition is in progress + const isIMEComposition = e.isComposing || e.keyCode === 229 + if (e.key === 'Enter' && userInput && !isIMEComposition) { if (!e.shiftKey && userInput) { handleSubmit(e) } diff --git a/packages/ui/src/views/marketplaces/index.js b/packages/ui/src/views/marketplaces/index.js index ba9eb3d63..a78361616 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 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 ( - - -

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 bd5af355e..77ef770dc 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 efe9e69d7..c97ec6609 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)} /> ))}