Merge branch 'FlowiseAI:main' into feature/winston-logging-clean

This commit is contained in:
Matthias Platzer 2023-07-06 11:26:09 +02:00 committed by GitHub
commit f9c76bd6b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 582 additions and 79 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "flowise", "name": "flowise",
"version": "1.2.13", "version": "1.2.14",
"private": true, "private": true,
"homepage": "https://flowiseai.com", "homepage": "https://flowiseai.com",
"workspaces": [ "workspaces": [

View File

@ -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<any> {
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 }

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 64 (93537) - https://sketch.com -->
<title>Icon-Architecture/16/Arch_Amazon-DynamoDB_16</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="0%" y1="100%" x2="100%" y2="0%" id="linearGradient-1">
<stop stop-color="#2E27AD" offset="0%"></stop>
<stop stop-color="#527FFF" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Icon-Architecture/16/Arch_Amazon-DynamoDB_16" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Icon-Architecture-BG/16/Database" fill="url(#linearGradient-1)">
<rect id="Rectangle" x="0" y="0" width="24" height="24"></rect>
</g>
<path d="M14.3871979,13.0634319 L15.4218955,9.61738691 C15.468467,9.46474602 15.4391067,9.29896386 15.3439388,9.17058378 C15.2487709,9.04220369 15.0979197,8.96739955 14.9379567,8.96739955 L14.2383715,8.96739955 L15.2507958,6.94566591 L17.7798316,6.94566591 L16.9881159,9.313116 C16.9374946,9.46676775 16.9628052,9.63659338 17.0589856,9.76800607 C17.153141,9.90042962 17.3060171,9.97826636 17.4690174,9.97826636 L18.095708,9.97826636 L14.3871979,13.0634319 Z M19.9697053,9.29997473 C19.8968108,9.10083397 19.7074875,8.96739955 19.4938659,8.96739955 L18.1706274,8.96739955 L18.9623432,6.59994946 C19.0129644,6.4462977 18.9876538,6.27647207 18.8914735,6.14404852 C18.7963056,6.01263584 18.644442,5.93479909 18.4814417,5.93479909 L14.9379567,5.93479909 C14.7455961,5.93479909 14.5714591,6.04296184 14.485403,6.21379833 L12.9667666,9.24639879 C12.88881,9.40308314 12.8958969,9.58908264 12.9880275,9.73768006 C13.0811706,9.88728835 13.2441709,9.97826636 13.4193203,9.97826636 L14.2576076,9.97826636 L12.9343691,14.3816022 C12.8705863,14.595906 12.9536051,14.8253728 13.1409036,14.9486985 C13.2259472,15.0042962 13.3221275,15.0326005 13.4193203,15.0326005 C13.5347366,15.0326005 13.6491406,14.9931766 13.743296,14.9153399 L19.8178417,9.86100581 C19.980842,9.72453879 20.0425999,9.50113723 19.9697053,9.29997473 L19.9697053,9.29997473 Z M14.8346894,17.6285064 C14.8346894,18.0904726 13.2775809,18.9891332 10.4235568,18.9891332 C7.56953281,18.9891332 6.01242428,18.0904726 6.01242428,17.6285064 L6.01242428,16.562042 C7.04914673,17.1786707 8.74293255,17.495072 10.4235568,17.495072 C12.1041811,17.495072 13.797967,17.1786707 14.8346894,16.562042 L14.8346894,17.6285064 Z M14.8346894,15.1235785 C14.8346894,15.5855446 13.2775809,16.4842052 10.4235568,16.4842052 C7.56953281,16.4842052 6.01242428,15.5855446 6.01242428,15.1235785 C6.01242428,15.0275461 6.08633125,14.9133182 6.21187186,14.7950468 C7.21214704,15.316654 8.74698225,15.6239575 10.4235568,15.6239575 C10.4438053,15.6239575 11.9948393,15.5916098 11.9948393,15.5916098 L11.9948393,14.580743 C11.9745908,14.580743 10.4235568,14.6130907 10.4235568,14.6130907 C8.77128043,14.6130907 7.24656947,14.2886025 6.44574187,13.7680061 C6.17542458,13.5900935 6.0134367,13.3980288 6.01242428,13.252464 L6.01242428,12.1859995 C7.04914673,12.8026283 8.74293255,13.1200404 10.4235568,13.1200404 C10.6898244,13.1200404 11.8348763,13.0391711 12.1922621,13.0138994 L12.213523,12.5054334 L12.1203799,12.0050543 C11.7761557,12.0293151 10.6786878,12.1091736 10.4235568,12.1091736 C7.56953281,12.1091736 6.01242428,11.2095021 6.01242428,10.747536 C6.01242428,10.6474602 6.09139337,10.5281779 6.22503337,10.405863 C7.01877401,10.7566338 8.57183285,11.1508719 12.3178027,11.1963609 L12.3299518,10.1854941 C9.27951741,10.1491029 7.3437622,9.88223402 6.44574187,9.39095274 C6.17542458,9.21304018 6.0134367,9.02097549 6.01242428,8.87541066 L6.01242428,7.80995704 C7.04914673,8.4265858 8.74293255,8.74298711 10.4235568,8.74298711 C10.5015135,8.74298711 12.480803,8.70659591 12.5587596,8.70356331 L12.5152254,7.69370735 C12.4079084,7.69775082 10.5187247,7.73212029 10.4235568,7.73212029 C7.56953281,7.73212029 6.01242428,6.83345969 6.01242428,6.37149356 C6.01242428,5.90952742 7.56953281,5.01086682 10.4235568,5.01086682 C11.7447705,5.01086682 13.0001766,5.21809452 13.8668118,5.5809957 L14.25862,4.64796563 C13.2573324,4.23047763 11.8956217,4 10.4235568,4 C7.72949585,4 5.00101242,4.81374779 5,6.36947182 L5,8.88147587 C5,8.88147587 5.09213061,9.46070255 5.40092001,9.80742987 C5.08808091,10.1551681 5,10.4938084 5,10.7465251 L5,13.2625727 C5.00101242,13.510235 5.09213061,13.8438211 5.39788274,14.1875158 C5.08808091,14.5342431 5,14.8718726 5,15.1235785 L5,17.6335608 C5.00506212,19.1872631 7.7315207,20 10.4235568,20 C13.1186303,20 15.8471137,19.1862522 15.8471137,17.6305282 L15.8471137,15.1235785 L14.8346894,15.1235785 Z" id="Amazon-DynamoDB_Icon_16_Squid" fill="#FFFFFF"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -36,7 +36,8 @@ class RedisBackedChatMemory_Memory implements INode {
type: 'string', type: 'string',
description: 'if empty, chatId will be used automatically', description: 'if empty, chatId will be used automatically',
default: '', default: '',
additionalParams: true additionalParams: true,
optional: true
}, },
{ {
label: 'Session Timeouts', label: 'Session Timeouts',

View File

@ -41,7 +41,8 @@ class ZepMemory_Memory implements INode {
type: 'string', type: 'string',
description: 'if empty, chatId will be used automatically', description: 'if empty, chatId will be used automatically',
default: '', default: '',
additionalParams: true additionalParams: true,
optional: true
}, },
{ {
label: 'Auto Summary Template', label: 'Auto Summary Template',

View File

@ -1,6 +1,6 @@
{ {
"name": "flowise-components", "name": "flowise-components",
"version": "1.2.14", "version": "1.2.15",
"description": "Flowiseai Components", "description": "Flowiseai Components",
"main": "dist/src/index", "main": "dist/src/index",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
@ -16,6 +16,7 @@
}, },
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"dependencies": { "dependencies": {
"@aws-sdk/client-dynamodb": "^3.360.0",
"@dqbd/tiktoken": "^1.0.7", "@dqbd/tiktoken": "^1.0.7",
"@getzep/zep-js": "^0.3.1", "@getzep/zep-js": "^0.3.1",
"@huggingface/inference": "1", "@huggingface/inference": "1",

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

@ -1,6 +1,6 @@
{ {
"name": "flowise", "name": "flowise",
"version": "1.2.13", "version": "1.2.14",
"description": "Flowiseai Server", "description": "Flowiseai Server",
"main": "dist/index", "main": "dist/index",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

View File

@ -9,9 +9,10 @@ export interface IChatFlow {
id: string id: string
name: string name: string
flowData: string flowData: string
isPublic: boolean
updatedDate: Date updatedDate: Date
createdDate: Date createdDate: Date
deployed?: boolean
isPublic?: boolean
apikeyid?: string apikeyid?: string
chatbotConfig?: string chatbotConfig?: string
} }
@ -30,6 +31,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

@ -13,8 +13,11 @@ export class ChatFlow implements IChatFlow {
@Column() @Column()
flowData: string flowData: string
@Column() @Column({ nullable: true })
isPublic: boolean deployed?: boolean
@Column({ nullable: true })
isPublic?: boolean
@Column({ nullable: true }) @Column({ nullable: true })
apikeyid?: string apikeyid?: string

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

@ -467,12 +467,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 = {
@ -486,6 +486,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,6 +1,6 @@
{ {
"name": "flowise-ui", "name": "flowise-ui",
"version": "1.2.12", "version": "1.2.13",
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://flowiseai.com", "homepage": "https://flowiseai.com",
"author": { "author": {

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 (
<> <>
<Button variant='outlined' onClick={addNewRow} startIcon={<IconPlus />}> {!disabled && (
Add Item <Button variant='outlined' onClick={addNewRow} startIcon={<IconPlus />}>
</Button> Add Item
</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

@ -201,6 +201,7 @@ const Canvas = () => {
if (!chatflow.id) { if (!chatflow.id) {
const newChatflowBody = { const newChatflowBody = {
name: chatflowName, name: chatflowName,
deployed: false,
isPublic: false, isPublic: false,
flowData flowData
} }

View File

@ -163,7 +163,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
// Prevent blank submissions and allow for multiline input // Prevent blank submissions and allow for multiline input
const handleEnter = (e) => { 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) { if (!e.shiftKey && userInput) {
handleSubmit(e) handleSubmit(e)
} }

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 : '' }}> <>
<Stack flexDirection='row'> <MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
<h1>Marketplace</h1> <Stack flexDirection='row'>
</Stack> <h1>Marketplace</h1>
<Grid container spacing={gridSpacing}>
{!isLoading &&
getAllMarketplacesApi.data &&
getAllMarketplacesApi.data.map((data, index) => (
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
<ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
</Grid>
))}
</Grid>
{!isLoading && (!getAllMarketplacesApi.data || getAllMarketplacesApi.data.length === 0) && (
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
<Box sx={{ p: 2, height: 'auto' }}>
<img style={{ objectFit: 'cover', height: '30vh', width: 'auto' }} src={WorkflowEmptySVG} alt='WorkflowEmptySVG' />
</Box>
<div>No Marketplace Yet</div>
</Stack> </Stack>
)} <Tabs sx={{ mb: 2 }} variant='fullWidth' value={value} onChange={handleChange} aria-label='tabs'>
</MainCard> {tabItems.map((item, index) => (
<Tab
key={index}
icon={index === 0 ? <IconHierarchy /> : <IconTool />}
iconPosition='start'
label={<span style={{ fontSize: '1.1rem' }}>{item}</span>}
/>
))}
</Tabs>
{tabItems.map((item, index) => (
<TabPanel key={index} value={value} index={index}>
{item === 'Chatflows' && (
<Grid container spacing={gridSpacing}>
{!isChatflowsLoading &&
getAllChatflowsMarketplacesApi.data &&
getAllChatflowsMarketplacesApi.data.map((data, index) => (
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
<ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
</Grid>
))}
</Grid>
)}
{item === 'Tools' && (
<Grid container spacing={gridSpacing}>
{!isToolsLoading &&
getAllToolsMarketplacesApi.data &&
getAllToolsMarketplacesApi.data.map((data, index) => (
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
<ItemCard data={data} onClick={() => goToTool(data)} />
</Grid>
))}
</Grid>
)}
</TabPanel>
))}
{!isChatflowsLoading && (!getAllChatflowsMarketplacesApi.data || getAllChatflowsMarketplacesApi.data.length === 0) && (
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
<Box sx={{ p: 2, height: 'auto' }}>
<img
style={{ objectFit: 'cover', height: '30vh', width: 'auto' }}
src={WorkflowEmptySVG}
alt='WorkflowEmptySVG'
/>
</Box>
<div>No Marketplace Yet</div>
</Stack>
)}
{!isToolsLoading && (!getAllToolsMarketplacesApi.data || getAllToolsMarketplacesApi.data.length === 0) && (
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
<Box sx={{ p: 2, height: 'auto' }}>
<img
style={{ objectFit: 'cover', height: '30vh', width: 'auto' }}
src={WorkflowEmptySVG}
alt='WorkflowEmptySVG'
/>
</Box>
<div>No Marketplace Yet</div>
</Stack>
)}
</MainCard>
<ToolDialog
show={showToolDialog}
dialogProps={toolDialogProps}
onCancel={() => setShowToolDialog(false)}
onConfirm={() => setShowToolDialog(false)}
onUseTemplate={(tool) => onUseTemplate(tool)}
></ToolDialog>
</>
) )
} }

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'>
{dialogProps.title} <div style={{ display: 'flex', flexDirection: 'row' }}>
{dialogProps.title}
<div style={{ flex: 1 }} />
{dialogProps.type === 'EDIT' && (
<Button variant='outlined' onClick={() => exportTool()} startIcon={<IconFileExport />}>
Export
</Button>
)}
</div>
</DialogTitle> </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>
<Button style={{ marginBottom: 10 }} variant='outlined' onClick={() => setToolFunc(exampleAPIFunc)}> {dialogProps.type !== 'TEMPLATE' && (
See Example <Button style={{ marginBottom: 10 }} variant='outlined' onClick={() => setToolFunc(exampleAPIFunc)}>
</Button> See Example
</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>
)} )}
<StyledButton {dialogProps.type === 'TEMPLATE' && (
disabled={!(toolName && toolDesc)} <StyledButton color='secondary' variant='contained' onClick={useToolTemplate}>
variant='contained' Use Template
onClick={() => (dialogProps.type === 'ADD' ? addNewTool() : saveTool())} </StyledButton>
> )}
{dialogProps.confirmButtonName} {dialogProps.type !== 'TEMPLATE' && (
</StyledButton> <StyledButton
disabled={!(toolName && toolDesc)}
variant='contained'
onClick={() => (dialogProps.type === 'ADD' || dialogProps.type === 'IMPORT' ? addNewTool() : saveTool())}
>
{dialogProps.confirmButtonName}
</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>