mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 21:00:58 +03:00
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:
@@ -273,5 +273,18 @@ export interface IApiKey {
|
||||
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
|
||||
export * from './Interface.DocumentStore'
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Request, Response, NextFunction } from 'express'
|
||||
import marketplacesService from '../../services/marketplaces'
|
||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
|
||||
// Get all templates for marketplaces
|
||||
const getAllTemplates = async (req: Request, res: Response, next: NextFunction) => {
|
||||
@@ -11,6 +13,48 @@ const getAllTemplates = async (req: Request, res: Response, next: NextFunction)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getAllTemplates
|
||||
const deleteCustomTemplate = async (req: Request, res: Response, next: NextFunction) => {
|
||||
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 { UpsertHistory } from './UpsertHistory'
|
||||
import { ApiKey } from './ApiKey'
|
||||
import { CustomTemplate } from './CustomTemplate'
|
||||
|
||||
export const entities = {
|
||||
ChatFlow,
|
||||
@@ -23,5 +24,6 @@ export const entities = {
|
||||
DocumentStoreFileChunk,
|
||||
Lead,
|
||||
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 { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
||||
import { LongTextColumn1722301395521 } from './1722301395521-LongTextColumn'
|
||||
import { AddCustomTemplate1725629836652 } from './1725629836652-AddCustomTemplate'
|
||||
|
||||
export const mariadbMigrations = [
|
||||
Init1693840429259,
|
||||
@@ -49,5 +50,6 @@ export const mariadbMigrations = [
|
||||
AddTypeToChatFlow1716300000000,
|
||||
AddApiKey1720230151480,
|
||||
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 { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
||||
import { LongTextColumn1722301395521 } from './1722301395521-LongTextColumn'
|
||||
import { AddCustomTemplate1725629836652 } from './1725629836652-AddCustomTemplate'
|
||||
|
||||
export const mysqlMigrations = [
|
||||
Init1693840429259,
|
||||
@@ -51,5 +52,6 @@ export const mysqlMigrations = [
|
||||
AddVectorStoreConfigToDocStore1715861032479,
|
||||
AddApiKey1720230151480,
|
||||
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 { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
|
||||
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
||||
import { AddCustomTemplate1725629836652 } from './1725629836652-AddCustomTemplate'
|
||||
|
||||
export const postgresMigrations = [
|
||||
Init1693891895163,
|
||||
@@ -51,5 +52,6 @@ export const postgresMigrations = [
|
||||
AddTypeToChatFlow1716300000000,
|
||||
AddVectorStoreConfigToDocStore1715861032479,
|
||||
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 { AddApiKey1720230151480 } from './1720230151480-AddApiKey'
|
||||
import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionToChatMessage'
|
||||
import { AddCustomTemplate1725629836652 } from './1725629836652-AddCustomTemplate'
|
||||
|
||||
export const sqliteMigrations = [
|
||||
Init1693835579790,
|
||||
@@ -49,5 +50,6 @@ export const sqliteMigrations = [
|
||||
AddTypeToChatFlow1716300000000,
|
||||
AddVectorStoreConfigToDocStore1715861032479,
|
||||
AddApiKey1720230151480,
|
||||
AddActionToChatMessage1721078251523
|
||||
AddActionToChatMessage1721078251523,
|
||||
AddCustomTemplate1725629836652
|
||||
]
|
||||
|
||||
@@ -5,4 +5,12 @@ const router = express.Router()
|
||||
// READ
|
||||
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
|
||||
|
||||
@@ -4,6 +4,11 @@ import { StatusCodes } from 'http-status-codes'
|
||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { getErrorMessage } from '../../errors/utils'
|
||||
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 = {
|
||||
badge: string
|
||||
@@ -96,6 +101,148 @@ const getAllTemplates = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getAllTemplates
|
||||
const deleteCustomTemplate = async (templateId: string): Promise<DeleteResult> => {
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user