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