Feature: Custom Templates (#3169)
* New Feature: Custom Templates in the marketplace. * New Feature: Custom Templates in the marketplace. * Custom Template Delete and Shortcut in the dropdown menu * auto detect framework * minor ui fixes * adding custom template feature for tools * ui tool dialog save template --------- Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
parent
44b70ca7e2
commit
b02bdc74ad
|
|
@ -273,5 +273,18 @@ export interface IApiKey {
|
||||||
updatedDate: Date
|
updatedDate: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ICustomTemplate {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
flowData: string
|
||||||
|
updatedDate: Date
|
||||||
|
createdDate: Date
|
||||||
|
description?: string
|
||||||
|
type?: string
|
||||||
|
badge?: string
|
||||||
|
framework?: string
|
||||||
|
usecases?: string
|
||||||
|
}
|
||||||
|
|
||||||
// DocumentStore related
|
// DocumentStore related
|
||||||
export * from './Interface.DocumentStore'
|
export * from './Interface.DocumentStore'
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { Request, Response, NextFunction } from 'express'
|
import { Request, Response, NextFunction } from 'express'
|
||||||
import marketplacesService from '../../services/marketplaces'
|
import marketplacesService from '../../services/marketplaces'
|
||||||
|
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||||
|
import { StatusCodes } from 'http-status-codes'
|
||||||
|
|
||||||
// Get all templates for marketplaces
|
// Get all templates for marketplaces
|
||||||
const getAllTemplates = async (req: Request, res: Response, next: NextFunction) => {
|
const getAllTemplates = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
|
@ -11,6 +13,48 @@ const getAllTemplates = async (req: Request, res: Response, next: NextFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
const deleteCustomTemplate = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
getAllTemplates
|
try {
|
||||||
|
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: marketplacesService.deleteCustomTemplate - id not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const apiResponse = await marketplacesService.deleteCustomTemplate(req.params.id)
|
||||||
|
return res.json(apiResponse)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllCustomTemplates = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const apiResponse = await marketplacesService.getAllCustomTemplates()
|
||||||
|
return res.json(apiResponse)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveCustomTemplate = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
if ((!req.body && !(req.body.chatflowId || req.body.tool)) || !req.body.name) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: marketplacesService.saveCustomTemplate - body not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const apiResponse = await marketplacesService.saveCustomTemplate(req.body)
|
||||||
|
return res.json(apiResponse)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getAllTemplates,
|
||||||
|
getAllCustomTemplates,
|
||||||
|
saveCustomTemplate,
|
||||||
|
deleteCustomTemplate
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { ICustomTemplate } from '../../Interface'
|
||||||
|
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
|
||||||
|
|
||||||
|
@Entity('custom_template')
|
||||||
|
export class CustomTemplate implements ICustomTemplate {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
name: string
|
||||||
|
|
||||||
|
@Column({ type: 'text' })
|
||||||
|
flowData: string
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'text' })
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'text' })
|
||||||
|
badge?: string
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'text' })
|
||||||
|
framework?: string
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'text' })
|
||||||
|
usecases?: string
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'text' })
|
||||||
|
type?: string
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp' })
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdDate: Date
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp' })
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updatedDate: Date
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import { DocumentStoreFileChunk } from './DocumentStoreFileChunk'
|
||||||
import { Lead } from './Lead'
|
import { Lead } from './Lead'
|
||||||
import { UpsertHistory } from './UpsertHistory'
|
import { UpsertHistory } from './UpsertHistory'
|
||||||
import { ApiKey } from './ApiKey'
|
import { ApiKey } from './ApiKey'
|
||||||
|
import { CustomTemplate } from './CustomTemplate'
|
||||||
|
|
||||||
export const entities = {
|
export const entities = {
|
||||||
ChatFlow,
|
ChatFlow,
|
||||||
|
|
@ -23,5 +24,6 @@ export const entities = {
|
||||||
DocumentStoreFileChunk,
|
DocumentStoreFileChunk,
|
||||||
Lead,
|
Lead,
|
||||||
UpsertHistory,
|
UpsertHistory,
|
||||||
ApiKey
|
ApiKey,
|
||||||
|
CustomTemplate
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddCustomTemplate1725629836652 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE IF NOT EXISTS \`custom_template\` (
|
||||||
|
\`id\` varchar(36) NOT NULL,
|
||||||
|
\`name\` varchar(255) NOT NULL,
|
||||||
|
\`flowData\` text NOT NULL,
|
||||||
|
\`description\` varchar(255) DEFAULT NULL,
|
||||||
|
\`badge\` varchar(255) DEFAULT NULL,
|
||||||
|
\`framework\` varchar(255) DEFAULT NULL,
|
||||||
|
\`usecases\` varchar(255) DEFAULT NULL,
|
||||||
|
\`type\` varchar(30) DEFAULT NULL,
|
||||||
|
\`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
\`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
PRIMARY KEY (\`id\`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE custom_template`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ import { AddTypeToChatFlow1716300000000 } from './1716300000000-AddTypeToChatFlo
|
||||||
import { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
|
import { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
|
||||||
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
||||||
import { LongTextColumn1722301395521 } from './1722301395521-LongTextColumn'
|
import { LongTextColumn1722301395521 } from './1722301395521-LongTextColumn'
|
||||||
|
import { AddCustomTemplate1725629836652 } from './1725629836652-AddCustomTemplate'
|
||||||
|
|
||||||
export const mariadbMigrations = [
|
export const mariadbMigrations = [
|
||||||
Init1693840429259,
|
Init1693840429259,
|
||||||
|
|
@ -49,5 +50,6 @@ export const mariadbMigrations = [
|
||||||
AddTypeToChatFlow1716300000000,
|
AddTypeToChatFlow1716300000000,
|
||||||
AddApiKey1720230151480,
|
AddApiKey1720230151480,
|
||||||
AddActionToChatMessage1721078251523,
|
AddActionToChatMessage1721078251523,
|
||||||
LongTextColumn1722301395521
|
LongTextColumn1722301395521,
|
||||||
|
AddCustomTemplate1725629836652
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddCustomTemplate1725629836652 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE IF NOT EXISTS \`custom_template\` (
|
||||||
|
\`id\` varchar(36) NOT NULL,
|
||||||
|
\`name\` varchar(255) NOT NULL,
|
||||||
|
\`flowData\` text NOT NULL,
|
||||||
|
\`description\` varchar(255) DEFAULT NULL,
|
||||||
|
\`badge\` varchar(255) DEFAULT NULL,
|
||||||
|
\`framework\` varchar(255) DEFAULT NULL,
|
||||||
|
\`usecases\` varchar(255) DEFAULT NULL,
|
||||||
|
\`type\` varchar(30) DEFAULT NULL,
|
||||||
|
\`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
\`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||||
|
PRIMARY KEY (\`id\`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE custom_template`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ import { AddTypeToChatFlow1716300000000 } from './1716300000000-AddTypeToChatFlo
|
||||||
import { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
|
import { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
|
||||||
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
||||||
import { LongTextColumn1722301395521 } from './1722301395521-LongTextColumn'
|
import { LongTextColumn1722301395521 } from './1722301395521-LongTextColumn'
|
||||||
|
import { AddCustomTemplate1725629836652 } from './1725629836652-AddCustomTemplate'
|
||||||
|
|
||||||
export const mysqlMigrations = [
|
export const mysqlMigrations = [
|
||||||
Init1693840429259,
|
Init1693840429259,
|
||||||
|
|
@ -51,5 +52,6 @@ export const mysqlMigrations = [
|
||||||
AddVectorStoreConfigToDocStore1715861032479,
|
AddVectorStoreConfigToDocStore1715861032479,
|
||||||
AddApiKey1720230151480,
|
AddApiKey1720230151480,
|
||||||
AddActionToChatMessage1721078251523,
|
AddActionToChatMessage1721078251523,
|
||||||
LongTextColumn1722301395521
|
LongTextColumn1722301395521,
|
||||||
|
AddCustomTemplate1725629836652
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddCustomTemplate1725629836652 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE IF NOT EXISTS custom_template (
|
||||||
|
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||||
|
"name" varchar NOT NULL,
|
||||||
|
"flowData" text NOT NULL,
|
||||||
|
"description" varchar NULL,
|
||||||
|
"badge" varchar NULL,
|
||||||
|
"framework" varchar NULL,
|
||||||
|
"usecases" varchar NULL,
|
||||||
|
"type" varchar NULL,
|
||||||
|
"createdDate" timestamp NOT NULL DEFAULT now(),
|
||||||
|
"updatedDate" timestamp NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT "PK_3c7cea7d087ac4b91764574cdbf" PRIMARY KEY (id)
|
||||||
|
);`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE custom_template`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ import { AddAgentReasoningToChatMessage1714679514451 } from './1714679514451-Add
|
||||||
import { AddTypeToChatFlow1716300000000 } from './1716300000000-AddTypeToChatFlow'
|
import { AddTypeToChatFlow1716300000000 } from './1716300000000-AddTypeToChatFlow'
|
||||||
import { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
|
import { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
|
||||||
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
||||||
|
import { AddCustomTemplate1725629836652 } from './1725629836652-AddCustomTemplate'
|
||||||
|
|
||||||
export const postgresMigrations = [
|
export const postgresMigrations = [
|
||||||
Init1693891895163,
|
Init1693891895163,
|
||||||
|
|
@ -51,5 +52,6 @@ export const postgresMigrations = [
|
||||||
AddTypeToChatFlow1716300000000,
|
AddTypeToChatFlow1716300000000,
|
||||||
AddVectorStoreConfigToDocStore1715861032479,
|
AddVectorStoreConfigToDocStore1715861032479,
|
||||||
AddApiKey1720230151480,
|
AddApiKey1720230151480,
|
||||||
AddActionToChatMessage1721078251523
|
AddActionToChatMessage1721078251523,
|
||||||
|
AddCustomTemplate1725629836652
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddCustomTemplate1725629836652 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE IF NOT EXISTS "custom_template" (
|
||||||
|
"id" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"name" varchar NOT NULL,
|
||||||
|
"flowData" text NOT NULL,
|
||||||
|
"description" varchar,
|
||||||
|
"badge" varchar,
|
||||||
|
"framework" varchar,
|
||||||
|
"usecases" varchar,
|
||||||
|
"type" varchar,
|
||||||
|
"updatedDate" datetime NOT NULL DEFAULT (datetime('now')),
|
||||||
|
"createdDate" datetime NOT NULL DEFAULT (datetime('now')));`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE IF EXISTS "custom_template";`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ import { AddAgentReasoningToChatMessage1714679514451 } from './1714679514451-Add
|
||||||
import { AddTypeToChatFlow1716300000000 } from './1716300000000-AddTypeToChatFlow'
|
import { AddTypeToChatFlow1716300000000 } from './1716300000000-AddTypeToChatFlow'
|
||||||
import { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
|
import { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
|
||||||
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
||||||
|
import { AddCustomTemplate1725629836652 } from './1725629836652-AddCustomTemplate'
|
||||||
|
|
||||||
export const sqliteMigrations = [
|
export const sqliteMigrations = [
|
||||||
Init1693835579790,
|
Init1693835579790,
|
||||||
|
|
@ -49,5 +50,6 @@ export const sqliteMigrations = [
|
||||||
AddTypeToChatFlow1716300000000,
|
AddTypeToChatFlow1716300000000,
|
||||||
AddVectorStoreConfigToDocStore1715861032479,
|
AddVectorStoreConfigToDocStore1715861032479,
|
||||||
AddApiKey1720230151480,
|
AddApiKey1720230151480,
|
||||||
AddActionToChatMessage1721078251523
|
AddActionToChatMessage1721078251523,
|
||||||
|
AddCustomTemplate1725629836652
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,12 @@ const router = express.Router()
|
||||||
// READ
|
// READ
|
||||||
router.get('/templates', marketplacesController.getAllTemplates)
|
router.get('/templates', marketplacesController.getAllTemplates)
|
||||||
|
|
||||||
|
router.post('/custom', marketplacesController.saveCustomTemplate)
|
||||||
|
|
||||||
|
// READ
|
||||||
|
router.get('/custom', marketplacesController.getAllCustomTemplates)
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
router.delete(['/', '/custom/:id'], marketplacesController.deleteCustomTemplate)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,11 @@ import { StatusCodes } from 'http-status-codes'
|
||||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||||
import { getErrorMessage } from '../../errors/utils'
|
import { getErrorMessage } from '../../errors/utils'
|
||||||
import { IReactFlowEdge, IReactFlowNode } from '../../Interface'
|
import { IReactFlowEdge, IReactFlowNode } from '../../Interface'
|
||||||
|
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||||
|
import { DeleteResult } from 'typeorm'
|
||||||
|
import { CustomTemplate } from '../../database/entities/CustomTemplate'
|
||||||
|
|
||||||
|
import chatflowsService from '../chatflows'
|
||||||
|
|
||||||
type ITemplate = {
|
type ITemplate = {
|
||||||
badge: string
|
badge: string
|
||||||
|
|
@ -96,6 +101,148 @@ const getAllTemplates = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
const deleteCustomTemplate = async (templateId: string): Promise<DeleteResult> => {
|
||||||
getAllTemplates
|
try {
|
||||||
|
const appServer = getRunningExpressApp()
|
||||||
|
return await appServer.AppDataSource.getRepository(CustomTemplate).delete({ id: templateId })
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
`Error: marketplacesService.deleteCustomTemplate - ${getErrorMessage(error)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllCustomTemplates = async (): Promise<any> => {
|
||||||
|
try {
|
||||||
|
const appServer = getRunningExpressApp()
|
||||||
|
const templates: any[] = await appServer.AppDataSource.getRepository(CustomTemplate).find()
|
||||||
|
templates.map((template) => {
|
||||||
|
template.usecases = template.usecases ? JSON.parse(template.usecases) : ''
|
||||||
|
if (template.type === 'Tool') {
|
||||||
|
template.flowData = JSON.parse(template.flowData)
|
||||||
|
template.iconSrc = template.flowData.iconSrc
|
||||||
|
template.schema = template.flowData.schema
|
||||||
|
template.func = template.flowData.func
|
||||||
|
template.categories = []
|
||||||
|
template.flowData = undefined
|
||||||
|
} else {
|
||||||
|
template.categories = getCategories(JSON.parse(template.flowData))
|
||||||
|
}
|
||||||
|
if (!template.badge) {
|
||||||
|
template.badge = ''
|
||||||
|
}
|
||||||
|
if (!template.framework) {
|
||||||
|
template.framework = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return templates
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
`Error: marketplacesService.getAllCustomTemplates - ${getErrorMessage(error)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveCustomTemplate = async (body: any): Promise<any> => {
|
||||||
|
try {
|
||||||
|
const appServer = getRunningExpressApp()
|
||||||
|
let flowDataStr = ''
|
||||||
|
let derivedFramework = ''
|
||||||
|
const customTemplate = new CustomTemplate()
|
||||||
|
Object.assign(customTemplate, body)
|
||||||
|
|
||||||
|
if (body.chatflowId) {
|
||||||
|
const chatflow = await chatflowsService.getChatflowById(body.chatflowId)
|
||||||
|
const flowData = JSON.parse(chatflow.flowData)
|
||||||
|
const { framework, exportJson } = _generateExportFlowData(flowData)
|
||||||
|
flowDataStr = JSON.stringify(exportJson)
|
||||||
|
customTemplate.framework = framework
|
||||||
|
} else if (body.tool) {
|
||||||
|
const flowData = {
|
||||||
|
iconSrc: body.tool.iconSrc,
|
||||||
|
schema: body.tool.schema,
|
||||||
|
func: body.tool.func
|
||||||
|
}
|
||||||
|
customTemplate.framework = ''
|
||||||
|
customTemplate.type = 'Tool'
|
||||||
|
flowDataStr = JSON.stringify(flowData)
|
||||||
|
}
|
||||||
|
customTemplate.framework = derivedFramework
|
||||||
|
if (customTemplate.usecases) {
|
||||||
|
customTemplate.usecases = JSON.stringify(customTemplate.usecases)
|
||||||
|
}
|
||||||
|
const entity = appServer.AppDataSource.getRepository(CustomTemplate).create(customTemplate)
|
||||||
|
entity.flowData = flowDataStr
|
||||||
|
const flowTemplate = await appServer.AppDataSource.getRepository(CustomTemplate).save(entity)
|
||||||
|
return flowTemplate
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
`Error: marketplacesService.saveCustomTemplate - ${getErrorMessage(error)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _generateExportFlowData = (flowData: any) => {
|
||||||
|
const nodes = flowData.nodes
|
||||||
|
const edges = flowData.edges
|
||||||
|
|
||||||
|
let framework = 'Langchain'
|
||||||
|
for (let i = 0; i < nodes.length; i += 1) {
|
||||||
|
nodes[i].selected = false
|
||||||
|
const node = nodes[i]
|
||||||
|
|
||||||
|
const newNodeData = {
|
||||||
|
id: node.data.id,
|
||||||
|
label: node.data.label,
|
||||||
|
version: node.data.version,
|
||||||
|
name: node.data.name,
|
||||||
|
type: node.data.type,
|
||||||
|
baseClasses: node.data.baseClasses,
|
||||||
|
tags: node.data.tags,
|
||||||
|
category: node.data.category,
|
||||||
|
description: node.data.description,
|
||||||
|
inputParams: node.data.inputParams,
|
||||||
|
inputAnchors: node.data.inputAnchors,
|
||||||
|
inputs: {},
|
||||||
|
outputAnchors: node.data.outputAnchors,
|
||||||
|
outputs: node.data.outputs,
|
||||||
|
selected: false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.data.tags && node.data.tags.length) {
|
||||||
|
if (node.data.tags.includes('LlamaIndex')) {
|
||||||
|
framework = 'LlamaIndex'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove password, file & folder
|
||||||
|
if (node.data.inputs && Object.keys(node.data.inputs).length) {
|
||||||
|
const nodeDataInputs: any = {}
|
||||||
|
for (const input in node.data.inputs) {
|
||||||
|
const inputParam = node.data.inputParams.find((inp: any) => inp.name === input)
|
||||||
|
if (inputParam && inputParam.type === 'password') continue
|
||||||
|
if (inputParam && inputParam.type === 'file') continue
|
||||||
|
if (inputParam && inputParam.type === 'folder') continue
|
||||||
|
nodeDataInputs[input] = node.data.inputs[input]
|
||||||
|
}
|
||||||
|
newNodeData.inputs = nodeDataInputs
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes[i].data = newNodeData
|
||||||
|
}
|
||||||
|
const exportJson = {
|
||||||
|
nodes,
|
||||||
|
edges
|
||||||
|
}
|
||||||
|
return { exportJson, framework }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getAllTemplates,
|
||||||
|
getAllCustomTemplates,
|
||||||
|
saveCustomTemplate,
|
||||||
|
deleteCustomTemplate
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,16 @@ const getAllChatflowsMarketplaces = () => client.get('/marketplaces/chatflows')
|
||||||
const getAllToolsMarketplaces = () => client.get('/marketplaces/tools')
|
const getAllToolsMarketplaces = () => client.get('/marketplaces/tools')
|
||||||
const getAllTemplatesFromMarketplaces = () => client.get('/marketplaces/templates')
|
const getAllTemplatesFromMarketplaces = () => client.get('/marketplaces/templates')
|
||||||
|
|
||||||
|
const getAllCustomTemplates = () => client.get('/marketplaces/custom')
|
||||||
|
const saveAsCustomTemplate = (body) => client.post('/marketplaces/custom', body)
|
||||||
|
const deleteCustomTemplate = (id) => client.delete(`/marketplaces/custom/${id}`)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getAllChatflowsMarketplaces,
|
getAllChatflowsMarketplaces,
|
||||||
getAllToolsMarketplaces,
|
getAllToolsMarketplaces,
|
||||||
getAllTemplatesFromMarketplaces
|
getAllTemplatesFromMarketplaces,
|
||||||
|
|
||||||
|
getAllCustomTemplates,
|
||||||
|
saveAsCustomTemplate,
|
||||||
|
deleteCustomTemplate
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ import {
|
||||||
IconMessage,
|
IconMessage,
|
||||||
IconDatabaseExport,
|
IconDatabaseExport,
|
||||||
IconAdjustmentsHorizontal,
|
IconAdjustmentsHorizontal,
|
||||||
IconUsers
|
IconUsers,
|
||||||
|
IconTemplate
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
|
|
||||||
// constant
|
// constant
|
||||||
|
|
@ -19,7 +20,8 @@ const icons = {
|
||||||
IconMessage,
|
IconMessage,
|
||||||
IconDatabaseExport,
|
IconDatabaseExport,
|
||||||
IconAdjustmentsHorizontal,
|
IconAdjustmentsHorizontal,
|
||||||
IconUsers
|
IconUsers,
|
||||||
|
IconTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
||||||
|
|
@ -50,6 +52,13 @@ const agent_settings = {
|
||||||
url: '',
|
url: '',
|
||||||
icon: icons.IconAdjustmentsHorizontal
|
icon: icons.IconAdjustmentsHorizontal
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'saveAsTemplate',
|
||||||
|
title: 'Save As Template',
|
||||||
|
type: 'item',
|
||||||
|
url: '',
|
||||||
|
icon: icons.IconTemplate
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'duplicateChatflow',
|
id: 'duplicateChatflow',
|
||||||
title: 'Duplicate Agents',
|
title: 'Duplicate Agents',
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ import {
|
||||||
IconMessage,
|
IconMessage,
|
||||||
IconDatabaseExport,
|
IconDatabaseExport,
|
||||||
IconAdjustmentsHorizontal,
|
IconAdjustmentsHorizontal,
|
||||||
IconUsers
|
IconUsers,
|
||||||
|
IconTemplate
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
|
|
||||||
// constant
|
// constant
|
||||||
|
|
@ -19,7 +20,8 @@ const icons = {
|
||||||
IconMessage,
|
IconMessage,
|
||||||
IconDatabaseExport,
|
IconDatabaseExport,
|
||||||
IconAdjustmentsHorizontal,
|
IconAdjustmentsHorizontal,
|
||||||
IconUsers
|
IconUsers,
|
||||||
|
IconTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
||||||
|
|
@ -57,6 +59,13 @@ const settings = {
|
||||||
url: '',
|
url: '',
|
||||||
icon: icons.IconAdjustmentsHorizontal
|
icon: icons.IconAdjustmentsHorizontal
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'saveAsTemplate',
|
||||||
|
title: 'Save As Template',
|
||||||
|
type: 'item',
|
||||||
|
url: '',
|
||||||
|
icon: icons.IconTemplate
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'duplicateChatflow',
|
id: 'duplicateChatflow',
|
||||||
title: 'Duplicate Chatflow',
|
title: 'Duplicate Chatflow',
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import PictureInPictureAltIcon from '@mui/icons-material/PictureInPictureAlt'
|
||||||
import ThumbsUpDownOutlinedIcon from '@mui/icons-material/ThumbsUpDownOutlined'
|
import ThumbsUpDownOutlinedIcon from '@mui/icons-material/ThumbsUpDownOutlined'
|
||||||
import VpnLockOutlinedIcon from '@mui/icons-material/VpnLockOutlined'
|
import VpnLockOutlinedIcon from '@mui/icons-material/VpnLockOutlined'
|
||||||
import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined'
|
import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined'
|
||||||
|
import ExportTemplateOutlinedIcon from '@mui/icons-material/BookmarksOutlined'
|
||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
|
||||||
import { IconX } from '@tabler/icons-react'
|
import { IconX } from '@tabler/icons-react'
|
||||||
|
|
@ -35,6 +36,7 @@ import useNotifier from '@/utils/useNotifier'
|
||||||
import ChatFeedbackDialog from '../dialog/ChatFeedbackDialog'
|
import ChatFeedbackDialog from '../dialog/ChatFeedbackDialog'
|
||||||
import AllowedDomainsDialog from '../dialog/AllowedDomainsDialog'
|
import AllowedDomainsDialog from '../dialog/AllowedDomainsDialog'
|
||||||
import SpeechToTextDialog from '../dialog/SpeechToTextDialog'
|
import SpeechToTextDialog from '../dialog/SpeechToTextDialog'
|
||||||
|
import ExportAsTemplateDialog from '@/ui-component/dialog/ExportAsTemplateDialog'
|
||||||
|
|
||||||
const StyledMenu = styled((props) => (
|
const StyledMenu = styled((props) => (
|
||||||
<Menu
|
<Menu
|
||||||
|
|
@ -95,6 +97,9 @@ export default function FlowListMenu({ chatflow, isAgentCanvas, setError, update
|
||||||
const [speechToTextDialogOpen, setSpeechToTextDialogOpen] = useState(false)
|
const [speechToTextDialogOpen, setSpeechToTextDialogOpen] = useState(false)
|
||||||
const [speechToTextDialogProps, setSpeechToTextDialogProps] = useState({})
|
const [speechToTextDialogProps, setSpeechToTextDialogProps] = useState({})
|
||||||
|
|
||||||
|
const [exportTemplateDialogOpen, setExportTemplateDialogOpen] = useState(false)
|
||||||
|
const [exportTemplateDialogProps, setExportTemplateDialogProps] = useState({})
|
||||||
|
|
||||||
const title = isAgentCanvas ? 'Agents' : 'Chatflow'
|
const title = isAgentCanvas ? 'Agents' : 'Chatflow'
|
||||||
|
|
||||||
const handleClick = (event) => {
|
const handleClick = (event) => {
|
||||||
|
|
@ -119,6 +124,14 @@ export default function FlowListMenu({ chatflow, isAgentCanvas, setError, update
|
||||||
setConversationStartersDialogOpen(true)
|
setConversationStartersDialogOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleExportTemplate = () => {
|
||||||
|
setAnchorEl(null)
|
||||||
|
setExportTemplateDialogProps({
|
||||||
|
chatflow: chatflow
|
||||||
|
})
|
||||||
|
setExportTemplateDialogOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
const handleFlowChatFeedback = () => {
|
const handleFlowChatFeedback = () => {
|
||||||
setAnchorEl(null)
|
setAnchorEl(null)
|
||||||
setChatFeedbackDialogProps({
|
setChatFeedbackDialogProps({
|
||||||
|
|
@ -306,6 +319,10 @@ export default function FlowListMenu({ chatflow, isAgentCanvas, setError, update
|
||||||
<FileDownloadIcon />
|
<FileDownloadIcon />
|
||||||
Export
|
Export
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem onClick={handleExportTemplate} disableRipple>
|
||||||
|
<ExportTemplateOutlinedIcon />
|
||||||
|
Save As Template
|
||||||
|
</MenuItem>
|
||||||
<Divider sx={{ my: 0.5 }} />
|
<Divider sx={{ my: 0.5 }} />
|
||||||
<MenuItem onClick={handleFlowStarterPrompts} disableRipple>
|
<MenuItem onClick={handleFlowStarterPrompts} disableRipple>
|
||||||
<PictureInPictureAltIcon />
|
<PictureInPictureAltIcon />
|
||||||
|
|
@ -369,6 +386,13 @@ export default function FlowListMenu({ chatflow, isAgentCanvas, setError, update
|
||||||
dialogProps={speechToTextDialogProps}
|
dialogProps={speechToTextDialogProps}
|
||||||
onCancel={() => setSpeechToTextDialogOpen(false)}
|
onCancel={() => setSpeechToTextDialogOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
{exportTemplateDialogOpen && (
|
||||||
|
<ExportAsTemplateDialog
|
||||||
|
show={exportTemplateDialogOpen}
|
||||||
|
dialogProps={exportTemplateDialogProps}
|
||||||
|
onCancel={() => setExportTemplateDialogOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,282 @@
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, OutlinedInput, Typography } from '@mui/material'
|
||||||
|
|
||||||
|
// store
|
||||||
|
import {
|
||||||
|
closeSnackbar as closeSnackbarAction,
|
||||||
|
enqueueSnackbar as enqueueSnackbarAction,
|
||||||
|
HIDE_CANVAS_DIALOG,
|
||||||
|
SHOW_CANVAS_DIALOG
|
||||||
|
} from '@/store/actions'
|
||||||
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||||
|
import Chip from '@mui/material/Chip'
|
||||||
|
import { IconX } from '@tabler/icons-react'
|
||||||
|
|
||||||
|
// API
|
||||||
|
import marketplacesApi from '@/api/marketplaces'
|
||||||
|
import useApi from '@/hooks/useApi'
|
||||||
|
|
||||||
|
// Project imports
|
||||||
|
|
||||||
|
const ExportAsTemplateDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
|
const portalElement = document.getElementById('portal')
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const [name, setName] = useState('')
|
||||||
|
const [flowType, setFlowType] = useState('')
|
||||||
|
const [description, setDescription] = useState('')
|
||||||
|
const [badge, setBadge] = useState('')
|
||||||
|
const [usecases, setUsecases] = useState([])
|
||||||
|
const [usecaseInput, setUsecaseInput] = useState('')
|
||||||
|
|
||||||
|
const saveCustomTemplateApi = useApi(marketplacesApi.saveAsCustomTemplate)
|
||||||
|
|
||||||
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
|
||||||
|
useNotifier()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dialogProps.chatflow) {
|
||||||
|
setName(dialogProps.chatflow.name)
|
||||||
|
setFlowType(dialogProps.chatflow.type === 'MULTIAGENT' ? 'Agentflow' : 'Chatflow')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialogProps.tool) {
|
||||||
|
setName(dialogProps.tool.name)
|
||||||
|
setDescription(dialogProps.tool.description)
|
||||||
|
setFlowType('Tool')
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setName('')
|
||||||
|
setDescription('')
|
||||||
|
setBadge('')
|
||||||
|
setUsecases([])
|
||||||
|
setFlowType('')
|
||||||
|
setUsecaseInput('')
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [dialogProps])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||||
|
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
|
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
|
}, [show, dispatch])
|
||||||
|
|
||||||
|
const handleUsecaseInputChange = (event) => {
|
||||||
|
setUsecaseInput(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUsecaseInputKeyDown = (event) => {
|
||||||
|
if (event.key === 'Enter' && usecaseInput.trim()) {
|
||||||
|
event.preventDefault()
|
||||||
|
if (!usecases.includes(usecaseInput)) {
|
||||||
|
setUsecases([...usecases, usecaseInput])
|
||||||
|
setUsecaseInput('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUsecaseDelete = (toDelete) => {
|
||||||
|
setUsecases(usecases.filter((category) => category !== toDelete))
|
||||||
|
}
|
||||||
|
|
||||||
|
const onConfirm = () => {
|
||||||
|
if (name.trim() === '') {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'Template Name is mandatory!',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
badge: badge ? badge.toUpperCase() : undefined,
|
||||||
|
usecases,
|
||||||
|
type: flowType
|
||||||
|
}
|
||||||
|
if (dialogProps.chatflow) {
|
||||||
|
template.chatflowId = dialogProps.chatflow.id
|
||||||
|
}
|
||||||
|
if (dialogProps.tool) {
|
||||||
|
template.tool = {
|
||||||
|
iconSrc: dialogProps.tool.iconSrc,
|
||||||
|
schema: dialogProps.tool.schema,
|
||||||
|
func: dialogProps.tool.func
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saveCustomTemplateApi.request(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (saveCustomTemplateApi.data) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'Saved as template successfully!',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'success',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onCancel()
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [saveCustomTemplateApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (saveCustomTemplateApi.error) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'Failed to save as template!',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onCancel()
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [saveCustomTemplateApi.error])
|
||||||
|
|
||||||
|
const component = show ? (
|
||||||
|
<Dialog
|
||||||
|
onClose={onCancel}
|
||||||
|
open={show}
|
||||||
|
fullWidth
|
||||||
|
maxWidth='sm'
|
||||||
|
aria-labelledby='alert-dialog-title'
|
||||||
|
aria-describedby='alert-dialog-description'
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||||
|
{dialogProps.title || 'Export As Template'}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Box sx={{ pt: 2, pb: 2 }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
|
||||||
|
<Typography sx={{ mb: 1 }}>
|
||||||
|
Name<span style={{ color: 'red' }}> *</span>
|
||||||
|
</Typography>
|
||||||
|
<OutlinedInput
|
||||||
|
id={'name'}
|
||||||
|
type={'string'}
|
||||||
|
fullWidth
|
||||||
|
value={name}
|
||||||
|
name='name'
|
||||||
|
size='small'
|
||||||
|
onChange={(e) => {
|
||||||
|
setName(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ pt: 2, pb: 2 }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
|
||||||
|
<Typography sx={{ mb: 1 }}>Description</Typography>
|
||||||
|
<OutlinedInput
|
||||||
|
id={'description'}
|
||||||
|
type={'string'}
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={2}
|
||||||
|
value={description}
|
||||||
|
name='description'
|
||||||
|
size='small'
|
||||||
|
onChange={(e) => {
|
||||||
|
setDescription(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ pt: 2, pb: 2 }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
|
||||||
|
<Typography sx={{ mb: 1 }}>Badge</Typography>
|
||||||
|
<OutlinedInput
|
||||||
|
id={'badge'}
|
||||||
|
type={'string'}
|
||||||
|
fullWidth
|
||||||
|
value={badge}
|
||||||
|
name='badge'
|
||||||
|
size='small'
|
||||||
|
onChange={(e) => {
|
||||||
|
setBadge(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ pt: 2, pb: 2 }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
|
||||||
|
<Typography sx={{ mb: 1 }}>Usecases</Typography>
|
||||||
|
{usecases.length > 0 && (
|
||||||
|
<div style={{ marginBottom: 10 }}>
|
||||||
|
{usecases.map((uc, index) => (
|
||||||
|
<Chip
|
||||||
|
key={index}
|
||||||
|
label={uc}
|
||||||
|
onDelete={() => handleUsecaseDelete(uc)}
|
||||||
|
style={{ marginRight: 5, marginBottom: 5 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<OutlinedInput
|
||||||
|
fullWidth
|
||||||
|
value={usecaseInput}
|
||||||
|
onChange={handleUsecaseInputChange}
|
||||||
|
onKeyDown={handleUsecaseInputKeyDown}
|
||||||
|
variant='outlined'
|
||||||
|
/>
|
||||||
|
<Typography variant='body2' sx={{ fontStyle: 'italic', mt: 1 }} color='text.secondary'>
|
||||||
|
Type a usecase and press enter to add it to the list. You can add as many items as you want.
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onCancel}>{dialogProps.cancelButtonName || 'Cancel'}</Button>
|
||||||
|
<StyledButton disabled={dialogProps.disabled} variant='contained' onClick={onConfirm}>
|
||||||
|
{dialogProps.confirmButtonName || 'Save Template'}
|
||||||
|
</StyledButton>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
return createPortal(component, portalElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExportAsTemplateDialog.propTypes = {
|
||||||
|
show: PropTypes.bool,
|
||||||
|
dialogProps: PropTypes.object,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
onConfirm: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExportAsTemplateDialog
|
||||||
|
|
@ -15,8 +15,10 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
Typography,
|
Typography,
|
||||||
Stack,
|
Stack,
|
||||||
useTheme
|
useTheme,
|
||||||
|
IconButton
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
|
import { IconTrash } from '@tabler/icons-react'
|
||||||
|
|
||||||
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
||||||
borderColor: theme.palette.grey[900] + 25,
|
borderColor: theme.palette.grey[900] + 25,
|
||||||
|
|
@ -46,7 +48,8 @@ export const MarketplaceTable = ({
|
||||||
filterByUsecases,
|
filterByUsecases,
|
||||||
goToCanvas,
|
goToCanvas,
|
||||||
goToTool,
|
goToTool,
|
||||||
isLoading
|
isLoading,
|
||||||
|
onDelete
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const customization = useSelector((state) => state.customization)
|
const customization = useSelector((state) => state.customization)
|
||||||
|
|
@ -87,6 +90,11 @@ export const MarketplaceTable = ({
|
||||||
<StyledTableCell component='th' scope='row' key='6'>
|
<StyledTableCell component='th' scope='row' key='6'>
|
||||||
|
|
||||||
</StyledTableCell>
|
</StyledTableCell>
|
||||||
|
{onDelete && (
|
||||||
|
<StyledTableCell component='th' scope='row' key='7'>
|
||||||
|
Delete
|
||||||
|
</StyledTableCell>
|
||||||
|
)}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
|
@ -114,6 +122,11 @@ export const MarketplaceTable = ({
|
||||||
<StyledTableCell>
|
<StyledTableCell>
|
||||||
<Skeleton variant='text' />
|
<Skeleton variant='text' />
|
||||||
</StyledTableCell>
|
</StyledTableCell>
|
||||||
|
{onDelete && (
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
)}
|
||||||
</StyledTableRow>
|
</StyledTableRow>
|
||||||
<StyledTableRow>
|
<StyledTableRow>
|
||||||
<StyledTableCell>
|
<StyledTableCell>
|
||||||
|
|
@ -137,6 +150,11 @@ export const MarketplaceTable = ({
|
||||||
<StyledTableCell>
|
<StyledTableCell>
|
||||||
<Skeleton variant='text' />
|
<Skeleton variant='text' />
|
||||||
</StyledTableCell>
|
</StyledTableCell>
|
||||||
|
{onDelete && (
|
||||||
|
<StyledTableCell>
|
||||||
|
<Skeleton variant='text' />
|
||||||
|
</StyledTableCell>
|
||||||
|
)}
|
||||||
</StyledTableRow>
|
</StyledTableRow>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -234,6 +252,13 @@ export const MarketplaceTable = ({
|
||||||
))}
|
))}
|
||||||
</Typography>
|
</Typography>
|
||||||
</StyledTableCell>
|
</StyledTableCell>
|
||||||
|
{onDelete && (
|
||||||
|
<StyledTableCell key='7'>
|
||||||
|
<IconButton title='Delete' color='error' onClick={() => onDelete(row)}>
|
||||||
|
<IconTrash />
|
||||||
|
</IconButton>
|
||||||
|
</StyledTableCell>
|
||||||
|
)}
|
||||||
</StyledTableRow>
|
</StyledTableRow>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|
@ -254,5 +279,6 @@ MarketplaceTable.propTypes = {
|
||||||
filterByUsecases: PropTypes.func,
|
filterByUsecases: PropTypes.func,
|
||||||
goToTool: PropTypes.func,
|
goToTool: PropTypes.func,
|
||||||
goToCanvas: PropTypes.func,
|
goToCanvas: PropTypes.func,
|
||||||
isLoading: PropTypes.bool
|
isLoading: PropTypes.bool,
|
||||||
|
onDelete: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import { useTheme } from '@mui/material/styles'
|
import { useTheme } from '@mui/material/styles'
|
||||||
import { Avatar, Box, ButtonBase, Typography, Stack, TextField } from '@mui/material'
|
import { Avatar, Box, ButtonBase, Typography, Stack, TextField, Button } from '@mui/material'
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck, IconX, IconCode } from '@tabler/icons-react'
|
import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck, IconX, IconCode } from '@tabler/icons-react'
|
||||||
|
|
@ -27,8 +27,9 @@ import useApi from '@/hooks/useApi'
|
||||||
// utils
|
// utils
|
||||||
import { generateExportFlowData } from '@/utils/genericHelper'
|
import { generateExportFlowData } from '@/utils/genericHelper'
|
||||||
import { uiBaseURL } from '@/store/constant'
|
import { uiBaseURL } from '@/store/constant'
|
||||||
import { SET_CHATFLOW } from '@/store/actions'
|
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||||
import ViewLeadsDialog from '@/ui-component/dialog/ViewLeadsDialog'
|
import ViewLeadsDialog from '@/ui-component/dialog/ViewLeadsDialog'
|
||||||
|
import ExportAsTemplateDialog from '@/ui-component/dialog/ExportAsTemplateDialog'
|
||||||
|
|
||||||
// ==============================|| CANVAS HEADER ||============================== //
|
// ==============================|| CANVAS HEADER ||============================== //
|
||||||
|
|
||||||
|
|
@ -54,6 +55,11 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, handleSaveFlow, handleDeleteFlo
|
||||||
const [chatflowConfigurationDialogOpen, setChatflowConfigurationDialogOpen] = useState(false)
|
const [chatflowConfigurationDialogOpen, setChatflowConfigurationDialogOpen] = useState(false)
|
||||||
const [chatflowConfigurationDialogProps, setChatflowConfigurationDialogProps] = useState({})
|
const [chatflowConfigurationDialogProps, setChatflowConfigurationDialogProps] = useState({})
|
||||||
|
|
||||||
|
const [exportAsTemplateDialogOpen, setExportAsTemplateDialogOpen] = useState(false)
|
||||||
|
const [exportAsTemplateDialogProps, setExportAsTemplateDialogProps] = useState({})
|
||||||
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
|
||||||
const title = isAgentCanvas ? 'Agents' : 'Chatflow'
|
const title = isAgentCanvas ? 'Agents' : 'Chatflow'
|
||||||
|
|
||||||
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
||||||
|
|
@ -76,6 +82,28 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, handleSaveFlow, handleDeleteFlo
|
||||||
chatflow: chatflow
|
chatflow: chatflow
|
||||||
})
|
})
|
||||||
setViewLeadsDialogOpen(true)
|
setViewLeadsDialogOpen(true)
|
||||||
|
} else if (setting === 'saveAsTemplate') {
|
||||||
|
if (canvas.isDirty) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'Please save the flow before exporting as template',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setExportAsTemplateDialogProps({
|
||||||
|
title: 'Export As Template',
|
||||||
|
chatflow: chatflow
|
||||||
|
})
|
||||||
|
setExportAsTemplateDialogOpen(true)
|
||||||
} else if (setting === 'viewUpsertHistory') {
|
} else if (setting === 'viewUpsertHistory') {
|
||||||
setUpsertHistoryDialogProps({
|
setUpsertHistoryDialogProps({
|
||||||
title: 'View Upsert History',
|
title: 'View Upsert History',
|
||||||
|
|
@ -419,6 +447,13 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, handleSaveFlow, handleDeleteFlo
|
||||||
onCancel={() => setViewMessagesDialogOpen(false)}
|
onCancel={() => setViewMessagesDialogOpen(false)}
|
||||||
/>
|
/>
|
||||||
<ViewLeadsDialog show={viewLeadsDialogOpen} dialogProps={viewLeadsDialogProps} onCancel={() => setViewLeadsDialogOpen(false)} />
|
<ViewLeadsDialog show={viewLeadsDialogOpen} dialogProps={viewLeadsDialogProps} onCancel={() => setViewLeadsDialogOpen(false)} />
|
||||||
|
{exportAsTemplateDialogOpen && (
|
||||||
|
<ExportAsTemplateDialog
|
||||||
|
show={exportAsTemplateDialogOpen}
|
||||||
|
dialogProps={exportAsTemplateDialogProps}
|
||||||
|
onCancel={() => setExportAsTemplateDialogOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<UpsertHistoryDialog
|
<UpsertHistoryDialog
|
||||||
show={upsertHistoryDialogOpen}
|
show={upsertHistoryDialogOpen}
|
||||||
dialogProps={upsertHistoryDialogProps}
|
dialogProps={upsertHistoryDialogProps}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import PropTypes from 'prop-types'
|
import { useDispatch } from 'react-redux'
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
import {
|
import {
|
||||||
|
|
@ -19,7 +19,9 @@ import {
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
ToggleButtonGroup,
|
ToggleButtonGroup,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Button
|
Button,
|
||||||
|
Tabs,
|
||||||
|
Tab
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useTheme } from '@mui/material/styles'
|
import { useTheme } from '@mui/material/styles'
|
||||||
import { IconLayoutGrid, IconList, IconX } from '@tabler/icons-react'
|
import { IconLayoutGrid, IconList, IconX } from '@tabler/icons-react'
|
||||||
|
|
@ -32,37 +34,21 @@ import ToolDialog from '@/views/tools/ToolDialog'
|
||||||
import { MarketplaceTable } from '@/ui-component/table/MarketplaceTable'
|
import { MarketplaceTable } from '@/ui-component/table/MarketplaceTable'
|
||||||
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||||
import ErrorBoundary from '@/ErrorBoundary'
|
import ErrorBoundary from '@/ErrorBoundary'
|
||||||
|
import { TabPanel } from '@/ui-component/tabs/TabPanel'
|
||||||
|
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions'
|
||||||
|
import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog'
|
||||||
|
|
||||||
// API
|
// API
|
||||||
import marketplacesApi from '@/api/marketplaces'
|
import marketplacesApi from '@/api/marketplaces'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import useApi from '@/hooks/useApi'
|
import useApi from '@/hooks/useApi'
|
||||||
|
import useConfirm from '@/hooks/useConfirm'
|
||||||
|
|
||||||
// const
|
// const
|
||||||
import { baseURL } from '@/store/constant'
|
import { baseURL } from '@/store/constant'
|
||||||
import { gridSpacing } from '@/store/constant'
|
import { gridSpacing } from '@/store/constant'
|
||||||
|
import useNotifier from '@/utils/useNotifier'
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
const badges = ['POPULAR', 'NEW']
|
const badges = ['POPULAR', 'NEW']
|
||||||
const types = ['Chatflow', 'Agentflow', 'Tool']
|
const types = ['Chatflow', 'Agentflow', 'Tool']
|
||||||
|
|
@ -83,6 +69,8 @@ const SelectStyles = {
|
||||||
|
|
||||||
const Marketplace = () => {
|
const Marketplace = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
useNotifier()
|
||||||
|
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
|
||||||
|
|
@ -104,8 +92,26 @@ const Marketplace = () => {
|
||||||
const [typeFilter, setTypeFilter] = useState([])
|
const [typeFilter, setTypeFilter] = useState([])
|
||||||
const [frameworkFilter, setFrameworkFilter] = useState([])
|
const [frameworkFilter, setFrameworkFilter] = useState([])
|
||||||
|
|
||||||
|
const getAllCustomTemplatesApi = useApi(marketplacesApi.getAllCustomTemplates)
|
||||||
|
const [activeTabValue, setActiveTabValue] = useState(0)
|
||||||
|
const [templateImages, setTemplateImages] = useState({})
|
||||||
|
const [templateUsecases, setTemplateUsecases] = useState([])
|
||||||
|
const [eligibleTemplateUsecases, setEligibleTemplateUsecases] = useState([])
|
||||||
|
const [selectedTemplateUsecases, setSelectedTemplateUsecases] = useState([])
|
||||||
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
const { confirm } = useConfirm()
|
||||||
|
|
||||||
|
const handleTabChange = (event, newValue) => {
|
||||||
|
if (newValue === 1 && !getAllCustomTemplatesApi.data) {
|
||||||
|
getAllCustomTemplatesApi.request()
|
||||||
|
}
|
||||||
|
setActiveTabValue(newValue)
|
||||||
|
}
|
||||||
|
|
||||||
const clearAllUsecases = () => {
|
const clearAllUsecases = () => {
|
||||||
setSelectedUsecases([])
|
if (activeTabValue === 0) setSelectedUsecases([])
|
||||||
|
else setSelectedTemplateUsecases([])
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBadgeFilterChange = (event) => {
|
const handleBadgeFilterChange = (event) => {
|
||||||
|
|
@ -116,7 +122,13 @@ const Marketplace = () => {
|
||||||
// On autofill we get a stringified value.
|
// On autofill we get a stringified value.
|
||||||
typeof value === 'string' ? value.split(',') : value
|
typeof value === 'string' ? value.split(',') : value
|
||||||
)
|
)
|
||||||
getEligibleUsecases({ typeFilter, badgeFilter: typeof value === 'string' ? value.split(',') : value, frameworkFilter, search })
|
const data = activeTabValue === 0 ? getAllTemplatesMarketplacesApi.data : getAllCustomTemplatesApi.data
|
||||||
|
getEligibleUsecases(data, {
|
||||||
|
typeFilter,
|
||||||
|
badgeFilter: typeof value === 'string' ? value.split(',') : value,
|
||||||
|
frameworkFilter,
|
||||||
|
search
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTypeFilterChange = (event) => {
|
const handleTypeFilterChange = (event) => {
|
||||||
|
|
@ -127,7 +139,13 @@ const Marketplace = () => {
|
||||||
// On autofill we get a stringified value.
|
// On autofill we get a stringified value.
|
||||||
typeof value === 'string' ? value.split(',') : value
|
typeof value === 'string' ? value.split(',') : value
|
||||||
)
|
)
|
||||||
getEligibleUsecases({ typeFilter: typeof value === 'string' ? value.split(',') : value, badgeFilter, frameworkFilter, search })
|
const data = activeTabValue === 0 ? getAllTemplatesMarketplacesApi.data : getAllCustomTemplatesApi.data
|
||||||
|
getEligibleUsecases(data, {
|
||||||
|
typeFilter: typeof value === 'string' ? value.split(',') : value,
|
||||||
|
badgeFilter,
|
||||||
|
frameworkFilter,
|
||||||
|
search
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFrameworkFilterChange = (event) => {
|
const handleFrameworkFilterChange = (event) => {
|
||||||
|
|
@ -138,7 +156,13 @@ const Marketplace = () => {
|
||||||
// On autofill we get a stringified value.
|
// On autofill we get a stringified value.
|
||||||
typeof value === 'string' ? value.split(',') : value
|
typeof value === 'string' ? value.split(',') : value
|
||||||
)
|
)
|
||||||
getEligibleUsecases({ typeFilter, badgeFilter, frameworkFilter: typeof value === 'string' ? value.split(',') : value, search })
|
const data = activeTabValue === 0 ? getAllTemplatesMarketplacesApi.data : getAllCustomTemplatesApi.data
|
||||||
|
getEligibleUsecases(data, {
|
||||||
|
typeFilter,
|
||||||
|
badgeFilter,
|
||||||
|
frameworkFilter: typeof value === 'string' ? value.split(',') : value,
|
||||||
|
search
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleViewChange = (event, nextView) => {
|
const handleViewChange = (event, nextView) => {
|
||||||
|
|
@ -149,7 +173,56 @@ const Marketplace = () => {
|
||||||
|
|
||||||
const onSearchChange = (event) => {
|
const onSearchChange = (event) => {
|
||||||
setSearch(event.target.value)
|
setSearch(event.target.value)
|
||||||
getEligibleUsecases({ typeFilter, badgeFilter, frameworkFilter, search: event.target.value })
|
const data = activeTabValue === 0 ? getAllTemplatesMarketplacesApi.data : getAllCustomTemplatesApi.data
|
||||||
|
|
||||||
|
getEligibleUsecases(data, { typeFilter, badgeFilter, frameworkFilter, search: event.target.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDeleteCustomTemplate = async (template) => {
|
||||||
|
const confirmPayload = {
|
||||||
|
title: `Delete`,
|
||||||
|
description: `Delete Custom Template ${template.name}?`,
|
||||||
|
confirmButtonName: 'Delete',
|
||||||
|
cancelButtonName: 'Cancel'
|
||||||
|
}
|
||||||
|
const isConfirmed = await confirm(confirmPayload)
|
||||||
|
|
||||||
|
if (isConfirmed) {
|
||||||
|
try {
|
||||||
|
const deleteResp = await marketplacesApi.deleteCustomTemplate(template.id)
|
||||||
|
if (deleteResp.data) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'Custom Template deleted successfully!',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'success',
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
getAllCustomTemplatesApi.request()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to delete custom template: ${
|
||||||
|
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||||
|
}`,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterFlows(data) {
|
function filterFlows(data) {
|
||||||
|
|
@ -173,13 +246,18 @@ const Marketplace = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterByUsecases(data) {
|
function filterByUsecases(data) {
|
||||||
return selectedUsecases.length > 0 ? (data.usecases || []).some((item) => selectedUsecases.includes(item)) : true
|
if (activeTabValue === 0)
|
||||||
|
return selectedUsecases.length > 0 ? (data.usecases || []).some((item) => selectedUsecases.includes(item)) : true
|
||||||
|
else
|
||||||
|
return selectedTemplateUsecases.length > 0
|
||||||
|
? (data.usecases || []).some((item) => selectedTemplateUsecases.includes(item))
|
||||||
|
: true
|
||||||
}
|
}
|
||||||
|
|
||||||
const getEligibleUsecases = (filter) => {
|
const getEligibleUsecases = (data, filter) => {
|
||||||
if (!getAllTemplatesMarketplacesApi.data) return
|
if (!data) return
|
||||||
|
|
||||||
let filteredData = getAllTemplatesMarketplacesApi.data
|
let filteredData = data
|
||||||
if (filter.badgeFilter.length > 0) filteredData = filteredData.filter((data) => filter.badgeFilter.includes(data.badge))
|
if (filter.badgeFilter.length > 0) filteredData = filteredData.filter((data) => filter.badgeFilter.includes(data.badge))
|
||||||
if (filter.typeFilter.length > 0) filteredData = filteredData.filter((data) => filter.typeFilter.includes(data.type))
|
if (filter.typeFilter.length > 0) filteredData = filteredData.filter((data) => filter.typeFilter.includes(data.type))
|
||||||
if (filter.frameworkFilter.length > 0)
|
if (filter.frameworkFilter.length > 0)
|
||||||
|
|
@ -199,7 +277,8 @@ const Marketplace = () => {
|
||||||
usecases.push(...filteredData[i].usecases)
|
usecases.push(...filteredData[i].usecases)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setEligibleUsecases(Array.from(new Set(usecases)).sort())
|
if (activeTabValue === 0) setEligibleUsecases(Array.from(new Set(usecases)).sort())
|
||||||
|
else setEligibleTemplateUsecases(Array.from(new Set(usecases)).sort())
|
||||||
}
|
}
|
||||||
|
|
||||||
const onUseTemplate = (selectedTool) => {
|
const onUseTemplate = (selectedTool) => {
|
||||||
|
|
@ -274,13 +353,57 @@ const Marketplace = () => {
|
||||||
}
|
}
|
||||||
}, [getAllTemplatesMarketplacesApi.error])
|
}, [getAllTemplatesMarketplacesApi.error])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(getAllCustomTemplatesApi.loading)
|
||||||
|
}, [getAllCustomTemplatesApi.loading])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAllCustomTemplatesApi.data) {
|
||||||
|
try {
|
||||||
|
const flows = getAllCustomTemplatesApi.data
|
||||||
|
const usecases = []
|
||||||
|
const tImages = {}
|
||||||
|
for (let i = 0; i < flows.length; i += 1) {
|
||||||
|
if (flows[i].flowData) {
|
||||||
|
const flowDataStr = flows[i].flowData
|
||||||
|
const flowData = JSON.parse(flowDataStr)
|
||||||
|
usecases.push(...flows[i].usecases)
|
||||||
|
if (flows[i].framework) {
|
||||||
|
flows[i].framework = [flows[i].framework] || []
|
||||||
|
}
|
||||||
|
const nodes = flowData.nodes || []
|
||||||
|
tImages[flows[i].id] = []
|
||||||
|
for (let j = 0; j < nodes.length; j += 1) {
|
||||||
|
const imageSrc = `${baseURL}/api/v1/node-icon/${nodes[j].data.name}`
|
||||||
|
if (!tImages[flows[i].id].includes(imageSrc)) {
|
||||||
|
tImages[flows[i].id].push(imageSrc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTemplateImages(tImages)
|
||||||
|
setTemplateUsecases(Array.from(new Set(usecases)).sort())
|
||||||
|
setEligibleTemplateUsecases(Array.from(new Set(usecases)).sort())
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getAllCustomTemplatesApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAllCustomTemplatesApi.error) {
|
||||||
|
setError(getAllCustomTemplatesApi.error)
|
||||||
|
}
|
||||||
|
}, [getAllCustomTemplatesApi.error])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MainCard>
|
<MainCard>
|
||||||
{error ? (
|
{error ? (
|
||||||
<ErrorBoundary error={error} />
|
<ErrorBoundary error={error} />
|
||||||
) : (
|
) : (
|
||||||
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
<Stack flexDirection='column'>
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
filters={
|
filters={
|
||||||
<>
|
<>
|
||||||
|
|
@ -432,119 +555,253 @@ const Marketplace = () => {
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
</ViewHeader>
|
</ViewHeader>
|
||||||
<Stack direction='row' sx={{ gap: 2, alignItems: 'center', flexWrap: 'wrap' }}>
|
<Tabs value={activeTabValue} onChange={handleTabChange} textColor='primary' aria-label='tabs' centered>
|
||||||
{usecases.map((usecase, index) => (
|
<Tab value={0} label='Community Templates'></Tab>
|
||||||
<FormControlLabel
|
<Tab value={1} label='My Templates' />
|
||||||
key={index}
|
</Tabs>
|
||||||
size='small'
|
<TabPanel value={activeTabValue} index={0}>
|
||||||
control={
|
<Stack direction='row' sx={{ gap: 2, my: 2, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
<Checkbox
|
{usecases.map((usecase, index) => (
|
||||||
disabled={eligibleUsecases.length === 0 ? true : !eligibleUsecases.includes(usecase)}
|
<FormControlLabel
|
||||||
color='success'
|
key={index}
|
||||||
checked={selectedUsecases.includes(usecase)}
|
size='small'
|
||||||
onChange={(event) => {
|
control={
|
||||||
setSelectedUsecases(
|
<Checkbox
|
||||||
event.target.checked
|
disabled={eligibleUsecases.length === 0 ? true : !eligibleUsecases.includes(usecase)}
|
||||||
? [...selectedUsecases, usecase]
|
color='success'
|
||||||
: selectedUsecases.filter((item) => item !== usecase)
|
checked={selectedUsecases.includes(usecase)}
|
||||||
)
|
onChange={(event) => {
|
||||||
}}
|
setSelectedUsecases(
|
||||||
/>
|
event.target.checked
|
||||||
}
|
? [...selectedUsecases, usecase]
|
||||||
label={usecase}
|
: selectedUsecases.filter((item) => item !== usecase)
|
||||||
/>
|
)
|
||||||
))}
|
}}
|
||||||
</Stack>
|
/>
|
||||||
{selectedUsecases.length > 0 && (
|
}
|
||||||
<Button
|
label={usecase}
|
||||||
sx={{ width: 'max-content', borderRadius: '20px' }}
|
|
||||||
variant='outlined'
|
|
||||||
onClick={() => clearAllUsecases()}
|
|
||||||
startIcon={<IconX />}
|
|
||||||
>
|
|
||||||
Clear All
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{!view || view === 'card' ? (
|
|
||||||
<>
|
|
||||||
{isLoading ? (
|
|
||||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
|
||||||
<Skeleton variant='rounded' height={160} />
|
|
||||||
<Skeleton variant='rounded' height={160} />
|
|
||||||
<Skeleton variant='rounded' height={160} />
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
|
||||||
{getAllTemplatesMarketplacesApi.data
|
|
||||||
?.filter(filterByBadge)
|
|
||||||
.filter(filterByType)
|
|
||||||
.filter(filterFlows)
|
|
||||||
.filter(filterByFramework)
|
|
||||||
.filter(filterByUsecases)
|
|
||||||
.map((data, index) => (
|
|
||||||
<Box key={index}>
|
|
||||||
{data.badge && (
|
|
||||||
<Badge
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
'& .MuiBadge-badge': {
|
|
||||||
right: 20
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
badgeContent={data.badge}
|
|
||||||
color={data.badge === 'POPULAR' ? 'primary' : 'error'}
|
|
||||||
>
|
|
||||||
{(data.type === 'Chatflow' || data.type === 'Agentflow') && (
|
|
||||||
<ItemCard
|
|
||||||
onClick={() => goToCanvas(data)}
|
|
||||||
data={data}
|
|
||||||
images={images[data.id]}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{data.type === 'Tool' && (
|
|
||||||
<ItemCard data={data} onClick={() => goToTool(data)} />
|
|
||||||
)}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
{!data.badge && (data.type === 'Chatflow' || data.type === 'Agentflow') && (
|
|
||||||
<ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
|
|
||||||
)}
|
|
||||||
{!data.badge && data.type === 'Tool' && (
|
|
||||||
<ItemCard data={data} onClick={() => goToTool(data)} />
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<MarketplaceTable
|
|
||||||
data={getAllTemplatesMarketplacesApi.data}
|
|
||||||
filterFunction={filterFlows}
|
|
||||||
filterByType={filterByType}
|
|
||||||
filterByBadge={filterByBadge}
|
|
||||||
filterByFramework={filterByFramework}
|
|
||||||
filterByUsecases={filterByUsecases}
|
|
||||||
goToTool={goToTool}
|
|
||||||
goToCanvas={goToCanvas}
|
|
||||||
isLoading={isLoading}
|
|
||||||
setError={setError}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isLoading && (!getAllTemplatesMarketplacesApi.data || getAllTemplatesMarketplacesApi.data.length === 0) && (
|
|
||||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
|
||||||
<Box sx={{ p: 2, height: 'auto' }}>
|
|
||||||
<img
|
|
||||||
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
|
||||||
src={WorkflowEmptySVG}
|
|
||||||
alt='WorkflowEmptySVG'
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
))}
|
||||||
<div>No Marketplace Yet</div>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
{selectedUsecases.length > 0 && (
|
||||||
|
<Button
|
||||||
|
sx={{ width: 'max-content', mb: 2, borderRadius: '20px' }}
|
||||||
|
variant='outlined'
|
||||||
|
onClick={() => clearAllUsecases()}
|
||||||
|
startIcon={<IconX />}
|
||||||
|
>
|
||||||
|
Clear All
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!view || view === 'card' ? (
|
||||||
|
<>
|
||||||
|
{isLoading ? (
|
||||||
|
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||||
|
<Skeleton variant='rounded' height={160} />
|
||||||
|
<Skeleton variant='rounded' height={160} />
|
||||||
|
<Skeleton variant='rounded' height={160} />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||||
|
{getAllTemplatesMarketplacesApi.data
|
||||||
|
?.filter(filterByBadge)
|
||||||
|
.filter(filterByType)
|
||||||
|
.filter(filterFlows)
|
||||||
|
.filter(filterByFramework)
|
||||||
|
.filter(filterByUsecases)
|
||||||
|
.map((data, index) => (
|
||||||
|
<Box key={index}>
|
||||||
|
{data.badge && (
|
||||||
|
<Badge
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
'& .MuiBadge-badge': {
|
||||||
|
right: 20
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
badgeContent={data.badge}
|
||||||
|
color={data.badge === 'POPULAR' ? 'primary' : 'error'}
|
||||||
|
>
|
||||||
|
{(data.type === 'Chatflow' || data.type === 'Agentflow') && (
|
||||||
|
<ItemCard
|
||||||
|
onClick={() => goToCanvas(data)}
|
||||||
|
data={data}
|
||||||
|
images={images[data.id]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{data.type === 'Tool' && (
|
||||||
|
<ItemCard data={data} onClick={() => goToTool(data)} />
|
||||||
|
)}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{!data.badge && (data.type === 'Chatflow' || data.type === 'Agentflow') && (
|
||||||
|
<ItemCard
|
||||||
|
onClick={() => goToCanvas(data)}
|
||||||
|
data={data}
|
||||||
|
images={images[data.id]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!data.badge && data.type === 'Tool' && (
|
||||||
|
<ItemCard data={data} onClick={() => goToTool(data)} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<MarketplaceTable
|
||||||
|
data={getAllTemplatesMarketplacesApi.data}
|
||||||
|
filterFunction={filterFlows}
|
||||||
|
filterByType={filterByType}
|
||||||
|
filterByBadge={filterByBadge}
|
||||||
|
filterByFramework={filterByFramework}
|
||||||
|
filterByUsecases={filterByUsecases}
|
||||||
|
goToTool={goToTool}
|
||||||
|
goToCanvas={goToCanvas}
|
||||||
|
isLoading={isLoading}
|
||||||
|
setError={setError}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoading && (!getAllTemplatesMarketplacesApi.data || getAllTemplatesMarketplacesApi.data.length === 0) && (
|
||||||
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||||
|
<Box sx={{ p: 2, height: 'auto' }}>
|
||||||
|
<img
|
||||||
|
style={{ objectFit: 'cover', height: '25vh', width: 'auto' }}
|
||||||
|
src={WorkflowEmptySVG}
|
||||||
|
alt='WorkflowEmptySVG'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<div>No Marketplace Yet</div>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={activeTabValue} index={1}>
|
||||||
|
<Stack direction='row' sx={{ gap: 2, my: 2, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
{templateUsecases.map((usecase, index) => (
|
||||||
|
<FormControlLabel
|
||||||
|
key={index}
|
||||||
|
size='small'
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
disabled={
|
||||||
|
eligibleTemplateUsecases.length === 0
|
||||||
|
? true
|
||||||
|
: !eligibleTemplateUsecases.includes(usecase)
|
||||||
|
}
|
||||||
|
color='success'
|
||||||
|
checked={selectedTemplateUsecases.includes(usecase)}
|
||||||
|
onChange={(event) => {
|
||||||
|
setSelectedTemplateUsecases(
|
||||||
|
event.target.checked
|
||||||
|
? [...selectedTemplateUsecases, usecase]
|
||||||
|
: selectedTemplateUsecases.filter((item) => item !== usecase)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={usecase}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
{selectedTemplateUsecases.length > 0 && (
|
||||||
|
<Button
|
||||||
|
sx={{ width: 'max-content', mb: 2, borderRadius: '20px' }}
|
||||||
|
variant='outlined'
|
||||||
|
onClick={() => clearAllUsecases()}
|
||||||
|
startIcon={<IconX />}
|
||||||
|
>
|
||||||
|
Clear All
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!view || view === 'card' ? (
|
||||||
|
<>
|
||||||
|
{isLoading ? (
|
||||||
|
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||||
|
<Skeleton variant='rounded' height={160} />
|
||||||
|
<Skeleton variant='rounded' height={160} />
|
||||||
|
<Skeleton variant='rounded' height={160} />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||||
|
{getAllCustomTemplatesApi.data
|
||||||
|
?.filter(filterByBadge)
|
||||||
|
.filter(filterByType)
|
||||||
|
.filter(filterFlows)
|
||||||
|
.filter(filterByFramework)
|
||||||
|
.filter(filterByUsecases)
|
||||||
|
.map((data, index) => (
|
||||||
|
<Box key={index}>
|
||||||
|
{data.badge && (
|
||||||
|
<Badge
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
'& .MuiBadge-badge': {
|
||||||
|
right: 20
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
badgeContent={data.badge}
|
||||||
|
color={data.badge === 'POPULAR' ? 'primary' : 'error'}
|
||||||
|
>
|
||||||
|
{(data.type === 'Chatflow' || data.type === 'Agentflow') && (
|
||||||
|
<ItemCard
|
||||||
|
onClick={() => goToCanvas(data)}
|
||||||
|
data={data}
|
||||||
|
images={templateImages[data.id]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{data.type === 'Tool' && (
|
||||||
|
<ItemCard data={data} onClick={() => goToTool(data)} />
|
||||||
|
)}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{!data.badge && (data.type === 'Chatflow' || data.type === 'Agentflow') && (
|
||||||
|
<ItemCard
|
||||||
|
onClick={() => goToCanvas(data)}
|
||||||
|
data={data}
|
||||||
|
images={templateImages[data.id]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!data.badge && data.type === 'Tool' && (
|
||||||
|
<ItemCard data={data} onClick={() => goToTool(data)} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<MarketplaceTable
|
||||||
|
data={getAllCustomTemplatesApi.data}
|
||||||
|
filterFunction={filterFlows}
|
||||||
|
filterByType={filterByType}
|
||||||
|
filterByBadge={filterByBadge}
|
||||||
|
filterByFramework={filterByFramework}
|
||||||
|
filterByUsecases={filterByUsecases}
|
||||||
|
goToTool={goToTool}
|
||||||
|
goToCanvas={goToCanvas}
|
||||||
|
isLoading={isLoading}
|
||||||
|
setError={setError}
|
||||||
|
onDelete={onDeleteCustomTemplate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!isLoading && (!getAllCustomTemplatesApi.data || getAllCustomTemplatesApi.data.length === 0) && (
|
||||||
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||||
|
<Box sx={{ p: 2, height: 'auto' }}>
|
||||||
|
<img
|
||||||
|
style={{ objectFit: 'cover', height: '25vh', width: 'auto' }}
|
||||||
|
src={WorkflowEmptySVG}
|
||||||
|
alt='WorkflowEmptySVG'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<div>No Saved Custom Templates</div>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</TabPanel>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</MainCard>
|
</MainCard>
|
||||||
|
|
@ -555,6 +812,7 @@ const Marketplace = () => {
|
||||||
onConfirm={() => setShowToolDialog(false)}
|
onConfirm={() => setShowToolDialog(false)}
|
||||||
onUseTemplate={(tool) => onUseTemplate(tool)}
|
onUseTemplate={(tool) => onUseTemplate(tool)}
|
||||||
></ToolDialog>
|
></ToolDialog>
|
||||||
|
<ConfirmDialog />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { CodeEditor } from '@/ui-component/editor/CodeEditor'
|
||||||
import HowToUseFunctionDialog from './HowToUseFunctionDialog'
|
import HowToUseFunctionDialog from './HowToUseFunctionDialog'
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import { IconX, IconFileDownload, IconPlus } from '@tabler/icons-react'
|
import { IconX, IconFileDownload, IconPlus, IconTemplate } from '@tabler/icons-react'
|
||||||
|
|
||||||
// API
|
// API
|
||||||
import toolsApi from '@/api/tools'
|
import toolsApi from '@/api/tools'
|
||||||
|
|
@ -29,6 +29,7 @@ import useApi from '@/hooks/useApi'
|
||||||
import useNotifier from '@/utils/useNotifier'
|
import useNotifier from '@/utils/useNotifier'
|
||||||
import { generateRandomGradient, formatDataGridRows } from '@/utils/genericHelper'
|
import { generateRandomGradient, formatDataGridRows } from '@/utils/genericHelper'
|
||||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||||
|
import ExportAsTemplateDialog from '@/ui-component/dialog/ExportAsTemplateDialog'
|
||||||
|
|
||||||
const exampleAPIFunc = `/*
|
const exampleAPIFunc = `/*
|
||||||
* You can use any libraries imported in Flowise
|
* You can use any libraries imported in Flowise
|
||||||
|
|
@ -79,6 +80,9 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
|
||||||
const [toolFunc, setToolFunc] = useState('')
|
const [toolFunc, setToolFunc] = useState('')
|
||||||
const [showHowToDialog, setShowHowToDialog] = useState(false)
|
const [showHowToDialog, setShowHowToDialog] = useState(false)
|
||||||
|
|
||||||
|
const [exportAsTemplateDialogOpen, setExportAsTemplateDialogOpen] = useState(false)
|
||||||
|
const [exportAsTemplateDialogProps, setExportAsTemplateDialogProps] = useState({})
|
||||||
|
|
||||||
const deleteItem = useCallback(
|
const deleteItem = useCallback(
|
||||||
(id) => () => {
|
(id) => () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -105,6 +109,20 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onSaveAsTemplate = () => {
|
||||||
|
setExportAsTemplateDialogProps({
|
||||||
|
title: 'Export As Template',
|
||||||
|
tool: {
|
||||||
|
name: toolName,
|
||||||
|
description: toolDesc,
|
||||||
|
iconSrc: toolIcon,
|
||||||
|
schema: toolSchema,
|
||||||
|
func: toolFunc
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setExportAsTemplateDialogOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
const onRowUpdate = (newRow) => {
|
const onRowUpdate = (newRow) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setToolSchema((prevRows) => {
|
setToolSchema((prevRows) => {
|
||||||
|
|
@ -401,11 +419,24 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
|
||||||
<DialogTitle sx={{ fontSize: '1rem', p: 3, pb: 0 }} id='alert-dialog-title'>
|
<DialogTitle sx={{ fontSize: '1rem', p: 3, pb: 0 }} id='alert-dialog-title'>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
|
||||||
{dialogProps.title}
|
{dialogProps.title}
|
||||||
{dialogProps.type === 'EDIT' && (
|
<Box>
|
||||||
<Button variant='outlined' onClick={() => exportTool()} startIcon={<IconFileDownload />}>
|
{dialogProps.type === 'EDIT' && (
|
||||||
Export
|
<>
|
||||||
</Button>
|
<Button
|
||||||
)}
|
style={{ marginRight: '10px' }}
|
||||||
|
variant='outlined'
|
||||||
|
onClick={() => onSaveAsTemplate()}
|
||||||
|
startIcon={<IconTemplate />}
|
||||||
|
color='secondary'
|
||||||
|
>
|
||||||
|
Save As Template
|
||||||
|
</Button>
|
||||||
|
<Button variant='outlined' onClick={() => exportTool()} startIcon={<IconFileDownload />}>
|
||||||
|
Export
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, maxHeight: '75vh', position: 'relative', px: 3, pb: 3 }}>
|
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, maxHeight: '75vh', position: 'relative', px: 3, pb: 3 }}>
|
||||||
|
|
@ -535,6 +566,14 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
|
||||||
)}
|
)}
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
<ConfirmDialog />
|
<ConfirmDialog />
|
||||||
|
{exportAsTemplateDialogOpen && (
|
||||||
|
<ExportAsTemplateDialog
|
||||||
|
show={exportAsTemplateDialogOpen}
|
||||||
|
dialogProps={exportAsTemplateDialogProps}
|
||||||
|
onCancel={() => setExportAsTemplateDialogOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<HowToUseFunctionDialog show={showHowToDialog} onCancel={() => setShowHowToDialog(false)} />
|
<HowToUseFunctionDialog show={showHowToDialog} onCancel={() => setShowHowToDialog(false)} />
|
||||||
</Dialog>
|
</Dialog>
|
||||||
) : null
|
) : null
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue