From 70da39629c84328ad864075011f8ba119c1645f2 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 21 Jun 2023 18:31:53 +0100 Subject: [PATCH 01/44] add custom tool --- .../OpenAIFunctionAgent.ts | 32 +- .../nodes/tools/ChainTool/chaintool.svg | 8 +- .../nodes/tools/CustomTool/CustomTool.ts | 108 +++++ .../components/nodes/tools/CustomTool/core.ts | 78 +++ .../nodes/tools/CustomTool/customtool.svg | 4 + packages/components/package.json | 3 +- packages/components/src/Interface.ts | 22 +- packages/components/src/utils.ts | 31 +- packages/server/src/ChildProcess.ts | 26 +- packages/server/src/DataSource.ts | 3 +- packages/server/src/Interface.ts | 11 + packages/server/src/entity/Tool.ts | 30 ++ packages/server/src/index.ts | 87 +++- packages/server/src/utils/index.ts | 14 +- packages/ui/package.json | 1 + packages/ui/src/api/tools.js | 19 + packages/ui/src/assets/images/tools_empty.svg | 1 + packages/ui/src/menu-items/dashboard.js | 12 +- packages/ui/src/routes/MainRoutes.js | 7 + .../ui/src/ui-component/cards/ItemCard.js | 46 +- .../ui-component/dropdown/AsyncDropdown.js | 147 ++++++ .../src/ui-component/editor/DarkCodeEditor.js | 1 + .../ui-component/editor/LightCodeEditor.js | 1 + packages/ui/src/ui-component/grid/Grid.js | 37 ++ packages/ui/src/utils/genericHelper.js | 21 +- .../ui/src/views/canvas/NodeInputHandler.js | 79 ++- packages/ui/src/views/tools/ToolDialog.js | 448 ++++++++++++++++++ packages/ui/src/views/tools/index.js | 112 +++++ 28 files changed, 1346 insertions(+), 43 deletions(-) create mode 100644 packages/components/nodes/tools/CustomTool/CustomTool.ts create mode 100644 packages/components/nodes/tools/CustomTool/core.ts create mode 100644 packages/components/nodes/tools/CustomTool/customtool.svg create mode 100644 packages/server/src/entity/Tool.ts create mode 100644 packages/ui/src/api/tools.js create mode 100644 packages/ui/src/assets/images/tools_empty.svg create mode 100644 packages/ui/src/ui-component/dropdown/AsyncDropdown.js create mode 100644 packages/ui/src/ui-component/grid/Grid.js create mode 100644 packages/ui/src/views/tools/ToolDialog.js create mode 100644 packages/ui/src/views/tools/index.js diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index 9efe602f..4c740874 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -1,9 +1,10 @@ -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' -import { Tool } from 'langchain/tools' import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { flatten } from 'lodash' +import { BaseChatMemory, ChatMessageHistory } from 'langchain/memory' +import { AIChatMessage, HumanChatMessage } from 'langchain/schema' class OpenAIFunctionAgent_Agents implements INode { label: string @@ -30,6 +31,11 @@ class OpenAIFunctionAgent_Agents implements INode { type: 'Tool', list: true }, + { + label: 'Memory', + name: 'memory', + type: 'BaseChatMemory' + }, { label: 'OpenAI Chat Model', name: 'model', @@ -42,18 +48,38 @@ class OpenAIFunctionAgent_Agents implements INode { async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as BaseLanguageModel - let tools = nodeData.inputs?.tools as Tool[] + const memory = nodeData.inputs?.memory as BaseChatMemory + + let tools = nodeData.inputs?.tools tools = flatten(tools) const executor = await initializeAgentExecutorWithOptions(tools, model, { agentType: 'openai-functions', verbose: process.env.DEBUG === 'true' ? true : false }) + if (memory) executor.memory = memory + return executor } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const executor = nodeData.instance as AgentExecutor + const memory = nodeData.inputs?.memory as BaseChatMemory + + if (options && options.chatHistory) { + const chatHistory = [] + const histories: IMessage[] = options.chatHistory + + for (const message of histories) { + if (message.type === 'apiMessage') { + chatHistory.push(new AIChatMessage(message.message)) + } else if (message.type === 'userMessage') { + chatHistory.push(new HumanChatMessage(message.message)) + } + } + memory.chatHistory = new ChatMessageHistory(chatHistory) + executor.memory = memory + } if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) diff --git a/packages/components/nodes/tools/ChainTool/chaintool.svg b/packages/components/nodes/tools/ChainTool/chaintool.svg index c5bd0fbc..ab76749b 100644 --- a/packages/components/nodes/tools/ChainTool/chaintool.svg +++ b/packages/components/nodes/tools/ChainTool/chaintool.svg @@ -1,4 +1,8 @@ - + - + + + + + \ No newline at end of file diff --git a/packages/components/nodes/tools/CustomTool/CustomTool.ts b/packages/components/nodes/tools/CustomTool/CustomTool.ts new file mode 100644 index 00000000..768e9092 --- /dev/null +++ b/packages/components/nodes/tools/CustomTool/CustomTool.ts @@ -0,0 +1,108 @@ +import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { DynamicStructuredTool } from './core' +import { z } from 'zod' +import { DataSource } from 'typeorm' + +class CustomTool_Tools implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Custom Tool' + this.name = 'customTool' + this.type = 'CustomTool' + this.icon = 'customtool.svg' + this.category = 'Tools' + this.description = `Use custom tool you've created in Flowise within chatflow` + this.inputs = [ + { + label: 'Select Tool', + name: 'selectedTool', + type: 'asyncOptions', + loadMethod: 'listTools' + } + ] + this.baseClasses = [this.type, 'Tool', ...getBaseClasses(DynamicStructuredTool)] + } + + //@ts-ignore + loadMethods = { + async listTools(nodeData: INodeData, options: ICommonObject): Promise { + const returnData: INodeOptionsValue[] = [] + + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + + if (appDataSource === undefined || !appDataSource) { + return returnData + } + + const tools = await appDataSource.getRepository(databaseEntities['Tool']).find() + + for (let i = 0; i < tools.length; i += 1) { + const data = { + label: tools[i].name, + name: tools[i].id, + description: tools[i].description + } as INodeOptionsValue + returnData.push(data) + } + return returnData + } + } + + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + const selectedToolId = nodeData.inputs?.selectedTool as string + + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + + try { + const tool = await appDataSource.getRepository(databaseEntities['Tool']).findOneBy({ + id: selectedToolId + }) + + if (!tool) throw new Error(`Tool ${selectedToolId} not found`) + const obj = { + name: tool.name, + description: tool.description, + schema: z.object(convertSchemaToZod(tool.schema)), + code: tool.func + } + return new DynamicStructuredTool(obj) + } catch (e) { + throw new Error(e) + } + } +} + +const convertSchemaToZod = (schema: string) => { + try { + const parsedSchema = JSON.parse(schema) + const zodObj: any = {} + for (const sch of parsedSchema) { + if (sch.type === 'string') { + if (sch.required) z.string({ required_error: `${sch.property} required` }).describe(sch.description) + zodObj[sch.property] = z.string().describe(sch.description) + } else if (sch.type === 'number') { + if (sch.required) z.number({ required_error: `${sch.property} required` }).describe(sch.description) + zodObj[sch.property] = z.number().describe(sch.description) + } else if (sch.type === 'boolean') { + if (sch.required) z.boolean({ required_error: `${sch.property} required` }).describe(sch.description) + zodObj[sch.property] = z.boolean().describe(sch.description) + } + } + return zodObj + } catch (e) { + throw new Error(e) + } +} + +module.exports = { nodeClass: CustomTool_Tools } diff --git a/packages/components/nodes/tools/CustomTool/core.ts b/packages/components/nodes/tools/CustomTool/core.ts new file mode 100644 index 00000000..0d3d7bcd --- /dev/null +++ b/packages/components/nodes/tools/CustomTool/core.ts @@ -0,0 +1,78 @@ +import { z } from 'zod' +import { CallbackManagerForToolRun } from 'langchain/callbacks' +import { StructuredTool, ToolParams } from 'langchain/tools' +import { NodeVM } from 'vm2' +import { availableDependencies } from '../../../src/utils' + +export interface BaseDynamicToolInput extends ToolParams { + name: string + description: string + code: string + returnDirect?: boolean +} + +export interface DynamicStructuredToolInput< + // eslint-disable-next-line + T extends z.ZodObject = z.ZodObject +> extends BaseDynamicToolInput { + func?: (input: z.infer, runManager?: CallbackManagerForToolRun) => Promise + schema: T +} + +export class DynamicStructuredTool< + // eslint-disable-next-line + T extends z.ZodObject = z.ZodObject +> extends StructuredTool { + name: string + + description: string + + code: string + + func: DynamicStructuredToolInput['func'] + + schema: T + + constructor(fields: DynamicStructuredToolInput) { + super(fields) + this.name = fields.name + this.description = fields.description + this.code = fields.code + this.func = fields.func + this.returnDirect = fields.returnDirect ?? this.returnDirect + this.schema = fields.schema + } + + protected async _call(arg: z.output): Promise { + let sandbox: any = {} + if (typeof arg === 'object' && Object.keys(arg).length) { + for (const item in arg) { + sandbox[`$${item}`] = arg[item] + } + } + + const options = { + console: 'inherit', + sandbox, + require: { + external: false as boolean | { modules: string[] }, + builtin: ['*'] + } + } as any + + const external = JSON.stringify(availableDependencies) + if (external) { + const deps = JSON.parse(external) + if (deps && deps.length) { + options.require.external = { + modules: deps + } + } + } + + const vm = new NodeVM(options) + const response = await vm.run(`module.exports = async function() {${this.code}}()`, __dirname) + + return response + } +} diff --git a/packages/components/nodes/tools/CustomTool/customtool.svg b/packages/components/nodes/tools/CustomTool/customtool.svg new file mode 100644 index 00000000..c5bd0fbc --- /dev/null +++ b/packages/components/nodes/tools/CustomTool/customtool.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index 738c7752..ec97d4d7 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -33,7 +33,7 @@ "form-data": "^4.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "^0.0.94", + "langchain": "^0.0.96", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", @@ -43,6 +43,7 @@ "playwright": "^1.35.0", "puppeteer": "^20.7.1", "srt-parser-2": "^1.2.3", + "vm2": "^3.9.19", "weaviate-ts-client": "^1.1.0", "ws": "^8.9.0" }, diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index bd94cca8..f8a6fd58 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -2,7 +2,18 @@ * Types */ -export type NodeParamsType = 'options' | 'string' | 'number' | 'boolean' | 'password' | 'json' | 'code' | 'date' | 'file' | 'folder' +export type NodeParamsType = + | 'asyncOptions' + | 'options' + | 'string' + | 'number' + | 'boolean' + | 'password' + | 'json' + | 'code' + | 'date' + | 'file' + | 'folder' export type CommonType = string | number | boolean | undefined | null @@ -16,6 +27,10 @@ export interface ICommonObject { [key: string]: any | CommonType | ICommonObject | CommonType[] | ICommonObject[] } +export type IDatabaseEntity = { + [key: string]: any +} + export interface IAttachment { content: string contentType: string @@ -50,6 +65,7 @@ export interface INodeParams { placeholder?: string fileType?: string additionalParams?: boolean + loadMethod?: string } export interface INodeExecutionData { @@ -74,6 +90,9 @@ export interface INodeProperties { export interface INode extends INodeProperties { inputs?: INodeParams[] output?: INodeOutputsValue[] + loadMethods?: { + [key: string]: (nodeData: INodeData, options?: ICommonObject) => Promise + } init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise } @@ -83,6 +102,7 @@ export interface INodeData extends INodeProperties { inputs?: ICommonObject outputs?: ICommonObject instance?: any + loadMethod?: string // method to load async options } export interface IMessage { diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index de026a35..ad8d28dc 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -18,6 +18,7 @@ export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is no */ export const getBaseClasses = (targetClass: any) => { const baseClasses: string[] = [] + const skipClassNames = ['BaseLangChain', 'Serializable'] if (targetClass instanceof Function) { let baseClass = targetClass @@ -26,7 +27,7 @@ export const getBaseClasses = (targetClass: any) => { const newBaseClass = Object.getPrototypeOf(baseClass) if (newBaseClass && newBaseClass !== Object && newBaseClass.name) { baseClass = newBaseClass - baseClasses.push(baseClass.name) + if (!skipClassNames.includes(baseClass.name)) baseClasses.push(baseClass.name) } else { break } @@ -284,3 +285,31 @@ const handleEscapeDoubleQuote = (value: string): string => { } return newValue === '' ? value : newValue } + +export const availableDependencies = [ + '@dqbd/tiktoken', + '@getzep/zep-js', + '@huggingface/inference', + '@pinecone-database/pinecone', + '@supabase/supabase-js', + 'axios', + 'cheerio', + 'chromadb', + 'cohere-ai', + 'd3-dsv', + 'form-data', + 'graphql', + 'html-to-text', + 'langchain', + 'linkifyjs', + 'mammoth', + 'moment', + 'node-fetch', + 'pdf-parse', + 'pdfjs-dist', + 'playwright', + 'puppeteer', + 'srt-parser-2', + 'typeorm', + 'weaviate-ts-client' +] diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index 08847a52..95f7368a 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -1,5 +1,10 @@ +import path from 'path' import { IChildProcessMessage, IReactFlowNode, IReactFlowObject, IRunChatflowMessageValue, INodeData } from './Interface' -import { buildLangchain, constructGraphs, getEndingNode, getStartingNodes, resolveVariables } from './utils' +import { buildLangchain, constructGraphs, getEndingNode, getStartingNodes, getUserHome, resolveVariables } from './utils' +import { DataSource } from 'typeorm' +import { ChatFlow } from './entity/ChatFlow' +import { ChatMessage } from './entity/ChatMessage' +import { Tool } from './entity/Tool' export class ChildProcess { /** @@ -22,6 +27,8 @@ export class ChildProcess { await sendToParentProcess('start', '_') + const childAppDataSource = await initDB() + // Create a Queue and add our initial node in it const { endingNodeData, chatflow, chatId, incomingInput, componentNodes } = messageValue @@ -84,6 +91,7 @@ export class ChildProcess { componentNodes, incomingInput.question, chatId, + childAppDataSource, incomingInput?.overrideConfig ) @@ -115,6 +123,22 @@ export class ChildProcess { } } +/** + * Initalize DB in child process + * @returns {DataSource} + */ +async function initDB() { + const homePath = path.join(getUserHome(), '.flowise') + const childAppDataSource = new DataSource({ + type: 'sqlite', + database: path.resolve(homePath, 'database.sqlite'), + synchronize: true, + entities: [ChatFlow, ChatMessage, Tool], + migrations: [] + }) + return await childAppDataSource.initialize() +} + /** * Send data back to parent process * @param {string} key Key of message diff --git a/packages/server/src/DataSource.ts b/packages/server/src/DataSource.ts index 76c8e144..2ec8104a 100644 --- a/packages/server/src/DataSource.ts +++ b/packages/server/src/DataSource.ts @@ -3,6 +3,7 @@ import path from 'path' import { DataSource } from 'typeorm' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' +import { Tool } from './entity/Tool' import { getUserHome } from './utils' let appDataSource: DataSource @@ -14,7 +15,7 @@ export const init = async (): Promise => { type: 'sqlite', database: path.resolve(homePath, 'database.sqlite'), synchronize: true, - entities: [ChatFlow, ChatMessage], + entities: [ChatFlow, ChatMessage, Tool], migrations: [] }) } diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 2c1fe406..1eafcae6 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -24,6 +24,17 @@ export interface IChatMessage { sourceDocuments: string } +export interface ITool { + id: string + name: string + description: string + color: string + schema: string + func: string + updatedDate: Date + createdDate: Date +} + export interface IComponentNodes { [key: string]: INode } diff --git a/packages/server/src/entity/Tool.ts b/packages/server/src/entity/Tool.ts new file mode 100644 index 00000000..d547374c --- /dev/null +++ b/packages/server/src/entity/Tool.ts @@ -0,0 +1,30 @@ +/* eslint-disable */ +import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm' +import { ITool } from '../Interface' + +@Entity() +export class Tool implements ITool { + @PrimaryGeneratedColumn('uuid') + id: string + + @Column() + name: string + + @Column() + description: string + + @Column() + color: string + + @Column({ nullable: true }) + schema: string + + @Column({ nullable: true }) + func: string + + @CreateDateColumn() + createdDate: Date + + @UpdateDateColumn() + updatedDate: Date +} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 40bd75cd..65bfef23 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -35,7 +35,8 @@ import { isSameOverrideConfig, replaceAllAPIKeys, isFlowValidForStream, - isVectorStoreFaiss + isVectorStoreFaiss, + databaseEntities } from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' @@ -43,8 +44,9 @@ import { NodesPool } from './NodesPool' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' import { ChatflowPool } from './ChatflowPool' -import { ICommonObject } from 'flowise-components' +import { ICommonObject, INodeOptionsValue } from 'flowise-components' import { fork } from 'child_process' +import { Tool } from './entity/Tool' export class App { app: express.Application @@ -142,6 +144,29 @@ export class App { } }) + // load async options + this.app.post('/api/v1/node-load-method/:name', async (req: Request, res: Response) => { + const nodeData: INodeData = req.body + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, req.params.name)) { + try { + const nodeInstance = this.nodesPool.componentNodes[req.params.name] + const methodName = nodeData.loadMethod || '' + + const returnOptions: INodeOptionsValue[] = await nodeInstance.loadMethods![methodName]!.call(nodeInstance, nodeData, { + appDataSource: this.AppDataSource, + databaseEntities: databaseEntities + }) + + return res.json(returnOptions) + } catch (error) { + return res.json([]) + } + } else { + res.status(404).send(`Node ${req.params.name} not found`) + return + } + }) + // ---------------------------------------- // Chatflows // ---------------------------------------- @@ -257,6 +282,63 @@ export class App { return res.json(results) }) + // ---------------------------------------- + // Tools + // ---------------------------------------- + + // Get all tools + this.app.get('/api/v1/tools', async (req: Request, res: Response) => { + const tools = await this.AppDataSource.getRepository(Tool).find() + return res.json(tools) + }) + + // Get specific tool + this.app.get('/api/v1/tools/:id', async (req: Request, res: Response) => { + const tool = await this.AppDataSource.getRepository(Tool).findOneBy({ + id: req.params.id + }) + return res.json(tool) + }) + + // Add tool + this.app.post('/api/v1/tools', async (req: Request, res: Response) => { + const body = req.body + const newTool = new Tool() + Object.assign(newTool, body) + + const tool = this.AppDataSource.getRepository(Tool).create(newTool) + const results = await this.AppDataSource.getRepository(Tool).save(tool) + + return res.json(results) + }) + + // Update tool + this.app.put('/api/v1/tools/:id', async (req: Request, res: Response) => { + const tool = await this.AppDataSource.getRepository(Tool).findOneBy({ + id: req.params.id + }) + + if (!tool) { + res.status(404).send(`Tool ${req.params.id} not found`) + return + } + + const body = req.body + const updateTool = new Tool() + Object.assign(updateTool, body) + + this.AppDataSource.getRepository(Tool).merge(tool, updateTool) + const result = await this.AppDataSource.getRepository(Tool).save(tool) + + return res.json(result) + }) + + // Delete tool + this.app.delete('/api/v1/tools/:id', async (req: Request, res: Response) => { + const results = await this.AppDataSource.getRepository(Tool).delete({ id: req.params.id }) + return res.json(results) + }) + // ---------------------------------------- // Configuration // ---------------------------------------- @@ -623,6 +705,7 @@ export class App { this.nodesPool.componentNodes, incomingInput.question, chatId, + this.AppDataSource, incomingInput?.overrideConfig ) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 8e19bf5b..e3005c7b 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -15,10 +15,15 @@ import { IOverrideConfig } from '../Interface' import { cloneDeep, get, omit, merge } from 'lodash' -import { ICommonObject, getInputVariables } from 'flowise-components' +import { ICommonObject, getInputVariables, IDatabaseEntity } from 'flowise-components' import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' +import { ChatFlow } from '../entity/ChatFlow' +import { ChatMessage } from '../entity/ChatMessage' +import { Tool } from '../entity/Tool' +import { DataSource } from 'typeorm' const QUESTION_VAR_PREFIX = 'question' +export const databaseEntities: IDatabaseEntity = { ChatFlow: ChatFlow, ChatMessage: ChatMessage, Tool: Tool } /** * Returns the home folder path of the user if @@ -183,6 +188,7 @@ export const buildLangchain = async ( componentNodes: IComponentNodes, question: string, chatId: string, + appDataSource: DataSource, overrideConfig?: ICommonObject ) => { const flowNodes = cloneDeep(reactFlowNodes) @@ -215,7 +221,11 @@ export const buildLangchain = async ( if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig) const reactFlowNodeData: INodeData = resolveVariables(flowNodeData, flowNodes, question) - flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, { chatId }) + flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, { + chatId, + appDataSource, + databaseEntities + }) } catch (e: any) { console.error(e) throw new Error(e) diff --git a/packages/ui/package.json b/packages/ui/package.json index af9ac5e0..1d7bc490 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.10.6", "@mui/icons-material": "^5.0.3", "@mui/material": "^5.11.12", + "@mui/x-data-grid": "^6.8.0", "@tabler/icons": "^1.39.1", "clsx": "^1.1.1", "formik": "^2.2.6", diff --git a/packages/ui/src/api/tools.js b/packages/ui/src/api/tools.js new file mode 100644 index 00000000..77992a2a --- /dev/null +++ b/packages/ui/src/api/tools.js @@ -0,0 +1,19 @@ +import client from './client' + +const getAllTools = () => client.get('/tools') + +const getSpecificTool = (id) => client.get(`/tools/${id}`) + +const createNewTool = (body) => client.post(`/tools`, body) + +const updateTool = (id, body) => client.put(`/tools/${id}`, body) + +const deleteTool = (id) => client.delete(`/tools/${id}`) + +export default { + getAllTools, + getSpecificTool, + createNewTool, + updateTool, + deleteTool +} diff --git a/packages/ui/src/assets/images/tools_empty.svg b/packages/ui/src/assets/images/tools_empty.svg new file mode 100644 index 00000000..9a2a2a77 --- /dev/null +++ b/packages/ui/src/assets/images/tools_empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/menu-items/dashboard.js b/packages/ui/src/menu-items/dashboard.js index f1cd5062..948b4e4a 100644 --- a/packages/ui/src/menu-items/dashboard.js +++ b/packages/ui/src/menu-items/dashboard.js @@ -1,8 +1,8 @@ // assets -import { IconHierarchy, IconBuildingStore, IconKey } from '@tabler/icons' +import { IconHierarchy, IconBuildingStore, IconKey, IconTool } from '@tabler/icons' // constant -const icons = { IconHierarchy, IconBuildingStore, IconKey } +const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool } // ==============================|| DASHBOARD MENU ITEMS ||============================== // @@ -27,6 +27,14 @@ const dashboard = { icon: icons.IconBuildingStore, breadcrumbs: true }, + { + id: 'tools', + title: 'Tools', + type: 'item', + url: '/tools', + icon: icons.IconTool, + breadcrumbs: true + }, { id: 'apikey', title: 'API Keys', diff --git a/packages/ui/src/routes/MainRoutes.js b/packages/ui/src/routes/MainRoutes.js index 5353e41a..28e60287 100644 --- a/packages/ui/src/routes/MainRoutes.js +++ b/packages/ui/src/routes/MainRoutes.js @@ -13,6 +13,9 @@ const Marketplaces = Loadable(lazy(() => import('views/marketplaces'))) // apikey routing const APIKey = Loadable(lazy(() => import('views/apikey'))) +// apikey routing +const Tools = Loadable(lazy(() => import('views/tools'))) + // ==============================|| MAIN ROUTING ||============================== // const MainRoutes = { @@ -34,6 +37,10 @@ const MainRoutes = { { path: '/apikey', element: + }, + { + path: '/tools', + element: } ] } diff --git a/packages/ui/src/ui-component/cards/ItemCard.js b/packages/ui/src/ui-component/cards/ItemCard.js index 506947ce..345a88d5 100644 --- a/packages/ui/src/ui-component/cards/ItemCard.js +++ b/packages/ui/src/ui-component/cards/ItemCard.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types' // material-ui -import { styled, useTheme } from '@mui/material/styles' -import { Box, Grid, Chip, Typography } from '@mui/material' +import { styled } from '@mui/material/styles' +import { Box, Grid, Typography } from '@mui/material' // project imports import MainCard from 'ui-component/cards/MainCard' @@ -27,20 +27,7 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({ // ===========================|| CONTRACT CARD ||=========================== // -const ItemCard = ({ isLoading, data, images, onClick }) => { - const theme = useTheme() - - const chipSX = { - height: 24, - padding: '0 6px' - } - - const activeChatflowSX = { - ...chipSX, - color: 'white', - backgroundColor: theme.palette.success.dark - } - +const ItemCard = ({ isLoading, data, images, color, onClick }) => { return ( <> {isLoading ? ( @@ -49,7 +36,24 @@ const ItemCard = ({ isLoading, data, images, onClick }) => { -
+
+ {color && ( +
+ )} @@ -61,13 +65,6 @@ const ItemCard = ({ isLoading, data, images, onClick }) => { {data.description} )} - - {data.deployed && ( - - - - )} - {images && (
{ + const loadMethod = nodeData.inputParams.find((param) => param.name === name)?.loadMethod + const username = localStorage.getItem('username') + const password = localStorage.getItem('password') + + let lists = await axios + .post( + `${baseURL}/api/v1/node-load-method/${nodeData.name}`, + { ...nodeData, loadMethod }, + { auth: username && password ? { username, password } : undefined } + ) + .then(async function (response) { + return response.data + }) + .catch(function (error) { + console.error(error) + }) + return lists +} + +export const AsyncDropdown = ({ + name, + nodeData, + value, + onSelect, + isCreateNewOption, + onCreateNew, + disabled = false, + disableClearable = false +}) => { + const customization = useSelector((state) => state.customization) + + const [open, setOpen] = useState(false) + const [options, setOptions] = useState([]) + const [loading, setLoading] = useState(false) + const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value) + const getDefaultOptionValue = () => '' + const addNewOption = [{ label: '- Create New -', name: '-create-' }] + let [internalValue, setInternalValue] = useState(value ?? 'choose an option') + + useEffect(() => { + setLoading(true) + ;(async () => { + const fetchData = async () => { + let response = await fetchList({ name, nodeData }) + if (isCreateNewOption) setOptions([...response, ...addNewOption]) + else setOptions([...response]) + setLoading(false) + } + fetchData() + })() + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return ( + <> + { + setOpen(true) + }} + onClose={() => { + setOpen(false) + }} + options={options} + value={findMatchingOptions(options, internalValue) || getDefaultOptionValue()} + onChange={(e, selection) => { + const value = selection ? selection.name : '' + if (isCreateNewOption && value === '-create-') { + onCreateNew() + } else { + setInternalValue(value) + onSelect(value) + } + }} + PopperComponent={StyledPopper} + loading={loading} + renderInput={(params) => ( + + {loading ? : null} + {params.InputProps.endAdornment} + + ) + }} + /> + )} + renderOption={(props, option) => ( + +
+ {option.label} + {option.description && ( + {option.description} + )} +
+
+ )} + /> + + ) +} + +AsyncDropdown.propTypes = { + name: PropTypes.string, + nodeData: PropTypes.object, + value: PropTypes.string, + onSelect: PropTypes.func, + onCreateNew: PropTypes.func, + disabled: PropTypes.bool, + disableClearable: PropTypes.bool, + isCreateNewOption: PropTypes.bool +} diff --git a/packages/ui/src/ui-component/editor/DarkCodeEditor.js b/packages/ui/src/ui-component/editor/DarkCodeEditor.js index 3925f4a6..bf0719dd 100644 --- a/packages/ui/src/ui-component/editor/DarkCodeEditor.js +++ b/packages/ui/src/ui-component/editor/DarkCodeEditor.js @@ -21,6 +21,7 @@ export const DarkCodeEditor = ({ value, placeholder, disabled = false, type, sty onValueChange={onValueChange} onMouseUp={onMouseUp} onBlur={onBlur} + tabSize={4} style={{ ...style, background: theme.palette.codeEditor.main diff --git a/packages/ui/src/ui-component/editor/LightCodeEditor.js b/packages/ui/src/ui-component/editor/LightCodeEditor.js index 86f7057d..14dcbf29 100644 --- a/packages/ui/src/ui-component/editor/LightCodeEditor.js +++ b/packages/ui/src/ui-component/editor/LightCodeEditor.js @@ -21,6 +21,7 @@ export const LightCodeEditor = ({ value, placeholder, disabled = false, type, st onValueChange={onValueChange} onMouseUp={onMouseUp} onBlur={onBlur} + tabSize={4} style={{ ...style, background: theme.palette.card.main diff --git a/packages/ui/src/ui-component/grid/Grid.js b/packages/ui/src/ui-component/grid/Grid.js new file mode 100644 index 00000000..2049f56c --- /dev/null +++ b/packages/ui/src/ui-component/grid/Grid.js @@ -0,0 +1,37 @@ +import PropTypes from 'prop-types' +import { DataGrid } from '@mui/x-data-grid' +import { IconPlus } from '@tabler/icons' +import { Button } from '@mui/material' + +export const Grid = ({ columns, rows, style, onRowUpdate, addNewRow }) => { + const handleProcessRowUpdate = (newRow) => { + onRowUpdate(newRow) + return newRow + } + + return ( + <> + + {rows && columns && ( +
+ console.error(error)} + rows={rows} + columns={columns} + /> +
+ )} + + ) +} + +Grid.propTypes = { + rows: PropTypes.array, + columns: PropTypes.array, + style: PropTypes.any, + addNewRow: PropTypes.func, + onRowUpdate: PropTypes.func +} diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index fac83225..03f891ec 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -39,7 +39,7 @@ export const initNode = (nodeData, newNodeId) => { const incoming = nodeData.inputs ? nodeData.inputs.length : 0 const outgoing = 1 - const whitelistTypes = ['options', 'string', 'number', 'boolean', 'password', 'json', 'code', 'date', 'file', 'folder'] + const whitelistTypes = ['asyncOptions', 'options', 'string', 'number', 'boolean', 'password', 'json', 'code', 'date', 'file', 'folder'] for (let i = 0; i < incoming; i += 1) { const newInput = { @@ -334,3 +334,22 @@ export const throttle = (func, limit) => { } } } + +export const generateRandomGradient = () => { + function randomColor() { + var color = 'rgb(' + for (var i = 0; i < 3; i++) { + var random = Math.floor(Math.random() * 256) + color += random + if (i < 2) { + color += ',' + } + } + color += ')' + return color + } + + var gradient = 'linear-gradient(' + randomColor() + ', ' + randomColor() + ')' + + return gradient +} diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index d58f7a66..31a8a37d 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -7,10 +7,11 @@ import { useSelector } from 'react-redux' import { useTheme, styled } from '@mui/material/styles' import { Box, Typography, Tooltip, IconButton } from '@mui/material' import { tooltipClasses } from '@mui/material/Tooltip' -import { IconArrowsMaximize } from '@tabler/icons' +import { IconArrowsMaximize, IconEdit } from '@tabler/icons' // project import import { Dropdown } from 'ui-component/dropdown/Dropdown' +import { AsyncDropdown } from 'ui-component/dropdown/AsyncDropdown' import { Input } from 'ui-component/input/Input' import { File } from 'ui-component/file/File' import { SwitchInput } from 'ui-component/switch/Switch' @@ -18,6 +19,9 @@ import { flowContext } from 'store/context/ReactFlowContext' import { isValidConnection, getAvailableNodesForVariable } from 'utils/genericHelper' import { JsonEditorInput } from 'ui-component/json/JsonEditor' import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' +import ToolDialog from 'views/tools/ToolDialog' + +const EDITABLE_TOOLS = ['selectedTool'] const CustomWidthTooltip = styled(({ className, ...props }) => )({ [`& .${tooltipClasses.tooltip}`]: { @@ -36,6 +40,9 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA const [position, setPosition] = useState(0) const [showExpandDialog, setShowExpandDialog] = useState(false) const [expandDialogProps, setExpandDialogProps] = useState({}) + const [showAsyncOptionDialog, setAsyncOptionEditDialog] = useState('') + const [asyncOptionEditDialogProps, setAsyncOptionEditDialogProps] = useState({}) + const [reloadTimestamp, setReloadTimestamp] = useState(Date.now().toString()) const onExpandDialogClicked = (value, inputParam) => { const dialogProp = { @@ -61,6 +68,42 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA data.inputs[inputParamName] = newValue } + const editAsyncOption = (inputParamName, inputValue) => { + if (inputParamName === 'selectedTool') { + setAsyncOptionEditDialogProps({ + title: 'Edit Tool', + type: 'EDIT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Save', + toolId: inputValue + }) + } + setAsyncOptionEditDialog(inputParamName) + } + + const addAsyncOption = (inputParamName) => { + if (inputParamName === 'selectedTool') { + setAsyncOptionEditDialogProps({ + title: 'Add New Tool', + type: 'ADD', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add' + }) + } + setAsyncOptionEditDialog(inputParamName) + } + + const onConfirmAsyncOption = (selectedOptionId = '') => { + if (!selectedOptionId) { + data.inputs[showAsyncOptionDialog] = '' + } else { + data.inputs[showAsyncOptionDialog] = selectedOptionId + setReloadTimestamp(Date.now().toString()) + } + setAsyncOptionEditDialogProps({}) + setAsyncOptionEditDialog('') + } + useEffect(() => { if (ref.current && ref.current.offsetTop && ref.current.clientHeight) { setPosition(ref.current.offsetTop + ref.current.clientHeight / 2) @@ -186,12 +229,44 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA name={inputParam.name} options={inputParam.options} onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)} - value={data.inputs[inputParam.name] ?? inputParam.default ?? 'chose an option'} + value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'} /> )} + {inputParam.type === 'asyncOptions' && ( + <> + {data.inputParams.length === 1 &&
} +
+ (data.inputs[inputParam.name] = newValue)} + onCreateNew={() => addAsyncOption(inputParam.name)} + /> + {EDITABLE_TOOLS.includes(inputParam.name) && data.inputs[inputParam.name] && ( + editAsyncOption(inputParam.name, data.inputs[inputParam.name])} + > + + + )} +
+ + )} )} + setAsyncOptionEditDialog('')} + onConfirm={onConfirmAsyncOption} + >
) } diff --git a/packages/ui/src/views/tools/ToolDialog.js b/packages/ui/src/views/tools/ToolDialog.js new file mode 100644 index 00000000..bd5af355 --- /dev/null +++ b/packages/ui/src/views/tools/ToolDialog.js @@ -0,0 +1,448 @@ +import { createPortal } from 'react-dom' +import PropTypes from 'prop-types' +import { useState, useEffect, useCallback, useMemo } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import { cloneDeep } from 'lodash' + +import { Box, Typography, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, OutlinedInput } from '@mui/material' +import { StyledButton } from 'ui-component/button/StyledButton' +import { Grid } from 'ui-component/grid/Grid' +import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' +import { GridActionsCellItem } from '@mui/x-data-grid' +import DeleteIcon from '@mui/icons-material/Delete' +import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' +import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' +import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' +import { useTheme } from '@mui/material/styles' + +// Icons +import { IconX } from '@tabler/icons' + +// API +import toolsApi from 'api/tools' + +// Hooks +import useConfirm from 'hooks/useConfirm' +import useApi from 'hooks/useApi' + +// utils +import useNotifier from 'utils/useNotifier' +import { generateRandomGradient } from 'utils/genericHelper' + +const exampleAPIFunc = `/* +* You can use any libraries imported in Flowise +* You can use properties specified in Output Schema as variables. Ex: Property = userid, Variable = $userid +* Must return a string value at the end of function +*/ + +const fetch = require('node-fetch'); +const url = 'https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t_weather=true'; +const options = { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } +}; +try { + const response = await fetch(url, options); + const text = await response.text(); + return text; +} catch (error) { + console.error(error); + return ''; +}` + +const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const portalElement = document.getElementById('portal') + const theme = useTheme() + + const customization = useSelector((state) => state.customization) + const dispatch = useDispatch() + + // ==============================|| Snackbar ||============================== // + + useNotifier() + const { confirm } = useConfirm() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const getSpecificToolApi = useApi(toolsApi.getSpecificTool) + + const [toolId, setToolId] = useState('') + const [toolName, setToolName] = useState('') + const [toolDesc, setToolDesc] = useState('') + const [toolSchema, setToolSchema] = useState([]) + const [toolFunc, setToolFunc] = useState('') + + const deleteItem = useCallback( + (id) => () => { + setTimeout(() => { + setToolSchema((prevRows) => prevRows.filter((row) => row.id !== id)) + }) + }, + [] + ) + + const addNewRow = () => { + setTimeout(() => { + setToolSchema((prevRows) => { + let allRows = [...cloneDeep(prevRows)] + const lastRowId = allRows.length ? allRows[allRows.length - 1].id + 1 : 1 + allRows.push({ + id: lastRowId, + property: '', + description: '', + type: '', + required: false + }) + return allRows + }) + }) + } + + const onRowUpdate = (newRow) => { + setTimeout(() => { + setToolSchema((prevRows) => { + let allRows = [...cloneDeep(prevRows)] + const indexToUpdate = allRows.findIndex((row) => row.id === newRow.id) + if (indexToUpdate >= 0) { + allRows[indexToUpdate] = { ...newRow } + } + return allRows + }) + }) + } + + const columns = useMemo( + () => [ + { field: 'property', headerName: 'Property', editable: true, flex: 1 }, + { + field: 'type', + headerName: 'Type', + type: 'singleSelect', + valueOptions: ['string', 'number', 'boolean', 'date'], + editable: true, + width: 120 + }, + { field: 'description', headerName: 'Description', editable: true, flex: 1 }, + { field: 'required', headerName: 'Required', type: 'boolean', editable: true, width: 80 }, + { + field: 'actions', + type: 'actions', + width: 80, + getActions: (params) => [ + } label='Delete' onClick={deleteItem(params.id)} /> + ] + } + ], + [deleteItem] + ) + + const formatSchema = (schema) => { + try { + const parsedSchema = JSON.parse(schema) + return parsedSchema.map((sch, index) => { + return { + ...sch, + id: index + } + }) + } catch (e) { + return [] + } + } + + useEffect(() => { + if (getSpecificToolApi.data) { + setToolId(getSpecificToolApi.data.id) + setToolName(getSpecificToolApi.data.name) + setToolDesc(getSpecificToolApi.data.description) + setToolSchema(formatSchema(getSpecificToolApi.data.schema)) + if (getSpecificToolApi.data.func) setToolFunc(getSpecificToolApi.data.func) + else setToolFunc('') + } + }, [getSpecificToolApi.data]) + + useEffect(() => { + if (dialogProps.type === 'EDIT' && dialogProps.data) { + setToolId(dialogProps.data.id) + setToolName(dialogProps.data.name) + setToolDesc(dialogProps.data.description) + setToolSchema(formatSchema(dialogProps.data.schema)) + if (dialogProps.data.func) setToolFunc(dialogProps.data.func) + else setToolFunc('') + } else if (dialogProps.type === 'EDIT' && dialogProps.toolId) { + getSpecificToolApi.request(dialogProps.toolId) + } else if (dialogProps.type === 'ADD') { + setToolId('') + setToolName('') + setToolDesc('') + setToolSchema([]) + setToolFunc('') + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dialogProps]) + + const addNewTool = async () => { + try { + const obj = { + name: toolName, + description: toolDesc, + color: generateRandomGradient(), + schema: JSON.stringify(toolSchema), + func: toolFunc + } + const createResp = await toolsApi.createNewTool(obj) + if (createResp.data) { + enqueueSnackbar({ + message: 'New Tool added', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm(createResp.data.id) + } + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to add new Tool: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + + const saveTool = async () => { + try { + const saveResp = await toolsApi.updateTool(toolId, { + name: toolName, + description: toolDesc, + schema: JSON.stringify(toolSchema), + func: toolFunc + }) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Tool saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm(saveResp.data.id) + } + } catch (error) { + console.error(error) + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to save Tool: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + + const deleteTool = async () => { + const confirmPayload = { + title: `Delete Tool`, + description: `Delete tool ${toolName}?`, + confirmButtonName: 'Delete', + cancelButtonName: 'Cancel' + } + const isConfirmed = await confirm(confirmPayload) + + if (isConfirmed) { + try { + const delResp = await toolsApi.deleteTool(toolId) + if (delResp.data) { + enqueueSnackbar({ + message: 'Tool deleted', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm() + } + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to delete Tool: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + } + + const component = show ? ( + + + {dialogProps.title} + + + + + + Tool Name +  * + + + setToolName(e.target.value)} + /> + + + + + Tool description +  * + + + setToolDesc(e.target.value)} + /> + + + + + Output Schema + + + + + + + + + Javascript Function + + + + + {customization.isDarkMode ? ( + setToolFunc(code)} + style={{ + fontSize: '0.875rem', + minHeight: 'calc(100vh - 220px)', + width: '100%', + borderRadius: 5 + }} + /> + ) : ( + setToolFunc(code)} + style={{ + fontSize: '0.875rem', + minHeight: 'calc(100vh - 220px)', + width: '100%', + border: `1px solid ${theme.palette.grey[300]}`, + borderRadius: 5 + }} + /> + )} + + + + {dialogProps.type === 'EDIT' && ( + deleteTool()}> + Delete + + )} + (dialogProps.type === 'ADD' ? addNewTool() : saveTool())} + > + {dialogProps.confirmButtonName} + + + + + ) : null + + return createPortal(component, portalElement) +} + +ToolDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func, + onConfirm: PropTypes.func +} + +export default ToolDialog diff --git a/packages/ui/src/views/tools/index.js b/packages/ui/src/views/tools/index.js new file mode 100644 index 00000000..efe9e69d --- /dev/null +++ b/packages/ui/src/views/tools/index.js @@ -0,0 +1,112 @@ +import { useEffect, useState } from 'react' +import { useSelector } from 'react-redux' + +// material-ui +import { Grid, Box, Stack } from '@mui/material' +import { useTheme } from '@mui/material/styles' + +// project imports +import MainCard from 'ui-component/cards/MainCard' +import ItemCard from 'ui-component/cards/ItemCard' +import { gridSpacing } from 'store/constant' +import ToolEmptySVG from 'assets/images/tools_empty.svg' +import { StyledButton } from 'ui-component/button/StyledButton' +import ToolDialog from './ToolDialog' + +// API +import toolsApi from 'api/tools' + +// Hooks +import useApi from 'hooks/useApi' + +// icons +import { IconPlus } from '@tabler/icons' + +// ==============================|| CHATFLOWS ||============================== // + +const Tools = () => { + const theme = useTheme() + const customization = useSelector((state) => state.customization) + + const getAllToolsApi = useApi(toolsApi.getAllTools) + + const [showDialog, setShowDialog] = useState(false) + const [dialogProps, setDialogProps] = useState({}) + + const addNew = () => { + const dialogProp = { + title: 'Add New Tool', + type: 'ADD', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add' + } + setDialogProps(dialogProp) + setShowDialog(true) + } + + const edit = (selectedTool) => { + const dialogProp = { + title: 'Edit Tool', + type: 'EDIT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Save', + data: selectedTool + } + setDialogProps(dialogProp) + setShowDialog(true) + } + + const onConfirm = () => { + setShowDialog(false) + getAllToolsApi.request() + } + + useEffect(() => { + getAllToolsApi.request() + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return ( + <> + + +

Tools

+ + + + }> + Create New + + + +
+ + {!getAllToolsApi.loading && + getAllToolsApi.data && + getAllToolsApi.data.map((data, index) => ( + + edit(data)} /> + + ))} + + {!getAllToolsApi.loading && (!getAllToolsApi.data || getAllToolsApi.data.length === 0) && ( + + + ToolEmptySVG + +
No Tools Created Yet
+
+ )} +
+ setShowDialog(false)} + onConfirm={onConfirm} + > + + ) +} + +export default Tools From bee16d4982819741fcf5a78308bc79bf32b55a17 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Wed, 21 Jun 2023 19:29:32 +0100 Subject: [PATCH 02/44] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index cd443a19..fa9d527b 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +github: [FlowiseAI] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username From 412539a9db9f865d0ed9142368d65c440cf94427 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 21 Jun 2023 20:56:25 +0100 Subject: [PATCH 03/44] update openai agent --- .../OpenAIFunctionAgent.ts | 14 +- .../server/marketplaces/OpenAI Agent.json | 322 +++++++++++++----- 2 files changed, 244 insertions(+), 92 deletions(-) diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index 4c740874..1cbcb547 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -42,6 +42,14 @@ class OpenAIFunctionAgent_Agents implements INode { description: 'Only works with gpt-3.5-turbo-0613 and gpt-4-0613. Refer docs for more info', type: 'BaseChatModel' + }, + { + label: 'System Message', + name: 'systemMessage', + type: 'string', + rows: 4, + optional: true, + additionalParams: true } ] } @@ -49,13 +57,17 @@ class OpenAIFunctionAgent_Agents implements INode { async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as BaseLanguageModel const memory = nodeData.inputs?.memory as BaseChatMemory + const systemMessage = nodeData.inputs?.systemMessage as string let tools = nodeData.inputs?.tools tools = flatten(tools) const executor = await initializeAgentExecutorWithOptions(tools, model, { agentType: 'openai-functions', - verbose: process.env.DEBUG === 'true' ? true : false + verbose: process.env.DEBUG === 'true' ? true : false, + agentArgs: { + prefix: systemMessage ?? `You are a helpful AI assistant.` + } }) if (memory) executor.memory = memory diff --git a/packages/server/marketplaces/OpenAI Agent.json b/packages/server/marketplaces/OpenAI Agent.json index 7e685546..75dc1527 100644 --- a/packages/server/marketplaces/OpenAI Agent.json +++ b/packages/server/marketplaces/OpenAI Agent.json @@ -6,8 +6,8 @@ "height": 524, "id": "chatOpenAI_0", "position": { - "x": 373.8366297840716, - "y": 448.58765780622326 + "x": 648.7470970481406, + "y": 462.3331811694268 }, "type": "customNode", "data": { @@ -34,33 +34,29 @@ "label": "gpt-4", "name": "gpt-4" }, + { + "label": "gpt-4-0314", + "name": "gpt-4-0314" + }, + { + "label": "gpt-4-32k-0314", + "name": "gpt-4-32k-0314" + }, { "label": "gpt-4-0613", "name": "gpt-4-0613" }, - { - "label": "gpt-4-32k", - "name": "gpt-4-32k" - }, - { - "label": "gpt-4-32k-0613", - "name": "gpt-4-32k-0613" - }, { "label": "gpt-3.5-turbo", "name": "gpt-3.5-turbo" }, + { + "label": "gpt-3.5-turbo-0301", + "name": "gpt-3.5-turbo-0301" + }, { "label": "gpt-3.5-turbo-0613", "name": "gpt-3.5-turbo-0613" - }, - { - "label": "gpt-3.5-turbo-16k", - "name": "gpt-3.5-turbo-16k" - }, - { - "label": "gpt-3.5-turbo-16k-0613", - "name": "gpt-3.5-turbo-16k-0613" } ], "default": "gpt-3.5-turbo", @@ -148,64 +144,8 @@ }, "selected": false, "positionAbsolute": { - "x": 373.8366297840716, - "y": 448.58765780622326 - }, - "dragging": false - }, - { - "width": 300, - "height": 280, - "id": "openAIFunctionAgent_0", - "position": { - "x": 1084.5405852317417, - "y": 384.4653768834282 - }, - "type": "customNode", - "data": { - "id": "openAIFunctionAgent_0", - "label": "OpenAI Function Agent", - "name": "openAIFunctionAgent", - "type": "AgentExecutor", - "baseClasses": ["AgentExecutor", "BaseChain", "BaseLangChain", "Serializable"], - "category": "Agents", - "description": "An agent that uses OpenAI's Function Calling functionality to pick the tool and args to call", - "inputParams": [], - "inputAnchors": [ - { - "label": "Allowed Tools", - "name": "tools", - "type": "Tool", - "list": true, - "id": "openAIFunctionAgent_0-input-tools-Tool" - }, - { - "label": "OpenAI Chat Model", - "name": "model", - "description": "Only works with gpt-3.5-turbo-0613 and gpt-4-0613. Refer docs for more info", - "type": "BaseChatModel", - "id": "openAIFunctionAgent_0-input-model-BaseChatModel" - } - ], - "inputs": { - "tools": ["{{calculator_0.data.instance}}", "{{serper_0.data.instance}}"], - "model": "{{chatOpenAI_0.data.instance}}" - }, - "outputAnchors": [ - { - "id": "openAIFunctionAgent_0-output-openAIFunctionAgent-AgentExecutor|BaseChain|BaseLangChain|Serializable", - "name": "openAIFunctionAgent", - "label": "AgentExecutor", - "type": "AgentExecutor | BaseChain | BaseLangChain | Serializable" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 1084.5405852317417, - "y": 384.4653768834282 + "x": 648.7470970481406, + "y": 462.3331811694268 }, "dragging": false }, @@ -214,8 +154,8 @@ "height": 278, "id": "serper_0", "position": { - "x": 691.7580226065319, - "y": 34.00444633899792 + "x": 486.27248799490576, + "y": 4.465900738576664 }, "type": "customNode", "data": { @@ -249,8 +189,8 @@ }, "selected": false, "positionAbsolute": { - "x": 691.7580226065319, - "y": 34.00444633899792 + "x": 486.27248799490576, + "y": 4.465900738576664 }, "dragging": false }, @@ -259,8 +199,8 @@ "height": 143, "id": "calculator_0", "position": { - "x": 341.63347110886497, - "y": 261.6753474034481 + "x": 286.4092336819905, + "y": 304.05673891709597 }, "type": "customNode", "data": { @@ -287,20 +227,198 @@ }, "selected": false, "positionAbsolute": { - "x": 341.63347110886497, - "y": 261.6753474034481 + "x": 286.4092336819905, + "y": 304.05673891709597 + }, + "dragging": false + }, + { + "width": 300, + "height": 383, + "id": "openAIFunctionAgent_0", + "position": { + "x": 1341.2259105169032, + "y": 318.35651549722945 + }, + "type": "customNode", + "data": { + "id": "openAIFunctionAgent_0", + "label": "OpenAI Function Agent", + "name": "openAIFunctionAgent", + "type": "AgentExecutor", + "baseClasses": ["AgentExecutor", "BaseChain"], + "category": "Agents", + "description": "An agent that uses OpenAI's Function Calling functionality to pick the tool and args to call", + "inputParams": [ + { + "label": "System Message", + "name": "systemMessage", + "type": "string", + "rows": 4, + "optional": true, + "additionalParams": true, + "id": "openAIFunctionAgent_0-input-systemMessage-string" + } + ], + "inputAnchors": [ + { + "label": "Allowed Tools", + "name": "tools", + "type": "Tool", + "list": true, + "id": "openAIFunctionAgent_0-input-tools-Tool" + }, + { + "label": "Memory", + "name": "memory", + "type": "BaseChatMemory", + "id": "openAIFunctionAgent_0-input-memory-BaseChatMemory" + }, + { + "label": "OpenAI Chat Model", + "name": "model", + "description": "Only works with gpt-3.5-turbo-0613 and gpt-4-0613. Refer docs for more info", + "type": "BaseChatModel", + "id": "openAIFunctionAgent_0-input-model-BaseChatModel" + } + ], + "inputs": { + "tools": ["{{serper_0.data.instance}}", "{{calculator_0.data.instance}}", "{{customTool_0.data.instance}}"], + "memory": "{{bufferMemory_0.data.instance}}", + "model": "{{chatOpenAI_0.data.instance}}", + "systemMessage": "" + }, + "outputAnchors": [ + { + "id": "openAIFunctionAgent_0-output-openAIFunctionAgent-AgentExecutor|BaseChain", + "name": "openAIFunctionAgent", + "label": "AgentExecutor", + "type": "AgentExecutor | BaseChain" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1341.2259105169032, + "y": 318.35651549722945 + }, + "dragging": false + }, + { + "width": 300, + "height": 376, + "id": "bufferMemory_0", + "position": { + "x": 285.7750469157585, + "y": 465.1140427303788 + }, + "type": "customNode", + "data": { + "id": "bufferMemory_0", + "label": "Buffer Memory", + "name": "bufferMemory", + "type": "BufferMemory", + "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], + "category": "Memory", + "description": "Remembers previous conversational back and forths directly", + "inputParams": [ + { + "label": "Memory Key", + "name": "memoryKey", + "type": "string", + "default": "chat_history", + "id": "bufferMemory_0-input-memoryKey-string" + }, + { + "label": "Input Key", + "name": "inputKey", + "type": "string", + "default": "input", + "id": "bufferMemory_0-input-inputKey-string" + } + ], + "inputAnchors": [], + "inputs": { + "memoryKey": "chat_history", + "inputKey": "input" + }, + "outputAnchors": [ + { + "id": "bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory", + "name": "bufferMemory", + "label": "BufferMemory", + "type": "BufferMemory | BaseChatMemory | BaseMemory" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 285.7750469157585, + "y": 465.1140427303788 + }, + "dragging": false + }, + { + "width": 300, + "height": 277, + "id": "customTool_0", + "position": { + "x": 883.9529939431576, + "y": -32.32503903826486 + }, + "type": "customNode", + "data": { + "id": "customTool_0", + "label": "Custom Tool", + "name": "customTool", + "type": "CustomTool", + "baseClasses": ["CustomTool", "Tool", "StructuredTool"], + "category": "Tools", + "description": "Use custom tool you've created in Flowise within chatflow", + "inputParams": [ + { + "label": "Select Tool", + "name": "selectedTool", + "type": "asyncOptions", + "loadMethod": "listTools", + "id": "customTool_0-input-selectedTool-asyncOptions" + } + ], + "inputAnchors": [], + "inputs": { + "selectedTool": "" + }, + "outputAnchors": [ + { + "id": "customTool_0-output-customTool-CustomTool|Tool|StructuredTool", + "name": "customTool", + "label": "CustomTool", + "type": "CustomTool | Tool | StructuredTool" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 883.9529939431576, + "y": -32.32503903826486 }, "dragging": false } ], "edges": [ { - "source": "chatOpenAI_0", - "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain|Serializable", + "source": "serper_0", + "sourceHandle": "serper_0-output-serper-Serper|Tool|StructuredTool|BaseLangChain|Serializable", "target": "openAIFunctionAgent_0", - "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel", + "targetHandle": "openAIFunctionAgent_0-input-tools-Tool", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain|Serializable-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-BaseChatModel", + "id": "serper_0-serper_0-output-serper-Serper|Tool|StructuredTool|BaseLangChain|Serializable-openAIFunctionAgent_0-openAIFunctionAgent_0-input-tools-Tool", "data": { "label": "" } @@ -317,12 +435,34 @@ } }, { - "source": "serper_0", - "sourceHandle": "serper_0-output-serper-Serper|Tool|StructuredTool|BaseLangChain|Serializable", + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain|Serializable", + "target": "openAIFunctionAgent_0", + "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain|Serializable-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-BaseChatModel", + "data": { + "label": "" + } + }, + { + "source": "bufferMemory_0", + "sourceHandle": "bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory", + "target": "openAIFunctionAgent_0", + "targetHandle": "openAIFunctionAgent_0-input-memory-BaseChatMemory", + "type": "buttonedge", + "id": "bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-openAIFunctionAgent_0-openAIFunctionAgent_0-input-memory-BaseChatMemory", + "data": { + "label": "" + } + }, + { + "source": "customTool_0", + "sourceHandle": "customTool_0-output-customTool-CustomTool|Tool|StructuredTool", "target": "openAIFunctionAgent_0", "targetHandle": "openAIFunctionAgent_0-input-tools-Tool", "type": "buttonedge", - "id": "serper_0-serper_0-output-serper-Serper|Tool|StructuredTool|BaseLangChain|Serializable-openAIFunctionAgent_0-openAIFunctionAgent_0-input-tools-Tool", + "id": "customTool_0-customTool_0-output-customTool-CustomTool|Tool|StructuredTool-openAIFunctionAgent_0-openAIFunctionAgent_0-input-tools-Tool", "data": { "label": "" } From 7ce0f71e2fc53cf9180c5af8659611b1b39d4c0f Mon Sep 17 00:00:00 2001 From: ivalkshfoeif Date: Wed, 21 Jun 2023 18:56:37 -0700 Subject: [PATCH 04/44] add JSONLines Loader --- .../documentloaders/Jsonlines/Jsonlines.ts | 105 ++++++++++++++++++ .../documentloaders/Jsonlines/jsonlines.svg | 16 +++ 2 files changed, 121 insertions(+) create mode 100644 packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts create mode 100644 packages/components/nodes/documentloaders/Jsonlines/jsonlines.svg diff --git a/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts b/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts new file mode 100644 index 00000000..e92a97aa --- /dev/null +++ b/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts @@ -0,0 +1,105 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { TextSplitter } from 'langchain/text_splitter' +import { JSONLinesLoader } from 'langchain/document_loaders/fs/json' + +class Jsonlines_DocumentLoaders implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Json Lines File' + this.name = 'jsonlinesFile' + this.type = 'Document' + this.icon = 'jsonlines.svg' + this.category = 'Document Loaders' + this.description = `Load data from JSON Lines files` + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Jsonlines File', + name: 'jsonlinesFile', + type: 'file', + fileType: '.jsonl' + }, + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + label: 'Pointer Extraction', + name: 'pointerName', + type: 'string', + description: 'Extracting the pointer', + placeholder: 'Enter pointer name', + optional: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const textSplitter = nodeData.inputs?.textSplitter as TextSplitter + const jsonLinesFileBase64 = nodeData.inputs?.jsonlinesFile as string + const pointerName = nodeData.inputs?.pointerName as string + const metadata = nodeData.inputs?.metadata + + let alldocs = [] + let files: string[] = [] + + if (jsonLinesFileBase64.startsWith('[') && jsonLinesFileBase64.endsWith(']')) { + files = JSON.parse(jsonLinesFileBase64) + } else { + files = [jsonLinesFileBase64] + } + + for (const file of files) { + const splitDataURI = file.split(',') + splitDataURI.pop() + const bf = Buffer.from(splitDataURI.pop() || '', 'base64') + const blob = new Blob([bf]) + const loader = new JSONLinesLoader(blob, pointerName) + + if (textSplitter) { + const docs = await loader.loadAndSplit(textSplitter) + alldocs.push(...docs) + } else { + const docs = await loader.load() + alldocs.push(...docs) + } + } + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + let finaldocs = [] + for (const doc of alldocs) { + const newdoc = { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + finaldocs.push(newdoc) + } + return finaldocs + } + + return alldocs + } +} + +module.exports = { nodeClass: Jsonlines_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/Jsonlines/jsonlines.svg b/packages/components/nodes/documentloaders/Jsonlines/jsonlines.svg new file mode 100644 index 00000000..f3686f0c --- /dev/null +++ b/packages/components/nodes/documentloaders/Jsonlines/jsonlines.svg @@ -0,0 +1,16 @@ + + + + + background + + + + + + + Layer 1 + JSON + Lines + + \ No newline at end of file From 7646e973e3a77d8f4500f82d3e70ba51bd203048 Mon Sep 17 00:00:00 2001 From: ivalkshfoeif Date: Wed, 21 Jun 2023 19:34:30 -0700 Subject: [PATCH 05/44] update pointer attribute and logic --- .../nodes/documentloaders/Jsonlines/Jsonlines.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts b/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts index e92a97aa..4af8c2ce 100644 --- a/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts +++ b/packages/components/nodes/documentloaders/Jsonlines/Jsonlines.ts @@ -37,9 +37,8 @@ class Jsonlines_DocumentLoaders implements INode { label: 'Pointer Extraction', name: 'pointerName', type: 'string', - description: 'Extracting the pointer', placeholder: 'Enter pointer name', - optional: true + optional: false }, { label: 'Metadata', @@ -60,6 +59,8 @@ class Jsonlines_DocumentLoaders implements INode { let alldocs = [] let files: string[] = [] + let pointer = '/' + pointerName.trim() + if (jsonLinesFileBase64.startsWith('[') && jsonLinesFileBase64.endsWith(']')) { files = JSON.parse(jsonLinesFileBase64) } else { @@ -71,7 +72,7 @@ class Jsonlines_DocumentLoaders implements INode { splitDataURI.pop() const bf = Buffer.from(splitDataURI.pop() || '', 'base64') const blob = new Blob([bf]) - const loader = new JSONLinesLoader(blob, pointerName) + const loader = new JSONLinesLoader(blob, pointer) if (textSplitter) { const docs = await loader.loadAndSplit(textSplitter) From dd328dcd51f73cb2666e35805f684f8a15d1b843 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 22 Jun 2023 15:47:03 +0100 Subject: [PATCH 06/44] add db_path --- docker/.env.example | 2 ++ docker/docker-compose.yml | 2 ++ packages/server/.env.example | 1 + packages/server/README.md | 24 +++++++++++++++++++----- packages/server/src/DataSource.ts | 2 +- packages/server/src/commands/start.ts | 2 ++ 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index f3211196..e313316d 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -1,3 +1,5 @@ PORT=3000 # FLOWISE_USERNAME=user # FLOWISE_PASSWORD=1234 +# DATABASE_PATH=/your_database_path/.flowise +# EXECUTION_MODE=child or main \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index c776f96e..0bb68097 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -8,6 +8,8 @@ services: - PORT=${PORT} - FLOWISE_USERNAME=${FLOWISE_USERNAME} - FLOWISE_PASSWORD=${FLOWISE_PASSWORD} + - DATABASE_PATH=${DATABASE_PATH} + - EXECUTION_MODE=${EXECUTION_MODE} ports: - '${PORT}:${PORT}' volumes: diff --git a/packages/server/.env.example b/packages/server/.env.example index fd82c096..e313316d 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -1,4 +1,5 @@ PORT=3000 # FLOWISE_USERNAME=user # FLOWISE_PASSWORD=1234 +# DATABASE_PATH=/your_database_path/.flowise # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/packages/server/README.md b/packages/server/README.md index 2cdf41d1..e4d1e439 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -31,14 +31,28 @@ FLOWISE_PASSWORD=1234 ## 📖 Documentation -Coming Soon - -## 💻 Cloud Hosted - -Coming Soon +[Flowise Docs](https://docs.flowiseai.com/) ## 🌐 Self Host +### [Railway](https://docs.flowiseai.com/deployment/railway) + +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/YK7J0v) + +### [Render](https://docs.flowiseai.com/deployment/render) + +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) + +### [AWS](https://docs.flowiseai.com/deployment/aws) + +### [Azure](https://docs.flowiseai.com/deployment/azure) + +### [DigitalOcean](https://docs.flowiseai.com/deployment/digital-ocean) + +### [GCP](https://docs.flowiseai.com/deployment/gcp) + +## 💻 Cloud Hosted + Coming Soon ## 🙋 Support diff --git a/packages/server/src/DataSource.ts b/packages/server/src/DataSource.ts index 76c8e144..19396315 100644 --- a/packages/server/src/DataSource.ts +++ b/packages/server/src/DataSource.ts @@ -8,7 +8,7 @@ import { getUserHome } from './utils' let appDataSource: DataSource export const init = async (): Promise => { - const homePath = path.join(getUserHome(), '.flowise') + const homePath = process.env.DATABASE_PATH ?? path.join(getUserHome(), '.flowise') appDataSource = new DataSource({ type: 'sqlite', diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index 94b8d995..9066f1cf 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -18,6 +18,7 @@ export default class Start extends Command { FLOWISE_USERNAME: Flags.string(), FLOWISE_PASSWORD: Flags.string(), PORT: Flags.string(), + DATABASE_PATH: Flags.string(), EXECUTION_MODE: Flags.string() } @@ -53,6 +54,7 @@ export default class Start extends Command { if (flags.FLOWISE_USERNAME) process.env.FLOWISE_USERNAME = flags.FLOWISE_USERNAME if (flags.FLOWISE_PASSWORD) process.env.FLOWISE_PASSWORD = flags.FLOWISE_PASSWORD if (flags.PORT) process.env.PORT = flags.PORT + if (flags.DATABASE_PATH) process.env.DATABASE_PATH = flags.DATABASE_PATH if (flags.EXECUTION_MODE) process.env.EXECUTION_MODE = flags.EXECUTION_MODE await (async () => { From 4a63b68bbe4d7c63d47d45c8168bc3e95ddef709 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 22 Jun 2023 16:22:12 +0100 Subject: [PATCH 07/44] add DATABSE_PATH --- packages/server/src/ChildProcess.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index 95f7368a..e8aeaff2 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -128,7 +128,7 @@ export class ChildProcess { * @returns {DataSource} */ async function initDB() { - const homePath = path.join(getUserHome(), '.flowise') + const homePath = process.env.DATABASE_PATH ?? path.join(getUserHome(), '.flowise') const childAppDataSource = new DataSource({ type: 'sqlite', database: path.resolve(homePath, 'database.sqlite'), From 60eb5254189992d9246016b943de8d1c29e514d0 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 22 Jun 2023 17:28:54 +0100 Subject: [PATCH 08/44] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.2.14?= =?UTF-8?q?=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index ec97d4d7..e5e0ba00 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.13", + "version": "1.2.14", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From de922e1a71afca98e5e5c7a1ec7ed34795d73093 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 22 Jun 2023 17:30:28 +0100 Subject: [PATCH 09/44] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.2.12=20releas?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 1d7bc490..258b5471 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.11", + "version": "1.2.12", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From 160aa87aba27a573e38d141c6bbf076199e3c09a Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 22 Jun 2023 17:31:01 +0100 Subject: [PATCH 10/44] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.2.13=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fc08e450..0ff76284 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.12", + "version": "1.2.13", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index e7a8bc61..eda69322 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.12", + "version": "1.2.13", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 8745776342e5d5379846f2126a68b037b4e72f64 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 23 Jun 2023 23:03:53 +0100 Subject: [PATCH 11/44] add env variables --- docker/.env.example | 1 + docker/docker-compose.yml | 1 + packages/components/.env.example | 1 - packages/components/README.md | 8 -------- packages/server/.env.example | 1 + packages/server/README.md | 4 ++++ packages/server/src/commands/start.ts | 2 ++ 7 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 packages/components/.env.example diff --git a/docker/.env.example b/docker/.env.example index e313316d..3d524e5c 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -1,5 +1,6 @@ PORT=3000 # FLOWISE_USERNAME=user # FLOWISE_PASSWORD=1234 +# DEBUG=true # DATABASE_PATH=/your_database_path/.flowise # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 0bb68097..7ab43142 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -10,6 +10,7 @@ services: - FLOWISE_PASSWORD=${FLOWISE_PASSWORD} - DATABASE_PATH=${DATABASE_PATH} - EXECUTION_MODE=${EXECUTION_MODE} + - DEBUG=${DEBUG} ports: - '${PORT}:${PORT}' volumes: diff --git a/packages/components/.env.example b/packages/components/.env.example deleted file mode 100644 index 352bc6cb..00000000 --- a/packages/components/.env.example +++ /dev/null @@ -1 +0,0 @@ -DEBUG=true \ No newline at end of file diff --git a/packages/components/README.md b/packages/components/README.md index 8014661e..5b564bec 100644 --- a/packages/components/README.md +++ b/packages/components/README.md @@ -12,14 +12,6 @@ Install: npm i flowise-components ``` -## Debug - -To view all the logs, create an `.env` file and add: - -``` -DEBUG=true -``` - ## License Source code in this repository is made available under the [MIT License](https://github.com/FlowiseAI/Flowise/blob/master/LICENSE.md). diff --git a/packages/server/.env.example b/packages/server/.env.example index e313316d..3d524e5c 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -1,5 +1,6 @@ PORT=3000 # FLOWISE_USERNAME=user # FLOWISE_PASSWORD=1234 +# DEBUG=true # DATABASE_PATH=/your_database_path/.flowise # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/packages/server/README.md b/packages/server/README.md index e4d1e439..74ba9a25 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -29,6 +29,10 @@ FLOWISE_USERNAME=user FLOWISE_PASSWORD=1234 ``` +## 🔎 Debugging + +You can set `DEBUG=true` to the `.env` file. Refer [here](https://docs.flowiseai.com/environment-variables) for full list of env variables + ## 📖 Documentation [Flowise Docs](https://docs.flowiseai.com/) diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index 9066f1cf..0f64322b 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -18,6 +18,7 @@ export default class Start extends Command { FLOWISE_USERNAME: Flags.string(), FLOWISE_PASSWORD: Flags.string(), PORT: Flags.string(), + DEBUG: Flags.string(), DATABASE_PATH: Flags.string(), EXECUTION_MODE: Flags.string() } @@ -56,6 +57,7 @@ export default class Start extends Command { if (flags.PORT) process.env.PORT = flags.PORT if (flags.DATABASE_PATH) process.env.DATABASE_PATH = flags.DATABASE_PATH if (flags.EXECUTION_MODE) process.env.EXECUTION_MODE = flags.EXECUTION_MODE + if (flags.DEBUG) process.env.DEBUG = flags.DEBUG await (async () => { try { From 40662b087a56e66154c87a9c8279b67ef5d82ddc Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 24 Jun 2023 01:22:39 +0100 Subject: [PATCH 12/44] add fix --- packages/ui/src/ui-component/input/Input.js | 1 + packages/ui/src/views/canvas/NodeInputHandler.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index 1861bf65..7f0e0610 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -37,6 +37,7 @@ export const Input = ({ inputParam, value, onChange, disabled = false, showDialo onChange(e.target.value) }} inputProps={{ + step: 0.1, style: { height: inputParam.rows ? '90px' : 'inherit' } diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 31a8a37d..4ad21904 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -205,6 +205,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA )} {(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && ( (data.inputs[inputParam.name] = newValue)} From 0fb1d8a1ff056e1462954d396b40c6a70dbf8b07 Mon Sep 17 00:00:00 2001 From: ivalkshfoeif Date: Fri, 23 Jun 2023 17:57:36 -0700 Subject: [PATCH 13/44] add redis-backed chat memory --- .../RedisBackedChatMemory.ts | 85 +++++++++++++++++++ .../memory/RedisBackedChatMemory/memory.svg | 8 ++ packages/components/package.json | 1 + 3 files changed, 94 insertions(+) create mode 100644 packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts create mode 100644 packages/components/nodes/memory/RedisBackedChatMemory/memory.svg diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts new file mode 100644 index 00000000..155ea5f5 --- /dev/null +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -0,0 +1,85 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { ICommonObject } from '../../../src' +import { BufferMemory } from 'langchain/memory' +import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/redis' +import { createClient } from 'redis' + +class RedisBackedChatMemory_Memory implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Redis-Backed Chat Memory' + this.name = 'RedisBackedChatMemory' + this.type = 'RedisBackedChatMemory' + this.icon = 'memory.svg' + this.category = 'Memory' + this.description = 'Summarizes the conversation and stores the memory in Redis server' + this.baseClasses = [this.type, ...getBaseClasses(BufferMemory)] + this.inputs = [ + { + label: 'Base URL', + name: 'baseURL', + type: 'string', + default: 'redis://localhost:6379' + }, + { + label: 'Session Id', + name: 'sessionId', + type: 'string', + description: 'if empty, chatId will be used automatically', + default: '', + additionalParams: true + }, + { + label: 'Session Timeouts', + name: 'sessionTTL', + type: 'number', + description: 'Omit this parameter to make sessions never expire', + optional: true + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history' + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const baseURL = nodeData.inputs?.baseURL as string + const sessionId = nodeData.inputs?.sessionId as string + const sessionTTL = nodeData.inputs?.sessionTTL as number + const memoryKey = nodeData.inputs?.memoryKey as string + + const chatId = options?.chatId as string + + const redisClient = createClient({ url: baseURL }) + let obj: RedisChatMessageHistoryInput = { + sessionId: sessionId ? sessionId : chatId, + client: redisClient + } + + if (sessionTTL) { + obj = { + ...obj, + sessionTTL + } + } + + let redisChatMessageHistory = new RedisChatMessageHistory(obj) + let redis = new BufferMemory({ memoryKey, chatHistory: redisChatMessageHistory, returnMessages: true }) + + return redis + } +} + +module.exports = { nodeClass: RedisBackedChatMemory_Memory } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/memory.svg b/packages/components/nodes/memory/RedisBackedChatMemory/memory.svg new file mode 100644 index 00000000..ca8e17da --- /dev/null +++ b/packages/components/nodes/memory/RedisBackedChatMemory/memory.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index e5e0ba00..12210951 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -42,6 +42,7 @@ "pdfjs-dist": "^3.7.107", "playwright": "^1.35.0", "puppeteer": "^20.7.1", + "redis": "^4.6.7", "srt-parser-2": "^1.2.3", "vm2": "^3.9.19", "weaviate-ts-client": "^1.1.0", From 3481d7decab88b08f224fa26ab5376fe8fa539e3 Mon Sep 17 00:00:00 2001 From: Govind Kumar Date: Sat, 24 Jun 2023 11:56:27 +0530 Subject: [PATCH 14/44] added qdrant vector store integration --- .../Qdrant_Existing/Qdrant_Existing.ts | 119 ++++++++++++++++++ .../Qdrant_Existing/qdrant_logo.svg | 27 ++++ .../Qdrant_Upsert/Qdrant_Upsert.ts | 119 ++++++++++++++++++ .../Qdrant_Upsert/qdrant_logo.svg | 27 ++++ packages/components/package.json | 1 + 5 files changed, 293 insertions(+) create mode 100644 packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts create mode 100644 packages/components/nodes/vectorstores/Qdrant_Existing/qdrant_logo.svg create mode 100644 packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts create mode 100644 packages/components/nodes/vectorstores/Qdrant_Upsert/qdrant_logo.svg diff --git a/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts new file mode 100644 index 00000000..031917f5 --- /dev/null +++ b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts @@ -0,0 +1,119 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { QdrantClient } from '@qdrant/js-client-rest' +import { QdrantVectorStore, QdrantLibArgs } from 'langchain/vectorstores/qdrant' +import { Embeddings } from 'langchain/embeddings/base' +import { getBaseClasses } from '../../../src/utils' + +class Qdrant_Existing_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Qdrant Load Existing Index' + this.name = 'qdrantExistingIndex' + this.type = 'Qdrant' + this.icon = 'qdrant_logo.svg' + this.category = 'Vector Stores' + this.description = 'Load existing index from Qdrant (i.e., documents have been upserted)' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Qdrant Server URL', + name: 'qdrantServerUrl', + type: 'string' + }, + { + label: 'Qdrant Collection Name', + name: 'qdrantCollection', + type: 'string' + }, + { + label: 'Qdrant API Key', + name: 'qdrantApiKey', + type: 'password' + }, + { + label: 'Qdrant Collection Cofiguration', + name: 'qdrantCollectionCofiguration', + type: 'json', + optional: true, + + additionalParams: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'Qdrant Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Qdrant Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(QdrantVectorStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const qdrantServerUrl = nodeData.inputs?.qdrantServerUrl as string + const collectionName = nodeData.inputs?.qdrantCollection as string + const qdrantApiKey = nodeData.inputs?.qdrantApiKey as string + let qdrantCollectionCofiguration = nodeData.inputs?.qdrantCollectionCofiguration + const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 + + // connect to Qdrant Cloud + const client = new QdrantClient({ + url: qdrantServerUrl, + apiKey: qdrantApiKey + }) + + const dbConfig: QdrantLibArgs = { + client, + collectionName + } + + if (qdrantCollectionCofiguration) { + qdrantCollectionCofiguration = + typeof qdrantCollectionCofiguration === 'object' ? qdrantCollectionCofiguration : JSON.parse(qdrantCollectionCofiguration) + dbConfig.collectionConfig = qdrantCollectionCofiguration + } + + const vectorStore = await QdrantVectorStore.fromExistingCollection(embeddings, dbConfig) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: Qdrant_Existing_VectorStores } diff --git a/packages/components/nodes/vectorstores/Qdrant_Existing/qdrant_logo.svg b/packages/components/nodes/vectorstores/Qdrant_Existing/qdrant_logo.svg new file mode 100644 index 00000000..82fb8b39 --- /dev/null +++ b/packages/components/nodes/vectorstores/Qdrant_Existing/qdrant_logo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts b/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts new file mode 100644 index 00000000..111fc5c3 --- /dev/null +++ b/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts @@ -0,0 +1,119 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { QdrantClient } from '@qdrant/js-client-rest' +import { QdrantVectorStore, QdrantLibArgs } from 'langchain/vectorstores/qdrant' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { getBaseClasses } from '../../../src/utils' +import { flatten } from 'lodash' + +class QdrantUpsert_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Qdrant Upsert Document' + this.name = 'qdrantUpsert' + this.type = 'Qdrant' + this.icon = 'qdrant_logo.svg' + this.category = 'Vector Stores' + this.description = 'Upsert documents to Qdrant' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Document', + name: 'document', + type: 'Document', + list: true + }, + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Qdrant Server URL', + name: 'qdrantServerUrl', + type: 'string' + }, + { + label: 'Qdrant Collection Name', + name: 'qdrantCollection', + type: 'string' + }, + { + label: 'Qdrant API Key', + name: 'qdrantApiKey', + type: 'password' + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'Qdrant Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Qdrant Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(QdrantVectorStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const qdrantServerUrl = nodeData.inputs?.qdrantServerUrl as string + const collectionName = nodeData.inputs?.qdrantCollection as string + const qdrantApiKey = nodeData.inputs?.qdrantApiKey as string + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 + + // connect to Qdrant Cloud + const client = new QdrantClient({ + url: qdrantServerUrl, + apiKey: qdrantApiKey + }) + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + finalDocs.push(new Document(flattenDocs[i])) + } + + const dbConfig: QdrantLibArgs = { + client, + url: qdrantServerUrl, + collectionName + } + const vectorStore = await QdrantVectorStore.fromDocuments(finalDocs, embeddings, dbConfig) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: QdrantUpsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/Qdrant_Upsert/qdrant_logo.svg b/packages/components/nodes/vectorstores/Qdrant_Upsert/qdrant_logo.svg new file mode 100644 index 00000000..82fb8b39 --- /dev/null +++ b/packages/components/nodes/vectorstores/Qdrant_Upsert/qdrant_logo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/components/package.json b/packages/components/package.json index 738c7752..fd7e1b14 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -20,6 +20,7 @@ "@getzep/zep-js": "^0.3.1", "@huggingface/inference": "1", "@pinecone-database/pinecone": "^0.0.12", + "@qdrant/js-client-rest": "^1.2.2", "@supabase/supabase-js": "^2.21.0", "@types/js-yaml": "^4.0.5", "axios": "^0.27.2", From e554ac54dddf54b742eac1234e66957d534e726e Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 26 Jun 2023 02:37:40 +0100 Subject: [PATCH 15/44] add full page chatbot and react chatbot --- packages/server/src/Interface.ts | 1 + packages/server/src/entity/ChatFlow.ts | 3 + packages/ui/craco.config.js | 16 + packages/ui/package.json | 14 +- packages/ui/src/assets/images/sharing.png | Bin 0 -> 17473 bytes packages/ui/src/routes/ChatbotRoutes.js | 23 + packages/ui/src/routes/index.js | 3 +- packages/ui/src/views/canvas/CanvasHeader.js | 5 +- packages/ui/src/views/chatbot/index.js | 59 +++ .../chatflows}/APICodeDialog.js | 204 +++------ packages/ui/src/views/chatflows/EmbedChat.js | 324 ++++++++++++++ .../ui/src/views/chatflows/ShareChatbot.js | 420 ++++++++++++++++++ 12 files changed, 924 insertions(+), 148 deletions(-) create mode 100644 packages/ui/craco.config.js create mode 100644 packages/ui/src/assets/images/sharing.png create mode 100644 packages/ui/src/routes/ChatbotRoutes.js create mode 100644 packages/ui/src/views/chatbot/index.js rename packages/ui/src/{ui-component/dialog => views/chatflows}/APICodeDialog.js (73%) create mode 100644 packages/ui/src/views/chatflows/EmbedChat.js create mode 100644 packages/ui/src/views/chatflows/ShareChatbot.js diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 1eafcae6..9473638f 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -13,6 +13,7 @@ export interface IChatFlow { deployed: boolean updatedDate: Date createdDate: Date + chatbotConfig?: string } export interface IChatMessage { diff --git a/packages/server/src/entity/ChatFlow.ts b/packages/server/src/entity/ChatFlow.ts index d9b12929..910272ad 100644 --- a/packages/server/src/entity/ChatFlow.ts +++ b/packages/server/src/entity/ChatFlow.ts @@ -19,6 +19,9 @@ export class ChatFlow implements IChatFlow { @Column() deployed: boolean + @Column({ nullable: true }) + chatbotConfig?: string + @CreateDateColumn() createdDate: Date diff --git a/packages/ui/craco.config.js b/packages/ui/craco.config.js new file mode 100644 index 00000000..142305e0 --- /dev/null +++ b/packages/ui/craco.config.js @@ -0,0 +1,16 @@ +module.exports = { + webpack: { + configure: { + module: { + rules: [ + { + test: /\.m?js$/, + resolve: { + fullySpecified: false + } + } + ] + } + } + } +} diff --git a/packages/ui/package.json b/packages/ui/package.json index 258b5471..1e55f1c8 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -16,6 +16,8 @@ "@mui/x-data-grid": "^6.8.0", "@tabler/icons": "^1.39.1", "clsx": "^1.1.1", + "flowise-embed": "*", + "flowise-embed-react": "*", "formik": "^2.2.6", "framer-motion": "^4.1.13", "history": "^5.0.0", @@ -27,6 +29,7 @@ "prop-types": "^15.7.2", "react": "^18.2.0", "react-code-blocks": "^0.0.9-0", + "react-color": "^2.19.3", "react-datepicker": "^4.8.0", "react-device-detect": "^1.17.0", "react-dom": "^18.2.0", @@ -47,11 +50,11 @@ "yup": "^0.32.9" }, "scripts": { - "start": "react-scripts start", - "dev": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" + "start": "craco start", + "dev": "craco start", + "build": "craco build", + "test": "craco test", + "eject": "craco eject" }, "babel": { "presets": [ @@ -72,6 +75,7 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.15.8", + "@craco/craco": "^7.1.0", "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^12.8.3", diff --git a/packages/ui/src/assets/images/sharing.png b/packages/ui/src/assets/images/sharing.png new file mode 100644 index 0000000000000000000000000000000000000000..1e538f2efd29a6d0d6d5dd2c88ca04aa74fffa36 GIT binary patch literal 17473 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 Jx@Z3G{{f}$cL4wZ literal 0 HcmV?d00001 diff --git a/packages/ui/src/routes/ChatbotRoutes.js b/packages/ui/src/routes/ChatbotRoutes.js new file mode 100644 index 00000000..25d298d6 --- /dev/null +++ b/packages/ui/src/routes/ChatbotRoutes.js @@ -0,0 +1,23 @@ +import { lazy } from 'react' + +// project imports +import Loadable from 'ui-component/loading/Loadable' +import MinimalLayout from 'layout/MinimalLayout' + +// canvas routing +const ChatbotFull = Loadable(lazy(() => import('views/chatbot'))) + +// ==============================|| CANVAS ROUTING ||============================== // + +const ChatbotRoutes = { + path: '/', + element: , + children: [ + { + path: '/chatbot/:id', + element: + } + ] +} + +export default ChatbotRoutes diff --git a/packages/ui/src/routes/index.js b/packages/ui/src/routes/index.js index 15fe4dca..ff8c1920 100644 --- a/packages/ui/src/routes/index.js +++ b/packages/ui/src/routes/index.js @@ -3,10 +3,11 @@ import { useRoutes } from 'react-router-dom' // routes import MainRoutes from './MainRoutes' import CanvasRoutes from './CanvasRoutes' +import ChatbotRoutes from './ChatbotRoutes' import config from 'config' // ==============================|| ROUTING RENDER ||============================== // export default function ThemeRoutes() { - return useRoutes([MainRoutes, CanvasRoutes], config.basename) + return useRoutes([MainRoutes, CanvasRoutes, ChatbotRoutes], config.basename) } diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index 1f4a1f93..521aa9d3 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -13,7 +13,7 @@ import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck, // project imports import Settings from 'views/settings' import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog' -import APICodeDialog from 'ui-component/dialog/APICodeDialog' +import APICodeDialog from 'views/chatflows/APICodeDialog' // API import chatflowsApi from 'api/chatflows' @@ -107,7 +107,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl title: 'Embed in website or use as API', chatflowid: chatflow.id, chatflowApiKeyId: chatflow.apikeyid, - isFormDataRequired + isFormDataRequired, + chatbotConfig: chatflow.chatbotConfig }) setAPIDialogOpen(true) } diff --git a/packages/ui/src/views/chatbot/index.js b/packages/ui/src/views/chatbot/index.js new file mode 100644 index 00000000..b33bec2c --- /dev/null +++ b/packages/ui/src/views/chatbot/index.js @@ -0,0 +1,59 @@ +import { useEffect, useState } from 'react' +import { baseURL } from 'store/constant' +import axios from 'axios' +import { FullPageChat } from 'flowise-embed-react' + +// ==============================|| Chatbot ||============================== // + +const fetchChatflow = async ({ chatflowId }) => { + const username = localStorage.getItem('username') + const password = localStorage.getItem('password') + + let chatflow = await axios + .get(`${baseURL}/api/v1/chatflows/${chatflowId}`, { auth: username && password ? { username, password } : undefined }) + .then(async function (response) { + return response.data + }) + .catch(function (error) { + console.error(error) + }) + return chatflow +} + +const ChatbotFull = () => { + const URLpath = document.location.pathname.toString().split('/') + const chatflowId = URLpath[URLpath.length - 1] === 'chatbot' ? '' : URLpath[URLpath.length - 1] + + const [chatflow, setChatflow] = useState(null) + const [chatbotTheme, setChatbotTheme] = useState({}) + + useEffect(() => { + ;(async () => { + const fetchData = async () => { + let response = await fetchChatflow({ chatflowId }) + setChatflow(response) + if (response.chatbotConfig) { + try { + setChatbotTheme(JSON.parse(response.chatbotConfig)) + } catch (e) { + console.error(e) + setChatbotTheme({}) + } + } + } + fetchData() + })() + }, [chatflowId]) + + return ( + <> + {!chatflow || chatflow.apikeyid ? ( +

Invalid Chatbot

+ ) : ( + + )} + + ) +} + +export default ChatbotFull diff --git a/packages/ui/src/ui-component/dialog/APICodeDialog.js b/packages/ui/src/views/chatflows/APICodeDialog.js similarity index 73% rename from packages/ui/src/ui-component/dialog/APICodeDialog.js rename to packages/ui/src/views/chatflows/APICodeDialog.js index e64f4bf8..fea49909 100644 --- a/packages/ui/src/ui-component/dialog/APICodeDialog.js +++ b/packages/ui/src/views/chatflows/APICodeDialog.js @@ -9,6 +9,8 @@ import { CopyBlock, atomOneDark } from 'react-code-blocks' // Project import import { Dropdown } from 'ui-component/dropdown/Dropdown' +import ShareChatbot from './ShareChatbot' +import EmbedChat from './EmbedChat' // Const import { baseURL } from 'store/constant' @@ -19,6 +21,7 @@ import pythonSVG from 'assets/images/python.svg' import javascriptSVG from 'assets/images/javascript.svg' import cURLSVG from 'assets/images/cURL.svg' import EmbedSVG from 'assets/images/embed.svg' +import ShareChatbotSVG from 'assets/images/sharing.png' // API import apiKeyApi from 'api/apikey' @@ -119,77 +122,19 @@ const getConfigExamplesForCurl = (configData, bodyType) => { return finalStr } -const embedCode = (chatflowid) => { - return `` -} - -const embedCodeCustomization = (chatflowid) => { - return `` -} - const APICodeDialog = ({ show, dialogProps, onCancel }) => { const portalElement = document.getElementById('portal') const navigate = useNavigate() const dispatch = useDispatch() - const codes = ['Embed', 'Python', 'JavaScript', 'cURL'] + + const codes = ['Embed', 'Python', 'JavaScript', 'cURL', 'Share Chatbot'] const [value, setValue] = useState(0) const [keyOptions, setKeyOptions] = useState([]) const [apiKeys, setAPIKeys] = useState([]) const [chatflowApiKeyId, setChatflowApiKeyId] = useState('') const [selectedApiKey, setSelectedApiKey] = useState({}) const [checkboxVal, setCheckbox] = useState(false) - const [embedChatCheckboxVal, setEmbedChatCheckbox] = useState(false) + const [chatbotConfig, setChatbotConfig] = useState(null) const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys) const updateChatflowApi = useApi(chatflowsApi.updateChatflow) @@ -203,10 +148,6 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { } } - const onCheckBoxEmbedChatChanged = (newVal) => { - setEmbedChatCheckbox(newVal) - } - const onApiKeySelected = (keyValue) => { if (keyValue === 'addnewkey') { navigate('/apikey') @@ -265,8 +206,6 @@ query({"question": "Hey, how are you?"}).then((response) => { return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\ -X POST \\ -d '{"question": "Hey, how are you?"}'` - } else if (codeLang === 'Embed') { - return embedCode(dialogProps.chatflowid) } return '' } @@ -309,8 +248,6 @@ query({"question": "Hey, how are you?"}).then((response) => { -X POST \\ -d '{"question": "Hey, how are you?"}' \\ -H "Authorization: Bearer ${selectedApiKey?.apiKey}"` - } else if (codeLang === 'Embed') { - return embedCode(dialogProps.chatflowid) } return '' } @@ -318,7 +255,7 @@ query({"question": "Hey, how are you?"}).then((response) => { const getLang = (codeLang) => { if (codeLang === 'Python') { return 'python' - } else if (codeLang === 'JavaScript' || codeLang === 'Embed') { + } else if (codeLang === 'JavaScript') { return 'javascript' } else if (codeLang === 'cURL') { return 'bash' @@ -335,6 +272,8 @@ query({"question": "Hey, how are you?"}).then((response) => { return EmbedSVG } else if (codeLang === 'cURL') { return cURLSVG + } else if (codeLang === 'Share Chatbot') { + return ShareChatbotSVG } return pythonSVG } @@ -552,6 +491,12 @@ query({ setChatflowApiKeyId(dialogProps.chatflowApiKeyId) setSelectedApiKey(getAllAPIKeysApi.data.find((key) => key.id === dialogProps.chatflowApiKeyId)) } + + if (dialogProps.chatbotConfig) { + setChatbotConfig(JSON.parse(dialogProps.chatbotConfig)) + } else { + setChatbotConfig(null) + } } }, [dialogProps, getAllAPIKeysApi.data]) @@ -593,92 +538,71 @@ query({ ))}
- {value !== 0 && ( -
- onApiKeySelected(newValue)} - value={dialogProps.chatflowApiKeyId ?? chatflowApiKeyId ?? 'Choose an API key'} - /> -
- )} +
+ onApiKeySelected(newValue)} + value={dialogProps.chatflowApiKeyId ?? chatflowApiKeyId ?? 'Choose an API key'} + /> +
{codes.map((codeLang, index) => ( - {value === 0 && ( + {(codeLang === 'Embed' || codeLang === 'Share Chatbot') && chatflowApiKeyId && ( <> - - Paste this anywhere in the {``} tag of your html file. -

- You can also specify a  - - version - - : {`https://cdn.jsdelivr.net/npm/flowise-embed@/dist/web.js`} -

-
-
+

You cannot use API key while embedding/sharing chatbot.

+

+ Please select "No Authorization" from the dropdown at the top right corner. +

)} - - {value !== 0 && } - {value !== 0 && checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && ( + {codeLang === 'Embed' && !chatflowApiKeyId && } + {codeLang !== 'Embed' && codeLang !== 'Share Chatbot' && ( <> - + + {checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && ( + <> + + + + )} + {getIsChatflowStreamingApi.data?.isStreaming && ( +

+ Read  + + here + +  on how to stream response back to application +

+ )} )} - {value === 0 && ( - - )} - {value === 0 && embedChatCheckboxVal && ( - - )} - {value !== 0 && getIsChatflowStreamingApi.data?.isStreaming && ( -

- Read  - - here - -  on how to stream response back to application -

+ {codeLang === 'Share Chatbot' && !chatflowApiKeyId && ( + )}
))} diff --git a/packages/ui/src/views/chatflows/EmbedChat.js b/packages/ui/src/views/chatflows/EmbedChat.js new file mode 100644 index 00000000..c6385efb --- /dev/null +++ b/packages/ui/src/views/chatflows/EmbedChat.js @@ -0,0 +1,324 @@ +import { useState } from 'react' +import PropTypes from 'prop-types' + +import { Tabs, Tab, Box } from '@mui/material' +import { CopyBlock, atomOneDark } from 'react-code-blocks' + +// Project import +import { CheckboxInput } from 'ui-component/checkbox/Checkbox' + +// Const +import { baseURL } from 'store/constant' + +function TabPanel(props) { + const { children, value, index, ...other } = props + return ( + + ) +} + +TabPanel.propTypes = { + children: PropTypes.node, + index: PropTypes.number.isRequired, + value: PropTypes.number.isRequired +} + +function a11yProps(index) { + return { + id: `attachment-tab-${index}`, + 'aria-controls': `attachment-tabpanel-${index}` + } +} + +const embedPopupHtmlCode = (chatflowid) => { + return `` +} + +const embedPopupReactCode = (chatflowid) => { + return `import { BubbleChat } from 'flowise-embed-react' + +const App = () => { + return ( + + ); +};` +} + +const embedFullpageHtmlCode = (chatflowid) => { + return ` +` +} + +const embedFullpageReactCode = (chatflowid) => { + return `import { FullPageChat } from "flowise-embed-react" + +const App = () => { + return ( + + ); +};` +} + +const buttonConfig = (isReact = false) => { + return isReact + ? `button: { + backgroundColor: "#3B81F6", + right: 20, + bottom: 20, + size: "medium", + iconColor: "white", + customIconSrc: "https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/svg/google-messages.svg", + }` + : `button: { + backgroundColor: "#3B81F6", + right: 20, + bottom: 20, + size: "medium", + iconColor: "white", + customIconSrc: "https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/svg/google-messages.svg", + }` +} + +const chatwindowConfig = (isReact = false) => { + return isReact + ? `chatWindow: { + welcomeMessage: "Hello! This is custom welcome message", + backgroundColor: "#ffffff", + height: 700, + width: 400, + fontSize: 16, + poweredByTextColor: "#303235", + botMessage: { + backgroundColor: "#f7f8ff", + textColor: "#303235", + showAvatar: true, + avatarSrc: "https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/parroticon.png", + }, + userMessage: { + backgroundColor: "#3B81F6", + textColor: "#ffffff", + showAvatar: true, + avatarSrc: "https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/usericon.png", + }, + textInput: { + placeholder: "Type your question", + backgroundColor: "#ffffff", + textColor: "#303235", + sendButtonColor: "#3B81F6", + } + }` + : `chatWindow: { + welcomeMessage: "Hello! This is custom welcome message", + backgroundColor: "#ffffff", + height: 700, + width: 400, + fontSize: 16, + poweredByTextColor: "#303235", + botMessage: { + backgroundColor: "#f7f8ff", + textColor: "#303235", + showAvatar: true, + avatarSrc: "https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/parroticon.png", + }, + userMessage: { + backgroundColor: "#3B81F6", + textColor: "#ffffff", + showAvatar: true, + avatarSrc: "https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/usericon.png", + }, + textInput: { + placeholder: "Type your question", + backgroundColor: "#ffffff", + textColor: "#303235", + sendButtonColor: "#3B81F6", + } + }` +} + +const embedPopupHtmlCodeCustomization = (chatflowid) => { + return `` +} + +const embedPopupReactCodeCustomization = (chatflowid) => { + return `import { BubbleChat } from 'flowise-embed-react' + +const App = () => { + return ( + + ); +};` +} + +const embedFullpageHtmlCodeCustomization = (chatflowid) => { + return ` +` +} + +const embedFullpageReactCodeCustomization = (chatflowid) => { + return `import { FullPageChat } from "flowise-embed-react" + +const App = () => { + return ( + + ); +};` +} + +const EmbedChat = ({ chatflowid }) => { + const codes = ['Popup Html', 'Fullpage Html', 'Popup React', 'Fullpage React'] + const [value, setValue] = useState(0) + const [embedChatCheckboxVal, setEmbedChatCheckbox] = useState(false) + + const onCheckBoxEmbedChatChanged = (newVal) => { + setEmbedChatCheckbox(newVal) + } + + const handleChange = (event, newValue) => { + setValue(newValue) + } + + const getCode = (codeLang) => { + switch (codeLang) { + case 'Popup Html': + return embedPopupHtmlCode(chatflowid) + case 'Fullpage Html': + return embedFullpageHtmlCode(chatflowid) + case 'Popup React': + return embedPopupReactCode(chatflowid) + case 'Fullpage React': + return embedFullpageReactCode(chatflowid) + default: + return '' + } + } + + const getCodeCustomization = (codeLang) => { + switch (codeLang) { + case 'Popup Html': + return embedPopupHtmlCodeCustomization(chatflowid) + case 'Fullpage Html': + return embedFullpageHtmlCodeCustomization(chatflowid) + case 'Popup React': + return embedPopupReactCodeCustomization(chatflowid) + case 'Fullpage React': + return embedFullpageReactCodeCustomization(chatflowid) + default: + return '' + } + } + + return ( + <> +
+
+ + {codes.map((codeLang, index) => ( + + ))} + +
+
+
+ {codes.map((codeLang, index) => ( + + {(value === 0 || value === 1) && ( + <> + + Paste this anywhere in the {``} tag of your html file. +

+ You can also specify a  + + version + + : {`https://cdn.jsdelivr.net/npm/flowise-embed@/dist/web.js`} +

+
+
+ + )} + + + + + {embedChatCheckboxVal && ( + + )} +
+ ))} + + ) +} + +EmbedChat.propTypes = { + chatflowid: PropTypes.string +} + +export default EmbedChat diff --git a/packages/ui/src/views/chatflows/ShareChatbot.js b/packages/ui/src/views/chatflows/ShareChatbot.js new file mode 100644 index 00000000..dffecf5b --- /dev/null +++ b/packages/ui/src/views/chatflows/ShareChatbot.js @@ -0,0 +1,420 @@ +import PropTypes from 'prop-types' +import { useState } from 'react' +import { useDispatch } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import { SketchPicker } from 'react-color' + +import { Box, Typography, Button, Switch, OutlinedInput, Popover, Stack, IconButton } from '@mui/material' +import { useTheme } from '@mui/material/styles' + +// Project import +import { StyledButton } from 'ui-component/button/StyledButton' + +// Icons +import { IconX, IconCopy, IconArrowUpRightCircle } from '@tabler/icons' + +// API +import chatflowsApi from 'api/chatflows' + +// utils +import useNotifier from 'utils/useNotifier' + +// Const +import { baseURL } from 'store/constant' + +const defaultConfig = { + backgroundColor: '#ffffff', + fontSize: 16, + poweredByTextColor: '#303235', + botMessage: { + backgroundColor: '#f7f8ff', + textColor: '#303235' + }, + userMessage: { + backgroundColor: '#3B81F6', + textColor: '#ffffff' + }, + textInput: { + backgroundColor: '#ffffff', + textColor: '#303235', + sendButtonColor: '#3B81F6' + } +} + +const ShareChatbot = ({ chatflowid, chatbotConfig }) => { + const dispatch = useDispatch() + const theme = useTheme() + + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [welcomeMessage, setWelcomeMessage] = useState(chatbotConfig?.welcomeMessage ?? '') + const [backgroundColor, setBackgroundColor] = useState(chatbotConfig?.backgroundColor ?? defaultConfig.backgroundColor) + const [fontSize, setFontSize] = useState(chatbotConfig?.fontSize ?? defaultConfig.fontSize) + const [poweredByTextColor, setPoweredByTextColor] = useState(chatbotConfig?.poweredByTextColor ?? defaultConfig.poweredByTextColor) + + const [botMessageBackgroundColor, setBotMessageBackgroundColor] = useState( + chatbotConfig?.botMessage?.backgroundColor ?? defaultConfig.botMessage.backgroundColor + ) + const [botMessageTextColor, setBotMessageTextColor] = useState( + chatbotConfig?.botMessage?.textColor ?? defaultConfig.botMessage.textColor + ) + const [botMessageAvatarSrc, setBotMessageAvatarSrc] = useState(chatbotConfig?.botMessage?.avatarSrc ?? '') + const [botMessageShowAvatar, setBotMessageShowAvatar] = useState(chatbotConfig?.botMessage?.showAvatar ?? false) + + const [userMessageBackgroundColor, setUserMessageBackgroundColor] = useState( + chatbotConfig?.userMessage?.backgroundColor ?? defaultConfig.userMessage.backgroundColor + ) + const [userMessageTextColor, setUserMessageTextColor] = useState( + chatbotConfig?.userMessage?.textColor ?? defaultConfig.userMessage.textColor + ) + const [userMessageAvatarSrc, setUserMessageAvatarSrc] = useState(chatbotConfig?.userMessage?.avatarSrc ?? '') + const [userMessageShowAvatar, setUserMessageShowAvatar] = useState(chatbotConfig?.userMessage?.showAvatar ?? false) + + const [textInputBackgroundColor, setTextInputBackgroundColor] = useState( + chatbotConfig?.textInput?.backgroundColor ?? defaultConfig.textInput.backgroundColor + ) + const [textInputTextColor, setTextInputTextColor] = useState(chatbotConfig?.textInput?.textColor ?? defaultConfig.textInput.textColor) + const [textInputPlaceholder, setTextInputPlaceholder] = useState(chatbotConfig?.textInput?.placeholder ?? '') + const [textInputSendButtonColor, setTextInputSendButtonColor] = useState( + chatbotConfig?.textInput?.sendButtonColor ?? defaultConfig.textInput.sendButtonColor + ) + + const [colorAnchorEl, setColorAnchorEl] = useState(null) + const [selectedColorConfig, setSelectedColorConfig] = useState('') + const [sketchPickerColor, setSketchPickerColor] = useState('') + const openColorPopOver = Boolean(colorAnchorEl) + + const [copyAnchorEl, setCopyAnchorEl] = useState(null) + const openCopyPopOver = Boolean(copyAnchorEl) + + const formatObj = () => { + const obj = { + botMessage: { + showAvatar: false + }, + userMessage: { + showAvatar: false + }, + textInput: {} + } + if (welcomeMessage) obj.welcomeMessage = welcomeMessage + if (backgroundColor) obj.backgroundColor = backgroundColor + if (fontSize) obj.fontSize = fontSize + if (poweredByTextColor) obj.poweredByTextColor = poweredByTextColor + + if (botMessageBackgroundColor) obj.botMessage.backgroundColor = botMessageBackgroundColor + if (botMessageTextColor) obj.botMessage.textColor = botMessageTextColor + if (botMessageAvatarSrc) obj.botMessage.avatarSrc = botMessageAvatarSrc + if (botMessageShowAvatar) obj.botMessage.showAvatar = botMessageShowAvatar + + if (userMessageBackgroundColor) obj.userMessage.backgroundColor = userMessageBackgroundColor + if (userMessageTextColor) obj.userMessage.textColor = userMessageTextColor + if (userMessageAvatarSrc) obj.userMessage.avatarSrc = userMessageAvatarSrc + if (userMessageShowAvatar) obj.userMessage.showAvatar = userMessageShowAvatar + + if (textInputBackgroundColor) obj.textInput.backgroundColor = textInputBackgroundColor + if (textInputTextColor) obj.textInput.textColor = textInputTextColor + if (textInputPlaceholder) obj.textInput.placeholder = textInputPlaceholder + if (textInputSendButtonColor) obj.textInput.sendButtonColor = textInputSendButtonColor + + return obj + } + + const onSave = async () => { + try { + const saveResp = await chatflowsApi.updateChatflow(chatflowid, { + chatbotConfig: JSON.stringify(formatObj()) + }) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Chatbot Configuration Saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + } + } catch (error) { + console.error(error) + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to save Chatbot Configuration: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + } + } + + const handleClosePopOver = () => { + setColorAnchorEl(null) + } + + const handleCloseCopyPopOver = () => { + setCopyAnchorEl(null) + } + + const onColorSelected = (hexColor) => { + switch (selectedColorConfig) { + case 'backgroundColor': + setBackgroundColor(hexColor) + break + case 'poweredByTextColor': + setPoweredByTextColor(hexColor) + break + case 'botMessageBackgroundColor': + setBotMessageBackgroundColor(hexColor) + break + case 'botMessageTextColor': + setBotMessageTextColor(hexColor) + break + case 'userMessageBackgroundColor': + setUserMessageBackgroundColor(hexColor) + break + case 'userMessageTextColor': + setUserMessageTextColor(hexColor) + break + case 'textInputBackgroundColor': + setTextInputBackgroundColor(hexColor) + break + case 'textInputTextColor': + setTextInputTextColor(hexColor) + break + case 'textInputSendButtonColor': + setTextInputSendButtonColor(hexColor) + break + } + setSketchPickerColor(hexColor) + } + + const onTextChanged = (value, fieldName) => { + switch (fieldName) { + case 'welcomeMessage': + setWelcomeMessage(value) + break + case 'fontSize': + setFontSize(value) + break + case 'botMessageAvatarSrc': + setBotMessageAvatarSrc(value) + break + case 'userMessageAvatarSrc': + setUserMessageAvatarSrc(value) + break + case 'textInputPlaceholder': + setTextInputPlaceholder(value) + break + } + } + + const onBooleanChanged = (value, fieldName) => { + switch (fieldName) { + case 'botMessageShowAvatar': + setBotMessageShowAvatar(value) + break + case 'userMessageShowAvatar': + setUserMessageShowAvatar(value) + break + } + } + + const colorField = (color, fieldName, fieldLabel) => { + return ( + +
+ {fieldLabel} + { + setSelectedColorConfig(fieldName) + setSketchPickerColor(color ?? '#ffffff') + setColorAnchorEl(event.currentTarget) + }} + > +
+
+ ) + } + + const booleanField = (value, fieldName, fieldLabel) => { + return ( + +
+ {fieldLabel} + { + onBooleanChanged(event.target.checked, fieldName) + }} + /> +
+
+ ) + } + + const textField = (message, fieldName, fieldLabel, fieldType = 'string', placeholder = '') => { + return ( + +
+ {fieldLabel} + { + onTextChanged(e.target.value, fieldName) + }} + /> +
+
+ ) + } + + return ( + <> + + + {`${baseURL}/chatbot/${chatflowid}`} + + { + navigator.clipboard.writeText(`${baseURL}/chatbot/${chatflowid}`) + setCopyAnchorEl(event.currentTarget) + setTimeout(() => { + handleCloseCopyPopOver() + }, 1500) + }} + > + + + window.open(`${baseURL}/chatbot/${chatflowid}`, '_blank')}> + + + + {textField(welcomeMessage, 'welcomeMessage', 'Welcome Message', 'string', 'Hello! This is custom welcome message')} + {colorField(backgroundColor, 'backgroundColor', 'Background Color')} + {textField(fontSize, 'fontSize', 'Font Size', 'number')} + {colorField(poweredByTextColor, 'poweredByTextColor', 'PoweredBy TextColor')} + + {/*BOT Message*/} + + Bot Message + + {colorField(botMessageBackgroundColor, 'botMessageBackgroundColor', 'Background Color')} + {colorField(botMessageTextColor, 'botMessageTextColor', 'Text Color')} + {textField( + botMessageAvatarSrc, + 'botMessageAvatarSrc', + 'Avatar Link', + 'string', + `https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/parroticon.png` + )} + {booleanField(botMessageShowAvatar, 'botMessageShowAvatar', 'Show Avatar')} + + {/*USER Message*/} + + User Message + + {colorField(userMessageBackgroundColor, 'userMessageBackgroundColor', 'Background Color')} + {colorField(userMessageTextColor, 'userMessageTextColor', 'Text Color')} + {textField( + userMessageAvatarSrc, + 'userMessageAvatarSrc', + 'Avatar Link', + 'string', + `https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/usericon.png` + )} + {booleanField(userMessageShowAvatar, 'userMessageShowAvatar', 'Show Avatar')} + + {/*TEXT Input*/} + + Text Input + + {colorField(textInputBackgroundColor, 'textInputBackgroundColor', 'Background Color')} + {colorField(textInputTextColor, 'textInputTextColor', 'Text Color')} + {textField(textInputPlaceholder, 'textInputPlaceholder', 'TextInput Placeholder', 'string', `Type question..`)} + {colorField(textInputSendButtonColor, 'textInputSendButtonColor', 'TextIntput Send Button Color')} + + onSave()}> + Save Changes + + + onColorSelected(color.hex)} /> + + + + Copied! + + + + ) +} + +ShareChatbot.propTypes = { + chatflowid: PropTypes.string, + chatbotConfig: PropTypes.object +} + +export default ShareChatbot From ab029a845e3a5a0ac4a20a6173194334eeed8f9c Mon Sep 17 00:00:00 2001 From: Govind Kumar Date: Mon, 26 Jun 2023 20:46:37 +0530 Subject: [PATCH 16/44] added revised changes --- .../nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts | 7 ++++--- .../nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts index 031917f5..f1eef8f9 100644 --- a/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts +++ b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts @@ -32,7 +32,8 @@ class Qdrant_Existing_VectorStores implements INode { { label: 'Qdrant Server URL', name: 'qdrantServerUrl', - type: 'string' + type: 'string', + placeholder: 'http://localhost:6333' }, { label: 'Qdrant Collection Name', @@ -42,14 +43,14 @@ class Qdrant_Existing_VectorStores implements INode { { label: 'Qdrant API Key', name: 'qdrantApiKey', - type: 'password' + type: 'password', + optional: true }, { label: 'Qdrant Collection Cofiguration', name: 'qdrantCollectionCofiguration', type: 'json', optional: true, - additionalParams: true }, { diff --git a/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts b/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts index 111fc5c3..dae1d31d 100644 --- a/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts +++ b/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts @@ -40,7 +40,8 @@ class QdrantUpsert_VectorStores implements INode { { label: 'Qdrant Server URL', name: 'qdrantServerUrl', - type: 'string' + type: 'string', + placeholder: 'http://localhost:6333' }, { label: 'Qdrant Collection Name', @@ -50,7 +51,8 @@ class QdrantUpsert_VectorStores implements INode { { label: 'Qdrant API Key', name: 'qdrantApiKey', - type: 'password' + type: 'password', + optional: true }, { label: 'Top K', From 3784a700b6ddf9c8c9c22d69f3d72ff5dafb02b9 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 26 Jun 2023 18:05:48 +0100 Subject: [PATCH 17/44] update faiss version --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index f5b67cc7..72feeed5 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -30,7 +30,7 @@ "d3-dsv": "2", "dotenv": "^16.0.0", "express": "^4.17.3", - "faiss-node": "^0.2.1", + "faiss-node": "^0.2.2", "form-data": "^4.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", From a0ddad8e52352ff9bcfc4c9880b1f916bb7b4c55 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 26 Jun 2023 19:26:13 +0100 Subject: [PATCH 18/44] update README --- README.md | 4 ++-- packages/server/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dbce8f3b..90f2711f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# Flowise - LangchainJS UI +# Flowise - Low-Code LLM apps builder -Drag & drop UI to build your customized LLM flow using [LangchainJS](https://github.com/hwchase17/langchainjs) +Drag & drop UI to build your customized LLM flow ## ⚡Quick Start diff --git a/packages/server/README.md b/packages/server/README.md index 74ba9a25..7895bd90 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -1,10 +1,10 @@ -# Flowise - LangchainJS UI +# Flowise - Low-Code LLM apps builder ![Flowise](https://github.com/FlowiseAI/Flowise/blob/main/images/flowise.gif?raw=true) -Drag & drop UI to build your customized LLM flow using [LangchainJS](https://github.com/hwchase17/langchainjs) +Drag & drop UI to build your customized LLM flow ## ⚡Quick Start From dd8b59abb8a3850eef2e9b5f78db70a59eb55ec7 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Tue, 27 Jun 2023 23:01:09 +0800 Subject: [PATCH 19/44] add zapier integration --- packages/server/src/index.ts | 32 +++++++++++++++++++++++++++++- packages/server/src/utils/index.ts | 12 +++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 65bfef23..3e729464 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -36,7 +36,8 @@ import { replaceAllAPIKeys, isFlowValidForStream, isVectorStoreFaiss, - databaseEntities + databaseEntities, + getApiKey } from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' @@ -177,6 +178,24 @@ export class App { return res.json(chatflows) }) + // Get specific chatflow via api key + this.app.get('/api/v1/chatflows/apikey/:apiKey', async (req: Request, res: Response) => { + try { + const apiKey = await getApiKey(req.params.apiKey) + if (!apiKey) return res.status(401).send('Unauthorized') + const chatflows = await this.AppDataSource.getRepository(ChatFlow) + .createQueryBuilder('cf') + .where('cf.apikeyid = :apikeyid', { apikeyid: apiKey.id }) + .orWhere('cf.apikeyid IS NULL') + .orderBy('cf.name', 'ASC') + .getMany() + if (chatflows.length >= 1) return res.status(200).send(chatflows) + return res.status(404).send('Chatflow not found') + } catch (err: any) { + return res.status(500).send(err?.message) + } + }) + // Get specific chatflow via id this.app.get('/api/v1/chatflows/:id', async (req: Request, res: Response) => { const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ @@ -472,6 +491,17 @@ export class App { return res.json(keys) }) + // Verify api key + this.app.get('/api/v1/apikey/:apiKey', async (req: Request, res: Response) => { + try { + const apiKey = await getApiKey(req.params.apiKey) + if (!apiKey) return res.status(401).send('Unauthorized') + return res.status(200).send('OK') + } catch (err: any) { + return res.status(500).send(err?.message) + } + }) + // ---------------------------------------- // Serve UI static // ---------------------------------------- diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index e3005c7b..e861e6fa 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -547,6 +547,18 @@ export const addAPIKey = async (keyName: string): Promise => { return content } +/** + * Get API Key details + * @param {string} apiKey + * @returns {Promise} + */ +export const getApiKey = async (apiKey: string) => { + const existingAPIKeys = await getAPIKeys() + const keyIndex = existingAPIKeys.findIndex((key) => key.apiKey === apiKey) + if (keyIndex < 0) return undefined + return existingAPIKeys[keyIndex] +} + /** * Update existing API key * @param {string} keyIdToUpdate From b6a5cd0cb327e9b380f62b61b790632cbdaf8a2b Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Tue, 27 Jun 2023 23:07:31 +0800 Subject: [PATCH 20/44] remove returnJSONStr function --- .../nodes/prompts/PromptTemplate/PromptTemplate.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts index cfa2c488..f976d64c 100644 --- a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts +++ b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts @@ -1,5 +1,5 @@ import { ICommonObject, INode, INodeData, INodeParams, PromptTemplate } from '../../../src/Interface' -import { getBaseClasses, getInputVariables, returnJSONStr } from '../../../src/utils' +import { getBaseClasses, getInputVariables } from '../../../src/utils' import { PromptTemplateInput } from 'langchain/prompts' class PromptTemplate_Prompts implements INode { @@ -46,12 +46,11 @@ class PromptTemplate_Prompts implements INode { async init(nodeData: INodeData): Promise { const template = nodeData.inputs?.template as string - let promptValuesStr = nodeData.inputs?.promptValues as string + const promptValuesStr = nodeData.inputs?.promptValues as string let promptValues: ICommonObject = {} if (promptValuesStr) { - promptValuesStr = promptValuesStr.replace(/\s/g, '') - promptValues = JSON.parse(returnJSONStr(promptValuesStr)) + promptValues = JSON.parse(promptValuesStr.replace(/\s/g, '')) } const inputVariables = getInputVariables(template) From c36489f9470aa81b92ec89d51b18f1e85f1da80c Mon Sep 17 00:00:00 2001 From: ivalkshfoeif Date: Wed, 28 Jun 2023 10:10:12 -0700 Subject: [PATCH 21/44] Update redis icon --- .../memory/RedisBackedChatMemory/RedisBackedChatMemory.ts | 2 +- .../nodes/memory/RedisBackedChatMemory/memory.svg | 8 -------- .../nodes/memory/RedisBackedChatMemory/redis.svg | 1 + 3 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 packages/components/nodes/memory/RedisBackedChatMemory/memory.svg create mode 100644 packages/components/nodes/memory/RedisBackedChatMemory/redis.svg diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 155ea5f5..e332181d 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -19,7 +19,7 @@ class RedisBackedChatMemory_Memory implements INode { this.label = 'Redis-Backed Chat Memory' this.name = 'RedisBackedChatMemory' this.type = 'RedisBackedChatMemory' - this.icon = 'memory.svg' + this.icon = 'redis.svg' this.category = 'Memory' this.description = 'Summarizes the conversation and stores the memory in Redis server' this.baseClasses = [this.type, ...getBaseClasses(BufferMemory)] diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/memory.svg b/packages/components/nodes/memory/RedisBackedChatMemory/memory.svg deleted file mode 100644 index ca8e17da..00000000 --- a/packages/components/nodes/memory/RedisBackedChatMemory/memory.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/redis.svg b/packages/components/nodes/memory/RedisBackedChatMemory/redis.svg new file mode 100644 index 00000000..90359069 --- /dev/null +++ b/packages/components/nodes/memory/RedisBackedChatMemory/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file From 511c4995e9cc9ffc5fd1b85ecc40e4591cfd97c5 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 28 Jun 2023 22:59:56 +0100 Subject: [PATCH 22/44] format prompt values revamp --- .../nodes/chains/LLMChain/LLMChain.ts | 2 +- .../ChatPromptTemplate/ChatPromptTemplate.ts | 7 +- .../prompts/PromptTemplate/PromptTemplate.ts | 7 +- packages/components/src/utils.ts | 41 -- packages/server/marketplaces/Antonym.json | 154 +++--- .../marketplaces/HuggingFace LLM Chain.json | 150 +++--- .../server/marketplaces/Prompt Chaining.json | 502 +++++++++--------- .../server/marketplaces/Simple LLM Chain.json | 136 +++-- packages/server/marketplaces/Translator.json | 154 +++--- packages/ui/package.json | 2 +- .../dialog/EditPromptValuesDialog.js | 256 --------- ...tValuesDialog.css => ExpandTextDialog.css} | 0 .../ui-component/dialog/ExpandTextDialog.js | 105 ++++ .../dialog/FormatPromptValuesDialog.js | 56 ++ .../ui-component/dialog/SourceDocDialog.js | 2 +- packages/ui/src/ui-component/input/Input.js | 6 +- .../ui/src/ui-component/json/JsonEditor.js | 115 +++- .../src/ui-component/json/SelectVariable.js | 126 +++++ packages/ui/src/utils/genericHelper.js | 30 +- .../ui/src/views/canvas/NodeInputHandler.js | 73 ++- 20 files changed, 1014 insertions(+), 910 deletions(-) delete mode 100644 packages/ui/src/ui-component/dialog/EditPromptValuesDialog.js rename packages/ui/src/ui-component/dialog/{EditPromptValuesDialog.css => ExpandTextDialog.css} (100%) create mode 100644 packages/ui/src/ui-component/dialog/ExpandTextDialog.js create mode 100644 packages/ui/src/ui-component/dialog/FormatPromptValuesDialog.js create mode 100644 packages/ui/src/ui-component/json/SelectVariable.js diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index 9cd08d35..67c21ce4 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -50,7 +50,7 @@ class LLMChain_Chains implements INode { { label: 'Output Prediction', name: 'outputPrediction', - baseClasses: ['string'] + baseClasses: ['string', 'json'] } ] } diff --git a/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts b/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts index c3c4d77f..4eeb1dd2 100644 --- a/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts +++ b/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts @@ -38,12 +38,7 @@ class ChatPromptTemplate_Prompts implements INode { { label: 'Format Prompt Values', name: 'promptValues', - type: 'string', - rows: 4, - placeholder: `{ - "input_language": "English", - "output_language": "French" -}`, + type: 'json', optional: true, acceptVariable: true, list: true diff --git a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts index f976d64c..f9c6c53e 100644 --- a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts +++ b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts @@ -31,12 +31,7 @@ class PromptTemplate_Prompts implements INode { { label: 'Format Prompt Values', name: 'promptValues', - type: 'string', - rows: 4, - placeholder: `{ - "input_language": "English", - "output_language": "French" -}`, + type: 'json', optional: true, acceptVariable: true, list: true diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index ad8d28dc..c247ebc2 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -245,47 +245,6 @@ export class CustomChainHandler extends BaseCallbackHandler { } } -export const returnJSONStr = (jsonStr: string): string => { - let jsonStrArray = jsonStr.split(':') - - let wholeString = '' - for (let i = 0; i < jsonStrArray.length; i++) { - if (jsonStrArray[i].includes(',') && jsonStrArray[i + 1] !== undefined) { - const splitValueAndTitle = jsonStrArray[i].split(',') - const value = splitValueAndTitle[0] - const newTitle = splitValueAndTitle[1] - wholeString += handleEscapeDoubleQuote(value) + ',' + newTitle + ':' - } else { - wholeString += wholeString === '' ? jsonStrArray[i] + ':' : handleEscapeDoubleQuote(jsonStrArray[i]) - } - } - return wholeString -} - -const handleEscapeDoubleQuote = (value: string): string => { - let newValue = '' - if (value.includes('"')) { - const valueArray = value.split('"') - for (let i = 0; i < valueArray.length; i++) { - if ((i + 1) % 2 !== 0) { - switch (valueArray[i]) { - case '': - newValue += '"' - break - case '}': - newValue += '"}' - break - default: - newValue += '\\"' + valueArray[i] + '\\"' - } - } else { - newValue += valueArray[i] - } - } - } - return newValue === '' ? value : newValue -} - export const availableDependencies = [ '@dqbd/tiktoken', '@getzep/zep-js', diff --git a/packages/server/marketplaces/Antonym.json b/packages/server/marketplaces/Antonym.json index 2e21fd22..817e3ee9 100644 --- a/packages/server/marketplaces/Antonym.json +++ b/packages/server/marketplaces/Antonym.json @@ -3,68 +3,7 @@ "nodes": [ { "width": 300, - "height": 534, - "id": "promptTemplate_1", - "position": { - "x": 532.2791692529131, - "y": -31.128527027841372 - }, - "type": "customNode", - "data": { - "id": "promptTemplate_1", - "label": "Prompt Template", - "name": "promptTemplate", - "type": "PromptTemplate", - "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a basic prompt for an LLM", - "inputParams": [ - { - "label": "Template", - "name": "template", - "type": "string", - "rows": 4, - "placeholder": "What is a good name for a company that makes {product}?", - "id": "promptTemplate_1-input-template-string" - }, - { - "label": "Format Prompt Values", - "name": "promptValues", - "type": "string", - "rows": 4, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true, - "acceptVariable": true, - "list": true, - "id": "promptTemplate_1-input-promptValues-string" - } - ], - "inputAnchors": [], - "inputs": { - "template": "Word: {word}\\nAntonym: {antonym}\\n", - "promptValues": "" - }, - "outputAnchors": [ - { - "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "name": "promptTemplate", - "label": "PromptTemplate", - "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 532.2791692529131, - "y": -31.128527027841372 - }, - "dragging": false - }, - { - "width": 300, - "height": 956, + "height": 955, "id": "fewShotPromptTemplate_1", "position": { "x": 886.3229032369354, @@ -139,7 +78,7 @@ ], "inputs": { "examples": "[\n { \"word\": \"happy\", \"antonym\": \"sad\" },\n { \"word\": \"tall\", \"antonym\": \"short\" }\n]", - "examplePrompt": "{{promptTemplate_1.data.instance}}", + "examplePrompt": "{{promptTemplate_0.data.instance}}", "prefix": "Give the antonym of every input", "suffix": "Word: {input}\\nAntonym:", "exampleSeparator": "\\n\\n", @@ -165,7 +104,7 @@ }, { "width": 300, - "height": 526, + "height": 524, "id": "openAI_1", "position": { "x": 1224.5139327142097, @@ -318,7 +257,7 @@ }, { "width": 300, - "height": 407, + "height": 405, "id": "llmChain_1", "position": { "x": 1635.363191180743, @@ -375,10 +314,10 @@ "type": "LLMChain | BaseChain | BaseLangChain" }, { - "id": "llmChain_1-output-outputPrediction-string", + "id": "llmChain_1-output-outputPrediction-string|json", "name": "outputPrediction", "label": "Output Prediction", - "type": "string" + "type": "string | json" } ], "default": "llmChain" @@ -395,20 +334,68 @@ }, "selected": false, "dragging": false + }, + { + "width": 300, + "height": 475, + "id": "promptTemplate_0", + "position": { + "x": 540.0140796251119, + "y": -33.31673494170347 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_0", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "Word: {word}\\nAntonym: {antonym}\\n", + "promptValues": "" + }, + "outputAnchors": [ + { + "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 540.0140796251119, + "y": -33.31673494170347 + }, + "dragging": false } ], "edges": [ - { - "source": "promptTemplate_1", - "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "target": "fewShotPromptTemplate_1", - "targetHandle": "fewShotPromptTemplate_1-input-examplePrompt-PromptTemplate", - "type": "buttonedge", - "id": "promptTemplate_1-promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-fewShotPromptTemplate_1-fewShotPromptTemplate_1-input-examplePrompt-PromptTemplate", - "data": { - "label": "" - } - }, { "source": "openAI_1", "sourceHandle": "openAI_1-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain", @@ -430,6 +417,17 @@ "data": { "label": "" } + }, + { + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "fewShotPromptTemplate_1", + "targetHandle": "fewShotPromptTemplate_1-input-examplePrompt-PromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-fewShotPromptTemplate_1-fewShotPromptTemplate_1-input-examplePrompt-PromptTemplate", + "data": { + "label": "" + } } ] } diff --git a/packages/server/marketplaces/HuggingFace LLM Chain.json b/packages/server/marketplaces/HuggingFace LLM Chain.json index 9d3492c6..d46f9d64 100644 --- a/packages/server/marketplaces/HuggingFace LLM Chain.json +++ b/packages/server/marketplaces/HuggingFace LLM Chain.json @@ -1,67 +1,6 @@ { "description": "Simple LLM Chain using HuggingFace Inference API on falcon-7b-instruct model", "nodes": [ - { - "width": 300, - "height": 532, - "id": "promptTemplate_1", - "position": { - "x": 514.5434056794296, - "y": 507.47798128037107 - }, - "type": "customNode", - "data": { - "id": "promptTemplate_1", - "label": "Prompt Template", - "name": "promptTemplate", - "type": "PromptTemplate", - "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a basic prompt for an LLM", - "inputParams": [ - { - "label": "Template", - "name": "template", - "type": "string", - "rows": 4, - "placeholder": "What is a good name for a company that makes {product}?", - "id": "promptTemplate_1-input-template-string" - }, - { - "label": "Format Prompt Values", - "name": "promptValues", - "type": "string", - "rows": 4, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true, - "acceptVariable": true, - "list": true, - "id": "promptTemplate_1-input-promptValues-string" - } - ], - "inputAnchors": [], - "inputs": { - "template": "Question: {question}\n\nAnswer: Let's think step by step.", - "promptValues": "" - }, - "outputAnchors": [ - { - "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "name": "promptTemplate", - "label": "PromptTemplate", - "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 514.5434056794296, - "y": 507.47798128037107 - }, - "dragging": false - }, { "width": 300, "height": 405, @@ -105,7 +44,7 @@ ], "inputs": { "model": "{{huggingFaceInference_LLMs_0.data.instance}}", - "prompt": "{{promptTemplate_1.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", "chainName": "" }, "outputAnchors": [ @@ -121,10 +60,10 @@ "type": "LLMChain | BaseChain | BaseLangChain" }, { - "id": "llmChain_1-output-outputPrediction-string", + "id": "llmChain_1-output-outputPrediction-string|json", "name": "outputPrediction", "label": "Output Prediction", - "type": "string" + "type": "string | json" } ], "default": "llmChain" @@ -144,7 +83,7 @@ }, { "width": 300, - "height": 427, + "height": 429, "id": "huggingFaceInference_LLMs_0", "position": { "x": 503.5630827259226, @@ -245,20 +184,68 @@ "y": 50.79125094823999 }, "dragging": false + }, + { + "width": 300, + "height": 475, + "id": "promptTemplate_0", + "position": { + "x": 506.50436294210306, + "y": 504.50766458127396 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_0", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "Question: {question}\n\nAnswer: Let's think step by step.", + "promptValues": "" + }, + "outputAnchors": [ + { + "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 506.50436294210306, + "y": 504.50766458127396 + }, + "dragging": false } ], "edges": [ - { - "source": "promptTemplate_1", - "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "target": "llmChain_1", - "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", - "type": "buttonedge", - "id": "promptTemplate_1-promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", - "data": { - "label": "" - } - }, { "source": "huggingFaceInference_LLMs_0", "sourceHandle": "huggingFaceInference_LLMs_0-output-huggingFaceInference_LLMs-HuggingFaceInference|LLM|BaseLLM|BaseLanguageModel|BaseLangChain", @@ -269,6 +256,17 @@ "data": { "label": "" } + }, + { + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } } ] } diff --git a/packages/server/marketplaces/Prompt Chaining.json b/packages/server/marketplaces/Prompt Chaining.json index 33a64081..96987660 100644 --- a/packages/server/marketplaces/Prompt Chaining.json +++ b/packages/server/marketplaces/Prompt Chaining.json @@ -3,7 +3,7 @@ "nodes": [ { "width": 300, - "height": 526, + "height": 524, "id": "openAI_2", "position": { "x": 793.6674026500068, @@ -156,213 +156,11 @@ }, { "width": 300, - "height": 534, - "id": "promptTemplate_2", - "position": { - "x": 796.3399644963663, - "y": 512.349657546027 - }, - "type": "customNode", - "data": { - "id": "promptTemplate_2", - "label": "Prompt Template", - "name": "promptTemplate", - "type": "PromptTemplate", - "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a basic prompt for an LLM", - "inputParams": [ - { - "label": "Template", - "name": "template", - "type": "string", - "rows": 4, - "placeholder": "What is a good name for a company that makes {product}?", - "id": "promptTemplate_2-input-template-string" - }, - { - "label": "Format Prompt Values", - "name": "promptValues", - "type": "string", - "rows": 4, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true, - "acceptVariable": true, - "list": true, - "id": "promptTemplate_2-input-promptValues-string" - } - ], - "inputAnchors": [], - "inputs": { - "template": "You are an AI who performs one task based on the following objective: {objective}.\nRespond with how you would complete this task:", - "promptValues": "{\n \"objective\": \"{{question}}\"\n}" - }, - "outputAnchors": [ - { - "id": "promptTemplate_2-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "name": "promptTemplate", - "label": "PromptTemplate", - "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 796.3399644963663, - "y": 512.349657546027 - }, - "dragging": false - }, - { - "width": 300, - "height": 407, - "id": "llmChain_2", - "position": { - "x": 1225.2861408370582, - "y": 485.62403908243243 - }, - "type": "customNode", - "data": { - "id": "llmChain_2", - "label": "LLM Chain", - "name": "llmChain", - "type": "LLMChain", - "baseClasses": ["LLMChain", "BaseChain", "BaseLangChain"], - "category": "Chains", - "description": "Chain to run queries against LLMs", - "inputParams": [ - { - "label": "Chain Name", - "name": "chainName", - "type": "string", - "placeholder": "Name Your Chain", - "optional": true, - "id": "llmChain_2-input-chainName-string" - } - ], - "inputAnchors": [ - { - "label": "Language Model", - "name": "model", - "type": "BaseLanguageModel", - "id": "llmChain_2-input-model-BaseLanguageModel" - }, - { - "label": "Prompt", - "name": "prompt", - "type": "BasePromptTemplate", - "id": "llmChain_2-input-prompt-BasePromptTemplate" - } - ], - "inputs": { - "model": "{{openAI_2.data.instance}}", - "prompt": "{{promptTemplate_2.data.instance}}", - "chainName": "First Chain" - }, - "outputAnchors": [ - { - "name": "output", - "label": "Output", - "type": "options", - "options": [ - { - "id": "llmChain_2-output-llmChain-LLMChain|BaseChain|BaseLangChain", - "name": "llmChain", - "label": "LLM Chain", - "type": "LLMChain | BaseChain | BaseLangChain" - }, - { - "id": "llmChain_2-output-outputPrediction-string", - "name": "outputPrediction", - "label": "Output Prediction", - "type": "string" - } - ], - "default": "llmChain" - } - ], - "outputs": { - "output": "outputPrediction" - }, - "selected": false - }, - "selected": false, - "dragging": false, - "positionAbsolute": { - "x": 1225.2861408370582, - "y": 485.62403908243243 - } - }, - { - "width": 300, - "height": 534, - "id": "promptTemplate_3", - "position": { - "x": 1589.206555911206, - "y": 460.23470154201766 - }, - "type": "customNode", - "data": { - "id": "promptTemplate_3", - "label": "Prompt Template", - "name": "promptTemplate", - "type": "PromptTemplate", - "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a basic prompt for an LLM", - "inputParams": [ - { - "label": "Template", - "name": "template", - "type": "string", - "rows": 4, - "placeholder": "What is a good name for a company that makes {product}?", - "id": "promptTemplate_3-input-template-string" - }, - { - "label": "Format Prompt Values", - "name": "promptValues", - "type": "string", - "rows": 4, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true, - "acceptVariable": true, - "list": true, - "id": "promptTemplate_3-input-promptValues-string" - } - ], - "inputAnchors": [], - "inputs": { - "template": "You are a task creation AI that uses the result of an execution agent to create new tasks with the following objective: {objective}.\nThe last completed task has the result: {result}.\nBased on the result, create new tasks to be completed by the AI system that do not overlap with result.\nReturn the tasks as an array.", - "promptValues": "{\n \"objective\": \"{{question}}\",\n \"result\": \"\"\n}" - }, - "outputAnchors": [ - { - "id": "promptTemplate_3-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "name": "promptTemplate", - "label": "PromptTemplate", - "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 1589.206555911206, - "y": 460.23470154201766 - }, - "dragging": false - }, - { - "width": 300, - "height": 526, + "height": 524, "id": "openAI_3", "position": { - "x": 1225.2861408370586, - "y": -62.7856517905272 + "x": 1216.061423775753, + "y": -20.35195330852082 }, "type": "customNode", "data": { @@ -503,27 +301,145 @@ "selected": false }, "positionAbsolute": { - "x": 1225.2861408370586, - "y": -62.7856517905272 + "x": 1216.061423775753, + "y": -20.35195330852082 }, "selected": false, "dragging": false }, { "width": 300, - "height": 407, - "id": "llmChain_3", + "height": 475, + "id": "promptTemplate_0", "position": { - "x": 1972.2671768945252, - "y": 142.73435419451476 + "x": 792.9464838535649, + "y": 527.1718536712464 }, "type": "customNode", "data": { - "id": "llmChain_3", + "id": "promptTemplate_0", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "You are an AI who performs one task based on the following objective: {objective}.\nRespond with how you would complete this task:", + "promptValues": "{\"objective\":\"{{question}}\"}" + }, + "outputAnchors": [ + { + "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 792.9464838535649, + "y": 527.1718536712464 + }, + "dragging": false + }, + { + "width": 300, + "height": 475, + "id": "promptTemplate_1", + "position": { + "x": 1577.7482561604884, + "y": 516.186942924815 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_1", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_1-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_1-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "You are a task creation AI that uses the result of an execution agent to create new tasks with the following objective: {objective}.\nThe last completed task has the result: {result}.\nBased on the result, create new tasks to be completed by the AI system that do not overlap with result.\nReturn the tasks as an array.", + "promptValues": "{\"objective\":\"{{question}}\",\"result\":\"{{llmChain_0.data.instance}}\"}" + }, + "outputAnchors": [ + { + "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "positionAbsolute": { + "x": 1577.7482561604884, + "y": 516.186942924815 + }, + "selected": false, + "dragging": false + }, + { + "width": 300, + "height": 405, + "id": "llmChain_0", + "position": { + "x": 1221.1346231272787, + "y": 538.9546839784628 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", "label": "LLM Chain", "name": "llmChain", "type": "LLMChain", - "baseClasses": ["LLMChain", "BaseChain", "BaseLangChain"], + "baseClasses": ["LLMChain", "BaseChain"], "category": "Chains", "description": "Chain to run queries against LLMs", "inputParams": [ @@ -533,7 +449,7 @@ "type": "string", "placeholder": "Name Your Chain", "optional": true, - "id": "llmChain_3-input-chainName-string" + "id": "llmChain_0-input-chainName-string" } ], "inputAnchors": [ @@ -541,18 +457,98 @@ "label": "Language Model", "name": "model", "type": "BaseLanguageModel", - "id": "llmChain_3-input-model-BaseLanguageModel" + "id": "llmChain_0-input-model-BaseLanguageModel" }, { "label": "Prompt", "name": "prompt", "type": "BasePromptTemplate", - "id": "llmChain_3-input-prompt-BasePromptTemplate" + "id": "llmChain_0-input-prompt-BasePromptTemplate" + } + ], + "inputs": { + "model": "{{openAI_2.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", + "chainName": "FirstChain" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_0-output-llmChain-LLMChain|BaseChain", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain" + }, + { + "id": "llmChain_0-output-outputPrediction-string|json", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string | json" + } + ], + "default": "llmChain" + } + ], + "outputs": { + "output": "outputPrediction" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1221.1346231272787, + "y": 538.9546839784628 + }, + "dragging": false + }, + { + "width": 300, + "height": 405, + "id": "llmChain_1", + "position": { + "x": 1971.8054567964418, + "y": 207.624530381245 + }, + "type": "customNode", + "data": { + "id": "llmChain_1", + "label": "LLM Chain", + "name": "llmChain", + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain"], + "category": "Chains", + "description": "Chain to run queries against LLMs", + "inputParams": [ + { + "label": "Chain Name", + "name": "chainName", + "type": "string", + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_1-input-chainName-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "llmChain_1-input-model-BaseLanguageModel" + }, + { + "label": "Prompt", + "name": "prompt", + "type": "BasePromptTemplate", + "id": "llmChain_1-input-prompt-BasePromptTemplate" } ], "inputs": { "model": "{{openAI_3.data.instance}}", - "prompt": "{{promptTemplate_3.data.instance}}", + "prompt": "{{promptTemplate_1.data.instance}}", "chainName": "LastChain" }, "outputAnchors": [ @@ -562,16 +558,16 @@ "type": "options", "options": [ { - "id": "llmChain_3-output-llmChain-LLMChain|BaseChain|BaseLangChain", + "id": "llmChain_1-output-llmChain-LLMChain|BaseChain", "name": "llmChain", "label": "LLM Chain", - "type": "LLMChain | BaseChain | BaseLangChain" + "type": "LLMChain | BaseChain" }, { - "id": "llmChain_3-output-outputPrediction-string", + "id": "llmChain_1-output-outputPrediction-string|json", "name": "outputPrediction", "label": "Output Prediction", - "type": "string" + "type": "string | json" } ], "default": "llmChain" @@ -583,43 +579,43 @@ "selected": false }, "selected": false, - "dragging": false, "positionAbsolute": { - "x": 1972.2671768945252, - "y": 142.73435419451476 - } + "x": 1971.8054567964418, + "y": 207.624530381245 + }, + "dragging": false } ], "edges": [ - { - "source": "llmChain_2", - "sourceHandle": "llmChain_2-output-outputPrediction-string", - "target": "promptTemplate_3", - "targetHandle": "promptTemplate_3-input-promptValues-string", - "type": "buttonedge", - "id": "llmChain_2-llmChain_2-output-outputPrediction-string-promptTemplate_3-promptTemplate_3-input-promptValues-string", - "data": { - "label": "" - } - }, { "source": "openAI_2", "sourceHandle": "openAI_2-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain", - "target": "llmChain_2", - "targetHandle": "llmChain_2-input-model-BaseLanguageModel", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-model-BaseLanguageModel", "type": "buttonedge", - "id": "openAI_2-openAI_2-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain-llmChain_2-llmChain_2-input-model-BaseLanguageModel", + "id": "openAI_2-openAI_2-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain-llmChain_0-llmChain_0-input-model-BaseLanguageModel", "data": { "label": "" } }, { - "source": "promptTemplate_2", - "sourceHandle": "promptTemplate_2-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "target": "llmChain_2", - "targetHandle": "llmChain_2-input-prompt-BasePromptTemplate", + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", "type": "buttonedge", - "id": "promptTemplate_2-promptTemplate_2-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_2-llmChain_2-input-prompt-BasePromptTemplate", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } + }, + { + "source": "llmChain_0", + "sourceHandle": "llmChain_0-output-outputPrediction-string|json", + "target": "promptTemplate_1", + "targetHandle": "promptTemplate_1-input-promptValues-json", + "type": "buttonedge", + "id": "llmChain_0-llmChain_0-output-outputPrediction-string|json-promptTemplate_1-promptTemplate_1-input-promptValues-json", "data": { "label": "" } @@ -627,21 +623,21 @@ { "source": "openAI_3", "sourceHandle": "openAI_3-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain", - "target": "llmChain_3", - "targetHandle": "llmChain_3-input-model-BaseLanguageModel", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-model-BaseLanguageModel", "type": "buttonedge", - "id": "openAI_3-openAI_3-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain-llmChain_3-llmChain_3-input-model-BaseLanguageModel", + "id": "openAI_3-openAI_3-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|BaseLangChain-llmChain_1-llmChain_1-input-model-BaseLanguageModel", "data": { "label": "" } }, { - "source": "promptTemplate_3", - "sourceHandle": "promptTemplate_3-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "target": "llmChain_3", - "targetHandle": "llmChain_3-input-prompt-BasePromptTemplate", + "source": "promptTemplate_1", + "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", "type": "buttonedge", - "id": "promptTemplate_3-promptTemplate_3-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_3-llmChain_3-input-prompt-BasePromptTemplate", + "id": "promptTemplate_1-promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", "data": { "label": "" } diff --git a/packages/server/marketplaces/Simple LLM Chain.json b/packages/server/marketplaces/Simple LLM Chain.json index c9d354bc..cc193d5c 100644 --- a/packages/server/marketplaces/Simple LLM Chain.json +++ b/packages/server/marketplaces/Simple LLM Chain.json @@ -3,7 +3,7 @@ "nodes": [ { "width": 300, - "height": 526, + "height": 524, "id": "openAI_1", "position": { "x": 510.75932526856377, @@ -156,68 +156,7 @@ }, { "width": 300, - "height": 534, - "id": "promptTemplate_1", - "position": { - "x": 514.5434056794296, - "y": 507.47798128037107 - }, - "type": "customNode", - "data": { - "id": "promptTemplate_1", - "label": "Prompt Template", - "name": "promptTemplate", - "type": "PromptTemplate", - "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a basic prompt for an LLM", - "inputParams": [ - { - "label": "Template", - "name": "template", - "type": "string", - "rows": 4, - "placeholder": "What is a good name for a company that makes {product}?", - "id": "promptTemplate_1-input-template-string" - }, - { - "label": "Format Prompt Values", - "name": "promptValues", - "type": "string", - "rows": 4, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true, - "acceptVariable": true, - "list": true, - "id": "promptTemplate_1-input-promptValues-string" - } - ], - "inputAnchors": [], - "inputs": { - "template": "", - "promptValues": "" - }, - "outputAnchors": [ - { - "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", - "name": "promptTemplate", - "label": "PromptTemplate", - "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 514.5434056794296, - "y": 507.47798128037107 - }, - "dragging": false - }, - { - "width": 300, - "height": 407, + "height": 405, "id": "llmChain_1", "position": { "x": 970.9254258940236, @@ -258,7 +197,7 @@ ], "inputs": { "model": "{{openAI_1.data.instance}}", - "prompt": "{{promptTemplate_1.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", "chainName": "" }, "outputAnchors": [ @@ -274,10 +213,10 @@ "type": "LLMChain | BaseChain | BaseLangChain" }, { - "id": "llmChain_1-output-outputPrediction-string", + "id": "llmChain_1-output-outputPrediction-string|json", "name": "outputPrediction", "label": "Output Prediction", - "type": "string" + "type": "string | json" } ], "default": "llmChain" @@ -294,6 +233,65 @@ }, "selected": false, "dragging": false + }, + { + "width": 300, + "height": 475, + "id": "promptTemplate_0", + "position": { + "x": 517.7412884791509, + "y": 506.7411400888471 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_0", + "label": "Prompt Template", + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "What is a good name for a company that makes {product}?", + "promptValues": "" + }, + "outputAnchors": [ + { + "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 517.7412884791509, + "y": 506.7411400888471 + }, + "dragging": false } ], "edges": [ @@ -309,12 +307,12 @@ } }, { - "source": "promptTemplate_1", - "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate", "target": "llmChain_1", "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", "type": "buttonedge", - "id": "promptTemplate_1-promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", "data": { "label": "" } diff --git a/packages/server/marketplaces/Translator.json b/packages/server/marketplaces/Translator.json index fda400e2..942bbecd 100644 --- a/packages/server/marketplaces/Translator.json +++ b/packages/server/marketplaces/Translator.json @@ -3,77 +3,7 @@ "nodes": [ { "width": 300, - "height": 711, - "id": "chatPromptTemplate_1", - "position": { - "x": 441.8516979620723, - "y": 636.1108860994266 - }, - "type": "customNode", - "data": { - "id": "chatPromptTemplate_1", - "label": "Chat Prompt Template", - "name": "chatPromptTemplate", - "type": "ChatPromptTemplate", - "baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate"], - "category": "Prompts", - "description": "Schema to represent a chat prompt", - "inputParams": [ - { - "label": "System Message", - "name": "systemMessagePrompt", - "type": "string", - "rows": 4, - "placeholder": "You are a helpful assistant that translates {input_language} to {output_language}.", - "id": "chatPromptTemplate_1-input-systemMessagePrompt-string" - }, - { - "label": "Human Message", - "name": "humanMessagePrompt", - "type": "string", - "rows": 4, - "placeholder": "{text}", - "id": "chatPromptTemplate_1-input-humanMessagePrompt-string" - }, - { - "label": "Format Prompt Values", - "name": "promptValues", - "type": "string", - "rows": 4, - "placeholder": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}", - "optional": true, - "acceptVariable": true, - "list": true, - "id": "chatPromptTemplate_1-input-promptValues-string" - } - ], - "inputAnchors": [], - "inputs": { - "systemMessagePrompt": "You are a helpful assistant that translates {input_language} to {output_language}.", - "humanMessagePrompt": "{input}", - "promptValues": "{\n \"input_language\": \"English\",\n \"output_language\": \"French\"\n}" - }, - "outputAnchors": [ - { - "id": "chatPromptTemplate_1-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate", - "name": "chatPromptTemplate", - "label": "ChatPromptTemplate", - "type": "ChatPromptTemplate | BaseChatPromptTemplate | BasePromptTemplate" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 441.8516979620723, - "y": 636.1108860994266 - }, - "dragging": false - }, - { - "width": 300, - "height": 526, + "height": 524, "id": "chatOpenAI_1", "position": { "x": 439.5219561593599, @@ -224,7 +154,7 @@ }, { "width": 300, - "height": 407, + "height": 405, "id": "llmChain_1", "position": { "x": 865.7775572410412, @@ -265,7 +195,7 @@ ], "inputs": { "model": "{{chatOpenAI_1.data.instance}}", - "prompt": "{{chatPromptTemplate_1.data.instance}}", + "prompt": "{{chatPromptTemplate_0.data.instance}}", "chainName": "Language Translation" }, "outputAnchors": [ @@ -281,10 +211,10 @@ "type": "LLMChain | BaseChain | BaseLangChain" }, { - "id": "llmChain_1-output-outputPrediction-string", + "id": "llmChain_1-output-outputPrediction-string|json", "name": "outputPrediction", "label": "Output Prediction", - "type": "string" + "type": "string | json" } ], "default": "llmChain" @@ -301,6 +231,74 @@ "y": 543.9211372857111 }, "dragging": false + }, + { + "width": 300, + "height": 652, + "id": "chatPromptTemplate_0", + "position": { + "x": 437.51367850489396, + "y": 649.7619214034173 + }, + "type": "customNode", + "data": { + "id": "chatPromptTemplate_0", + "label": "Chat Prompt Template", + "name": "chatPromptTemplate", + "type": "ChatPromptTemplate", + "baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate"], + "category": "Prompts", + "description": "Schema to represent a chat prompt", + "inputParams": [ + { + "label": "System Message", + "name": "systemMessagePrompt", + "type": "string", + "rows": 4, + "placeholder": "You are a helpful assistant that translates {input_language} to {output_language}.", + "id": "chatPromptTemplate_0-input-systemMessagePrompt-string" + }, + { + "label": "Human Message", + "name": "humanMessagePrompt", + "type": "string", + "rows": 4, + "placeholder": "{text}", + "id": "chatPromptTemplate_0-input-humanMessagePrompt-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "chatPromptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "systemMessagePrompt": "You are a helpful assistant that translates {input_language} to {output_language}.", + "humanMessagePrompt": "{text}", + "promptValues": "{\"input_language\":\"English\",\"output_language\":\"French\",\"text\":\"{{question}}\"}" + }, + "outputAnchors": [ + { + "id": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate", + "name": "chatPromptTemplate", + "label": "ChatPromptTemplate", + "type": "ChatPromptTemplate | BaseChatPromptTemplate | BasePromptTemplate" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 437.51367850489396, + "y": 649.7619214034173 + }, + "dragging": false } ], "edges": [ @@ -316,12 +314,12 @@ } }, { - "source": "chatPromptTemplate_1", - "sourceHandle": "chatPromptTemplate_1-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate", + "source": "chatPromptTemplate_0", + "sourceHandle": "chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate", "target": "llmChain_1", "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", "type": "buttonedge", - "id": "chatPromptTemplate_1-chatPromptTemplate_1-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "id": "chatPromptTemplate_0-chatPromptTemplate_0-output-chatPromptTemplate-ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", "data": { "label": "" } diff --git a/packages/ui/package.json b/packages/ui/package.json index 1e55f1c8..ba1483b4 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -18,6 +18,7 @@ "clsx": "^1.1.1", "flowise-embed": "*", "flowise-embed-react": "*", + "flowise-react-json-view": "*", "formik": "^2.2.6", "framer-motion": "^4.1.13", "history": "^5.0.0", @@ -33,7 +34,6 @@ "react-datepicker": "^4.8.0", "react-device-detect": "^1.17.0", "react-dom": "^18.2.0", - "react-json-view": "^1.21.3", "react-markdown": "^8.0.6", "react-perfect-scrollbar": "^1.5.8", "react-redux": "^8.0.5", diff --git a/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.js b/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.js deleted file mode 100644 index 199b1306..00000000 --- a/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.js +++ /dev/null @@ -1,256 +0,0 @@ -import { createPortal } from 'react-dom' -import { useState, useEffect } from 'react' -import { useSelector } from 'react-redux' -import PropTypes from 'prop-types' -import { - Button, - Dialog, - DialogActions, - DialogContent, - Box, - List, - ListItemButton, - ListItem, - ListItemAvatar, - ListItemText, - Typography, - Stack -} from '@mui/material' -import { useTheme } from '@mui/material/styles' -import PerfectScrollbar from 'react-perfect-scrollbar' -import { StyledButton } from 'ui-component/button/StyledButton' -import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' -import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' - -import './EditPromptValuesDialog.css' -import { baseURL } from 'store/constant' - -const EditPromptValuesDialog = ({ show, dialogProps, onCancel, onConfirm }) => { - const portalElement = document.getElementById('portal') - - const theme = useTheme() - const customization = useSelector((state) => state.customization) - const languageType = 'json' - - const [inputValue, setInputValue] = useState('') - const [inputParam, setInputParam] = useState(null) - const [textCursorPosition, setTextCursorPosition] = useState({}) - - useEffect(() => { - if (dialogProps.value) setInputValue(dialogProps.value) - if (dialogProps.inputParam) setInputParam(dialogProps.inputParam) - - return () => { - setInputValue('') - setInputParam(null) - setTextCursorPosition({}) - } - }, [dialogProps]) - - const onMouseUp = (e) => { - if (e.target && e.target.selectionEnd && e.target.value) { - const cursorPosition = e.target.selectionEnd - const textBeforeCursorPosition = e.target.value.substring(0, cursorPosition) - const textAfterCursorPosition = e.target.value.substring(cursorPosition, e.target.value.length) - const body = { - textBeforeCursorPosition, - textAfterCursorPosition - } - setTextCursorPosition(body) - } else { - setTextCursorPosition({}) - } - } - - const onSelectOutputResponseClick = (node, isUserQuestion = false) => { - let variablePath = isUserQuestion ? `question` : `${node.id}.data.instance` - if (textCursorPosition) { - let newInput = '' - if (textCursorPosition.textBeforeCursorPosition === undefined && textCursorPosition.textAfterCursorPosition === undefined) - newInput = `${inputValue}${`{{${variablePath}}}`}` - else newInput = `${textCursorPosition.textBeforeCursorPosition}{{${variablePath}}}${textCursorPosition.textAfterCursorPosition}` - setInputValue(newInput) - } - } - - const component = show ? ( - - -
- {inputParam && inputParam.type === 'string' && ( -
- - {inputParam.label} - - - {customization.isDarkMode ? ( - setInputValue(code)} - placeholder={inputParam.placeholder} - type={languageType} - onMouseUp={(e) => onMouseUp(e)} - onBlur={(e) => onMouseUp(e)} - style={{ - fontSize: '0.875rem', - minHeight: 'calc(100vh - 220px)', - width: '100%' - }} - /> - ) : ( - setInputValue(code)} - placeholder={inputParam.placeholder} - type={languageType} - onMouseUp={(e) => onMouseUp(e)} - onBlur={(e) => onMouseUp(e)} - style={{ - fontSize: '0.875rem', - minHeight: 'calc(100vh - 220px)', - width: '100%' - }} - /> - )} - -
- )} - {!dialogProps.disabled && inputParam && inputParam.acceptVariable && ( -
- - Select Variable - - - - - onSelectOutputResponseClick(null, true)} - > - - -
- AI -
-
- -
-
- {dialogProps.availableNodesForVariable && - dialogProps.availableNodesForVariable.length > 0 && - dialogProps.availableNodesForVariable.map((node, index) => { - const selectedOutputAnchor = node.data.outputAnchors[0].options.find( - (ancr) => ancr.name === node.data.outputs['output'] - ) - return ( - onSelectOutputResponseClick(node)} - > - - -
- {node.data.name} -
-
- -
-
- ) - })} -
-
-
-
- )} -
-
- - - onConfirm(inputValue, inputParam.name)}> - {dialogProps.confirmButtonName} - - -
- ) : null - - return createPortal(component, portalElement) -} - -EditPromptValuesDialog.propTypes = { - show: PropTypes.bool, - dialogProps: PropTypes.object, - onCancel: PropTypes.func, - onConfirm: PropTypes.func -} - -export default EditPromptValuesDialog diff --git a/packages/ui/src/ui-component/dialog/EditPromptValuesDialog.css b/packages/ui/src/ui-component/dialog/ExpandTextDialog.css similarity index 100% rename from packages/ui/src/ui-component/dialog/EditPromptValuesDialog.css rename to packages/ui/src/ui-component/dialog/ExpandTextDialog.css diff --git a/packages/ui/src/ui-component/dialog/ExpandTextDialog.js b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js new file mode 100644 index 00000000..b955ccdb --- /dev/null +++ b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js @@ -0,0 +1,105 @@ +import { createPortal } from 'react-dom' +import { useState, useEffect } from 'react' +import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' +import { Button, Dialog, DialogActions, DialogContent, Typography } from '@mui/material' +import { useTheme } from '@mui/material/styles' +import PerfectScrollbar from 'react-perfect-scrollbar' +import { StyledButton } from 'ui-component/button/StyledButton' +import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' +import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' + +import './ExpandTextDialog.css' + +const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const portalElement = document.getElementById('portal') + + const theme = useTheme() + const customization = useSelector((state) => state.customization) + const languageType = 'json' + + const [inputValue, setInputValue] = useState('') + const [inputParam, setInputParam] = useState(null) + + useEffect(() => { + if (dialogProps.value) setInputValue(dialogProps.value) + if (dialogProps.inputParam) setInputParam(dialogProps.inputParam) + + return () => { + setInputValue('') + setInputParam(null) + } + }, [dialogProps]) + + const component = show ? ( + + +
+ {inputParam && inputParam.type === 'string' && ( +
+ + {inputParam.label} + + + {customization.isDarkMode ? ( + setInputValue(code)} + placeholder={inputParam.placeholder} + type={languageType} + style={{ + fontSize: '0.875rem', + minHeight: 'calc(100vh - 220px)', + width: '100%' + }} + /> + ) : ( + setInputValue(code)} + placeholder={inputParam.placeholder} + type={languageType} + style={{ + fontSize: '0.875rem', + minHeight: 'calc(100vh - 220px)', + width: '100%' + }} + /> + )} + +
+ )} +
+
+ + + onConfirm(inputValue, inputParam.name)}> + {dialogProps.confirmButtonName} + + +
+ ) : null + + return createPortal(component, portalElement) +} + +ExpandTextDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func, + onConfirm: PropTypes.func +} + +export default ExpandTextDialog diff --git a/packages/ui/src/ui-component/dialog/FormatPromptValuesDialog.js b/packages/ui/src/ui-component/dialog/FormatPromptValuesDialog.js new file mode 100644 index 00000000..df1d357e --- /dev/null +++ b/packages/ui/src/ui-component/dialog/FormatPromptValuesDialog.js @@ -0,0 +1,56 @@ +import { createPortal } from 'react-dom' +import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' +import { Dialog, DialogContent, DialogTitle } from '@mui/material' +import PerfectScrollbar from 'react-perfect-scrollbar' +import { JsonEditorInput } from 'ui-component/json/JsonEditor' + +const FormatPromptValuesDialog = ({ show, dialogProps, onChange, onCancel }) => { + const portalElement = document.getElementById('portal') + const customization = useSelector((state) => state.customization) + + const component = show ? ( + + + Format Prompt Values + + + + onChange(newValue)} + value={dialogProps.value} + isDarkMode={customization.isDarkMode} + inputParam={dialogProps.inputParam} + nodes={dialogProps.nodes} + edges={dialogProps.edges} + nodeId={dialogProps.nodeId} + /> + + + + ) : null + + return createPortal(component, portalElement) +} + +FormatPromptValuesDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onChange: PropTypes.func, + onCancel: PropTypes.func +} + +export default FormatPromptValuesDialog diff --git a/packages/ui/src/ui-component/dialog/SourceDocDialog.js b/packages/ui/src/ui-component/dialog/SourceDocDialog.js index a088a6c4..6bf8692f 100644 --- a/packages/ui/src/ui-component/dialog/SourceDocDialog.js +++ b/packages/ui/src/ui-component/dialog/SourceDocDialog.js @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' import { Dialog, DialogContent, DialogTitle } from '@mui/material' -import ReactJson from 'react-json-view' +import ReactJson from 'flowise-react-json-view' const SourceDocDialog = ({ show, dialogProps, onCancel }) => { const portalElement = document.getElementById('portal') diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index 7f0e0610..e7744764 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -1,7 +1,7 @@ import { useState } from 'react' import PropTypes from 'prop-types' import { FormControl, OutlinedInput } from '@mui/material' -import EditPromptValuesDialog from 'ui-component/dialog/EditPromptValuesDialog' +import ExpandTextDialog from 'ui-component/dialog/ExpandTextDialog' export const Input = ({ inputParam, value, onChange, disabled = false, showDialog, dialogProps, onDialogCancel, onDialogConfirm }) => { const [myValue, setMyValue] = useState(value ?? '') @@ -45,7 +45,7 @@ export const Input = ({ inputParam, value, onChange, disabled = false, showDialo /> {showDialog && ( - + > )} ) diff --git a/packages/ui/src/ui-component/json/JsonEditor.js b/packages/ui/src/ui-component/json/JsonEditor.js index 06442df2..4bf8f306 100644 --- a/packages/ui/src/ui-component/json/JsonEditor.js +++ b/packages/ui/src/ui-component/json/JsonEditor.js @@ -1,10 +1,32 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import PropTypes from 'prop-types' -import { FormControl } from '@mui/material' -import ReactJson from 'react-json-view' +import { FormControl, Popover } from '@mui/material' +import ReactJson from 'flowise-react-json-view' +import SelectVariable from './SelectVariable' +import { cloneDeep } from 'lodash' +import { getAvailableNodesForVariable } from 'utils/genericHelper' -export const JsonEditorInput = ({ value, onChange, disabled = false, isDarkMode = false }) => { +export const JsonEditorInput = ({ value, onChange, inputParam, nodes, edges, nodeId, disabled = false, isDarkMode = false }) => { const [myValue, setMyValue] = useState(value ? JSON.parse(value) : {}) + const [availableNodesForVariable, setAvailableNodesForVariable] = useState([]) + const [mouseUpKey, setMouseUpKey] = useState('') + + const [anchorEl, setAnchorEl] = useState(null) + const openPopOver = Boolean(anchorEl) + + const handleClosePopOver = () => { + setAnchorEl(null) + } + + const setNewVal = (val) => { + const newVal = cloneDeep(myValue) + newVal[mouseUpKey] = val + onChange(JSON.stringify(newVal)) + setMyValue((params) => ({ + ...params, + [mouseUpKey]: val + })) + } const onClipboardCopy = (e) => { const src = e.src @@ -15,6 +37,13 @@ export const JsonEditorInput = ({ value, onChange, disabled = false, isDarkMode } } + useEffect(() => { + if (!disabled && nodes && edges && nodeId && inputParam) { + const nodesForVariable = inputParam?.acceptVariable ? getAvailableNodesForVariable(nodes, edges, nodeId, inputParam.id) : [] + setAvailableNodesForVariable(nodesForVariable) + } + }, [disabled, inputParam, nodes, edges, nodeId]) + return ( <> @@ -30,28 +59,60 @@ export const JsonEditorInput = ({ value, onChange, disabled = false, isDarkMode /> )} {!disabled && ( - onClipboardCopy(e)} - onEdit={(edit) => { - setMyValue(edit.updated_src) - onChange(JSON.stringify(edit.updated_src)) - }} - onAdd={() => { - //console.log(add) - }} - onDelete={(deleteobj) => { - setMyValue(deleteobj.updated_src) - onChange(JSON.stringify(deleteobj.updated_src)) - }} - /> +
+ onClipboardCopy(e)} + onMouseUp={(event) => { + if (inputParam?.acceptVariable) { + setMouseUpKey(event.name) + setAnchorEl(event.currentTarget) + } + }} + onEdit={(edit) => { + setMyValue(edit.updated_src) + onChange(JSON.stringify(edit.updated_src)) + }} + onAdd={() => { + //console.log(add) + }} + onDelete={(deleteobj) => { + setMyValue(deleteobj.updated_src) + onChange(JSON.stringify(deleteobj.updated_src)) + }} + /> +
)}
+ {inputParam?.acceptVariable && ( + + { + setNewVal(val) + handleClosePopOver() + }} + /> + + )} ) } @@ -60,5 +121,9 @@ JsonEditorInput.propTypes = { value: PropTypes.string, onChange: PropTypes.func, disabled: PropTypes.bool, - isDarkMode: PropTypes.bool + isDarkMode: PropTypes.bool, + inputParam: PropTypes.object, + nodes: PropTypes.array, + edges: PropTypes.array, + nodeId: PropTypes.string } diff --git a/packages/ui/src/ui-component/json/SelectVariable.js b/packages/ui/src/ui-component/json/SelectVariable.js new file mode 100644 index 00000000..1b891ed1 --- /dev/null +++ b/packages/ui/src/ui-component/json/SelectVariable.js @@ -0,0 +1,126 @@ +import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' +import { Box, List, ListItemButton, ListItem, ListItemAvatar, ListItemText, Typography, Stack } from '@mui/material' +import PerfectScrollbar from 'react-perfect-scrollbar' + +import { baseURL } from 'store/constant' + +const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectAndReturnVal }) => { + const customization = useSelector((state) => state.customization) + + const onSelectOutputResponseClick = (node, isUserQuestion = false) => { + let variablePath = isUserQuestion ? `question` : `${node.id}.data.instance` + const newInput = `{{${variablePath}}}` + onSelectAndReturnVal(newInput) + } + + return ( + <> + {!disabled && ( +
+ + Select Variable + + + + + onSelectOutputResponseClick(null, true)} + > + + +
+ AI +
+
+ +
+
+ {availableNodesForVariable && + availableNodesForVariable.length > 0 && + availableNodesForVariable.map((node, index) => { + const selectedOutputAnchor = node.data.outputAnchors[0].options.find( + (ancr) => ancr.name === node.data.outputs['output'] + ) + return ( + onSelectOutputResponseClick(node)} + > + + +
+ {node.data.name} +
+
+ +
+
+ ) + })} +
+
+
+
+ )} + + ) +} + +SelectVariable.propTypes = { + availableNodesForVariable: PropTypes.array, + disabled: PropTypes.bool, + onSelectAndReturnVal: PropTypes.func +} + +export default SelectVariable diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index 03f891ec..42a63057 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -285,7 +285,7 @@ export const generateExportFlowData = (flowData) => { } export const getAvailableNodesForVariable = (nodes, edges, target, targetHandle) => { - // example edge id = "llmChain_0-llmChain_0-output-outputPrediction-string-llmChain_1-llmChain_1-input-promptValues-string" + // example edge id = "llmChain_0-llmChain_0-output-outputPrediction-string|json-llmChain_1-llmChain_1-input-promptValues-string" // {source} -{sourceHandle} -{target} -{targetHandle} const parentNodes = [] const inputEdges = edges.filter((edg) => edg.target === target && edg.targetHandle === targetHandle) @@ -353,3 +353,31 @@ export const generateRandomGradient = () => { return gradient } + +export const getInputVariables = (paramValue) => { + let returnVal = paramValue + const variableStack = [] + const inputVariables = [] + let startIdx = 0 + const endIdx = returnVal.length + + while (startIdx < endIdx) { + const substr = returnVal.substring(startIdx, startIdx + 1) + + // Store the opening double curly bracket + if (substr === '{') { + variableStack.push({ substr, startIdx: startIdx + 1 }) + } + + // Found the complete variable + if (substr === '}' && variableStack.length > 0 && variableStack[variableStack.length - 1].substr === '{') { + const variableStartIdx = variableStack[variableStack.length - 1].startIdx + const variableEndIdx = startIdx + const variableFullPath = returnVal.substring(variableStartIdx, variableEndIdx) + inputVariables.push(variableFullPath) + variableStack.pop() + } + startIdx += 1 + } + return inputVariables +} diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 4ad21904..2d96bcb5 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -5,7 +5,7 @@ import { useSelector } from 'react-redux' // material-ui import { useTheme, styled } from '@mui/material/styles' -import { Box, Typography, Tooltip, IconButton } from '@mui/material' +import { Box, Typography, Tooltip, IconButton, Button } from '@mui/material' import { tooltipClasses } from '@mui/material/Tooltip' import { IconArrowsMaximize, IconEdit } from '@tabler/icons' @@ -16,10 +16,13 @@ import { Input } from 'ui-component/input/Input' import { File } from 'ui-component/file/File' import { SwitchInput } from 'ui-component/switch/Switch' import { flowContext } from 'store/context/ReactFlowContext' -import { isValidConnection, getAvailableNodesForVariable } from 'utils/genericHelper' +import { isValidConnection } from 'utils/genericHelper' import { JsonEditorInput } from 'ui-component/json/JsonEditor' import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' import ToolDialog from 'views/tools/ToolDialog' +import FormatPromptValuesDialog from 'ui-component/dialog/FormatPromptValuesDialog' + +import { getInputVariables } from 'utils/genericHelper' const EDITABLE_TOOLS = ['selectedTool'] @@ -43,6 +46,8 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA const [showAsyncOptionDialog, setAsyncOptionEditDialog] = useState('') const [asyncOptionEditDialogProps, setAsyncOptionEditDialogProps] = useState({}) const [reloadTimestamp, setReloadTimestamp] = useState(Date.now().toString()) + const [showFormatPromptValuesDialog, setShowFormatPromptValuesDialog] = useState(false) + const [formatPromptValuesDialogProps, setFormatPromptValuesDialogProps] = useState({}) const onExpandDialogClicked = (value, inputParam) => { const dialogProp = { @@ -52,17 +57,34 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA confirmButtonName: 'Save', cancelButtonName: 'Cancel' } - - if (!disabled) { - const nodes = reactFlowInstance.getNodes() - const edges = reactFlowInstance.getEdges() - const nodesForVariable = inputParam.acceptVariable ? getAvailableNodesForVariable(nodes, edges, data.id, inputParam.id) : [] - dialogProp.availableNodesForVariable = nodesForVariable - } setExpandDialogProps(dialogProp) setShowExpandDialog(true) } + const onFormatPromptValuesClicked = (value, inputParam) => { + // Preset values if the field is format prompt values + let inputValue = value + if (inputParam.name === 'promptValues' && !value) { + const obj = {} + const templateValue = + (data.inputs['template'] ?? '') + (data.inputs['systemMessagePrompt'] ?? '') + (data.inputs['humanMessagePrompt'] ?? '') + const inputVariables = getInputVariables(templateValue) + for (const inputVariable of inputVariables) { + obj[inputVariable] = '' + } + if (Object.keys(obj).length) inputValue = JSON.stringify(obj) + } + const dialogProp = { + value: inputValue, + inputParam, + nodes: reactFlowInstance.getNodes(), + edges: reactFlowInstance.getEdges(), + nodeId: data.id + } + setFormatPromptValuesDialogProps(dialogProp) + setShowFormatPromptValuesDialog(true) + } + const onExpandDialogSave = (newValue, inputParamName) => { setShowExpandDialog(false) data.inputs[inputParamName] = newValue @@ -217,12 +239,33 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA /> )} {inputParam.type === 'json' && ( - (data.inputs[inputParam.name] = newValue)} - value={data.inputs[inputParam.name] ?? inputParam.default ?? ''} - isDarkMode={customization.isDarkMode} - /> + <> + {!inputParam?.acceptVariable && ( + (data.inputs[inputParam.name] = newValue)} + value={data.inputs[inputParam.name] ?? inputParam.default ?? ''} + isDarkMode={customization.isDarkMode} + /> + )} + {inputParam?.acceptVariable && ( + <> + + setShowFormatPromptValuesDialog(false)} + onChange={(newValue) => (data.inputs[inputParam.name] = newValue)} + > + + )} + )} {inputParam.type === 'options' && ( Date: Thu, 29 Jun 2023 14:21:44 +0800 Subject: [PATCH 23/44] fix: temperature should convert to float --- packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts index 26f54db8..955563ff 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts @@ -132,7 +132,7 @@ class ChatOpenAI_ChatModels implements INode { const basePath = nodeData.inputs?.basepath as string const obj: Partial & { openAIApiKey?: string } = { - temperature: parseInt(temperature, 10), + temperature: parseFloat(temperature), modelName, openAIApiKey, streaming: streaming ?? true From 184a783847c0b7388c48f06c863a94ccca8a37e5 Mon Sep 17 00:00:00 2001 From: Jeffrey-Wang Date: Thu, 29 Jun 2023 21:23:21 +0800 Subject: [PATCH 24/44] fix: temperature convert to float --- .../nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts | 2 +- .../components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts | 2 +- .../nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts | 2 +- packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts | 2 +- packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts | 2 +- packages/components/nodes/llms/Cohere/Cohere.ts | 2 +- .../nodes/llms/HuggingFaceInference/HuggingFaceInference.ts | 2 +- packages/components/nodes/llms/OpenAI/OpenAI.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts index 7857bfdf..60295890 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts @@ -124,7 +124,7 @@ class AzureChatOpenAI_ChatModels implements INode { const streaming = nodeData.inputs?.streaming as boolean const obj: Partial & Partial = { - temperature: parseInt(temperature, 10), + temperature: parseFloat(temperature), modelName, azureOpenAIApiKey, azureOpenAIApiInstanceName, diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts index 708849e5..3d861d24 100644 --- a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts +++ b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts @@ -120,7 +120,7 @@ class ChatAnthropic_ChatModels implements INode { const streaming = nodeData.inputs?.streaming as boolean const obj: Partial & { anthropicApiKey?: string } = { - temperature: parseInt(temperature, 10), + temperature: parseFloat(temperature), modelName, anthropicApiKey, streaming: streaming ?? true diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts index 3252a61a..1dae41e4 100644 --- a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts @@ -89,7 +89,7 @@ class ChatHuggingFace_ChatModels implements INode { apiKey } - if (temperature) obj.temperature = parseInt(temperature, 10) + if (temperature) obj.temperature = parseFloat(temperature) if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) if (topP) obj.topP = parseInt(topP, 10) if (hfTopK) obj.topK = parseInt(hfTopK, 10) diff --git a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts index bd25a9fa..c5860b24 100644 --- a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts +++ b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts @@ -74,7 +74,7 @@ class ChatLocalAI_ChatModels implements INode { const basePath = nodeData.inputs?.basePath as string const obj: Partial & { openAIApiKey?: string } = { - temperature: parseInt(temperature, 10), + temperature: parseFloat(temperature), modelName, openAIApiKey: 'sk-' } diff --git a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts index c19aa83a..f81c9349 100644 --- a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts +++ b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts @@ -179,7 +179,7 @@ class AzureOpenAI_LLMs implements INode { const streaming = nodeData.inputs?.streaming as boolean const obj: Partial & Partial = { - temperature: parseInt(temperature, 10), + temperature: parseFloat(temperature), modelName, azureOpenAIApiKey, azureOpenAIApiInstanceName, diff --git a/packages/components/nodes/llms/Cohere/Cohere.ts b/packages/components/nodes/llms/Cohere/Cohere.ts index a7e9c696..75151571 100644 --- a/packages/components/nodes/llms/Cohere/Cohere.ts +++ b/packages/components/nodes/llms/Cohere/Cohere.ts @@ -87,7 +87,7 @@ class Cohere_LLMs implements INode { if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) if (modelName) obj.model = modelName - if (temperature) obj.temperature = parseInt(temperature, 10) + if (temperature) obj.temperature = parseFloat(temperature) const model = new Cohere(obj) return model diff --git a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts index 88a7db07..291f67c9 100644 --- a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts +++ b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts @@ -89,7 +89,7 @@ class HuggingFaceInference_LLMs implements INode { apiKey } - if (temperature) obj.temperature = parseInt(temperature, 10) + if (temperature) obj.temperature = parseFloat(temperature) if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) if (topP) obj.topP = parseInt(topP, 10) if (hfTopK) obj.topK = parseInt(hfTopK, 10) diff --git a/packages/components/nodes/llms/OpenAI/OpenAI.ts b/packages/components/nodes/llms/OpenAI/OpenAI.ts index fb7e5b6b..b0af867d 100644 --- a/packages/components/nodes/llms/OpenAI/OpenAI.ts +++ b/packages/components/nodes/llms/OpenAI/OpenAI.ts @@ -132,7 +132,7 @@ class OpenAI_LLMs implements INode { const basePath = nodeData.inputs?.basepath as string const obj: Partial & { openAIApiKey?: string } = { - temperature: parseInt(temperature, 10), + temperature: parseFloat(temperature), modelName, openAIApiKey, streaming: streaming ?? true From 7141401e265a7dfa8261970c430470683697eeeb Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 29 Jun 2023 23:47:20 +0100 Subject: [PATCH 25/44] fix bug where share chatflow is not able to open on any other browser --- packages/server/.env.example | 1 + packages/server/src/Interface.ts | 10 +- packages/server/src/entity/ChatFlow.ts | 8 +- packages/server/src/entity/ChatMessage.ts | 2 +- packages/server/src/entity/Tool.ts | 4 +- packages/server/src/index.ts | 12 +- packages/server/src/utils/index.ts | 2 +- packages/ui/src/api/chatflows.js | 3 + packages/ui/src/views/canvas/CanvasHeader.js | 8 +- packages/ui/src/views/canvas/index.js | 2 +- packages/ui/src/views/chatbot/index.js | 122 ++++++++++++------ .../ui/src/views/chatflows/APICodeDialog.js | 11 +- .../ui/src/views/chatflows/ShareChatbot.js | 71 ++++++++-- 13 files changed, 183 insertions(+), 73 deletions(-) diff --git a/packages/server/.env.example b/packages/server/.env.example index 3d524e5c..80fbc3be 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -3,4 +3,5 @@ PORT=3000 # FLOWISE_PASSWORD=1234 # DEBUG=true # DATABASE_PATH=/your_database_path/.flowise +# APIKEY_PATH=/your_api_key_path/.flowise # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 9473638f..9c47405c 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -9,10 +9,10 @@ export interface IChatFlow { id: string name: string flowData: string - apikeyid: string - deployed: boolean + isPublic: boolean updatedDate: Date createdDate: Date + apikeyid?: string chatbotConfig?: string } @@ -22,7 +22,7 @@ export interface IChatMessage { content: string chatflowid: string createdDate: Date - sourceDocuments: string + sourceDocuments?: string } export interface ITool { @@ -30,8 +30,8 @@ export interface ITool { name: string description: string color: string - schema: string - func: string + schema?: string + func?: string updatedDate: Date createdDate: Date } diff --git a/packages/server/src/entity/ChatFlow.ts b/packages/server/src/entity/ChatFlow.ts index 910272ad..400e0517 100644 --- a/packages/server/src/entity/ChatFlow.ts +++ b/packages/server/src/entity/ChatFlow.ts @@ -13,11 +13,11 @@ export class ChatFlow implements IChatFlow { @Column() flowData: string - @Column({ nullable: true }) - apikeyid: string - @Column() - deployed: boolean + isPublic: boolean + + @Column({ nullable: true }) + apikeyid?: string @Column({ nullable: true }) chatbotConfig?: string diff --git a/packages/server/src/entity/ChatMessage.ts b/packages/server/src/entity/ChatMessage.ts index 236dc5f9..3e4e41d2 100644 --- a/packages/server/src/entity/ChatMessage.ts +++ b/packages/server/src/entity/ChatMessage.ts @@ -18,7 +18,7 @@ export class ChatMessage implements IChatMessage { content: string @Column({ nullable: true }) - sourceDocuments: string + sourceDocuments?: string @CreateDateColumn() createdDate: Date diff --git a/packages/server/src/entity/Tool.ts b/packages/server/src/entity/Tool.ts index d547374c..307e8d23 100644 --- a/packages/server/src/entity/Tool.ts +++ b/packages/server/src/entity/Tool.ts @@ -17,10 +17,10 @@ export class Tool implements ITool { color: string @Column({ nullable: true }) - schema: string + schema?: string @Column({ nullable: true }) - func: string + func?: string @CreateDateColumn() createdDate: Date diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 65bfef23..e13934b2 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -92,7 +92,7 @@ export class App { const basicAuthMiddleware = basicAuth({ users: { [username]: password } }) - const whitelistURLs = ['/api/v1/prediction/', '/api/v1/node-icon/', '/api/v1/chatflows-streaming'] + const whitelistURLs = ['/api/v1/public-chatflows', '/api/v1/prediction/', '/api/v1/node-icon/', '/api/v1/chatflows-streaming'] this.app.use((req, res, next) => { if (req.url.includes('/api/v1/')) { whitelistURLs.some((url) => req.url.includes(url)) ? next() : basicAuthMiddleware(req, res, next) @@ -186,6 +186,16 @@ export class App { return res.status(404).send(`Chatflow ${req.params.id} not found`) }) + // Get specific chatflow via id (PUBLIC endpoint, used when sharing chatbot link) + this.app.get('/api/v1/public-chatflows/:id', async (req: Request, res: Response) => { + const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ + id: req.params.id + }) + if (chatflow && chatflow.isPublic) return res.json(chatflow) + else if (chatflow && !chatflow.isPublic) return res.status(401).send(`Unauthorized`) + return res.status(404).send(`Chatflow ${req.params.id} not found`) + }) + // Save chatflow this.app.post('/api/v1/chatflows', async (req: Request, res: Response) => { const body = req.body diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index e3005c7b..3601c77d 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -463,7 +463,7 @@ export const isSameOverrideConfig = ( * @returns {string} */ export const getAPIKeyPath = (): string => { - return path.join(__dirname, '..', '..', 'api.json') + return process.env.APIKEY_PATH ? path.join(process.env.APIKEY_PATH, 'api.json') : path.join(__dirname, '..', '..', 'api.json') } /** diff --git a/packages/ui/src/api/chatflows.js b/packages/ui/src/api/chatflows.js index 1cd1ebb0..8810b5a5 100644 --- a/packages/ui/src/api/chatflows.js +++ b/packages/ui/src/api/chatflows.js @@ -4,6 +4,8 @@ const getAllChatflows = () => client.get('/chatflows') const getSpecificChatflow = (id) => client.get(`/chatflows/${id}`) +const getSpecificChatflowFromPublicEndpoint = (id) => client.get(`/public-chatflows/${id}`) + const createNewChatflow = (body) => client.post(`/chatflows`, body) const updateChatflow = (id, body) => client.put(`/chatflows/${id}`, body) @@ -15,6 +17,7 @@ const getIsChatflowStreaming = (id) => client.get(`/chatflows-streaming/${id}`) export default { getAllChatflows, getSpecificChatflow, + getSpecificChatflowFromPublicEndpoint, createNewChatflow, updateChatflow, deleteChatflow, diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index 521aa9d3..1c1e5212 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types' import { useNavigate } from 'react-router-dom' -import { useSelector } from 'react-redux' +import { useSelector, useDispatch } from 'react-redux' import { useEffect, useRef, useState } from 'react' // material-ui @@ -24,11 +24,13 @@ import useApi from 'hooks/useApi' // utils import { generateExportFlowData } from 'utils/genericHelper' import { uiBaseURL } from 'store/constant' +import { SET_CHATFLOW } from 'store/actions' // ==============================|| CANVAS HEADER ||============================== // const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFlow }) => { const theme = useTheme() + const dispatch = useDispatch() const navigate = useNavigate() const flowNameRef = useRef() const settingsRef = useRef() @@ -107,8 +109,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl title: 'Embed in website or use as API', chatflowid: chatflow.id, chatflowApiKeyId: chatflow.apikeyid, - isFormDataRequired, - chatbotConfig: chatflow.chatbotConfig + isFormDataRequired }) setAPIDialogOpen(true) } @@ -126,6 +127,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl useEffect(() => { if (updateChatflowApi.data) { setFlowName(updateChatflowApi.data.name) + dispatch({ type: SET_CHATFLOW, chatflow: updateChatflowApi.data }) } setEditingFlowName(false) diff --git a/packages/ui/src/views/canvas/index.js b/packages/ui/src/views/canvas/index.js index 2d71f03a..03098963 100644 --- a/packages/ui/src/views/canvas/index.js +++ b/packages/ui/src/views/canvas/index.js @@ -201,7 +201,7 @@ const Canvas = () => { if (!chatflow.id) { const newChatflowBody = { name: chatflowName, - deployed: false, + isPublic: false, flowData } createNewChatflowApi.request(newChatflowBody) diff --git a/packages/ui/src/views/chatbot/index.js b/packages/ui/src/views/chatbot/index.js index b33bec2c..f29c35ee 100644 --- a/packages/ui/src/views/chatbot/index.js +++ b/packages/ui/src/views/chatbot/index.js @@ -1,57 +1,107 @@ import { useEffect, useState } from 'react' -import { baseURL } from 'store/constant' -import axios from 'axios' import { FullPageChat } from 'flowise-embed-react' +import { useNavigate } from 'react-router-dom' + +// Project import +import LoginDialog from 'ui-component/dialog/LoginDialog' + +// API +import chatflowsApi from 'api/chatflows' + +// Hooks +import useApi from 'hooks/useApi' + +//Const +import { baseURL } from 'store/constant' // ==============================|| Chatbot ||============================== // -const fetchChatflow = async ({ chatflowId }) => { - const username = localStorage.getItem('username') - const password = localStorage.getItem('password') - - let chatflow = await axios - .get(`${baseURL}/api/v1/chatflows/${chatflowId}`, { auth: username && password ? { username, password } : undefined }) - .then(async function (response) { - return response.data - }) - .catch(function (error) { - console.error(error) - }) - return chatflow -} - const ChatbotFull = () => { const URLpath = document.location.pathname.toString().split('/') const chatflowId = URLpath[URLpath.length - 1] === 'chatbot' ? '' : URLpath[URLpath.length - 1] + const navigate = useNavigate() const [chatflow, setChatflow] = useState(null) const [chatbotTheme, setChatbotTheme] = useState({}) + const [loginDialogOpen, setLoginDialogOpen] = useState(false) + const [loginDialogProps, setLoginDialogProps] = useState({}) + const [isLoading, setLoading] = useState(true) + + const getSpecificChatflowFromPublicApi = useApi(chatflowsApi.getSpecificChatflowFromPublicEndpoint) + const getSpecificChatflowApi = useApi(chatflowsApi.getSpecificChatflow) + + const onLoginClick = (username, password) => { + localStorage.setItem('username', username) + localStorage.setItem('password', password) + navigate(0) + } useEffect(() => { - ;(async () => { - const fetchData = async () => { - let response = await fetchChatflow({ chatflowId }) - setChatflow(response) - if (response.chatbotConfig) { - try { - setChatbotTheme(JSON.parse(response.chatbotConfig)) - } catch (e) { - console.error(e) - setChatbotTheme({}) - } + getSpecificChatflowFromPublicApi.request(chatflowId) + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (getSpecificChatflowFromPublicApi.error) { + if (getSpecificChatflowFromPublicApi.error?.response?.status === 401) { + if (localStorage.getItem('username') && localStorage.getItem('password')) { + getSpecificChatflowApi.request(chatflowId) + } else { + setLoginDialogProps({ + title: 'Login', + confirmButtonName: 'Login' + }) + setLoginDialogOpen(true) } } - fetchData() - })() - }, [chatflowId]) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getSpecificChatflowFromPublicApi.error]) + + useEffect(() => { + if (getSpecificChatflowApi.error) { + if (getSpecificChatflowApi.error?.response?.status === 401) { + setLoginDialogProps({ + title: 'Login', + confirmButtonName: 'Login' + }) + setLoginDialogOpen(true) + } + } + }, [getSpecificChatflowApi.error]) + + useEffect(() => { + if (getSpecificChatflowFromPublicApi.data || getSpecificChatflowApi.data) { + const chatflowData = getSpecificChatflowFromPublicApi.data || getSpecificChatflowApi.data + setChatflow(chatflowData) + if (chatflowData.chatbotConfig) { + try { + setChatbotTheme(JSON.parse(chatflowData.chatbotConfig)) + } catch (e) { + console.error(e) + setChatbotTheme({}) + } + } + } + }, [getSpecificChatflowFromPublicApi.data, getSpecificChatflowApi.data]) + + useEffect(() => { + setLoading(getSpecificChatflowFromPublicApi.loading || getSpecificChatflowApi.loading) + }, [getSpecificChatflowFromPublicApi.loading, getSpecificChatflowApi.loading]) return ( <> - {!chatflow || chatflow.apikeyid ? ( -

Invalid Chatbot

- ) : ( - - )} + {!isLoading ? ( + <> + {!chatflow || chatflow.apikeyid ? ( +

Invalid Chatbot

+ ) : ( + + )} + + + ) : null} ) } diff --git a/packages/ui/src/views/chatflows/APICodeDialog.js b/packages/ui/src/views/chatflows/APICodeDialog.js index fea49909..5e32c1d4 100644 --- a/packages/ui/src/views/chatflows/APICodeDialog.js +++ b/packages/ui/src/views/chatflows/APICodeDialog.js @@ -134,7 +134,6 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { const [chatflowApiKeyId, setChatflowApiKeyId] = useState('') const [selectedApiKey, setSelectedApiKey] = useState({}) const [checkboxVal, setCheckbox] = useState(false) - const [chatbotConfig, setChatbotConfig] = useState(null) const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys) const updateChatflowApi = useApi(chatflowsApi.updateChatflow) @@ -491,12 +490,6 @@ query({ setChatflowApiKeyId(dialogProps.chatflowApiKeyId) setSelectedApiKey(getAllAPIKeysApi.data.find((key) => key.id === dialogProps.chatflowApiKeyId)) } - - if (dialogProps.chatbotConfig) { - setChatbotConfig(JSON.parse(dialogProps.chatbotConfig)) - } else { - setChatbotConfig(null) - } } }, [dialogProps, getAllAPIKeysApi.data]) @@ -601,9 +594,7 @@ query({ )} )} - {codeLang === 'Share Chatbot' && !chatflowApiKeyId && ( - - )} + {codeLang === 'Share Chatbot' && !chatflowApiKeyId && } ))} diff --git a/packages/ui/src/views/chatflows/ShareChatbot.js b/packages/ui/src/views/chatflows/ShareChatbot.js index dffecf5b..51e12e54 100644 --- a/packages/ui/src/views/chatflows/ShareChatbot.js +++ b/packages/ui/src/views/chatflows/ShareChatbot.js @@ -1,7 +1,6 @@ -import PropTypes from 'prop-types' import { useState } from 'react' -import { useDispatch } from 'react-redux' -import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import { useDispatch, useSelector } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions' import { SketchPicker } from 'react-color' import { Box, Typography, Button, Switch, OutlinedInput, Popover, Stack, IconButton } from '@mui/material' @@ -9,6 +8,7 @@ import { useTheme } from '@mui/material/styles' // Project import import { StyledButton } from 'ui-component/button/StyledButton' +import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' // Icons import { IconX, IconCopy, IconArrowUpRightCircle } from '@tabler/icons' @@ -41,15 +41,20 @@ const defaultConfig = { } } -const ShareChatbot = ({ chatflowid, chatbotConfig }) => { +const ShareChatbot = () => { const dispatch = useDispatch() const theme = useTheme() + const chatflow = useSelector((state) => state.canvas.chatflow) + const chatflowid = chatflow.id + const chatbotConfig = chatflow.chatbotConfig ? JSON.parse(chatflow.chatbotConfig) : {} useNotifier() const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + const [isPublicChatflow, setChatflowIsPublic] = useState(chatflow.isPublic ?? false) + const [welcomeMessage, setWelcomeMessage] = useState(chatbotConfig?.welcomeMessage ?? '') const [backgroundColor, setBackgroundColor] = useState(chatbotConfig?.backgroundColor ?? defaultConfig.backgroundColor) const [fontSize, setFontSize] = useState(chatbotConfig?.fontSize ?? defaultConfig.fontSize) @@ -141,6 +146,44 @@ const ShareChatbot = ({ chatflowid, chatbotConfig }) => { ) } }) + dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) + } + } catch (error) { + console.error(error) + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to save Chatbot Configuration: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + } + } + + const onSwitchChange = async (checked) => { + try { + const saveResp = await chatflowsApi.updateChatflow(chatflowid, { isPublic: checked }) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Chatbot Configuration Saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) } } catch (error) { console.error(error) @@ -328,6 +371,21 @@ const ShareChatbot = ({ chatflowid, chatbotConfig }) => { window.open(`${baseURL}/chatbot/${chatflowid}`, '_blank')}> +
+
+ { + setChatflowIsPublic(event.target.checked) + onSwitchChange(event.target.checked) + }} + /> + Make Public + +
{textField(welcomeMessage, 'welcomeMessage', 'Welcome Message', 'string', 'Hello! This is custom welcome message')} {colorField(backgroundColor, 'backgroundColor', 'Background Color')} @@ -412,9 +470,4 @@ const ShareChatbot = ({ chatflowid, chatbotConfig }) => { ) } -ShareChatbot.propTypes = { - chatflowid: PropTypes.string, - chatbotConfig: PropTypes.object -} - export default ShareChatbot From 0729d0dea37114641c71ad554f28d2e81e85943d Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Fri, 30 Jun 2023 09:18:32 +0800 Subject: [PATCH 26/44] modify whitelistURLs --- packages/server/src/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 3e729464..0b39f73b 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -93,7 +93,13 @@ export class App { const basicAuthMiddleware = basicAuth({ users: { [username]: password } }) - const whitelistURLs = ['/api/v1/prediction/', '/api/v1/node-icon/', '/api/v1/chatflows-streaming'] + const whitelistURLs = [ + '/api/v1/verify/apikey/', + '/api/v1/chatflows/apikey/', + '/api/v1/prediction/', + '/api/v1/node-icon/', + '/api/v1/chatflows-streaming' + ] this.app.use((req, res, next) => { if (req.url.includes('/api/v1/')) { whitelistURLs.some((url) => req.url.includes(url)) ? next() : basicAuthMiddleware(req, res, next) @@ -492,7 +498,7 @@ export class App { }) // Verify api key - this.app.get('/api/v1/apikey/:apiKey', async (req: Request, res: Response) => { + this.app.get('/api/v1/verify/apikey/:apiKey', async (req: Request, res: Response) => { try { const apiKey = await getApiKey(req.params.apiKey) if (!apiKey) return res.status(401).send('Unauthorized') From 4796d84b50f1dfce423d6572cbfcffd4adeaf75e Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Fri, 30 Jun 2023 17:37:33 +0800 Subject: [PATCH 27/44] fix missing public-chatflows in whitelist --- packages/server/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index fba5be5e..3c97a2e7 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -96,6 +96,7 @@ export class App { const whitelistURLs = [ '/api/v1/verify/apikey/', '/api/v1/chatflows/apikey/', + '/api/v1/public-chatflows', '/api/v1/prediction/', '/api/v1/node-icon/', '/api/v1/chatflows-streaming' From 55489c1c774294f6f0fd23de487e5419d0a1fc94 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 30 Jun 2023 14:00:57 +0100 Subject: [PATCH 28/44] add blank space --- packages/server/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 3c97a2e7..15762a23 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -194,6 +194,7 @@ export class App { .createQueryBuilder('cf') .where('cf.apikeyid = :apikeyid', { apikeyid: apiKey.id }) .orWhere('cf.apikeyid IS NULL') + .orWhere('cf.apikeyid = ""') .orderBy('cf.name', 'ASC') .getMany() if (chatflows.length >= 1) return res.status(200).send(chatflows) From bc71e57834c2419e4e96f3017dbb5911bea15e10 Mon Sep 17 00:00:00 2001 From: Govind Kumar Date: Fri, 30 Jun 2023 18:58:15 +0530 Subject: [PATCH 29/44] added initial code --- .../nodes/memory/DynamoDb/DynamoDb.ts | 90 +++++++++++++++++++ .../nodes/memory/DynamoDb/dynamodb.svg | 18 ++++ packages/components/package.json | 1 + 3 files changed, 109 insertions(+) create mode 100644 packages/components/nodes/memory/DynamoDb/DynamoDb.ts create mode 100644 packages/components/nodes/memory/DynamoDb/dynamodb.svg diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts new file mode 100644 index 00000000..d0845d96 --- /dev/null +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -0,0 +1,90 @@ +import { ICommonObject, INode, INodeData, INodeParams, getBaseClasses } from '../../../src' +import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' +import { BufferMemory } from 'langchain/memory' + +class DynamoDb_Memory implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'DynamoDB Memory' + this.name = 'DynamoDbMemory' + this.icon = 'dynamodb.svg' + this.category = 'Memory' + this.description = 'Stores the conversation in dynamo db table' + this.baseClasses = [this.type, ...getBaseClasses(DynamoDBChatMessageHistory)] + this.inputs = [ + { + label: 'Table Name', + name: 'tableName', + type: 'string' + }, + { + label: 'Partition Key', + name: 'partitionKey', + type: 'string' + }, + { + label: 'Session ID', + name: 'sessionId', + type: 'string', + description: 'if empty, chatId will be used automatically', + default: '', + additionalParams: true + }, + { + label: 'Region', + name: 'region', + type: 'string', + description: 'The aws region in which table is located', + placeholder: 'us-east-1' + }, + { + label: 'Access Key', + name: 'accessKey', + type: 'password' + }, + { + label: 'Secret Access Key', + name: 'secretAccessKey', + type: 'password' + } + ] + } + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const tableName = nodeData.inputs?.tableName as string + const partitionKey = nodeData.inputs?.partitionKey as string + const sessionId = nodeData.inputs?.sessionId as string + const region = nodeData.inputs?.region as string + const accessKey = nodeData.inputs?.accessKey as string + const secretAccessKey = nodeData.inputs?.secretAccessKey as string + + const chatId = options.chatId + + const dynamoDb = new DynamoDBChatMessageHistory({ + tableName, + partitionKey, + sessionId: sessionId ? sessionId : chatId, + config: { + region, + credentials: { + accessKeyId: accessKey, + secretAccessKey + } + } + }) + + const memory = new BufferMemory({ + chatHistory: dynamoDb + }) + return memory + } +} + +module.exports = { nodeClass: DynamoDb_Memory } diff --git a/packages/components/nodes/memory/DynamoDb/dynamodb.svg b/packages/components/nodes/memory/DynamoDb/dynamodb.svg new file mode 100644 index 00000000..f2798350 --- /dev/null +++ b/packages/components/nodes/memory/DynamoDb/dynamodb.svg @@ -0,0 +1,18 @@ + + + + Icon-Architecture/16/Arch_Amazon-DynamoDB_16 + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index f5b67cc7..5ecd12e4 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -16,6 +16,7 @@ }, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { + "@aws-sdk/client-dynamodb": "^3.360.0", "@dqbd/tiktoken": "^1.0.7", "@getzep/zep-js": "^0.3.1", "@huggingface/inference": "1", From 894902ea831d1e2b3313af9c245b6f3fec496602 Mon Sep 17 00:00:00 2001 From: Govind Kumar Date: Fri, 30 Jun 2023 22:33:11 +0530 Subject: [PATCH 30/44] added gitBook integration --- .../nodes/documentloaders/Gitbook/Gitbook.ts | 82 +++++++++++++++++++ .../documentloaders/Gitbook/gitbook_logo.svg | 11 +++ 2 files changed, 93 insertions(+) create mode 100644 packages/components/nodes/documentloaders/Gitbook/Gitbook.ts create mode 100644 packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg diff --git a/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts b/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts new file mode 100644 index 00000000..836cd2cb --- /dev/null +++ b/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts @@ -0,0 +1,82 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { TextSplitter } from 'langchain/text_splitter' +import { GitbookLoader } from 'langchain/document_loaders/web/gitbook' + +class Gitbook_DocumentLoaders implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs?: INodeParams[] + + constructor() { + this.label = 'GitBook' + this.name = 'gitbook' + this.type = 'Document' + this.icon = 'gitbook_logo.svg' + this.category = 'Document Loaders' + this.description = `Load data from GitBook` + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Web Path', + name: 'webPath', + type: 'string', + placeholder: 'https://docs.gitbook.com/product-tour/navigation', + description: 'If want to load all paths from the GitBook provide only root path e.g.https://docs.gitbook.com/ ' + }, + { + label: 'Should Load All Paths', + name: 'shouldLoadAllPaths', + type: 'boolean', + description: 'Load from all paths in a given GitBook', + optional: true + }, + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + async init(nodeData: INodeData): Promise { + const webPath = nodeData.inputs?.webPath as string + const shouldLoadAllPaths = nodeData.inputs?.shouldLoadAllPaths as boolean + const textSplitter = nodeData.inputs?.textSplitter as TextSplitter + const metadata = nodeData.inputs?.metadata + + const loader = shouldLoadAllPaths ? new GitbookLoader(webPath, { shouldLoadAllPaths }) : new GitbookLoader(webPath) + + const docs = textSplitter ? await loader.loadAndSplit() : await loader.load() + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + return docs.map((doc) => { + return { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + }) + } + + return docs + } +} + +module.exports = { + nodeClass: Gitbook_DocumentLoaders +} diff --git a/packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg b/packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg new file mode 100644 index 00000000..9839f9bf --- /dev/null +++ b/packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + From c66c7eadc7e660049bd57f61bbbfe9f3b9a3a872 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 30 Jun 2023 18:31:41 +0100 Subject: [PATCH 31/44] add APIKEY_PATH --- docker/.env.example | 1 + docker/docker-compose.yml | 1 + packages/server/src/commands/start.ts | 2 ++ 3 files changed, 4 insertions(+) diff --git a/docker/.env.example b/docker/.env.example index 3d524e5c..80fbc3be 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -3,4 +3,5 @@ PORT=3000 # FLOWISE_PASSWORD=1234 # DEBUG=true # DATABASE_PATH=/your_database_path/.flowise +# APIKEY_PATH=/your_api_key_path/.flowise # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 7ab43142..97aea017 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -9,6 +9,7 @@ services: - FLOWISE_USERNAME=${FLOWISE_USERNAME} - FLOWISE_PASSWORD=${FLOWISE_PASSWORD} - DATABASE_PATH=${DATABASE_PATH} + - APIKEY_PATH=${APIKEY_PATH} - EXECUTION_MODE=${EXECUTION_MODE} - DEBUG=${DEBUG} ports: diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index 0f64322b..d3efc1eb 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -20,6 +20,7 @@ export default class Start extends Command { PORT: Flags.string(), DEBUG: Flags.string(), DATABASE_PATH: Flags.string(), + APIKEY_PATH: Flags.string(), EXECUTION_MODE: Flags.string() } @@ -56,6 +57,7 @@ export default class Start extends Command { if (flags.FLOWISE_PASSWORD) process.env.FLOWISE_PASSWORD = flags.FLOWISE_PASSWORD if (flags.PORT) process.env.PORT = flags.PORT if (flags.DATABASE_PATH) process.env.DATABASE_PATH = flags.DATABASE_PATH + if (flags.APIKEY_PATH) process.env.APIKEY_PATH = flags.APIKEY_PATH if (flags.EXECUTION_MODE) process.env.EXECUTION_MODE = flags.EXECUTION_MODE if (flags.DEBUG) process.env.DEBUG = flags.DEBUG From 40075b12e732405a844f9c982b5591cd99de132b Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sat, 1 Jul 2023 02:29:11 +0100 Subject: [PATCH 32/44] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 90f2711f..4613e19f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Flowise - Low-Code LLM apps builder +# Flowise From 7dda0d19c01f37f6b1c6db768092a667aa73e46e Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 1 Jul 2023 13:21:40 +0100 Subject: [PATCH 33/44] update Gitbook icon --- .../nodes/documentloaders/Gitbook/Gitbook.ts | 2 +- .../nodes/documentloaders/Gitbook/gitbook.svg | 1 + .../nodes/documentloaders/Gitbook/gitbook_logo.svg | 11 ----------- 3 files changed, 2 insertions(+), 12 deletions(-) create mode 100644 packages/components/nodes/documentloaders/Gitbook/gitbook.svg delete mode 100644 packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg diff --git a/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts b/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts index 836cd2cb..933fa9d4 100644 --- a/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts +++ b/packages/components/nodes/documentloaders/Gitbook/Gitbook.ts @@ -16,7 +16,7 @@ class Gitbook_DocumentLoaders implements INode { this.label = 'GitBook' this.name = 'gitbook' this.type = 'Document' - this.icon = 'gitbook_logo.svg' + this.icon = 'gitbook.svg' this.category = 'Document Loaders' this.description = `Load data from GitBook` this.baseClasses = [this.type] diff --git a/packages/components/nodes/documentloaders/Gitbook/gitbook.svg b/packages/components/nodes/documentloaders/Gitbook/gitbook.svg new file mode 100644 index 00000000..df16237a --- /dev/null +++ b/packages/components/nodes/documentloaders/Gitbook/gitbook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg b/packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg deleted file mode 100644 index 9839f9bf..00000000 --- a/packages/components/nodes/documentloaders/Gitbook/gitbook_logo.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - From c8ba8e2aee4328da69795cc6a0e18c038cdb344a Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 1 Jul 2023 23:59:51 +0100 Subject: [PATCH 34/44] add tools marketplace --- .../{ => chatflows}/API Agent.json | 0 .../marketplaces/{ => chatflows}/Antonym.json | 0 .../marketplaces/{ => chatflows}/AutoGPT.json | 0 .../marketplaces/{ => chatflows}/BabyAGI.json | 0 .../{ => chatflows}/ChatGPTPlugin.json | 0 .../{ => chatflows}/Conversational Agent.json | 0 .../Conversational Retrieval QA Chain.json | 0 .../{ => chatflows}/Github Repo QnA.json | 0 .../HuggingFace LLM Chain.json | 0 .../{ => chatflows}/Local QnA.json | 0 .../{ => chatflows}/MRKLAgent.json | 0 .../{ => chatflows}/Metadata Filter Load.json | 0 .../Metadata Filter Upsert.json | 0 .../{ => chatflows}/Multi Prompt Chain.json | 0 .../Multi Retrieval QA Chain.json | 0 .../{ => chatflows}/Multiple VectorDB.json | 0 .../{ => chatflows}/OpenAI Agent.json | 0 .../{ => chatflows}/Prompt Chaining.json | 0 .../{ => chatflows}/SQL DB Chain.json | 0 .../Simple Conversation Chain.json | 0 .../{ => chatflows}/Simple LLM Chain.json | 0 .../{ => chatflows}/Translator.json | 0 .../{ => chatflows}/WebBrowser.json | 0 .../{ => chatflows}/Zapier NLA.json | 0 .../tools/Add Hubspot Contact.json | 8 + .../tools/Create Airtable Record.json | 8 + .../marketplaces/tools/Get Stock Mover.json | 8 + .../tools/Send Discord Message.json | 8 + .../tools/Send Slack Message.json | 8 + .../tools/Send Teams Message.json | 8 + .../marketplaces/tools/SendGrid Email.json | 8 + packages/server/src/Interface.ts | 1 + packages/server/src/entity/Tool.ts | 3 + packages/server/src/index.ts | 25 ++- packages/ui/public/index.html | 8 +- packages/ui/src/api/marketplaces.js | 6 +- packages/ui/src/themes/compStyleOverride.js | 3 + .../ui/src/ui-component/cards/ItemCard.js | 23 ++- packages/ui/src/ui-component/grid/Grid.js | 14 +- packages/ui/src/views/marketplaces/index.js | 173 +++++++++++++++--- packages/ui/src/views/tools/ToolDialog.js | 148 +++++++++++++-- packages/ui/src/views/tools/index.js | 53 +++++- 42 files changed, 444 insertions(+), 69 deletions(-) rename packages/server/marketplaces/{ => chatflows}/API Agent.json (100%) rename packages/server/marketplaces/{ => chatflows}/Antonym.json (100%) rename packages/server/marketplaces/{ => chatflows}/AutoGPT.json (100%) rename packages/server/marketplaces/{ => chatflows}/BabyAGI.json (100%) rename packages/server/marketplaces/{ => chatflows}/ChatGPTPlugin.json (100%) rename packages/server/marketplaces/{ => chatflows}/Conversational Agent.json (100%) rename packages/server/marketplaces/{ => chatflows}/Conversational Retrieval QA Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Github Repo QnA.json (100%) rename packages/server/marketplaces/{ => chatflows}/HuggingFace LLM Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Local QnA.json (100%) rename packages/server/marketplaces/{ => chatflows}/MRKLAgent.json (100%) rename packages/server/marketplaces/{ => chatflows}/Metadata Filter Load.json (100%) rename packages/server/marketplaces/{ => chatflows}/Metadata Filter Upsert.json (100%) rename packages/server/marketplaces/{ => chatflows}/Multi Prompt Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Multi Retrieval QA Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Multiple VectorDB.json (100%) rename packages/server/marketplaces/{ => chatflows}/OpenAI Agent.json (100%) rename packages/server/marketplaces/{ => chatflows}/Prompt Chaining.json (100%) rename packages/server/marketplaces/{ => chatflows}/SQL DB Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Simple Conversation Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Simple LLM Chain.json (100%) rename packages/server/marketplaces/{ => chatflows}/Translator.json (100%) rename packages/server/marketplaces/{ => chatflows}/WebBrowser.json (100%) rename packages/server/marketplaces/{ => chatflows}/Zapier NLA.json (100%) create mode 100644 packages/server/marketplaces/tools/Add Hubspot Contact.json create mode 100644 packages/server/marketplaces/tools/Create Airtable Record.json create mode 100644 packages/server/marketplaces/tools/Get Stock Mover.json create mode 100644 packages/server/marketplaces/tools/Send Discord Message.json create mode 100644 packages/server/marketplaces/tools/Send Slack Message.json create mode 100644 packages/server/marketplaces/tools/Send Teams Message.json create mode 100644 packages/server/marketplaces/tools/SendGrid Email.json diff --git a/packages/server/marketplaces/API Agent.json b/packages/server/marketplaces/chatflows/API Agent.json similarity index 100% rename from packages/server/marketplaces/API Agent.json rename to packages/server/marketplaces/chatflows/API Agent.json diff --git a/packages/server/marketplaces/Antonym.json b/packages/server/marketplaces/chatflows/Antonym.json similarity index 100% rename from packages/server/marketplaces/Antonym.json rename to packages/server/marketplaces/chatflows/Antonym.json diff --git a/packages/server/marketplaces/AutoGPT.json b/packages/server/marketplaces/chatflows/AutoGPT.json similarity index 100% rename from packages/server/marketplaces/AutoGPT.json rename to packages/server/marketplaces/chatflows/AutoGPT.json diff --git a/packages/server/marketplaces/BabyAGI.json b/packages/server/marketplaces/chatflows/BabyAGI.json similarity index 100% rename from packages/server/marketplaces/BabyAGI.json rename to packages/server/marketplaces/chatflows/BabyAGI.json diff --git a/packages/server/marketplaces/ChatGPTPlugin.json b/packages/server/marketplaces/chatflows/ChatGPTPlugin.json similarity index 100% rename from packages/server/marketplaces/ChatGPTPlugin.json rename to packages/server/marketplaces/chatflows/ChatGPTPlugin.json diff --git a/packages/server/marketplaces/Conversational Agent.json b/packages/server/marketplaces/chatflows/Conversational Agent.json similarity index 100% rename from packages/server/marketplaces/Conversational Agent.json rename to packages/server/marketplaces/chatflows/Conversational Agent.json diff --git a/packages/server/marketplaces/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json similarity index 100% rename from packages/server/marketplaces/Conversational Retrieval QA Chain.json rename to packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json diff --git a/packages/server/marketplaces/Github Repo QnA.json b/packages/server/marketplaces/chatflows/Github Repo QnA.json similarity index 100% rename from packages/server/marketplaces/Github Repo QnA.json rename to packages/server/marketplaces/chatflows/Github Repo QnA.json diff --git a/packages/server/marketplaces/HuggingFace LLM Chain.json b/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json similarity index 100% rename from packages/server/marketplaces/HuggingFace LLM Chain.json rename to packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json diff --git a/packages/server/marketplaces/Local QnA.json b/packages/server/marketplaces/chatflows/Local QnA.json similarity index 100% rename from packages/server/marketplaces/Local QnA.json rename to packages/server/marketplaces/chatflows/Local QnA.json diff --git a/packages/server/marketplaces/MRKLAgent.json b/packages/server/marketplaces/chatflows/MRKLAgent.json similarity index 100% rename from packages/server/marketplaces/MRKLAgent.json rename to packages/server/marketplaces/chatflows/MRKLAgent.json diff --git a/packages/server/marketplaces/Metadata Filter Load.json b/packages/server/marketplaces/chatflows/Metadata Filter Load.json similarity index 100% rename from packages/server/marketplaces/Metadata Filter Load.json rename to packages/server/marketplaces/chatflows/Metadata Filter Load.json diff --git a/packages/server/marketplaces/Metadata Filter Upsert.json b/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json similarity index 100% rename from packages/server/marketplaces/Metadata Filter Upsert.json rename to packages/server/marketplaces/chatflows/Metadata Filter Upsert.json diff --git a/packages/server/marketplaces/Multi Prompt Chain.json b/packages/server/marketplaces/chatflows/Multi Prompt Chain.json similarity index 100% rename from packages/server/marketplaces/Multi Prompt Chain.json rename to packages/server/marketplaces/chatflows/Multi Prompt Chain.json diff --git a/packages/server/marketplaces/Multi Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json similarity index 100% rename from packages/server/marketplaces/Multi Retrieval QA Chain.json rename to packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json diff --git a/packages/server/marketplaces/Multiple VectorDB.json b/packages/server/marketplaces/chatflows/Multiple VectorDB.json similarity index 100% rename from packages/server/marketplaces/Multiple VectorDB.json rename to packages/server/marketplaces/chatflows/Multiple VectorDB.json diff --git a/packages/server/marketplaces/OpenAI Agent.json b/packages/server/marketplaces/chatflows/OpenAI Agent.json similarity index 100% rename from packages/server/marketplaces/OpenAI Agent.json rename to packages/server/marketplaces/chatflows/OpenAI Agent.json diff --git a/packages/server/marketplaces/Prompt Chaining.json b/packages/server/marketplaces/chatflows/Prompt Chaining.json similarity index 100% rename from packages/server/marketplaces/Prompt Chaining.json rename to packages/server/marketplaces/chatflows/Prompt Chaining.json diff --git a/packages/server/marketplaces/SQL DB Chain.json b/packages/server/marketplaces/chatflows/SQL DB Chain.json similarity index 100% rename from packages/server/marketplaces/SQL DB Chain.json rename to packages/server/marketplaces/chatflows/SQL DB Chain.json diff --git a/packages/server/marketplaces/Simple Conversation Chain.json b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json similarity index 100% rename from packages/server/marketplaces/Simple Conversation Chain.json rename to packages/server/marketplaces/chatflows/Simple Conversation Chain.json diff --git a/packages/server/marketplaces/Simple LLM Chain.json b/packages/server/marketplaces/chatflows/Simple LLM Chain.json similarity index 100% rename from packages/server/marketplaces/Simple LLM Chain.json rename to packages/server/marketplaces/chatflows/Simple LLM Chain.json diff --git a/packages/server/marketplaces/Translator.json b/packages/server/marketplaces/chatflows/Translator.json similarity index 100% rename from packages/server/marketplaces/Translator.json rename to packages/server/marketplaces/chatflows/Translator.json diff --git a/packages/server/marketplaces/WebBrowser.json b/packages/server/marketplaces/chatflows/WebBrowser.json similarity index 100% rename from packages/server/marketplaces/WebBrowser.json rename to packages/server/marketplaces/chatflows/WebBrowser.json diff --git a/packages/server/marketplaces/Zapier NLA.json b/packages/server/marketplaces/chatflows/Zapier NLA.json similarity index 100% rename from packages/server/marketplaces/Zapier NLA.json rename to packages/server/marketplaces/chatflows/Zapier NLA.json diff --git a/packages/server/marketplaces/tools/Add Hubspot Contact.json b/packages/server/marketplaces/tools/Add Hubspot Contact.json new file mode 100644 index 00000000..584df4c3 --- /dev/null +++ b/packages/server/marketplaces/tools/Add Hubspot Contact.json @@ -0,0 +1,8 @@ +{ + "name": "add_contact_hubspot", + "description": "Add new contact to Hubspot", + "color": "linear-gradient(rgb(85,198,123), rgb(0,230,99))", + "iconSrc": "https://cdn.worldvectorlogo.com/logos/hubspot-1.svg", + "schema": "[{\"id\":1,\"property\":\"email\",\"description\":\"email address of contact\",\"type\":\"string\",\"required\":true},{\"id\":2,\"property\":\"firstname\",\"description\":\"first name of contact\",\"type\":\"string\",\"required\":false},{\"id\":3,\"property\":\"lastname\",\"description\":\"last name of contact\",\"type\":\"string\",\"required\":false}]", + "func": "const fetch = require('node-fetch');\nconst url = 'https://api.hubapi.com/crm/v3/objects/contacts'\nconst token = 'YOUR-TOKEN';\n\nconst body = {\n\t\"properties\": {\n\t \"email\": $email\n\t}\n};\n\nif ($firstname) body.properties.firstname = $firstname;\nif ($lastname) body.properties.lastname = $lastname;\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t 'Authorization': `Bearer ${token}`,\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/marketplaces/tools/Create Airtable Record.json b/packages/server/marketplaces/tools/Create Airtable Record.json new file mode 100644 index 00000000..c52c9199 --- /dev/null +++ b/packages/server/marketplaces/tools/Create Airtable Record.json @@ -0,0 +1,8 @@ +{ + "name": "add_airtable", + "description": "Add column1, column2 to Airtable", + "color": "linear-gradient(rgb(125,71,222), rgb(128,102,23))", + "iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/airtable.svg", + "schema": "[{\"id\":0,\"property\":\"column1\",\"description\":\"this is column1\",\"type\":\"string\",\"required\":true},{\"id\":1,\"property\":\"column2\",\"description\":\"this is column2\",\"type\":\"string\",\"required\":true}]", + "func": "const fetch = require('node-fetch');\nconst baseId = 'YOUR-BASE-ID';\nconst tableId = 'YOUR-TABLE-ID';\nconst token = 'YOUR-TOKEN';\n\nconst body = {\n\t\"records\": [\n\t\t{\n\t\t\t\"fields\": {\n\t\t\t\t\"column1\": $column1,\n\t\t\t\t\"column2\": $column2,\n\t\t\t}\n\t\t}\n\t]\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t\t'Authorization': `Bearer ${token}`,\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\nconst url = `https://api.airtable.com/v0/${baseId}/${tableId}`\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/marketplaces/tools/Get Stock Mover.json b/packages/server/marketplaces/tools/Get Stock Mover.json new file mode 100644 index 00000000..9108cc50 --- /dev/null +++ b/packages/server/marketplaces/tools/Get Stock Mover.json @@ -0,0 +1,8 @@ +{ + "name": "get_stock_movers", + "description": "Get the stocks that has biggest price/volume moves, e.g. actives, gainers, losers, etc.", + "iconSrc": "https://rapidapi.com/cdn/images?url=https://rapidapi-prod-apis.s3.amazonaws.com/9c/e743343bdd41edad39a3fdffd5b974/016c33699f51603ae6fe4420c439124b.png", + "color": "linear-gradient(rgb(191,202,167), rgb(143,202,246))", + "schema": "[]", + "func": "const fetch = require('node-fetch');\nconst url = 'https://morning-star.p.rapidapi.com/market/v2/get-movers';\nconst options = {\n\tmethod: 'GET',\n\theaders: {\n\t\t'X-RapidAPI-Key': 'YOUR-API-KEY',\n\t\t'X-RapidAPI-Host': 'morning-star.p.rapidapi.com'\n\t}\n};\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst result = await response.text();\n\tconsole.log(result);\n\treturn result;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/marketplaces/tools/Send Discord Message.json b/packages/server/marketplaces/tools/Send Discord Message.json new file mode 100644 index 00000000..bbfaaa90 --- /dev/null +++ b/packages/server/marketplaces/tools/Send Discord Message.json @@ -0,0 +1,8 @@ +{ + "name": "send_message_to_discord_channel", + "description": "Send message to Discord channel", + "color": "linear-gradient(rgb(155,190,84), rgb(176,69,245))", + "iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/discord-icon.svg", + "schema": "[{\"id\":1,\"property\":\"content\",\"description\":\"message to send\",\"type\":\"string\",\"required\":true}]", + "func": "const fetch = require('node-fetch');\nconst webhookUrl = 'YOUR-WEBHOOK-URL'\n\nconst body = {\n\t\"content\": $content\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\nconst url = `${webhookUrl}?wait=true`\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/marketplaces/tools/Send Slack Message.json b/packages/server/marketplaces/tools/Send Slack Message.json new file mode 100644 index 00000000..f15d4050 --- /dev/null +++ b/packages/server/marketplaces/tools/Send Slack Message.json @@ -0,0 +1,8 @@ +{ + "name": "send_message_to_slack_channel", + "description": "Send message to Slack channel", + "color": "linear-gradient(rgb(155,190,84), rgb(176,69,245))", + "iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/slack-icon.svg", + "schema": "[{\"id\":1,\"property\":\"text\",\"description\":\"message to send\",\"type\":\"string\",\"required\":true}]", + "func": "const fetch = require('node-fetch');\nconst webhookUrl = 'YOUR-WEBHOOK-URL'\n\nconst body = {\n\t\"text\": $text\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\nconst url = `${webhookUrl}`\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/marketplaces/tools/Send Teams Message.json b/packages/server/marketplaces/tools/Send Teams Message.json new file mode 100644 index 00000000..1af8111b --- /dev/null +++ b/packages/server/marketplaces/tools/Send Teams Message.json @@ -0,0 +1,8 @@ +{ + "name": "send_message_to_teams_channel", + "description": "Send message to Teams channel", + "color": "linear-gradient(rgb(155,190,84), rgb(176,69,245))", + "iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/microsoft-teams.svg", + "schema": "[{\"id\":1,\"property\":\"content\",\"description\":\"message to send\",\"type\":\"string\",\"required\":true}]", + "func": "const fetch = require('node-fetch');\nconst webhookUrl = 'YOUR-WEBHOOK-URL'\n\nconst body = {\n\t\"content\": $content\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\nconst url = `${webhookUrl}?wait=true`\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/marketplaces/tools/SendGrid Email.json b/packages/server/marketplaces/tools/SendGrid Email.json new file mode 100644 index 00000000..18f6dad8 --- /dev/null +++ b/packages/server/marketplaces/tools/SendGrid Email.json @@ -0,0 +1,8 @@ +{ + "name": "sendgrid_email", + "description": "Send email using SendGrid", + "color": "linear-gradient(rgb(230,108,70), rgb(222,4,98))", + "iconSrc": "https://raw.githubusercontent.com/gilbarbara/logos/main/logos/sendgrid-icon.svg", + "schema": "[{\"id\":0,\"property\":\"fromEmail\",\"description\":\"Email address used to send the message\",\"type\":\"string\",\"required\":true},{\"id\":1,\"property\":\"toEmail \",\"description\":\"The intended recipient's email address\",\"type\":\"string\",\"required\":true},{\"id\":2,\"property\":\"subject\",\"description\":\"The subject of email\",\"type\":\"string\",\"required\":true},{\"id\":3,\"property\":\"content\",\"description\":\"Content of email\",\"type\":\"string\",\"required\":true}]", + "func": "const fetch = require('node-fetch');\nconst url = 'https://api.sendgrid.com/v3/mail/send';\nconst api_key = 'YOUR-API-KEY';\n\nconst body = {\n \"personalizations\": [\n {\n \"to\": [{ \"email\": $toEmail }]\n }\n ],\n\t\"from\": {\n\t \"email\": $fromEmail\n\t},\n\t\"subject\": $subject,\n\t\"content\": [\n\t {\n\t \"type\": 'text/plain',\n\t \"value\": $content\n\t }\n\t]\n};\n\nconst options = {\n\tmethod: 'POST',\n\theaders: {\n\t 'Authorization': `Bearer ${api_key}`,\n\t\t'Content-Type': 'application/json'\n\t},\n\tbody: JSON.stringify(body)\n};\n\ntry {\n\tconst response = await fetch(url, options);\n\tconst text = await response.text();\n\treturn text;\n} catch (error) {\n\tconsole.error(error);\n\treturn '';\n}" +} diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 9c47405c..b4783f76 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -30,6 +30,7 @@ export interface ITool { name: string description: string color: string + iconSrc?: string schema?: string func?: string updatedDate: Date diff --git a/packages/server/src/entity/Tool.ts b/packages/server/src/entity/Tool.ts index 307e8d23..222fd766 100644 --- a/packages/server/src/entity/Tool.ts +++ b/packages/server/src/entity/Tool.ts @@ -16,6 +16,9 @@ export class Tool implements ITool { @Column() color: string + @Column({ nullable: true }) + iconSrc?: string + @Column({ nullable: true }) schema?: string diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 15762a23..cd4978a0 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -462,12 +462,12 @@ export class App { // ---------------------------------------- // Get all chatflows for marketplaces - this.app.get('/api/v1/marketplaces', async (req: Request, res: Response) => { - const marketplaceDir = path.join(__dirname, '..', 'marketplaces') + this.app.get('/api/v1/marketplaces/chatflows', async (req: Request, res: Response) => { + const marketplaceDir = path.join(__dirname, '..', 'marketplaces', 'chatflows') const jsonsInDir = fs.readdirSync(marketplaceDir).filter((file) => path.extname(file) === '.json') const templates: any[] = [] jsonsInDir.forEach((file, index) => { - const filePath = path.join(__dirname, '..', 'marketplaces', file) + const filePath = path.join(__dirname, '..', 'marketplaces', 'chatflows', file) const fileData = fs.readFileSync(filePath) const fileDataObj = JSON.parse(fileData.toString()) const template = { @@ -481,6 +481,25 @@ export class App { return res.json(templates) }) + // Get all tools for marketplaces + this.app.get('/api/v1/marketplaces/tools', async (req: Request, res: Response) => { + const marketplaceDir = path.join(__dirname, '..', 'marketplaces', 'tools') + const jsonsInDir = fs.readdirSync(marketplaceDir).filter((file) => path.extname(file) === '.json') + const templates: any[] = [] + jsonsInDir.forEach((file, index) => { + const filePath = path.join(__dirname, '..', 'marketplaces', 'tools', file) + const fileData = fs.readFileSync(filePath) + const fileDataObj = JSON.parse(fileData.toString()) + const template = { + ...fileDataObj, + id: index, + templateName: file.split('.json')[0] + } + templates.push(template) + }) + return res.json(templates) + }) + // ---------------------------------------- // API Keys // ---------------------------------------- diff --git a/packages/ui/public/index.html b/packages/ui/public/index.html index 270cc805..b4ec9ea1 100644 --- a/packages/ui/public/index.html +++ b/packages/ui/public/index.html @@ -1,13 +1,13 @@ - Flowise - LangchainJS UI + Flowise - Low-code LLM apps builder - + @@ -17,13 +17,13 @@ - + - + diff --git a/packages/ui/src/api/marketplaces.js b/packages/ui/src/api/marketplaces.js index 6906fb4e..3fd4ae87 100644 --- a/packages/ui/src/api/marketplaces.js +++ b/packages/ui/src/api/marketplaces.js @@ -1,7 +1,9 @@ import client from './client' -const getAllMarketplaces = () => client.get('/marketplaces') +const getAllChatflowsMarketplaces = () => client.get('/marketplaces/chatflows') +const getAllToolsMarketplaces = () => client.get('/marketplaces/tools') export default { - getAllMarketplaces + getAllChatflowsMarketplaces, + getAllToolsMarketplaces } diff --git a/packages/ui/src/themes/compStyleOverride.js b/packages/ui/src/themes/compStyleOverride.js index b7ebc8b2..c04cc3f1 100644 --- a/packages/ui/src/themes/compStyleOverride.js +++ b/packages/ui/src/themes/compStyleOverride.js @@ -136,6 +136,9 @@ export default function componentStyleOverrides(theme) { '&::placeholder': { color: theme.darkTextSecondary, fontSize: '0.875rem' + }, + '&.Mui-disabled': { + WebkitTextFillColor: theme?.customization?.isDarkMode ? theme.colors?.grey500 : theme.darkTextSecondary } } } diff --git a/packages/ui/src/ui-component/cards/ItemCard.js b/packages/ui/src/ui-component/cards/ItemCard.js index 345a88d5..1e8789d7 100644 --- a/packages/ui/src/ui-component/cards/ItemCard.js +++ b/packages/ui/src/ui-component/cards/ItemCard.js @@ -27,7 +27,7 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({ // ===========================|| CONTRACT CARD ||=========================== // -const ItemCard = ({ isLoading, data, images, color, onClick }) => { +const ItemCard = ({ isLoading, data, images, onClick }) => { return ( <> {isLoading ? ( @@ -43,21 +43,35 @@ const ItemCard = ({ isLoading, data, images, color, onClick }) => { alignItems: 'center' }} > - {color && ( + {data.iconSrc && (
+ )} + {!data.iconSrc && data.color && ( +
)} - {data.name} + {data.templateName || data.name}
{data.description && ( @@ -107,7 +121,6 @@ ItemCard.propTypes = { isLoading: PropTypes.bool, data: PropTypes.object, images: PropTypes.array, - color: PropTypes.string, onClick: PropTypes.func } diff --git a/packages/ui/src/ui-component/grid/Grid.js b/packages/ui/src/ui-component/grid/Grid.js index 2049f56c..0670d69b 100644 --- a/packages/ui/src/ui-component/grid/Grid.js +++ b/packages/ui/src/ui-component/grid/Grid.js @@ -3,7 +3,7 @@ import { DataGrid } from '@mui/x-data-grid' import { IconPlus } from '@tabler/icons' import { Button } from '@mui/material' -export const Grid = ({ columns, rows, style, onRowUpdate, addNewRow }) => { +export const Grid = ({ columns, rows, style, disabled = false, onRowUpdate, addNewRow }) => { const handleProcessRowUpdate = (newRow) => { onRowUpdate(newRow) return newRow @@ -11,13 +11,18 @@ export const Grid = ({ columns, rows, style, onRowUpdate, addNewRow }) => { return ( <> - + {!disabled && ( + + )} {rows && columns && (
{ + return !disabled + }} onProcessRowUpdateError={(error) => console.error(error)} rows={rows} columns={columns} @@ -32,6 +37,7 @@ Grid.propTypes = { rows: PropTypes.array, columns: PropTypes.array, style: PropTypes.any, + disabled: PropTypes.bool, addNewRow: PropTypes.func, onRowUpdate: PropTypes.func } diff --git a/packages/ui/src/views/marketplaces/index.js b/packages/ui/src/views/marketplaces/index.js index ba9eb3d6..a7836161 100644 --- a/packages/ui/src/views/marketplaces/index.js +++ b/packages/ui/src/views/marketplaces/index.js @@ -1,16 +1,19 @@ import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' // material-ui -import { Grid, Box, Stack } from '@mui/material' +import { Grid, Box, Stack, Tabs, Tab } from '@mui/material' import { useTheme } from '@mui/material/styles' +import { IconHierarchy, IconTool } from '@tabler/icons' // project imports import MainCard from 'ui-component/cards/MainCard' import ItemCard from 'ui-component/cards/ItemCard' import { gridSpacing } from 'store/constant' import WorkflowEmptySVG from 'assets/images/workflow_empty.svg' +import ToolDialog from 'views/tools/ToolDialog' // API import marketplacesApi from 'api/marketplaces' @@ -21,6 +24,27 @@ import useApi from 'hooks/useApi' // const import { baseURL } from 'store/constant' +function TabPanel(props) { + const { children, value, index, ...other } = props + return ( + + ) +} + +TabPanel.propTypes = { + children: PropTypes.node, + index: PropTypes.number.isRequired, + value: PropTypes.number.isRequired +} + // ==============================|| Marketplace ||============================== // const Marketplace = () => { @@ -29,29 +53,66 @@ const Marketplace = () => { const theme = useTheme() const customization = useSelector((state) => state.customization) - const [isLoading, setLoading] = useState(true) + const [isChatflowsLoading, setChatflowsLoading] = useState(true) + const [isToolsLoading, setToolsLoading] = useState(true) const [images, setImages] = useState({}) + const tabItems = ['Chatflows', 'Tools'] + const [value, setValue] = useState(0) + const [showToolDialog, setShowToolDialog] = useState(false) + const [toolDialogProps, setToolDialogProps] = useState({}) - const getAllMarketplacesApi = useApi(marketplacesApi.getAllMarketplaces) + const getAllChatflowsMarketplacesApi = useApi(marketplacesApi.getAllChatflowsMarketplaces) + const getAllToolsMarketplacesApi = useApi(marketplacesApi.getAllToolsMarketplaces) + + const onUseTemplate = (selectedTool) => { + const dialogProp = { + title: 'Add New Tool', + type: 'IMPORT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add', + data: selectedTool + } + setToolDialogProps(dialogProp) + setShowToolDialog(true) + } + + const goToTool = (selectedTool) => { + const dialogProp = { + title: selectedTool.templateName, + type: 'TEMPLATE', + data: selectedTool + } + setToolDialogProps(dialogProp) + setShowToolDialog(true) + } const goToCanvas = (selectedChatflow) => { navigate(`/marketplace/${selectedChatflow.id}`, { state: selectedChatflow }) } + const handleChange = (event, newValue) => { + setValue(newValue) + } + useEffect(() => { - getAllMarketplacesApi.request() + getAllChatflowsMarketplacesApi.request() + getAllToolsMarketplacesApi.request() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { - setLoading(getAllMarketplacesApi.loading) - }, [getAllMarketplacesApi.loading]) + setChatflowsLoading(getAllChatflowsMarketplacesApi.loading) + }, [getAllChatflowsMarketplacesApi.loading]) useEffect(() => { - if (getAllMarketplacesApi.data) { + setToolsLoading(getAllToolsMarketplacesApi.loading) + }, [getAllToolsMarketplacesApi.loading]) + + useEffect(() => { + if (getAllChatflowsMarketplacesApi.data) { try { - const chatflows = getAllMarketplacesApi.data + const chatflows = getAllChatflowsMarketplacesApi.data const images = {} for (let i = 0; i < chatflows.length; i += 1) { const flowDataStr = chatflows[i].flowData @@ -70,31 +131,83 @@ const Marketplace = () => { console.error(e) } } - }, [getAllMarketplacesApi.data]) + }, [getAllChatflowsMarketplacesApi.data]) return ( - - -

Marketplace

-
- - {!isLoading && - getAllMarketplacesApi.data && - getAllMarketplacesApi.data.map((data, index) => ( - - goToCanvas(data)} data={data} images={images[data.id]} /> - - ))} - - {!isLoading && (!getAllMarketplacesApi.data || getAllMarketplacesApi.data.length === 0) && ( - - - WorkflowEmptySVG - -
No Marketplace Yet
+ <> + + +

Marketplace

- )} -
+ + {tabItems.map((item, index) => ( + : } + iconPosition='start' + label={{item}} + /> + ))} + + {tabItems.map((item, index) => ( + + {item === 'Chatflows' && ( + + {!isChatflowsLoading && + getAllChatflowsMarketplacesApi.data && + getAllChatflowsMarketplacesApi.data.map((data, index) => ( + + goToCanvas(data)} data={data} images={images[data.id]} /> + + ))} + + )} + {item === 'Tools' && ( + + {!isToolsLoading && + getAllToolsMarketplacesApi.data && + getAllToolsMarketplacesApi.data.map((data, index) => ( + + goToTool(data)} /> + + ))} + + )} + + ))} + {!isChatflowsLoading && (!getAllChatflowsMarketplacesApi.data || getAllChatflowsMarketplacesApi.data.length === 0) && ( + + + WorkflowEmptySVG + +
No Marketplace Yet
+
+ )} + {!isToolsLoading && (!getAllToolsMarketplacesApi.data || getAllToolsMarketplacesApi.data.length === 0) && ( + + + WorkflowEmptySVG + +
No Marketplace Yet
+
+ )} +
+ setShowToolDialog(false)} + onConfirm={() => setShowToolDialog(false)} + onUseTemplate={(tool) => onUseTemplate(tool)} + > + ) } diff --git a/packages/ui/src/views/tools/ToolDialog.js b/packages/ui/src/views/tools/ToolDialog.js index bd5af355..77ef770d 100644 --- a/packages/ui/src/views/tools/ToolDialog.js +++ b/packages/ui/src/views/tools/ToolDialog.js @@ -17,7 +17,7 @@ import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' import { useTheme } from '@mui/material/styles' // Icons -import { IconX } from '@tabler/icons' +import { IconX, IconFileExport } from '@tabler/icons' // API import toolsApi from 'api/tools' @@ -53,7 +53,7 @@ try { return ''; }` -const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { +const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) => { const portalElement = document.getElementById('portal') const theme = useTheme() @@ -73,6 +73,7 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const [toolId, setToolId] = useState('') const [toolName, setToolName] = useState('') const [toolDesc, setToolDesc] = useState('') + const [toolIcon, setToolIcon] = useState('') const [toolSchema, setToolSchema] = useState([]) const [toolFunc, setToolFunc] = useState('') @@ -167,18 +168,39 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { useEffect(() => { if (dialogProps.type === 'EDIT' && dialogProps.data) { + // When tool dialog is opened from Tools dashboard setToolId(dialogProps.data.id) setToolName(dialogProps.data.name) setToolDesc(dialogProps.data.description) + setToolIcon(dialogProps.data.iconSrc) setToolSchema(formatSchema(dialogProps.data.schema)) if (dialogProps.data.func) setToolFunc(dialogProps.data.func) else setToolFunc('') } else if (dialogProps.type === 'EDIT' && dialogProps.toolId) { + // When tool dialog is opened from CustomTool node in canvas getSpecificToolApi.request(dialogProps.toolId) + } else if (dialogProps.type === 'IMPORT' && dialogProps.data) { + // When tool dialog is to import existing tool + setToolName(dialogProps.data.name) + setToolDesc(dialogProps.data.description) + setToolIcon(dialogProps.data.iconSrc) + setToolSchema(formatSchema(dialogProps.data.schema)) + if (dialogProps.data.func) setToolFunc(dialogProps.data.func) + else setToolFunc('') + } else if (dialogProps.type === 'TEMPLATE' && dialogProps.data) { + // When tool dialog is a template + setToolName(dialogProps.data.name) + setToolDesc(dialogProps.data.description) + setToolIcon(dialogProps.data.iconSrc) + setToolSchema(formatSchema(dialogProps.data.schema)) + if (dialogProps.data.func) setToolFunc(dialogProps.data.func) + else setToolFunc('') } else if (dialogProps.type === 'ADD') { + // When tool dialog is to add a new tool setToolId('') setToolName('') setToolDesc('') + setToolIcon('') setToolSchema([]) setToolFunc('') } @@ -186,6 +208,47 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [dialogProps]) + const useToolTemplate = () => { + onUseTemplate(dialogProps.data) + } + + const exportTool = async () => { + try { + const toolResp = await toolsApi.getSpecificTool(toolId) + if (toolResp.data) { + const toolData = toolResp.data + delete toolData.id + delete toolData.createdDate + delete toolData.updatedDate + let dataStr = JSON.stringify(toolData) + let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr) + + let exportFileDefaultName = `${toolName}-CustomTool.json` + + let linkElement = document.createElement('a') + linkElement.setAttribute('href', dataUri) + linkElement.setAttribute('download', exportFileDefaultName) + linkElement.click() + } + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to export Tool: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + const addNewTool = async () => { try { const obj = { @@ -193,7 +256,8 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { description: toolDesc, color: generateRandomGradient(), schema: JSON.stringify(toolSchema), - func: toolFunc + func: toolFunc, + iconSrc: toolIcon } const createResp = await toolsApi.createNewTool(obj) if (createResp.data) { @@ -236,7 +300,8 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { name: toolName, description: toolDesc, schema: JSON.stringify(toolSchema), - func: toolFunc + func: toolFunc, + iconSrc: toolIcon }) if (saveResp.data) { enqueueSnackbar({ @@ -330,7 +395,15 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { aria-describedby='alert-dialog-description' > - {dialogProps.title} +
+ {dialogProps.title} +
+ {dialogProps.type === 'EDIT' && ( + + )} +
@@ -338,12 +411,17 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { Tool Name  * + { Tool description  * + { onChange={(e) => setToolDesc(e.target.value)} /> + + + Tool Icon Src + + setToolIcon(e.target.value)} + /> + @@ -376,7 +474,13 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { - + @@ -388,12 +492,15 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { /> - + {dialogProps.type !== 'TEMPLATE' && ( + + )} {customization.isDarkMode ? ( setToolFunc(code)} style={{ fontSize: '0.875rem', @@ -405,6 +512,7 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { ) : ( setToolFunc(code)} style={{ fontSize: '0.875rem', @@ -423,13 +531,20 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { Delete )} - (dialogProps.type === 'ADD' ? addNewTool() : saveTool())} - > - {dialogProps.confirmButtonName} - + {dialogProps.type === 'TEMPLATE' && ( + + Use Template + + )} + {dialogProps.type !== 'TEMPLATE' && ( + (dialogProps.type === 'ADD' || dialogProps.type === 'IMPORT' ? addNewTool() : saveTool())} + > + {dialogProps.confirmButtonName} + + )} @@ -441,6 +556,7 @@ const ToolDialog = ({ show, dialogProps, onCancel, onConfirm }) => { ToolDialog.propTypes = { show: PropTypes.bool, dialogProps: PropTypes.object, + onUseTemplate: PropTypes.func, onCancel: PropTypes.func, onConfirm: PropTypes.func } diff --git a/packages/ui/src/views/tools/index.js b/packages/ui/src/views/tools/index.js index efe9e69d..c97ec660 100644 --- a/packages/ui/src/views/tools/index.js +++ b/packages/ui/src/views/tools/index.js @@ -1,8 +1,8 @@ -import { useEffect, useState } from 'react' +import { useEffect, useState, useRef } from 'react' import { useSelector } from 'react-redux' // material-ui -import { Grid, Box, Stack } from '@mui/material' +import { Grid, Box, Stack, Button } from '@mui/material' import { useTheme } from '@mui/material/styles' // project imports @@ -20,7 +20,7 @@ import toolsApi from 'api/tools' import useApi from 'hooks/useApi' // icons -import { IconPlus } from '@tabler/icons' +import { IconPlus, IconFileImport } from '@tabler/icons' // ==============================|| CHATFLOWS ||============================== // @@ -33,6 +33,40 @@ const Tools = () => { const [showDialog, setShowDialog] = useState(false) const [dialogProps, setDialogProps] = useState({}) + const inputRef = useRef(null) + + const onUploadFile = (file) => { + try { + const dialogProp = { + title: 'Add New Tool', + type: 'IMPORT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Save', + data: JSON.parse(file) + } + setDialogProps(dialogProp) + setShowDialog(true) + } catch (e) { + console.error(e) + } + } + + const handleFileUpload = (e) => { + if (!e.target.files) return + + const file = e.target.files[0] + + const reader = new FileReader() + reader.onload = (evt) => { + if (!evt?.target?.result) { + return + } + const { result } = evt.target + onUploadFile(result) + } + reader.readAsText(file) + } + const addNew = () => { const dialogProp = { title: 'Add New Tool', @@ -75,8 +109,17 @@ const Tools = () => { + + handleFileUpload(e)} /> }> - Create New + Create @@ -86,7 +129,7 @@ const Tools = () => { getAllToolsApi.data && getAllToolsApi.data.map((data, index) => ( - edit(data)} /> + edit(data)} /> ))} From 2ac229a242b4664cae47cef0e54daf617bc0326c Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 2 Jul 2023 00:55:25 +0100 Subject: [PATCH 35/44] add 16k model to Azure --- .../AzureChatOpenAI/AzureChatOpenAI.ts | 16 ++++++++-------- .../AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts | 2 +- .../nodes/llms/Azure OpenAI/AzureOpenAI.ts | 16 ++++------------ 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts index 60295890..2cdb505d 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts @@ -43,6 +43,10 @@ class AzureChatOpenAI_ChatModels implements INode { { label: 'gpt-35-turbo', name: 'gpt-35-turbo' + }, + { + label: 'gpt-35-turbo-16k', + name: 'gpt-35-turbo-16k' } ], default: 'gpt-35-turbo', @@ -70,14 +74,10 @@ class AzureChatOpenAI_ChatModels implements INode { { label: 'Azure OpenAI Api Version', name: 'azureOpenAIApiVersion', - type: 'options', - options: [ - { - label: '2023-03-15-preview', - name: '2023-03-15-preview' - } - ], - default: '2023-03-15-preview' + type: 'string', + placeholder: '2023-06-01-preview', + description: + 'Description of Supported API Versions. Please refer examples' }, { label: 'Max Tokens', diff --git a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts index 2a211622..4133539e 100644 --- a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts +++ b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts @@ -43,7 +43,7 @@ class AzureOpenAIEmbedding_Embeddings implements INode { label: 'Azure OpenAI Api Version', name: 'azureOpenAIApiVersion', type: 'string', - placeholder: 'YOUR-API-VERSION', + placeholder: '2023-03-15-preview', description: 'Description of Supported API Versions. Please refer examples' }, diff --git a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts index f81c9349..130eed33 100644 --- a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts +++ b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts @@ -105,18 +105,10 @@ class AzureOpenAI_LLMs implements INode { { label: 'Azure OpenAI Api Version', name: 'azureOpenAIApiVersion', - type: 'options', - options: [ - { - label: '2023-03-15-preview', - name: '2023-03-15-preview' - }, - { - label: '2022-12-01', - name: '2022-12-01' - } - ], - default: '2023-03-15-preview' + type: 'string', + placeholder: '2023-06-01-preview', + description: + 'Description of Supported API Versions. Please refer examples' }, { label: 'Max Tokens', From 0efed9d7d43bf66a8e10155bfeb865300984925d Mon Sep 17 00:00:00 2001 From: Govind Kumar Date: Sun, 2 Jul 2023 23:45:42 +0530 Subject: [PATCH 36/44] added dynamo db backed memory --- .../components/nodes/memory/DynamoDb/DynamoDb.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index d0845d96..8b4cd69d 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -18,7 +18,7 @@ class DynamoDb_Memory implements INode { this.icon = 'dynamodb.svg' this.category = 'Memory' this.description = 'Stores the conversation in dynamo db table' - this.baseClasses = [this.type, ...getBaseClasses(DynamoDBChatMessageHistory)] + this.baseClasses = [this.type, ...getBaseClasses(BufferMemory)] this.inputs = [ { label: 'Table Name', @@ -54,6 +54,12 @@ class DynamoDb_Memory implements INode { label: 'Secret Access Key', name: 'secretAccessKey', type: 'password' + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history' } ] } @@ -64,6 +70,7 @@ class DynamoDb_Memory implements INode { const region = nodeData.inputs?.region as string const accessKey = nodeData.inputs?.accessKey as string const secretAccessKey = nodeData.inputs?.secretAccessKey as string + const memoryKey = nodeData.inputs?.memoryKey as string const chatId = options.chatId @@ -81,7 +88,9 @@ class DynamoDb_Memory implements INode { }) const memory = new BufferMemory({ - chatHistory: dynamoDb + memoryKey, + chatHistory: dynamoDb, + returnMessages: true }) return memory } From 0ca7e8db01b1bdeee32e82dee84c1cae346f53ab Mon Sep 17 00:00:00 2001 From: toshilow Date: Mon, 3 Jul 2023 21:28:09 +0900 Subject: [PATCH 37/44] do not submit during IME composition --- packages/ui/src/views/chatmessage/ChatMessage.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 5021cd9b..52ff4bb9 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -163,7 +163,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { // Prevent blank submissions and allow for multiline input const handleEnter = (e) => { - if (e.key === 'Enter' && userInput) { + // Check if IME composition is in progress + const isIMEComposition = e.isComposing || e.keyCode === 229 + if (e.key === 'Enter' && userInput && !isIMEComposition) { if (!e.shiftKey && userInput) { handleSubmit(e) } From 479a6bc7eb2020abc9d12e1d547ec3d3441f8331 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 3 Jul 2023 17:59:52 +0100 Subject: [PATCH 38/44] update sessionId to optional --- packages/components/nodes/memory/DynamoDb/DynamoDb.ts | 3 ++- .../memory/RedisBackedChatMemory/RedisBackedChatMemory.ts | 3 ++- packages/components/nodes/memory/ZepMemory/ZepMemory.ts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index 8b4cd69d..b1368044 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -36,7 +36,8 @@ class DynamoDb_Memory implements INode { type: 'string', description: 'if empty, chatId will be used automatically', default: '', - additionalParams: true + additionalParams: true, + optional: true }, { label: 'Region', diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index e332181d..2b4e51c2 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -36,7 +36,8 @@ class RedisBackedChatMemory_Memory implements INode { type: 'string', description: 'if empty, chatId will be used automatically', default: '', - additionalParams: true + additionalParams: true, + optional: true }, { label: 'Session Timeouts', diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 1fb6d9ff..2e7ba001 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -41,7 +41,8 @@ class ZepMemory_Memory implements INode { type: 'string', description: 'if empty, chatId will be used automatically', default: '', - additionalParams: true + additionalParams: true, + optional: true }, { label: 'Auto Summary Template', From 636ad5dc0c8209961a5cf22e0cb39b11f1eb9712 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 5 Jul 2023 18:14:54 +0100 Subject: [PATCH 39/44] add deployed boolean back --- packages/server/src/Interface.ts | 1 + packages/server/src/entity/ChatFlow.ts | 3 +++ packages/ui/src/views/canvas/index.js | 1 + 3 files changed, 5 insertions(+) diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index b4783f76..f8f2f4ac 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -9,6 +9,7 @@ export interface IChatFlow { id: string name: string flowData: string + deployed: boolean isPublic: boolean updatedDate: Date createdDate: Date diff --git a/packages/server/src/entity/ChatFlow.ts b/packages/server/src/entity/ChatFlow.ts index 400e0517..c454160d 100644 --- a/packages/server/src/entity/ChatFlow.ts +++ b/packages/server/src/entity/ChatFlow.ts @@ -13,6 +13,9 @@ export class ChatFlow implements IChatFlow { @Column() flowData: string + @Column() + deployed: boolean + @Column() isPublic: boolean diff --git a/packages/ui/src/views/canvas/index.js b/packages/ui/src/views/canvas/index.js index 03098963..1c6d610f 100644 --- a/packages/ui/src/views/canvas/index.js +++ b/packages/ui/src/views/canvas/index.js @@ -201,6 +201,7 @@ const Canvas = () => { if (!chatflow.id) { const newChatflowBody = { name: chatflowName, + deployed: false, isPublic: false, flowData } From 9bca78b66a2eecad44c567f6212c194a402986fd Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 5 Jul 2023 18:27:02 +0100 Subject: [PATCH 40/44] add nullable to isPublic --- packages/server/src/Interface.ts | 2 +- packages/server/src/entity/ChatFlow.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index f8f2f4ac..ab55e87a 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -10,9 +10,9 @@ export interface IChatFlow { name: string flowData: string deployed: boolean - isPublic: boolean updatedDate: Date createdDate: Date + isPublic?: boolean apikeyid?: string chatbotConfig?: string } diff --git a/packages/server/src/entity/ChatFlow.ts b/packages/server/src/entity/ChatFlow.ts index c454160d..0e1e8698 100644 --- a/packages/server/src/entity/ChatFlow.ts +++ b/packages/server/src/entity/ChatFlow.ts @@ -16,8 +16,8 @@ export class ChatFlow implements IChatFlow { @Column() deployed: boolean - @Column() - isPublic: boolean + @Column({ nullable: true }) + isPublic?: boolean @Column({ nullable: true }) apikeyid?: string From 1d74473ef38dc948225bb3eb951b3e81da5611c0 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 5 Jul 2023 18:50:09 +0100 Subject: [PATCH 41/44] update deployed as nullable --- packages/server/src/Interface.ts | 2 +- packages/server/src/entity/ChatFlow.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index ab55e87a..0c630490 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -9,9 +9,9 @@ export interface IChatFlow { id: string name: string flowData: string - deployed: boolean updatedDate: Date createdDate: Date + deployed?: boolean isPublic?: boolean apikeyid?: string chatbotConfig?: string diff --git a/packages/server/src/entity/ChatFlow.ts b/packages/server/src/entity/ChatFlow.ts index 0e1e8698..e1d212cf 100644 --- a/packages/server/src/entity/ChatFlow.ts +++ b/packages/server/src/entity/ChatFlow.ts @@ -13,8 +13,8 @@ export class ChatFlow implements IChatFlow { @Column() flowData: string - @Column() - deployed: boolean + @Column({ nullable: true }) + deployed?: boolean @Column({ nullable: true }) isPublic?: boolean From 5707c414bd03cc5f4d0928c36fad8b38b9ddab9c Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 5 Jul 2023 19:28:47 +0100 Subject: [PATCH 42/44] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.2.15?= =?UTF-8?q?=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index 4555a883..a5f03e10 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.14", + "version": "1.2.15", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From d4b299ba324b4c22e11309330f8e92e47df0f07f Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 5 Jul 2023 19:29:25 +0100 Subject: [PATCH 43/44] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.2.13=20releas?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index ba1483b4..2cadc89d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.12", + "version": "1.2.13", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From c78c74f73d1dda648e0fa50e1171380aebe34ab4 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 5 Jul 2023 19:34:35 +0100 Subject: [PATCH 44/44] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.2.14=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0ff76284..61ea436b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.13", + "version": "1.2.14", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index eda69322..3e18c3d7 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.13", + "version": "1.2.14", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts",