From b16b8c62655392dba1912d75e653c3949ac7dd82 Mon Sep 17 00:00:00 2001 From: Vikram Segta Date: Thu, 15 Jun 2023 01:16:51 +0530 Subject: [PATCH 001/183] bug/Chat multiline fix --- packages/ui/src/views/chatmessage/ChatMessage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index 5021cd9b..f73a2ba0 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -366,6 +366,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { value={userInput} onChange={onChange} multiline={true} + maxRows={2} endAdornment={ From 92bbc938c914b8c4717b179ad9bfa5df2e43c010 Mon Sep 17 00:00:00 2001 From: Vikram Segta Date: Thu, 15 Jun 2023 10:41:06 +0530 Subject: [PATCH 002/183] bug/Chat input fix --- packages/ui/src/views/chatmessage/ChatExpandDialog.js | 2 +- packages/ui/src/views/chatmessage/ChatMessage.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/views/chatmessage/ChatExpandDialog.js b/packages/ui/src/views/chatmessage/ChatExpandDialog.js index aa5cd504..1b2037a8 100644 --- a/packages/ui/src/views/chatmessage/ChatExpandDialog.js +++ b/packages/ui/src/views/chatmessage/ChatExpandDialog.js @@ -43,7 +43,7 @@ const ChatExpandDialog = ({ show, dialogProps, onClear, onCancel }) => { )} - + diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index f73a2ba0..f37abe64 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -366,7 +366,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { value={userInput} onChange={onChange} multiline={true} - maxRows={2} + maxRows={isDialog ? 7 : 2} endAdornment={ From 8745776342e5d5379846f2126a68b037b4e72f64 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 23 Jun 2023 23:03:53 +0100 Subject: [PATCH 003/183] 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 004/183] 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 005/183] 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 006/183] 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 007/183] 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 zcmX|p2Rv2(|NlYJFe2F_H>0RMSiunY!2Uwp0p)CB;jo1yov0JsilDBsn8o4SH?Po>|*5H|<&Jie;ZvE_4h z$4#u2g^%Ys$zhXgT8(&?PUkQ+)vI@faQK!BEO$HSuSnH|XgnB*V99zxFD!fG_V_#L z3(nmS1{d}zV&7aVvChp?cr3BLFw+|)oB3eyGH1U@`l-R21TGlt8`H)Mn8Io#}ib;67l5(H1F}=z+b>l_H#8-HPl39b}R$q5{_~%z=?_FO*5{_T0y^y?gKziWQt98wT zeA`&W>~|9Hn4=X3d%PP9psWlBh;4Heeb3}~G3>1M*PFO%QnCjTE1Hi zi{FsqhwA2(8=Lqv*XIP1eY;oBs(Q2wC+IP8ywDVvS{$*N!dhB+Bnv=|+Wz0)_b1`8 z^ZYCPab{tWZDdpc40e7>%FFGFMt`+=mS0h?++E3c-9cNZ+Bz(6q|Qy_H;EIjYYz=7>^^8B0{x8GOmGyQEnlY zmfz+qxS`v61=;;Ov;K@ulswvv=;f(6o7sZUDz=yL49i@Vm1zwB;Q85( zB|_jIRW$Kkq|3z#dneNy8rtP?r*oh1@q4*0Ami`+bo}5gd$9UyfwOgpOlN(IRR|#p z~j321QYVm5Ce^-!(4AlFFjcTAO&glEe^&o0gnnn}_w+|HoK!F&yVuG)gHzQ;%J zWkQpZ9`3r_Bui3t`%T?hqSvAm68TxO5LCN!o68G-voO9K5B6AvYDD0fBju#mVRVg- zAGWnp|70Pu&v)>Swq3PSwbl=V1j@3_Ds&8Ad-7?2ves#=0hz_y5k{R)ezFx3{5N_+ zWMNxcfnq7sIQwzCw69%OD-j0IE`-$fZ?Ku$;dAcAbw3RFRBPb5SD!XB@ij`_7!q?n z3Gqj`+^v6ts*Hzxp@X-0ZmeYcy34_{E1HImIJq{d*2~@(86M->{yrs3%;-L(GKft( zE6VJm@^eEHSe21~w)ZzboASM(RjQYHrDt z8nTFyrg6E35GQ+7;NK43r^LRey|jX#lt4RpJijf>8J@t|wChczwvyxFwBpry#=x$7 z5#q4E3H%9yNS#m7xmSk|zHf70Q8_!*>-4K{i%`B~bVCrbiC{q_4i0&>_E5LwBaWl3UC3D*?y4 z8Jegt%l4anTRxIjTl~FTqi4!8a$Bog1>x)7P!zv(Q%%i2>Wrn0#Wk72;|H)FL9EKR z-;zBmo}-Ib$HRr(g&{J_0~}AS%eRwOx7Pj+GxuH_&4kRvU@Jvvxn*>Y@yC&Bm3nSr zMme$sW7NP0pT$9X)@Nkro$Y$6>vtOONpL_ldqR3_Be*Zxdoh+Vv3la#nMls3BaVUl zvLWMYIl3>Y%lmm{*)gRF(4LI4n)5-92*$kDMg6Qf8_eY$R#VoE%07_eeY%N};o8xu_xm69UNk-%FNzSwDsHtmTrnwm zY!#^z96al1CT4eGXuFp^*7}r-Rm|xrDga3#0PCaU^UaCU?z@@BM$r+`)x!mnn^-bi z25#NW=NXFROs|I$i(lucsky0lF}Eu7q9F@v?@TFcC^`{-+%r*}R&9oQ-$eo%K>Tp; zceN+#wA?us=$ua%V^|q!+pg)TDf6n|m7Zce2W?iDPcu-BSN+l3gY{TwqD$XvrsM^% zN|!9&?s$_xhN3@B;X+P=P_tHlzB4_N?5RqQ=T@YRxi5Yw|Dg!reylI#ec>&S?Y0rL z;(JkU_}xo458TeoLdE{|Yc##q5~SyMx`+9}M=kabXXhoLX@5R@0>s-cU#)h+oVx|n z4f*S_EQHp5S$9LK2omyr!<9SyPK5HI=g~&nUxsYPJtKU!Y0WRhi@>(cz+el8APLhGCS|4B~i zuITDd2-(bshnCE|6O5r?V{;pi1gqv@;D{7K_u5*}gYBzdI@b(re`_1iq0Q z|Nc~a5uF&UetryhFuw9wu^7+5_x`c#K=XJE$r(@4$ueCQ()dqm*ElpU@@QgTEb>&!B+ILUX zdq~Z1TUt^3IHH1PO%L*XAy3hFr;7-xMpreC0RuqnGrC!72I@vv!hr*d^i1Pvk6W5w zHj05-YpgT!^y)oF((;70^We}IK9`X*S^V5lD@ncQjnCZCFG4P!w2iMUU76QDr(z0|Iz%XxWhe9<+E*8bsq?TAj_|>5 z{g4qDY^`@;FE3mL2UEBPdAt0y&g380IvYJnMTKzrt(F4%J>HJu2<^ARkoW{=3kJO- zbr)KtUmcX9mKiWNk{9@g+)ui}ahi}C1bEH0r_~>=7I2*i9$dfAGeo@6`!x0q^2td4 zZc3)ue=nW)`Zvm%rOJE| z<;vYp`I?MFZoY4qLOxb$P)iSvJk?{(6suH$b~-v$Fn%g6Hz-g3z)EdBiGuSIk9S(^ zzKG;q8!J@H9>igH8n^YOf{FV0_lW#iTVI?<<0(bCXVPqhZ+Ay!3dWc_B)2LKE@IyVJr8PB z3M=5`m6`2dS;^OhQ#f$#(8_Vk5SDk6)? z=3Q_}^`N;mjMwgHT%flZGeDye70UY}=2Q154fe zh5h0_&wEZ4)%<9`R>ctlO5(nU2ss~?cmP#_R31BTIXpb-P-=w(G4&{CPItSe{87UQnGP(ycMpu0;?QPTOj7DD&`i2q=yN}LN zp#5f(&oxuv+7+La2}XYCE$MozP9OUrf=hTYt@n>I)0%rTR)&YFuL8?c{@4?m*HzWZ z1GV+8#r%BjHd82JaZ8M1h!4Ba9eKbcF5b8`n5CJGUCUYM(#yPIqzTP;;dH>*fB5^M zb)yuzt5=Q~#7xO{p<97MORHBxm%aGm;G*!#*D`0%9~2m}>#*wiwfO>rs9z2jU)!U) zUEeHIxBB}0d2)_teIWKIWUte)D45#jG8jqKk%wO%`wtDh6?;wH!-HKQd$USjvm3~m zMzN1)vBBKwx|JfnScH#03t-i=R0V49PA)OA;QC;Q^=~gFCg;X5;zE-jqDn8u`LM9- zQo%1iRCfL~VJL;hZao`oV8Zp;9&-Y7R^J^Tp+d^vA9I7#Ve#R%&}m{eQ0gm(M(p^d z@{=y_-lM(It%f5jMfndVpb$-2Fpsnt*ssI3?t1;go}z5)Sr&@3jKA2^N(t8vPMFHc z!<8$$pR3wTH;?(?96fgg0opckM?<+-?0oCx%I%5wq5(M9!}l0@t9RAuzY-A_)GNRL zIDoAY?td|kC(_R@D>Ro4#%O%!S?XxGQrQ(P0w3vyn zQ0kujGT`nmp0<4H)|=maw@OI)VcnBANSm-iAh9PL1Qk{4%afwwVA~xbJL6iwkFlR=sbj&= z-CY1jX}LEob;sO{iWxCvYx)F4(SLte<-u;cicvsoeJmf7EF$uY9p5Y|8tI-ODU{eQ zb7(s1e)AWjKySCj5JR~=Xu7OzGI&|^rtVi7&hDvH+Y{GgB|*t~F{_oSq5Z~C)bnhk zql$!;2Pq;`zwptoz#x^4I}Cl?8eIYmNnvwB*}exl&q%Y1n0t3h$y@9tv03@i6zx`S zx1~$PhDf^q?9S9YyW^F!`X0_9QHz^}q&`b#>4B!O1Y{&v7}Ak7C00-!91S(llUg5RcM##>a9M()(FPyhhKIV6NWGE9E3A_ zX4^OyC8v{v_tlHv#&N#U*%;x9?NgO~qj3^2nCVKK&ZnqNw~M)JBR9 z9IfsB!AB2l932&*RyTRvh%7C+p z#^zw{xiG7!<$ueC_*mNw8yERhBlyTh1BE<2u3vKRYuu?g-|#L-Zy)|+EktkO^cu$S za6o!YxCPIK(^L6Vx`SFC-Dm9OTK$#REf4#p#^M7zlzFxt&sxQuHfFh?{Z<5?djZnD zu*0p#O_g576EtDS{BHr`uJ9)EwXx@sT*o_${#=KeRkJSNImvdSz+{H=B!^f{gw=5b zrug!{ehR4W`5$TqOScL)&lCPOknW3p^-ZUCQ~I#Kl`OWO)gj8~`aOo4f|Yz_q$|by zvf=cQMMz#)ewk-Q`NS!-vlk z=xBzNq2tDQ_D;(!zc-{4i(uyFvN%~MQ$Zt3BkS0zmaI3QXktu!VsEOf^@7{j@}?TZ z62$Bpk3qczNncnaR(W4Ps`wnD|7f>f2CK~djvP(hh>Oq^n()WAaAixLvoHROC)<%E zVEEF;rQawPyZL+-BYN8O?luk_e=Kf!MPB(}+mNg$dXirwE{QL}ETek+PSwEVGZ>}^UC+$6oM6xvf3q(F$~UbGZ9rSXfxH)BGCpG`)|7w4yLGJ zb#W>{W*+ni8N*T9Q%qtAmW3-BcBny$q4%4?H4`a@&Qdlrb*|&?{Fzy^zK2`?lrr4q ztA;dnVxa*N=!3(I7yy?|i{mT5k zHGHBcb^HaRwmS^|`NqcJ1j_^6Q<9Bj(14BwJ0)B8R!*Ej5(ZuYzpoSbzH)J&sszRP zWtzOo$w@Pw!gTE3Z&<|XB z^=EL~iY?B_IrfK!Vii6&^;Pphb%F!Og3bX(eySq1VO%7(d0~3hC!oq`1nnY9=Fxqp zOdPJty(|u#+h7*+=zEn{y-8)Bj!*L{v!*J_c0YMzVAddny}3+?kRp%1>Eb60MzEv_ zrRhAQ1|O`~SM>mK;BT~_zMx9`^w z0+Oi`f1*EHww|7;q`ak0rij>D?-=IKaewzbPw$w2^9@QX@`RgDVsNy2L7tQS>Mt{2 z3vR0?B;J|EUtV2B?o#6AMmtcCHDV0tmaYP_@P%*YX-Y-d1qv|*2g%_uRhbP zy`mM<#SP<(aBt4-2FO7iYdDmz_AcauyshmA9;(Q@A$i9QU2>aL8iji0wvd%w8VlSt zs1&4idYue5SMG2t!K^WvVub^ztD-fTB$}~)-fDoJ{f6#27T`;BgWPgiq!|et6Ap$b z{~Nx&LeRLJrSzpUsqSAGvQzAJ3&_`DI$cf&&pl6RxN^3-I8Ywr>p5^msy^y?vsbVW z)*z|bRldzz%LJa2PLEi5&0VRw(*7^@KK2$a|1@y$!}po8(LFLnes^#0dwnN%NJ)xX zbt&ydbx9^4(PNrq%%Cy-F&ON74AY+0>YSWPBeNW$>SD&%!o) z@eKK-3-V<iT1r7)m|dZsu8tFmU7QQobItmY=zR?zfNfaZ?EjMv1cku8qsf z%eu>(>|*I`I9SDki11>=pQo-zi=QSC`%j**`44-UH~fyg?XqE~?v+Z#R3Z;o&A!nG zl(5S?7@0X19bfFS&NSwZop_1#b=-|h9Q67l+%Oh_6`Hs`nRoi~1IDUmzfKGk+Kqmo zgvwOG;(f{q#V>f*?ZW*p$r0t3d4<0GBR@&FrWP~gWll75C?f|$d~q2V;?t2h0lEid zjh+W8VruX)02P6Zc`$5$@MP6Ej#wurH4O!KyC{#;i;DSjRgaDLU?CVQvZFB$6Bx{x z-sWj(Cm}O=L7H%Gl`waLm3Fl_%wz^nR3v8g1a<7rTPEu}zlIMH2eNR{2MFI7``$}{ zvCiJ|sUvxwT9x{qjHCi{$@bzuxJ(ZYu{pxJ@Z44Y*YGA;t@aBG2gM09)u7Hb|iNd|}hy}OqUeD_8LPwu)E&r~GL{17IGS1B3C&rOoA2ad3GEiPP;PLXq}YbbxgdB1}~p4Q~?8G(1+;rF7p%{fefmCnam}tsabi2k$O-Xd zy?Xul;b#o7@a^Jt)*&-c-tTS^E}_k&mr31-9;hIch>qN4Ik!M1>QEt@0txfD@)<_H zG?W@2AUaO$C(bdri&uzf!(f2n-U-C~nW&B_trfu%XIcv-JOy)|xl_jlsfw$NyQ z+`O9Kc&{LA^k2p0y_~C`JP$Pd_Fv@aIhapagHeZDmCFFAt(SMYuRs>4S{1zV(1n@B zz~Y!Hgrz}hD)o;knRb0T4A`!*-~%G@|@PQ$!>wc?oU@fW(7cYkn@nB82h ztx!Sg5{Gp(iq#MJ6KUI}#rPcIW8^jq!mxxt6PFbA1|3O{Rjh;c|9z}5*1o{zYOd+C zbYMtl7CL2S6WO_&88^++81nPy_)Aar{jl1cJG|`Q1BKemM$>8( zjQ#J>@?|_L1hON1KHari%ZBMbj?g-2(lJ+y8tP3qQnk$+j>fHjeUuPPuaE2^*=S3m zzC*C5cYb*ZYEfj&5b6HkZZ>MLr0Nr-8kIMy+1jO!j5 z_e@2e92JKBri+doZmgh4$^;8=tX6WUP*I?~VK7fdSi^0bIdAV2kdJ?fjqx&U}W1o)rbq+KH<;cbolV-YHe9Se-5}>@a>BaVKb}vxgxyNN zpqTa4Jwq!Udk1vop*K?a0ZfWo7N+610p?S--Sc4e$1k_;DW;9I^-{r}PIqZEHmJG< zt2T4Z*1_tcmdo6YH;FIyy6#_Ti*{aAo?OIJ z(zZ3&7XlyBui&o7zNGeu0QY1$zg_7&6N;%DZISZR23-L2fF*0qZYh7a4+5lPOAx^i z;3PSMKo#K3zKWA#V`b-p9VR7{Tn}gnM_y_37uP~DDJqW$i1Ctq%+mSN*r@!!UH}Q$ zH4#ETNNucdvNT0wo@w7i%2J}5Tk!1r6kam>y=E!MwKRNlx{#6I-ROI3 zV1+R^iv}Zu%Z&~K&;k-*TCeRb0InVX-V0utGX-o<0~{@^)1|e43IP)gXO_k$FtuuDS$fnkn#v< zw+VO>bBm2Vl`ZXJuKzwghCUT>!4;La_zvvQwfT;uq2+$ZDQEoo#19ln3CBn}iPZ&y zbH1Y?`H(2AGV*hWg)U~`B0E60v#63S7%Tn{%7V~&(@paK0J&R}lt~5&L5Eb_OqIn5 z+(&-H70h+mA$<*Lw7BqP97v-(##9NUrj)EvsIxe$fF!hhK(*-OcNqL0xDOOia&$!h zR`lCl?y6Kq%Kij#aPdqX(sP7GJZVg7pPibrk>JsN^jllaj)NDVk&R2&b7Rl6=lpfQ z)=XCLPFBoLYZJCX0!Y<`gp{l5LV5ObBLYz3{96p4mR3r1#Q{|DHSIK}m zcwKUcepl&MCjJ6#Th#+fz;(A-3T>T*PkA| zFrI^k%nSe>T0tbru8oStw6yu59jWIoC{8zAe`y3x;5sbWdmg4sIc!97 zFdol~=FLPX-&vwPKAlxiQGW&(mZ?)Zl7Lr-T(kTYZg;T zs#ynHHe1!MO+fL$@Qw^Hw4W>aeb^Urs(v$4qqP?uA%9zH=?nnsxNMuH5^gPm3Q}DBoj5YqcFZ~to-%a>l1)fNIyw)kz-XpE@AU-XOKU@u%nkrU z53vldg1fF<{1zIK1D0Q4x(|{$fR&bzW%hJN92eb>&IW5K?PuU|0QM_>kp#HTmp5@< z65Pz+p?PjTlWU+d{7N_^X)Ka+3_vLSyh|eo@0daVc-cirxdGO|d6PK-Al4c8OVI+< z6Rmij~rOP&#rWFMW*}^Isrs^bwg-2|5I0wuC53WM%9GL$u)SO%>Q|O_!6r zqoEwi%SZ+&_SrL%v4Z>@d2Di#wUkIavF#jS%}FnE0#6AKk>mh6TjoTvYiZ+Z`A$!k90vwraf5o2 zjFO}Q$LVWTq;T{qisq;ZEXu_dkTlNgnAprufbz1Tah>(+sc_+GdKYttVY#w7Np`#K z`wQB(vS(BPP+M_$KV0GL#DkQ3J``khq{m5$+~?`;*m(H|Td4%DY`8`a7#mK!N_!Bz zb|wGjadU~AX@sD>>qFA?=8+bPlTC$1ED21vbPSx%wKLo7-3c74lzlsp5`Fh%^hkh{ zYtGbct0Si#Ws_uU8KPb~2Wo-Hw%F@|!1V7qK^zY5s4q?OZ$uvJQphJqES{V;iv{T~ zYp7&@*g^muDO<73jLL3c+XTBDy@xAtQeo&$6W=je{cN)4NeD0e$#I)XHj^^~07@2e z`G2g35Sj9VI11nHOk4}3`h>B6N_A^cZZb+huDnhXcIE>d=FIA5uUA=$X3V6;a=zhN zS&stiEk(ZuB*_>{mnxpRu9S9G3}pmcnypa)0C8kzbFCv1Bs|`G%Us$r-N(n80Wnem2W=@b^*oLa}HWzYq%{*wDjH!}4gu-Z37 zaKe`%MDNMZ`i;_Bu+}55oxhWzu=Ur_O?Z~#9PMyskML`S)xJlO#1HSN;jvESt_0eR zsWBnt6R8|*@o@7-hj(d(ZL%%Pvpl;vp;+&Lj?~FRUp%W4D-y4@2 z4d(RnOkNH#gY8OwY~@mWjOVfE8jcGMZNk~V5evZkvMzgoDap|7;hIf*RE}ak#%!dN zBAw)G6m>pJ+0{nQL0h!C6Ds%`0C2iLm71{GF80#_vk@`F?3G2^X2U<7SiLvOz%5LR3 zqPsH;-M)HV-gj9ki5g>K2O9zUKY<;eoSut92KM9oTCRwaf%osQ;5&4lp;9S)$fKC>tF;BpZ6J zly14Zl_i>fue3-JjaYRhMw%UY-kjg~VZ+GBWqFFd8n8jCaKbo$Cpi%m z=*nc2Y}dy*wfZuao*``qTGhUQ)|06`q8TbU*sFxPhO{Ihs&IWx%LLgX!v(I_e3;{a zztheTIXaY^=f1Hx#l1+Hp~y%Dlmwu(8<0vt$o-Pw;O3i&y+#27scnb(_Meqxh4t%{%tjjUzOtGca9#7GS(dC;8Nk zT8b10Ce`#LSyOEX(~oEN#)2nGzw+1Ef2FtyHY`2zk~#@ts;OaC@lN@<^16iVW0-e7DKbbIQ;0Pu>o1^ zyp!w3k6+a`vFeep@(f564*Q4;2NjCxU@A34ijw#RCn6zcU3@(K3#WlKyYL`eo!@P~ zd&cz(`(8{<}-!K}7w3oZP$@24S`+~S+?T9k8 zDE+bofjcCsQi;anI+Uus)t92ydBB%M{bt|JFAB7Y$76OcIW36tyKVUB#0?5g1&%5^ zQF5qYErxU%%IU8vXSv>252mGKXIeCpBAq@^?S!wz7z~?t{jwjot`uZ@92sdtWkwhi z8p=&>90Cg>o@}wbt_OZJpiw~iWy!5LVfNXB$I`yw4OP&b@bms$nf>XZuv(tJzrs%Q zMd?An6I`x0@gExL^NEVxC_Ck5h$UFUy1f~f=R1J$JrCwyepAa?%+Hje0(~nEnrQZ6 ztFrz-3(1F|%()t+w>0)FbFLo~g&7TrDA0cM{haqp5exf@YP0tF&ZNTa8-u_I1 zY6LWbtC}NkuWjUj1lDkBs*O%HrG-+{wjrp1)!#ZF{|WETJCQGB+|3wZVQ1jhIDAnv zMR<~Bk$Eu9t4jLh(bsGFT9do73aHA6Z_m4I-IGA4qvL%!;3h3#wEe> ziAJoAYZKTHV)3nU-Sex$o3{NQ#G7Eblprt5?x4m-$ivgc$o2sbmDayrNVR#*(1XC{ znGbw-@z%ZFz4AN#z|HcHE!sEz-pSgq>PDEf7hXW@&(8T&1=#-;huh^ue(l4&OQ6-3do-MU?XI)YOpZB^bs=h+t#pfRoRjWt*PD!ZQe8dAW=neuI~kj;tYkg}@REQ?s- zl@R+da&-81@dE@p7K2CfqB+S>kw{zX2x&J~AAXHRhT|zq%#;aPJwE?Oe7i#9!2^(1 znTGS67M$z`!J6`6`f=oFw#|O4o$OKqWBzthp!X%|>)8BDY;Q-V_CrP;UybY_j13!v z;9qWd4B9rbndjltmP#cWxe+h$wV-6cs(!SEyt9!RoJTEh!%0kmW#J)Mzz0j1=*{3J zxsXF#JYr%rrl=>0;cCon-LPJ8Ht8ErOPF^SKF|$LmgvR*M^@I>j$W1B4NnTDm zWL(T5$@?+9)eurjSC+)LP5Nf;!2){l1hM&{IwNocEbFw}a}6v$U@dLv zQW>TuZteWwUc4f}91HjNt8@6%{@gJ`>aC-Q#h(Bj@R+}oAvUqA zuy+16;xPzzxSc-PwSdlQ>nhB8GCgKDKTM^34nC3x-VF8IS+V@|i?7<#BdUZ1>zT6h4K#Wo=ZnNP@ z*34zPrT@V$Vq9nT$44Xjv?oY!l4_uIt_j|#=wJE#+)Y*WAY>QUkAdq*T9fw3edB48 z{2=CEzARuY{XalPjOzl8tPf>=H%0fKCtbHoUrJ**&@6>NVp3a@EbgOv+MiAkC8{*|nz^)J6#yk@I{-s)mmq>C6v z`PpXn6XRQlqJ#{=E+s9Hv47(y_Ju*J@91zybL(q7q1KF zNKJZu`Pe-=#T+J3>bvwgiCL)UvX1gr3-RFh=693J0q{s%_#qpMhu0tZiM@XSr^E?A z^3;2T7%0pB&v^dyP0eC$N~V%6w8!^bzrx%;RU>Z>f${l^GY5rsX%@B5=f?`Ci$~aU zv%GaC$?Eo(->8uC!Wt>{&o3{tW*OV6fCDYx2^%?+fiZTGKTD-CiY9h(aBTjynyDNM65=2z#F9@*A6s)oNB8Q|P_l*G0^ zbT7j?Vsw^h#wQEqP=M zca6=`3W!XBT8Qs-L$TQORDMGW3{@{HgO`1LiyJDCUZ_y}^qTHlIIr<|e55U;@70@N zDE!xm>=pbs8J0L51}9vz*`7Ae3D6UcjQo5PG~-x)AL3QATT)%;kHJuDe4r#e=_b#N z;S!vBAcT{x5DBt{wkT9r#v;N9;y-e72)#)g6LQpG?N@O2gLj>N^i?vj;~i|loc>kQ zuP;S&a1W)KLpV%0=w?*aDYU*-6 zS3s=qf}LJa?@_>JLjj5Wf6b@5ye4lIYO%Mm%3ppzf7HgJfEi@HzyraP{=Kk5s&U?l zdf$Zh%=E0>8cjz(F>sJs)QS2j+aYsX3(*bBgCG_GvuhXkm=MV^IgBO=~`f$MOF^Pluw756g16ohmA2369*$&*@F{QDOK+4oP3f0G-BE8FXz?L zv-i=#&w(>qsj;8Z7YoRdSMe#Xb~U=RI+|o+d|P`8QWG6_VnDczsy1YU8}@%ce`9y~ zO)1OlcdS$)Ex$rjk8T)ySksXG{?1CA4R+@%J~9!)O)NenL?V{(waafe!u+~czr{$f zRtcOeAD^;!9%buuSX~x2{692}deDnjj#2wowmW3}5rFS!xyq?3xgv1!zfJwvbrm&z z2+V*|ciq`&ef7+R`+$C<#kTO|Sg&|mxc=#OLzJa|O?nTZs=uBd_XxuK#889k_gea8 z4Tp5Lmd@YQSW`E~uQ+{-NLxWtTy6{5O5zb!FBbyBvMg?_SOf__*XoWU5kouMrMD6f zYF$rBOTZ@d?eF+tnlrqM+opnmd@T1lmNDj35Kj-O@m{fT%-a_z3|ou#QtxO{ zma0vnCV?KLORu8Er!;otZ(qBwOI>c9E6H&jmA0u__h%Vf;z$S zQQ=x~CM)+uUpDF)itInI#f=ktl>cms45;_YTg^@`D|m26Qb87(tcnW|f~G?!)m8Bl zSdCLZ1!pTc*?O1riRujG6ZfqeEIU4q7rbb8{tN`h<-$sfyZ*}@`9IhXSI|^1l`bWc zIhOsnt?SNSihaanQ>G|Wus)gN1O315$e4eL{0!1#$R0&phK*yvWK>2f`Jcns)#y9DVW4}u& z!0j@zafBj!G|({Ewc8RdI!2*I(Xd+y?QUvPXN{T3B^=>xK#|N*4{tpo6T%}B*Zh9!3b@{)~_cW%ZR6v8_F+I{iH5nYS zWCW}8$4Ax7K27z`L~yHxFWe`3kJtAy8!ti}8c?rh{=kOel9{%g?^ifOEf#Rkq2wpQ zO|qsLdU&In|AXnBubFwLAY(cGPAJx!+-<~xQ6OUNEmR?d6~1)ULcN<+x-_T?dIU(5 z3~ozE2%Yo1fmOQWsFGj3Xx&S@o&#xM!b;bXR<_SI5bjrsQ)^FfwLZCd2f8_^vvPx| zpK}AC^;$WocbwsWpt&8)8w$R6hRIDbu*f}q_r$?=QK0}0k>FCg*7yxyv zv#?71WC6mJLNMApH75i{(T3|-CEE_^!8NS;QOM|YjR7H0D0gQrR3fs-bG1~C% zOGG4+-ZjPq)f>hg80esTJ0Z=*$Anwu(R3wZTfusM-_J#EBJ6pZLU-~4o4>=>TJ-$& zhnI7jl*ZXDJ`urPh+nWS{_wH4lI+6@4n|cWtlax@@6-ZOr_^!}5*iuL;9j@X3yfQk zICb0Tw9%Z_dSq!I2qTFh4^bGvcA2B^qX{rm5TiZ8-=91Rb^xh|vyl`@Jf$F&-+rQfd zbxJIEL4bD*o4VDWp!f(29;Il?{T?@Xf1g0qfcU4)CbWyYVqQ!t-By#+$$ovc>$~YV zp1n`~%*npbnM<<1zzRIrMcU|8@>?!;R=ymz9_NWT!zNk}m2K(6JOdi-*ArduedGAG z=*3#Jwu!VMXD6elZDTGF#@Rh`z7bwfYdjJ-RgF*TFS1Me#n}US*&-e}?`_IG0d(8A zprCDh^Ut@MqGpQ-j&8+*z5xPBYCc_ua>*!K7RUb2ir z;B}^(&W}|JL+V}B?}weW7PU}d<-6kgq~;a7(mBUXBp*zy)`5>ou%autw9{VYWqDgb7uIZ*lA4i(fCHy1Ze-FwYFgXw z0>b2FBn3%so~^TIf82nfX_1WAI*wWxXre=#jLT_#V{DO_dMN%XU4VTzl8^MvABUEG zY0V3Q&yx(xSsC10KAG=F8v8m5XD_4ZM3&}G)&gB8688CDuy0A*{kNj?z4Vd%< z-(f2Da5iGdaiMgT`+)@VJf+x6ThWE*CN7t|?M|kJKuHG1`m)W`zm5CaceX;ekU1cca8p8YJH<-zdNX(5Lz#cEatjd4)6w!({NT=jiM zi>4;*roB4>Vs8vaol_+fz28Iwxb@kqDX##VZ))npTSwPFHPWl{Oy(Ygf9?a&P|;Q{ 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 008/183] 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 009/183] 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 010/183] 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 011/183] 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 012/183] 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 013/183] 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 014/183] 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 015/183] 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 016/183] 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 017/183] 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 018/183] 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 019/183] 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 020/183] 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 021/183] 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 022/183] 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 023/183] 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 024/183] 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 025/183] 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 026/183] 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 027/183] 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 028/183] 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 029/183] 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 a9e269b52c8578301a1f098f1d175e0148551cc4 Mon Sep 17 00:00:00 2001 From: Matthias Platzer Date: Mon, 3 Jul 2023 17:58:41 +0200 Subject: [PATCH 030/183] Added winston logging - use logger.xxx instead of console.xxx - added express middleware logging (using jsonl) - added LOG_PATH as environment variable - more configs postponed for later iteration --- .gitignore | 1 + docker/.env.example | 1 + packages/server/.env.example | 1 + packages/server/package.json | 3 +- packages/server/src/commands/start.ts | 13 ++-- packages/server/src/index.ts | 15 ++-- packages/server/src/utils/config.ts | 25 +++++++ packages/server/src/utils/index.ts | 5 +- packages/server/src/utils/logger.ts | 100 ++++++++++++++++++++++++++ 9 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 packages/server/src/utils/config.ts create mode 100644 packages/server/src/utils/logger.ts diff --git a/.gitignore b/.gitignore index 9f5ef2e5..3ae87776 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ **/yarn.lock ## logs +logs/**/* **/*.log ## build diff --git a/docker/.env.example b/docker/.env.example index 80fbc3be..8e66d25e 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -4,4 +4,5 @@ PORT=3000 # DEBUG=true # DATABASE_PATH=/your_database_path/.flowise # APIKEY_PATH=/your_api_key_path/.flowise +# LOG_PATH=/your_api_key_path/logs # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/packages/server/.env.example b/packages/server/.env.example index 80fbc3be..f1fbf990 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -4,4 +4,5 @@ PORT=3000 # DEBUG=true # DATABASE_PATH=/your_database_path/.flowise # APIKEY_PATH=/your_api_key_path/.flowise +# LOG_PATH=./logs # EXECUTION_MODE=child or main \ No newline at end of file diff --git a/packages/server/package.json b/packages/server/package.json index eda69322..05abd6c9 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -58,7 +58,8 @@ "reflect-metadata": "^0.1.13", "socket.io": "^4.6.1", "sqlite3": "^5.1.6", - "typeorm": "^0.3.6" + "typeorm": "^0.3.6", + "winston": "^3.9.0" }, "devDependencies": { "@types/cors": "^2.8.12", diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index d3efc1eb..9bd1d64b 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -3,6 +3,7 @@ import path from 'path' import * as Server from '../index' import * as DataSource from '../DataSource' import dotenv from 'dotenv' +import logger from '../utils/logger' dotenv.config({ path: path.join(__dirname, '..', '..', '.env'), override: true }) @@ -25,11 +26,11 @@ export default class Start extends Command { } async stopProcess() { - console.info('Shutting down Flowise...') + logger.info('Shutting down Flowise...') try { // Shut down the app after timeout if it ever stuck removing pools setTimeout(() => { - console.info('Flowise was forced to shut down after 30 secs') + logger.info('Flowise was forced to shut down after 30 secs') process.exit(processExitCode) }, 30000) @@ -37,7 +38,7 @@ export default class Start extends Command { const serverApp = Server.getInstance() if (serverApp) await serverApp.stopApp() } catch (error) { - console.error('There was an error shutting down Flowise...', error) + logger.error('There was an error shutting down Flowise...', error) } process.exit(processExitCode) } @@ -49,7 +50,7 @@ export default class Start extends Command { // Prevent throw new Error from crashing the app // TODO: Get rid of this and send proper error message to ui process.on('uncaughtException', (err) => { - console.error('uncaughtException: ', err) + logger.error('uncaughtException: ', err) }) const { flags } = await this.parse(Start) @@ -63,11 +64,11 @@ export default class Start extends Command { await (async () => { try { - this.log('Starting Flowise...') + logger.info('Starting Flowise...') await DataSource.init() await Server.start() } catch (error) { - console.error('There was an error starting Flowise...', error) + logger.error('There was an error starting Flowise...', error) processExitCode = EXIT_CODE.FAILED // @ts-ignore process.emit('SIGINT') diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 15762a23..73dbada4 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -6,6 +6,8 @@ import http from 'http' import * as fs from 'fs' import basicAuth from 'express-basic-auth' import { Server } from 'socket.io' +import logger from './utils/logger' +import { expressRequestLogger } from './utils/logger' import { IChatFlow, @@ -57,13 +59,16 @@ export class App { constructor() { this.app = express() + + // Add the expressRequestLogger middleware to log all requests + this.app.use(expressRequestLogger) } async initDatabase() { // Initialize database this.AppDataSource.initialize() .then(async () => { - console.info('📦[server]: Data Source has been initialized!') + logger.info('📦 [server]: Data Source has been initialized!') // Initialize pools this.nodesPool = new NodesPool() @@ -75,7 +80,7 @@ export class App { await getAPIKeys() }) .catch((err) => { - console.error('❌[server]: Error during Data Source initialization:', err) + logger.error('❌ [server]: Error during Data Source initialization:', err) }) } @@ -614,7 +619,7 @@ export class App { }) }) } catch (err) { - console.error(err) + logger.error(err) } } @@ -792,7 +797,7 @@ export class App { const removePromises: any[] = [] await Promise.all(removePromises) } catch (e) { - console.error(`❌[server]: Flowise Server shut down error: ${e}`) + logger.error(`❌[server]: Flowise Server shut down error: ${e}`) } } } @@ -832,7 +837,7 @@ export async function start(): Promise { await serverApp.config(io) server.listen(port, () => { - console.info(`⚡️[server]: Flowise Server is listening at ${port}`) + logger.info(`⚡️ [server]: Flowise Server is listening at ${port}`) }) } diff --git a/packages/server/src/utils/config.ts b/packages/server/src/utils/config.ts new file mode 100644 index 00000000..a4a33937 --- /dev/null +++ b/packages/server/src/utils/config.ts @@ -0,0 +1,25 @@ +// BEWARE: This file is an intereem solution until we have a proper config strategy + +import path from 'path' +import dotenv from 'dotenv' + +dotenv.config({ path: path.join(__dirname, '..', '..', '.env'), override: true }) + +// default config +const loggingConfig = { + dir: process.env.LOG_PATH ?? './logs', + server: { + level: 'info', + filename: 'server.log', + errorFilename: 'server-error.log' + }, + express: { + level: 'info', + format: 'jsonl', // can't be changed currently + filename: 'server-requests.log.jsonl' // should end with .jsonl + } +} + +export default { + logging: loggingConfig +} diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 005f4a4b..55529afb 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -1,6 +1,7 @@ import path from 'path' import fs from 'fs' import moment from 'moment' +import logger from './logger' import { IComponentNodes, IDepthQueue, @@ -227,7 +228,7 @@ export const buildLangchain = async ( databaseEntities }) } catch (e: any) { - console.error(e) + logger.error(e) throw new Error(e) } @@ -595,7 +596,7 @@ export const replaceAllAPIKeys = async (content: ICommonObject[]): Promise try { await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') } catch (error) { - console.error(error) + logger.error(error) } } diff --git a/packages/server/src/utils/logger.ts b/packages/server/src/utils/logger.ts new file mode 100644 index 00000000..277cf3ad --- /dev/null +++ b/packages/server/src/utils/logger.ts @@ -0,0 +1,100 @@ +import * as path from 'path' +import * as fs from 'fs' +import config from './config' // should be replaced by node-config or similar +import { createLogger, transports, format } from 'winston' +import { NextFunction, Request, Response } from 'express' + +const { combine, timestamp, printf } = format + +// expect the log dir be relative to the projects root +const logDir = path.join(__dirname, '../../../..', config.logging.dir ?? './logs') + +// Create the log directory if it doesn't exist +if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir) +} + +const logger = createLogger({ + format: combine( + timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + format.json(), + printf(({ level, message, timestamp }) => { + return `${timestamp} [${level.toUpperCase()}]: ${message}` + }) + ), + defaultMeta: { + package: 'server' + }, + transports: [ + new transports.Console(), + new transports.File({ + filename: path.join(logDir, config.logging.server.filename ?? 'server.log'), + level: config.logging.server.level ?? 'info' + }), + new transports.File({ + filename: path.join(logDir, config.logging.server.errorFilename ?? 'server-error.log'), + level: 'error' // Log only errors to this file + }) + ], + exceptionHandlers: [ + new transports.File({ + filename: path.join(logDir, config.logging.server.errorFilename ?? 'server-error.log') + }) + ], + rejectionHandlers: [ + new transports.File({ + filename: path.join(logDir, config.logging.server.errorFilename ?? 'server-error.log') + }) + ] +}) + +/** + * This function is used by express as a middleware. + * @example + * this.app = express() + * this.app.use(expressRequestLogger) + */ +export function expressRequestLogger(req: Request, res: Response, next: NextFunction): void { + const fileLogger = createLogger({ + format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.json()), + defaultMeta: { + package: 'server', + request: { + method: req.method, + url: req.url, + body: req.body, + query: req.query, + params: req.params, + headers: req.headers + } + }, + transports: [ + new transports.File({ + filename: path.join(logDir, config.logging.express.filename ?? 'server-requests.log.jsonl'), + level: 'debug' + }) + ] + }) + + const getRequestEmoji = (method: string) => { + const requetsEmojis: Record = { + GET: '⬇️', + POST: '⬆️', + PUT: '🖊', + DELETE: '❌' + } + + return requetsEmojis[method] || '?' + } + + if (req.method !== 'GET') { + fileLogger.info(`${getRequestEmoji(req.method)} ${req.method} ${req.url}`) + logger.info(`${getRequestEmoji(req.method)} ${req.method} ${req.url}`) + } else { + fileLogger.http(`${getRequestEmoji(req.method)} ${req.method} ${req.url}`) + } + + next() +} + +export default logger From 479a6bc7eb2020abc9d12e1d547ec3d3441f8331 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 3 Jul 2023 17:59:52 +0100 Subject: [PATCH 031/183] 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 89511c83950b826bed24964381eb00c1d21bfd7b Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 22:23:34 +0100 Subject: [PATCH 032/183] Update config.ts --- packages/server/src/utils/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/config.ts b/packages/server/src/utils/config.ts index a4a33937..d81fe7c3 100644 --- a/packages/server/src/utils/config.ts +++ b/packages/server/src/utils/config.ts @@ -7,7 +7,7 @@ dotenv.config({ path: path.join(__dirname, '..', '..', '.env'), override: true } // default config const loggingConfig = { - dir: process.env.LOG_PATH ?? './logs', + dir: process.env.LOG_PATH ?? path.join(__dirname, '..', '..', '..', 'logs'), server: { level: 'info', filename: 'server.log', From ec777c65eac979172af911fc7498be311fb52af6 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 22:23:54 +0100 Subject: [PATCH 033/183] Update logger.ts --- packages/server/src/utils/logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/logger.ts b/packages/server/src/utils/logger.ts index 277cf3ad..1c28b173 100644 --- a/packages/server/src/utils/logger.ts +++ b/packages/server/src/utils/logger.ts @@ -7,7 +7,7 @@ import { NextFunction, Request, Response } from 'express' const { combine, timestamp, printf } = format // expect the log dir be relative to the projects root -const logDir = path.join(__dirname, '../../../..', config.logging.dir ?? './logs') +const logDir = config.logging.dir // Create the log directory if it doesn't exist if (!fs.existsSync(logDir)) { From 19758105584ee09bd8e1f431b3dceea249425b4d Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 22:26:01 +0100 Subject: [PATCH 034/183] Update .env.example --- packages/server/.env.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/.env.example b/packages/server/.env.example index f1fbf990..262e08a6 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -4,5 +4,5 @@ PORT=3000 # DEBUG=true # DATABASE_PATH=/your_database_path/.flowise # APIKEY_PATH=/your_api_key_path/.flowise -# LOG_PATH=./logs -# EXECUTION_MODE=child or main \ No newline at end of file +# LOG_PATH=/your_log_path/logs +# EXECUTION_MODE=child or main From 13c4a732eb194267aeba0a7df01f71959bf552bd Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 22:26:22 +0100 Subject: [PATCH 035/183] Update .env.example --- docker/.env.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index 8e66d25e..262e08a6 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -4,5 +4,5 @@ PORT=3000 # DEBUG=true # DATABASE_PATH=/your_database_path/.flowise # APIKEY_PATH=/your_api_key_path/.flowise -# LOG_PATH=/your_api_key_path/logs -# EXECUTION_MODE=child or main \ No newline at end of file +# LOG_PATH=/your_log_path/logs +# EXECUTION_MODE=child or main From 1fc9e9e4362282a3ab8f2a9d667943cb9c82335b Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 22:27:23 +0100 Subject: [PATCH 036/183] Update docker-compose.yml --- docker/docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 97aea017..3077c43d 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -10,6 +10,7 @@ services: - FLOWISE_PASSWORD=${FLOWISE_PASSWORD} - DATABASE_PATH=${DATABASE_PATH} - APIKEY_PATH=${APIKEY_PATH} + - LOG_PATH=${LOG_PATH} - EXECUTION_MODE=${EXECUTION_MODE} - DEBUG=${DEBUG} ports: From c0f36387a7ca62c44d3dc1a0064cdff76cf1ac5c Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 22:28:17 +0100 Subject: [PATCH 037/183] Update start.ts --- packages/server/src/commands/start.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index 9bd1d64b..c05c042a 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -22,6 +22,7 @@ export default class Start extends Command { DEBUG: Flags.string(), DATABASE_PATH: Flags.string(), APIKEY_PATH: Flags.string(), + LOG_PATH: Flags.string(), EXECUTION_MODE: Flags.string() } @@ -59,6 +60,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.APIKEY_PATH) process.env.APIKEY_PATH = flags.APIKEY_PATH + if (flags.LOG_PATH) process.env.LOG_PATH = flags.LOG_PATH if (flags.EXECUTION_MODE) process.env.EXECUTION_MODE = flags.EXECUTION_MODE if (flags.DEBUG) process.env.DEBUG = flags.DEBUG From 7ee2b1194d0290d8904d17ba2e884e796465e94a Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 23:05:51 +0100 Subject: [PATCH 038/183] Only enable logger when DEBUG=true --- packages/server/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 73dbada4..c7206154 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -61,7 +61,7 @@ export class App { this.app = express() // Add the expressRequestLogger middleware to log all requests - this.app.use(expressRequestLogger) + if (process.env.DEBUG === 'true') this.app.use(expressRequestLogger) } async initDatabase() { From da1cfc79c424b1ee82ff515cdb84e5eaccd05de1 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Mon, 3 Jul 2023 23:07:31 +0100 Subject: [PATCH 039/183] ignore all files inside logs folder --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3ae87776..533f68a5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ **/yarn.lock ## logs -logs/**/* +**/logs **/*.log ## build @@ -43,4 +43,4 @@ logs/**/* **/uploads ## compressed -**/*.tgz \ No newline at end of file +**/*.tgz From 92e50a676c27ba29f3168f1720b11b4ba1258b0d Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Wed, 5 Jul 2023 16:47:01 +0800 Subject: [PATCH 040/183] add web crawl --- .../nodes/documentloaders/Cheerio/Cheerio.ts | 48 ++++++----- packages/components/package.json | 1 + packages/components/src/utils.ts | 84 +++++++++++++++++++ 3 files changed, 113 insertions(+), 20 deletions(-) diff --git a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts index 9e113505..10eff77e 100644 --- a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts +++ b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts @@ -2,7 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { CheerioWebBaseLoader } from 'langchain/document_loaders/web/cheerio' import { test } from 'linkifyjs' -import { getAvailableURLs } from '../../../src' +import { webCrawl } from '../../../src' class Cheerio_DocumentLoaders implements INode { label: string @@ -35,19 +35,20 @@ class Cheerio_DocumentLoaders implements INode { optional: true }, { - label: 'Web Scrap for Relative Links', - name: 'webScrap', + label: 'Web Crawl for Relative Links', + name: 'boolWebCrawl', type: 'boolean', optional: true, additionalParams: true }, { - label: 'Web Scrap Links Limit', + label: 'Web Crawl Links Limit', name: 'limit', type: 'number', default: 10, optional: true, - additionalParams: true + additionalParams: true, + description: 'Set 0 to crawl all relative links' }, { label: 'Metadata', @@ -62,7 +63,7 @@ class Cheerio_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const webScrap = nodeData.inputs?.webScrap as boolean + const boolWebCrawl = nodeData.inputs?.boolWebCrawl as boolean let limit = nodeData.inputs?.limit as string let url = nodeData.inputs?.url as string @@ -71,25 +72,32 @@ class Cheerio_DocumentLoaders implements INode { throw new Error('Invalid URL') } - const cheerioLoader = async (url: string): Promise => { - let docs = [] - const loader = new CheerioWebBaseLoader(url) - if (textSplitter) { - docs = await loader.loadAndSplit(textSplitter) - } else { - docs = await loader.load() + async function cheerioLoader(url: string): Promise { + try { + let docs = [] + const loader = new CheerioWebBaseLoader(url) + if (textSplitter) { + docs = await loader.loadAndSplit(textSplitter) + } else { + docs = await loader.load() + } + return docs + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error in CheerioWebBaseLoader: ${err.message}, on page: ${url}`) } - return docs } - let availableUrls: string[] let docs = [] - if (webScrap) { - if (!limit) limit = '10' - availableUrls = await getAvailableURLs(url, parseInt(limit)) - for (let i = 0; i < availableUrls.length; i++) { - docs.push(...(await cheerioLoader(availableUrls[i]))) + if (boolWebCrawl) { + if (process.env.DEBUG === 'true') console.info('Start Web Crawl') + if (!limit) throw new Error('Please set a limit to crawl') + else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') + const pages: string[] = await webCrawl(url, parseInt(limit)) + if (process.env.DEBUG === 'true') console.info(`pages: ${JSON.stringify(pages)}, length: ${pages.length}`) + for (const page of pages) { + docs.push(...(await cheerioLoader(page))) } + if (process.env.DEBUG === 'true') console.info('Finish Web Crawl') } else { docs = await cheerioLoader(url) } diff --git a/packages/components/package.json b/packages/components/package.json index 4555a883..81f963ed 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -24,6 +24,7 @@ "@qdrant/js-client-rest": "^1.2.2", "@supabase/supabase-js": "^2.21.0", "@types/js-yaml": "^4.0.5", + "@types/jsdom": "^21.1.1", "axios": "^0.27.2", "cheerio": "^1.0.0-rc.12", "chromadb": "^1.4.2", diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index c247ebc2..63bb3969 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -2,6 +2,7 @@ import axios from 'axios' import { load } from 'cheerio' import * as fs from 'fs' import * as path from 'path' +import { JSDOM } from 'jsdom' import { BaseCallbackHandler } from 'langchain/callbacks' import { Server } from 'socket.io' import { ChainValues } from 'langchain/dist/schema' @@ -201,6 +202,89 @@ export const getAvailableURLs = async (url: string, limit: number) => { } } +function getURLsFromHTML(htmlBody: string, baseURL: string): string[] { + const dom = new JSDOM(htmlBody) + const linkElements = dom.window.document.querySelectorAll('a') + const urls: string[] = [] + for (const linkElement of linkElements) { + if (linkElement.href.slice(0, 1) === '/') { + try { + const urlObj = new URL(baseURL + linkElement.href) + urls.push(urlObj.href) //relative + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error with relative url: ${err.message}`) + continue + } + } else { + try { + const urlObj = new URL(linkElement.href) + urls.push(urlObj.href) //absolute + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error with absolute url: ${err.message}`) + continue + } + } + } + return urls +} + +function normalizeURL(urlString: string): string { + const urlObj = new URL(urlString) + const hostPath = urlObj.hostname + urlObj.pathname + if (hostPath.length > 0 && hostPath.slice(-1) == '/') { + // handling trailing slash + return hostPath.slice(0, -1) + } + return hostPath +} + +export async function crawl(baseURL: string, currentURL: string, pages: string[], limit: number): Promise { + const baseURLObj = new URL(baseURL) + const currentURLObj = new URL(currentURL) + + if (limit !== 0) if (pages.length === limit) return pages + + if (baseURLObj.hostname !== currentURLObj.hostname) return pages + + const normalizeCurrentURL = baseURLObj.protocol + '//' + normalizeURL(currentURL) + if (pages.includes(normalizeCurrentURL)) { + return pages + } + + pages.push(normalizeCurrentURL) + + if (process.env.DEBUG === 'true') console.info(`actively crawling ${currentURL}`) + try { + const resp = await fetch(currentURL) + + if (resp.status > 399) { + if (process.env.DEBUG === 'true') console.error(`error in fetch with status code: ${resp.status}, on page: ${currentURL}`) + return pages + } + + const contentType: string | null = resp.headers.get('content-type') + if ((contentType && !contentType.includes('text/html')) || !contentType) { + if (process.env.DEBUG === 'true') console.error(`non html response, content type: ${contentType}, on page: ${currentURL}`) + return pages + } + + const htmlBody = await resp.text() + const nextURLs = getURLsFromHTML(htmlBody, baseURL) + for (const nextURL of nextURLs) { + pages = await crawl(baseURL, nextURL, pages, limit) + } + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error in fetch url: ${err.message}, on page: ${currentURL}`) + } + return pages +} + +export async function webCrawl(stringURL: string, limit: number): Promise { + const URLObj = new URL(stringURL) + const modifyURL = stringURL.slice(-1) === '/' ? stringURL.slice(0, -1) : stringURL + return await crawl(URLObj.protocol + '//' + URLObj.hostname, modifyURL, [], limit) +} + /** * Custom chain handler class */ From c18e98761af3dd908df5ae6969289c3dc331bf28 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Wed, 5 Jul 2023 17:07:45 +0800 Subject: [PATCH 041/183] modify puppeteer web crawl --- .../documentloaders/Puppeteer/Puppeteer.ts | 53 ++++++++++--------- packages/components/src/utils.ts | 2 +- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index 1331c736..3f27dc03 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -2,7 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { PuppeteerWebBaseLoader } from 'langchain/document_loaders/web/puppeteer' import { test } from 'linkifyjs' -import { getAvailableURLs } from '../../../src' +import { webCrawl } from '../../../src' class Puppeteer_DocumentLoaders implements INode { label: string @@ -35,19 +35,20 @@ class Puppeteer_DocumentLoaders implements INode { optional: true }, { - label: 'Web Scrape for Relative Links', - name: 'webScrape', + label: 'Web Crawl for Relative Links', + name: 'boolWebCrawl', type: 'boolean', optional: true, additionalParams: true }, { - label: 'Web Scrape Links Limit', + label: 'Web Crawl Links Limit', name: 'limit', type: 'number', default: 10, optional: true, - additionalParams: true + additionalParams: true, + description: 'Set 0 to crawl all relative links' }, { label: 'Metadata', @@ -62,7 +63,7 @@ class Puppeteer_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const webScrape = nodeData.inputs?.webScrape as boolean + const boolWebCrawl = nodeData.inputs?.boolWebCrawl as boolean let limit = nodeData.inputs?.limit as string let url = nodeData.inputs?.url as string @@ -71,30 +72,32 @@ class Puppeteer_DocumentLoaders implements INode { throw new Error('Invalid URL') } - const puppeteerLoader = async (url: string): Promise => { - let docs = [] - const loader = new PuppeteerWebBaseLoader(url) - if (textSplitter) { - docs = await loader.loadAndSplit(textSplitter) - } else { - docs = await loader.load() + async function puppeteerLoader(url: string): Promise { + try { + let docs = [] + const loader = new PuppeteerWebBaseLoader(url) + if (textSplitter) { + docs = await loader.loadAndSplit(textSplitter) + } else { + docs = await loader.load() + } + return docs + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error in CheerioWebBaseLoader: ${err.message}, on page: ${url}`) } - return docs } - let availableUrls: string[] let docs = [] - if (webScrape) { - if (!limit) limit = '10' - availableUrls = await getAvailableURLs(url, parseInt(limit)) - for (let i = 0; i < availableUrls.length; i++) { - try { - docs.push(...(await puppeteerLoader(availableUrls[i]))) - } catch (error) { - console.error('Error loading url with puppeteer. URL: ', availableUrls[i], 'Error: ', error) - continue - } + if (boolWebCrawl) { + if (process.env.DEBUG === 'true') console.info('Start Web Crawl') + if (!limit) throw new Error('Please set a limit to crawl') + else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') + const pages: string[] = await webCrawl(url, parseInt(limit)) + if (process.env.DEBUG === 'true') console.info(`pages: ${JSON.stringify(pages)}, length: ${pages.length}`) + for (const page of pages) { + docs.push(...(await puppeteerLoader(page))) } + if (process.env.DEBUG === 'true') console.info('Finish Web Crawl') } else { docs = await puppeteerLoader(url) } diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 63bb3969..d99517f0 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -242,7 +242,7 @@ export async function crawl(baseURL: string, currentURL: string, pages: string[] const baseURLObj = new URL(baseURL) const currentURLObj = new URL(currentURL) - if (limit !== 0) if (pages.length === limit) return pages + if (limit !== 0 && pages.length === limit) return pages if (baseURLObj.hostname !== currentURLObj.hostname) return pages From 607d4a3394b0a3de05aedef72b4400a6d82ed907 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Wed, 5 Jul 2023 17:17:13 +0800 Subject: [PATCH 042/183] modify playwright web crawl --- .../documentloaders/Playwright/Playwright.ts | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts index 6b7790af..6e22d55d 100644 --- a/packages/components/nodes/documentloaders/Playwright/Playwright.ts +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -2,7 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { PlaywrightWebBaseLoader } from 'langchain/document_loaders/web/playwright' import { test } from 'linkifyjs' -import { getAvailableURLs } from '../../../src' +import { webCrawl } from '../../../src' class Playwright_DocumentLoaders implements INode { label: string @@ -35,19 +35,20 @@ class Playwright_DocumentLoaders implements INode { optional: true }, { - label: 'Web Scrap for Relative Links', - name: 'webScrap', + label: 'Web Crawl for Relative Links', + name: 'boolWebCrawl', type: 'boolean', optional: true, additionalParams: true }, { - label: 'Web Scrap Links Limit', + label: 'Web Crawl Links Limit', name: 'limit', type: 'number', default: 10, optional: true, - additionalParams: true + additionalParams: true, + description: 'Set 0 to crawl all relative links' }, { label: 'Metadata', @@ -62,7 +63,7 @@ class Playwright_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const webScrap = nodeData.inputs?.webScrap as boolean + const boolWebCrawl = nodeData.inputs?.boolWebCrawl as boolean let limit = nodeData.inputs?.limit as string let url = nodeData.inputs?.url as string @@ -71,25 +72,32 @@ class Playwright_DocumentLoaders implements INode { throw new Error('Invalid URL') } - const playwrightLoader = async (url: string): Promise => { - let docs = [] - const loader = new PlaywrightWebBaseLoader(url) - if (textSplitter) { - docs = await loader.loadAndSplit(textSplitter) - } else { - docs = await loader.load() + async function playwrightLoader(url: string): Promise { + try { + let docs = [] + const loader = new PlaywrightWebBaseLoader(url) + if (textSplitter) { + docs = await loader.loadAndSplit(textSplitter) + } else { + docs = await loader.load() + } + return docs + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error in CheerioWebBaseLoader: ${err.message}, on page: ${url}`) } - return docs } - let availableUrls: string[] let docs = [] - if (webScrap) { - if (!limit) limit = '10' - availableUrls = await getAvailableURLs(url, parseInt(limit)) - for (let i = 0; i < availableUrls.length; i++) { - docs.push(...(await playwrightLoader(availableUrls[i]))) + if (boolWebCrawl) { + if (process.env.DEBUG === 'true') console.info('Start Web Crawl') + if (!limit) throw new Error('Please set a limit to crawl') + else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') + const pages: string[] = await webCrawl(url, parseInt(limit)) + if (process.env.DEBUG === 'true') console.info(`pages: ${JSON.stringify(pages)}, length: ${pages.length}`) + for (const page of pages) { + docs.push(...(await playwrightLoader(page))) } + if (process.env.DEBUG === 'true') console.info('Finish Web Crawl') } else { docs = await playwrightLoader(url) } From af67d70cfcd0514e3462070a531d539630827d12 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Wed, 5 Jul 2023 17:28:59 +0800 Subject: [PATCH 043/183] add function desc --- packages/components/src/utils.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index d99517f0..39d7e333 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -202,6 +202,9 @@ export const getAvailableURLs = async (url: string, limit: number) => { } } +/** + * Search for href through htmlBody string + */ function getURLsFromHTML(htmlBody: string, baseURL: string): string[] { const dom = new JSDOM(htmlBody) const linkElements = dom.window.document.querySelectorAll('a') @@ -228,6 +231,9 @@ function getURLsFromHTML(htmlBody: string, baseURL: string): string[] { return urls } +/** + * Normalize URL to prevent crawling the same page + */ function normalizeURL(urlString: string): string { const urlObj = new URL(urlString) const hostPath = urlObj.hostname + urlObj.pathname @@ -238,7 +244,10 @@ function normalizeURL(urlString: string): string { return hostPath } -export async function crawl(baseURL: string, currentURL: string, pages: string[], limit: number): Promise { +/** + * Recursive crawl using normalizeURL and getURLsFromHTML + */ +async function crawl(baseURL: string, currentURL: string, pages: string[], limit: number): Promise { const baseURLObj = new URL(baseURL) const currentURLObj = new URL(currentURL) @@ -279,6 +288,9 @@ export async function crawl(baseURL: string, currentURL: string, pages: string[] return pages } +/** + * Prep URL before passing into recursive carwl function + */ export async function webCrawl(stringURL: string, limit: number): Promise { const URLObj = new URL(stringURL) const modifyURL = stringURL.slice(-1) === '/' ? stringURL.slice(0, -1) : stringURL From 636ad5dc0c8209961a5cf22e0cb39b11f1eb9712 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 5 Jul 2023 18:14:54 +0100 Subject: [PATCH 044/183] 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 045/183] 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 046/183] 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 047/183] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.2.1?= =?UTF-8?q?5=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 048/183] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.2.13=20rele?= =?UTF-8?q?ase?= 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 049/183] =?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", From c2dde8bf8f2e936572876984e96f4fc618be9048 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 6 Jul 2023 12:02:43 +0100 Subject: [PATCH 050/183] add fix to streaming --- .../nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts index 27a245e1..c6c40600 100644 --- a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts +++ b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts @@ -66,7 +66,7 @@ class SqlDatabaseChain_Chains implements INode { const chain = await getSQLDBChain(databaseType, dbFilePath, model) if (options.socketIO && options.socketIOClientId) { - const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) const res = await chain.run(input, [handler]) return res } else { From e977fe7d70db31ecec37abc2265e6545135d82b6 Mon Sep 17 00:00:00 2001 From: Matthias Platzer Date: Thu, 6 Jul 2023 14:29:37 +0200 Subject: [PATCH 051/183] Update config.ts /logs was in /packages -> moved it to project root (one level up) --- packages/server/src/utils/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/config.ts b/packages/server/src/utils/config.ts index d81fe7c3..c38d5a0c 100644 --- a/packages/server/src/utils/config.ts +++ b/packages/server/src/utils/config.ts @@ -7,7 +7,7 @@ dotenv.config({ path: path.join(__dirname, '..', '..', '.env'), override: true } // default config const loggingConfig = { - dir: process.env.LOG_PATH ?? path.join(__dirname, '..', '..', '..', 'logs'), + dir: process.env.LOG_PATH ?? path.join(__dirname, '..', '..', '..', '..', 'logs'), server: { level: 'info', filename: 'server.log', From c78ca494f57d1c6c8193d306b7b9b92942b66bfd Mon Sep 17 00:00:00 2001 From: vjsai Date: Thu, 6 Jul 2023 18:28:33 +0530 Subject: [PATCH 052/183] Added OpenAPI chain --- .../nodes/chains/ApiChain/OpenAPIChain.ts | 84 +++++++++++++++++++ .../SqlDatabaseChain/SqlDatabaseChain.ts | 2 +- packages/components/package.json | 2 +- 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 packages/components/nodes/chains/ApiChain/OpenAPIChain.ts diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts new file mode 100644 index 00000000..f0656f6d --- /dev/null +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -0,0 +1,84 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { APIChain, createOpenAPIChain } from 'langchain/chains' +import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { ChatOpenAI } from 'langchain/chat_models/openai' + +class OpenApiChain_Chains implements INode { + label: string + name: string + type: string + icon: string + category: string + baseClasses: string[] + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'OpenAPI Chain' + this.name = 'openApiChain' + this.type = 'openApiChain' + this.icon = 'apichain.svg' + this.category = 'Chains' + this.description = 'Chain to run queries against OpenAPI' + this.baseClasses = [this.type, ...getBaseClasses(APIChain)] + this.inputs = [ + { + label: 'ChatOpenAI Model', + name: 'model', + type: 'ChatOpenAI' + }, + { + label: 'YAML File', + name: 'yamlFile', + type: 'file', + fileType: '.yaml' + }, + { + label: 'Headers', + name: 'headers', + type: 'json', + additionalParams: true, + optional: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const model = nodeData.inputs?.model as ChatOpenAI + const headers = nodeData.inputs?.headers as Record + const yamlFileBase64 = nodeData.inputs?.yamlFile as string + const splitDataURI = yamlFileBase64.split(',') + splitDataURI.pop() + const bf = Buffer.from(splitDataURI.pop() || '', 'base64') + const utf8String = bf.toString('utf-8') + const chain = await createOpenAPIChain(utf8String, { + llm: model, + headers + }) + return chain + } + + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { + const model = nodeData.inputs?.model as ChatOpenAI + const headers = nodeData.inputs?.headers as Record + const yamlFileBase64 = nodeData.inputs?.yamlFile as string + const splitDataURI = yamlFileBase64.split(',') + splitDataURI.pop() + const bf = Buffer.from(splitDataURI.pop() || '', 'base64') + const utf8String = bf.toString('utf-8') + const chain = await createOpenAPIChain(utf8String, { + llm: model, + headers + }) + if (options.socketIO && options.socketIOClientId) { + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) + const res = await chain.run(input, [handler]) + return res + } else { + const res = await chain.run(input) + return res + } + } +} + +module.exports = { nodeClass: OpenApiChain_Chains } diff --git a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts index 27a245e1..441ec4cd 100644 --- a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts +++ b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts @@ -1,5 +1,5 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { SqlDatabaseChain, SqlDatabaseChainInput } from 'langchain/chains' +import { SqlDatabaseChain, SqlDatabaseChainInput } from 'langchain/chains/sql_db' import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { DataSource } from 'typeorm' import { SqlDatabase } from 'langchain/sql_db' diff --git a/packages/components/package.json b/packages/components/package.json index a5f03e10..3e5f540b 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -35,7 +35,7 @@ "form-data": "^4.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "^0.0.96", + "langchain": "^0.0.103", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", From 6bd44e2d47581849024d4f829bd4e2a361199d69 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Fri, 7 Jul 2023 00:10:12 +0800 Subject: [PATCH 053/183] cheerio add xml scraper --- .../nodes/documentloaders/Cheerio/Cheerio.ts | 36 +++++++++++------ packages/components/src/utils.ts | 39 +++++++++++++++++++ 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts index 10eff77e..2106b86f 100644 --- a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts +++ b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts @@ -2,7 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { CheerioWebBaseLoader } from 'langchain/document_loaders/web/cheerio' import { test } from 'linkifyjs' -import { webCrawl } from '../../../src' +import { webCrawl, xmlScrape } from '../../../src' class Cheerio_DocumentLoaders implements INode { label: string @@ -35,20 +35,30 @@ class Cheerio_DocumentLoaders implements INode { optional: true }, { - label: 'Web Crawl for Relative Links', - name: 'boolWebCrawl', - type: 'boolean', + label: 'Get Relative Links Method', + name: 'relativeLinksMethod', + type: 'options', + options: [ + { + label: 'Web Crawl', + name: 'webCrawl' + }, + { + label: 'Scrape XML Sitemap', + name: 'scrapeXMLSitemap' + } + ], optional: true, additionalParams: true }, { - label: 'Web Crawl Links Limit', + label: 'Crawl/Scrape Links Limit', name: 'limit', type: 'number', default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl all relative links' + description: 'Set 0 to crawl/scrape all relative links' }, { label: 'Metadata', @@ -63,7 +73,7 @@ class Cheerio_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const boolWebCrawl = nodeData.inputs?.boolWebCrawl as boolean + const relativeLinksMethod = nodeData.inputs?.relativeLinksMethod as string let limit = nodeData.inputs?.limit as string let url = nodeData.inputs?.url as string @@ -88,16 +98,18 @@ class Cheerio_DocumentLoaders implements INode { } let docs = [] - if (boolWebCrawl) { - if (process.env.DEBUG === 'true') console.info('Start Web Crawl') - if (!limit) throw new Error('Please set a limit to crawl') + if (relativeLinksMethod) { + if (process.env.DEBUG === 'true') console.info(`Start ${relativeLinksMethod}`) + if (!limit) throw new Error('Please set a limit to crawl/scrape') else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') - const pages: string[] = await webCrawl(url, parseInt(limit)) + const pages: string[] = + relativeLinksMethod === 'webCrawl' ? await webCrawl(url, parseInt(limit)) : await xmlScrape(url, parseInt(limit)) if (process.env.DEBUG === 'true') console.info(`pages: ${JSON.stringify(pages)}, length: ${pages.length}`) + if (!pages || pages.length === 0) throw new Error('No relative links found') for (const page of pages) { docs.push(...(await cheerioLoader(page))) } - if (process.env.DEBUG === 'true') console.info('Finish Web Crawl') + if (process.env.DEBUG === 'true') console.info(`Finish ${relativeLinksMethod}`) } else { docs = await cheerioLoader(url) } diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 39d7e333..a5f69ad9 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -297,6 +297,45 @@ export async function webCrawl(stringURL: string, limit: number): Promise { + let urls: string[] = [] + if (process.env.DEBUG === 'true') console.info(`actively scarping ${currentURL}`) + try { + const resp = await fetch(currentURL) + + if (resp.status > 399) { + if (process.env.DEBUG === 'true') console.error(`error in fetch with status code: ${resp.status}, on page: ${currentURL}`) + return urls + } + + const contentType: string | null = resp.headers.get('content-type') + if ((contentType && !contentType.includes('application/xml')) || !contentType) { + if (process.env.DEBUG === 'true') console.error(`non xml response, content type: ${contentType}, on page: ${currentURL}`) + return urls + } + + const xmlBody = await resp.text() + urls = getURLsFromXML(xmlBody, limit) + } catch (err) { + if (process.env.DEBUG === 'true') console.error(`error in fetch url: ${err.message}, on page: ${currentURL}`) + } + return urls +} + /** * Custom chain handler class */ From c2523d8eecc75e434d51d07abb1317bbcae4740a Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Fri, 7 Jul 2023 00:21:11 +0800 Subject: [PATCH 054/183] puppeteer add xml scraper --- .../documentloaders/Puppeteer/Puppeteer.ts | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index 3f27dc03..101a41ea 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -2,7 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { PuppeteerWebBaseLoader } from 'langchain/document_loaders/web/puppeteer' import { test } from 'linkifyjs' -import { webCrawl } from '../../../src' +import { webCrawl, xmlScrape } from '../../../src' class Puppeteer_DocumentLoaders implements INode { label: string @@ -35,20 +35,30 @@ class Puppeteer_DocumentLoaders implements INode { optional: true }, { - label: 'Web Crawl for Relative Links', - name: 'boolWebCrawl', - type: 'boolean', + label: 'Get Relative Links Method', + name: 'relativeLinksMethod', + type: 'options', + options: [ + { + label: 'Web Crawl', + name: 'webCrawl' + }, + { + label: 'Scrape XML Sitemap', + name: 'scrapeXMLSitemap' + } + ], optional: true, additionalParams: true }, { - label: 'Web Crawl Links Limit', + label: 'Crawl/Scrape Links Limit', name: 'limit', type: 'number', default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl all relative links' + description: 'Set 0 to crawl/scrape all relative links' }, { label: 'Metadata', @@ -63,7 +73,7 @@ class Puppeteer_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const boolWebCrawl = nodeData.inputs?.boolWebCrawl as boolean + const relativeLinksMethod = nodeData.inputs?.relativeLinksMethod as string let limit = nodeData.inputs?.limit as string let url = nodeData.inputs?.url as string @@ -88,16 +98,18 @@ class Puppeteer_DocumentLoaders implements INode { } let docs = [] - if (boolWebCrawl) { - if (process.env.DEBUG === 'true') console.info('Start Web Crawl') - if (!limit) throw new Error('Please set a limit to crawl') + if (relativeLinksMethod) { + if (process.env.DEBUG === 'true') console.info(`Start ${relativeLinksMethod}`) + if (!limit) throw new Error('Please set a limit to crawl/scrape') else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') - const pages: string[] = await webCrawl(url, parseInt(limit)) + const pages: string[] = + relativeLinksMethod === 'webCrawl' ? await webCrawl(url, parseInt(limit)) : await xmlScrape(url, parseInt(limit)) if (process.env.DEBUG === 'true') console.info(`pages: ${JSON.stringify(pages)}, length: ${pages.length}`) + if (!pages || pages.length === 0) throw new Error('No relative links found') for (const page of pages) { docs.push(...(await puppeteerLoader(page))) } - if (process.env.DEBUG === 'true') console.info('Finish Web Crawl') + if (process.env.DEBUG === 'true') console.info(`Finish ${relativeLinksMethod}`) } else { docs = await puppeteerLoader(url) } From e4a488c211b05c5e4f6d126fb4cabb176f53dafd Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Fri, 7 Jul 2023 00:37:10 +0800 Subject: [PATCH 055/183] playwright add xml scraper --- .../documentloaders/Playwright/Playwright.ts | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts index 6e22d55d..c02ab442 100644 --- a/packages/components/nodes/documentloaders/Playwright/Playwright.ts +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -2,7 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { PlaywrightWebBaseLoader } from 'langchain/document_loaders/web/playwright' import { test } from 'linkifyjs' -import { webCrawl } from '../../../src' +import { webCrawl, xmlScrape } from '../../../src' class Playwright_DocumentLoaders implements INode { label: string @@ -35,20 +35,30 @@ class Playwright_DocumentLoaders implements INode { optional: true }, { - label: 'Web Crawl for Relative Links', - name: 'boolWebCrawl', - type: 'boolean', + label: 'Get Relative Links Method', + name: 'relativeLinksMethod', + type: 'options', + options: [ + { + label: 'Web Crawl', + name: 'webCrawl' + }, + { + label: 'Scrape XML Sitemap', + name: 'scrapeXMLSitemap' + } + ], optional: true, additionalParams: true }, { - label: 'Web Crawl Links Limit', + label: 'Crawl/Scrape Links Limit', name: 'limit', type: 'number', default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl all relative links' + description: 'Set 0 to crawl/scrape all relative links' }, { label: 'Metadata', @@ -63,7 +73,7 @@ class Playwright_DocumentLoaders implements INode { async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const boolWebCrawl = nodeData.inputs?.boolWebCrawl as boolean + const relativeLinksMethod = nodeData.inputs?.relativeLinksMethod as string let limit = nodeData.inputs?.limit as string let url = nodeData.inputs?.url as string @@ -88,16 +98,18 @@ class Playwright_DocumentLoaders implements INode { } let docs = [] - if (boolWebCrawl) { - if (process.env.DEBUG === 'true') console.info('Start Web Crawl') - if (!limit) throw new Error('Please set a limit to crawl') + if (relativeLinksMethod) { + if (process.env.DEBUG === 'true') console.info(`Start ${relativeLinksMethod}`) + if (!limit) throw new Error('Please set a limit to crawl/scrape') else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') - const pages: string[] = await webCrawl(url, parseInt(limit)) + const pages: string[] = + relativeLinksMethod === 'webCrawl' ? await webCrawl(url, parseInt(limit)) : await xmlScrape(url, parseInt(limit)) if (process.env.DEBUG === 'true') console.info(`pages: ${JSON.stringify(pages)}, length: ${pages.length}`) + if (!pages || pages.length === 0) throw new Error('No relative links found') for (const page of pages) { docs.push(...(await playwrightLoader(page))) } - if (process.env.DEBUG === 'true') console.info('Finish Web Crawl') + if (process.env.DEBUG === 'true') console.info(`Finish ${relativeLinksMethod}`) } else { docs = await playwrightLoader(url) } From a570893a161b8972286ee3c2fa55a6f0a958f466 Mon Sep 17 00:00:00 2001 From: ivalkshfoeif Date: Thu, 6 Jul 2023 16:20:21 -0700 Subject: [PATCH 056/183] add motorhead memory --- .../memory/MotorheadMemory/MotorheadMemory.ts | 96 +++++++++++++++++++ .../nodes/memory/MotorheadMemory/memory.svg | 8 ++ 2 files changed, 104 insertions(+) create mode 100644 packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts create mode 100644 packages/components/nodes/memory/MotorheadMemory/memory.svg diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts new file mode 100644 index 00000000..383ad613 --- /dev/null +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -0,0 +1,96 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { ICommonObject } from '../../../src' +import { MotorheadMemory, MotorheadMemoryInput } from 'langchain/memory' + +class MotorMemory_Memory implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Motorhead Memory' + this.name = 'motorheadMemory' + this.type = 'MotorheadMemory' + this.icon = 'memory.svg' + this.category = 'Memory' + this.description = 'Remembers previous conversational back and forths directly' + this.baseClasses = [this.type, ...getBaseClasses(MotorheadMemory)] + this.inputs = [ + { + label: 'Base URL', + name: 'baseURL', + type: 'string', + optional: true, + description: 'To use the online version, leave the URL blank. More details at https://getmetal.io.' + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history' + }, + { + label: 'Session Id', + name: 'sessionId', + type: 'string', + description: 'if empty, chatId will be used automatically', + default: '', + additionalParams: true, + optional: true + }, + { + label: 'API Key', + name: 'apiKey', + type: 'string', + optional: true + }, + { + label: 'Client ID', + name: 'clientId', + type: 'string', + optional: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const baseURL = nodeData.inputs?.baseURL as string + const sessionId = nodeData.inputs?.sessionId as string + const apiKey = nodeData.inputs?.apiKey as string + const clientId = nodeData.inputs?.clientId as string + + const chatId = options?.chatId as string + + console.log(chatId) + + let obj: MotorheadMemoryInput = { + returnMessages: true, + sessionId: sessionId ? sessionId : chatId, + memoryKey + } + + if (baseURL) { + obj = { + ...obj, + url: baseURL + } + } else { + obj = { + ...obj, + apiKey, + clientId + } + } + + return new MotorheadMemory(obj) + } +} + +module.exports = { nodeClass: MotorMemory_Memory } diff --git a/packages/components/nodes/memory/MotorheadMemory/memory.svg b/packages/components/nodes/memory/MotorheadMemory/memory.svg new file mode 100644 index 00000000..ca8e17da --- /dev/null +++ b/packages/components/nodes/memory/MotorheadMemory/memory.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From ab535e011ff030263c45fc5e6562865601be2865 Mon Sep 17 00:00:00 2001 From: atilgner Date: Thu, 6 Jul 2023 16:41:54 -0700 Subject: [PATCH 057/183] feat: added category search and clear search button --- packages/ui/src/views/canvas/AddNodes.js | 37 ++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/views/canvas/AddNodes.js b/packages/ui/src/views/canvas/AddNodes.js index 50978700..810fc53f 100644 --- a/packages/ui/src/views/canvas/AddNodes.js +++ b/packages/ui/src/views/canvas/AddNodes.js @@ -34,7 +34,7 @@ import Transitions from 'ui-component/extended/Transitions' import { StyledFab } from 'ui-component/button/StyledFab' // icons -import { IconPlus, IconSearch, IconMinus } from '@tabler/icons' +import { IconPlus, IconSearch, IconMinus, IconX } from '@tabler/icons' // const import { baseURL } from 'store/constant' @@ -61,11 +61,20 @@ const AddNodes = ({ nodesData, node }) => { } } + const getSearchedNodes = (value) => { + const passed = nodesData.filter((nd) => { + const passesQuery = nd.name.toLowerCase().includes(value.toLowerCase()) + const passesCategory = nd.category.toLowerCase().includes(value.toLowerCase()) + return passesQuery || passesCategory + }) + return passed + } + const filterSearch = (value) => { setSearchValue(value) setTimeout(() => { if (value) { - const returnData = nodesData.filter((nd) => nd.name.toLowerCase().includes(value.toLowerCase())) + const returnData = getSearchedNodes(value) groupByCategory(returnData, true) scrollTop() } else if (value === '') { @@ -167,7 +176,7 @@ const AddNodes = ({ nodesData, node }) => { Add Nodes filterSearch(e.target.value)} @@ -177,6 +186,28 @@ const AddNodes = ({ nodesData, node }) => { } + endAdornment={ + + filterSearch('')} + style={{ + cursor: 'pointer' + }} + /> + + } aria-describedby='search-helper-text' inputProps={{ 'aria-label': 'weight' From 717468a018b724bd83a4de2f0953023c11698b88 Mon Sep 17 00:00:00 2001 From: vjsai Date: Fri, 7 Jul 2023 18:38:22 +0530 Subject: [PATCH 058/183] Updated appropriate icon for OpenAPIChain --- .../nodes/chains/ApiChain/OpenAPIChain.ts | 2 +- .../nodes/chains/ApiChain/openapi.png | Bin 0 -> 25114 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 packages/components/nodes/chains/ApiChain/openapi.png diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts index f0656f6d..b5970bb8 100644 --- a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -17,7 +17,7 @@ class OpenApiChain_Chains implements INode { this.label = 'OpenAPI Chain' this.name = 'openApiChain' this.type = 'openApiChain' - this.icon = 'apichain.svg' + this.icon = 'openapi.png' this.category = 'Chains' this.description = 'Chain to run queries against OpenAPI' this.baseClasses = [this.type, ...getBaseClasses(APIChain)] diff --git a/packages/components/nodes/chains/ApiChain/openapi.png b/packages/components/nodes/chains/ApiChain/openapi.png new file mode 100644 index 0000000000000000000000000000000000000000..457c2e4050c8eef06588f25c0acc67a6059ebe01 GIT binary patch literal 25114 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 Date: Fri, 7 Jul 2023 16:54:19 +0100 Subject: [PATCH 059/183] add fix to allow chaintool with llmchain --- .../nodes/tools/ChainTool/ChainTool.ts | 2 +- .../components/nodes/tools/ChainTool/core.ts | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 packages/components/nodes/tools/ChainTool/core.ts diff --git a/packages/components/nodes/tools/ChainTool/ChainTool.ts b/packages/components/nodes/tools/ChainTool/ChainTool.ts index 32e414af..669b5947 100644 --- a/packages/components/nodes/tools/ChainTool/ChainTool.ts +++ b/packages/components/nodes/tools/ChainTool/ChainTool.ts @@ -1,7 +1,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { ChainTool } from 'langchain/tools' import { BaseChain } from 'langchain/chains' +import { ChainTool } from './core' class ChainTool_Tools implements INode { label: string diff --git a/packages/components/nodes/tools/ChainTool/core.ts b/packages/components/nodes/tools/ChainTool/core.ts new file mode 100644 index 00000000..6c3dba55 --- /dev/null +++ b/packages/components/nodes/tools/ChainTool/core.ts @@ -0,0 +1,25 @@ +import { DynamicTool, DynamicToolInput } from 'langchain/tools' +import { BaseChain } from 'langchain/chains' + +export interface ChainToolInput extends Omit { + chain: BaseChain +} + +export class ChainTool extends DynamicTool { + chain: BaseChain + + constructor({ chain, ...rest }: ChainToolInput) { + super({ + ...rest, + func: async (input, runManager) => { + // To enable LLM Chain which has promptValues + if ((chain as any).prompt && (chain as any).prompt.promptValues) { + const values = await chain.call((chain as any).prompt.promptValues, runManager?.getChild()) + return values?.text + } + return chain.run(input, runManager?.getChild()) + } + }) + this.chain = chain + } +} From 0923a356834607a728be3ebccc9a534bb2e73c39 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 7 Jul 2023 17:36:23 +0100 Subject: [PATCH 060/183] add endpoint to HF --- .../ChatHuggingFace/ChatHuggingFace.ts | 13 ++- .../nodes/chatmodels/ChatHuggingFace/core.ts | 109 ++++++++++++++++++ .../HuggingFaceInferenceEmbedding.ts | 12 +- .../HuggingFaceInferenceEmbedding/core.ts | 48 ++++++++ .../HuggingFaceInference.ts | 13 ++- .../nodes/llms/HuggingFaceInference/core.ts | 109 ++++++++++++++++++ packages/components/package.json | 2 +- packages/components/src/utils.ts | 14 +++ 8 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 packages/components/nodes/chatmodels/ChatHuggingFace/core.ts create mode 100644 packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts create mode 100644 packages/components/nodes/llms/HuggingFaceInference/core.ts diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts index 1dae41e4..d92dd1e0 100644 --- a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts @@ -1,6 +1,6 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { HFInput, HuggingFaceInference } from 'langchain/llms/hf' +import { HFInput, HuggingFaceInference } from './core' class ChatHuggingFace_ChatModels implements INode { label: string @@ -71,6 +71,15 @@ class ChatHuggingFace_ChatModels implements INode { description: 'Frequency Penalty parameter may not apply to certain model. Please check available model parameters', optional: true, additionalParams: true + }, + { + label: 'Endpoint', + name: 'endpoint', + type: 'string', + placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2', + description: 'Using your own inference endpoint', + optional: true, + additionalParams: true } ] } @@ -83,6 +92,7 @@ class ChatHuggingFace_ChatModels implements INode { const topP = nodeData.inputs?.topP as string const hfTopK = nodeData.inputs?.hfTopK as string const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string + const endpoint = nodeData.inputs?.endpoint as string const obj: Partial = { model, @@ -94,6 +104,7 @@ class ChatHuggingFace_ChatModels implements INode { if (topP) obj.topP = parseInt(topP, 10) if (hfTopK) obj.topK = parseInt(hfTopK, 10) if (frequencyPenalty) obj.frequencyPenalty = parseInt(frequencyPenalty, 10) + if (endpoint) obj.endpoint = endpoint const huggingFace = new HuggingFaceInference(obj) return huggingFace diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts b/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts new file mode 100644 index 00000000..958e9072 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts @@ -0,0 +1,109 @@ +import { getEnvironmentVariable } from '../../../src/utils' +import { LLM, BaseLLMParams } from 'langchain/llms/base' + +export interface HFInput { + /** Model to use */ + model: string + + /** Sampling temperature to use */ + temperature?: number + + /** + * Maximum number of tokens to generate in the completion. + */ + maxTokens?: number + + /** Total probability mass of tokens to consider at each step */ + topP?: number + + /** Integer to define the top tokens considered within the sample operation to create new text. */ + topK?: number + + /** Penalizes repeated tokens according to frequency */ + frequencyPenalty?: number + + /** API key to use. */ + apiKey?: string + + /** Private endpoint to use. */ + endpoint?: string +} + +export class HuggingFaceInference extends LLM implements HFInput { + get lc_secrets(): { [key: string]: string } | undefined { + return { + apiKey: 'HUGGINGFACEHUB_API_KEY' + } + } + + model = 'gpt2' + + temperature: number | undefined = undefined + + maxTokens: number | undefined = undefined + + topP: number | undefined = undefined + + topK: number | undefined = undefined + + frequencyPenalty: number | undefined = undefined + + apiKey: string | undefined = undefined + + endpoint: string | undefined = undefined + + constructor(fields?: Partial & BaseLLMParams) { + super(fields ?? {}) + + this.model = fields?.model ?? this.model + this.temperature = fields?.temperature ?? this.temperature + this.maxTokens = fields?.maxTokens ?? this.maxTokens + this.topP = fields?.topP ?? this.topP + this.topK = fields?.topK ?? this.topK + this.frequencyPenalty = fields?.frequencyPenalty ?? this.frequencyPenalty + this.endpoint = fields?.endpoint ?? '' + this.apiKey = fields?.apiKey ?? getEnvironmentVariable('HUGGINGFACEHUB_API_KEY') + if (!this.apiKey) { + throw new Error( + 'Please set an API key for HuggingFace Hub in the environment variable HUGGINGFACEHUB_API_KEY or in the apiKey field of the HuggingFaceInference constructor.' + ) + } + } + + _llmType() { + return 'hf' + } + + /** @ignore */ + async _call(prompt: string, options: this['ParsedCallOptions']): Promise { + const { HfInference } = await HuggingFaceInference.imports() + const hf = new HfInference(this.apiKey) + if (this.endpoint) hf.endpoint(this.endpoint) + const res = await this.caller.callWithOptions({ signal: options.signal }, hf.textGeneration.bind(hf), { + model: this.model, + parameters: { + // make it behave similar to openai, returning only the generated text + return_full_text: false, + temperature: this.temperature, + max_new_tokens: this.maxTokens, + top_p: this.topP, + top_k: this.topK, + repetition_penalty: this.frequencyPenalty + }, + inputs: prompt + }) + return res.generated_text + } + + /** @ignore */ + static async imports(): Promise<{ + HfInference: typeof import('@huggingface/inference').HfInference + }> { + try { + const { HfInference } = await import('@huggingface/inference') + return { HfInference } + } catch (e) { + throw new Error('Please install huggingface as a dependency with, e.g. `yarn add @huggingface/inference`') + } + } +} diff --git a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts index 6f14325a..d77d623f 100644 --- a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts +++ b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts @@ -1,6 +1,6 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { HuggingFaceInferenceEmbeddings, HuggingFaceInferenceEmbeddingsParams } from 'langchain/embeddings/hf' +import { HuggingFaceInferenceEmbeddings, HuggingFaceInferenceEmbeddingsParams } from './core' class HuggingFaceInferenceEmbedding_Embeddings implements INode { label: string @@ -31,6 +31,14 @@ class HuggingFaceInferenceEmbedding_Embeddings implements INode { name: 'modelName', type: 'string', optional: true + }, + { + label: 'Endpoint', + name: 'endpoint', + type: 'string', + placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/sentence-transformers/all-MiniLM-L6-v2', + description: 'Using your own inference endpoint', + optional: true } ] } @@ -38,12 +46,14 @@ class HuggingFaceInferenceEmbedding_Embeddings implements INode { async init(nodeData: INodeData): Promise { const apiKey = nodeData.inputs?.apiKey as string const modelName = nodeData.inputs?.modelName as string + const endpoint = nodeData.inputs?.endpoint as string const obj: Partial = { apiKey } if (modelName) obj.model = modelName + if (endpoint) obj.endpoint = endpoint const model = new HuggingFaceInferenceEmbeddings(obj) return model diff --git a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts new file mode 100644 index 00000000..b8d89ebe --- /dev/null +++ b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts @@ -0,0 +1,48 @@ +import { HfInference } from '@huggingface/inference' +import { Embeddings, EmbeddingsParams } from 'langchain/embeddings/base' +import { getEnvironmentVariable } from '../../../src/utils' + +export interface HuggingFaceInferenceEmbeddingsParams extends EmbeddingsParams { + apiKey?: string + model?: string + endpoint?: string +} + +export class HuggingFaceInferenceEmbeddings extends Embeddings implements HuggingFaceInferenceEmbeddingsParams { + apiKey?: string + + endpoint?: string + + model: string + + client: HfInference + + constructor(fields?: HuggingFaceInferenceEmbeddingsParams) { + super(fields ?? {}) + + this.model = fields?.model ?? 'sentence-transformers/distilbert-base-nli-mean-tokens' + this.apiKey = fields?.apiKey ?? getEnvironmentVariable('HUGGINGFACEHUB_API_KEY') + this.endpoint = fields?.endpoint ?? '' + this.client = new HfInference(this.apiKey) + if (this.endpoint) this.client.endpoint(this.endpoint) + } + + async _embed(texts: string[]): Promise { + // replace newlines, which can negatively affect performance. + const clean = texts.map((text) => text.replace(/\n/g, ' ')) + return this.caller.call(() => + this.client.featureExtraction({ + model: this.model, + inputs: clean + }) + ) as Promise + } + + embedQuery(document: string): Promise { + return this._embed([document]).then((embeddings) => embeddings[0]) + } + + embedDocuments(documents: string[]): Promise { + return this._embed(documents) + } +} diff --git a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts index 291f67c9..92eb46d5 100644 --- a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts +++ b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts @@ -1,6 +1,6 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { HFInput, HuggingFaceInference } from 'langchain/llms/hf' +import { HFInput, HuggingFaceInference } from './core' class HuggingFaceInference_LLMs implements INode { label: string @@ -71,6 +71,15 @@ class HuggingFaceInference_LLMs implements INode { description: 'Frequency Penalty parameter may not apply to certain model. Please check available model parameters', optional: true, additionalParams: true + }, + { + label: 'Endpoint', + name: 'endpoint', + type: 'string', + placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2', + description: 'Using your own inference endpoint', + optional: true, + additionalParams: true } ] } @@ -83,6 +92,7 @@ class HuggingFaceInference_LLMs implements INode { const topP = nodeData.inputs?.topP as string const hfTopK = nodeData.inputs?.hfTopK as string const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string + const endpoint = nodeData.inputs?.endpoint as string const obj: Partial = { model, @@ -94,6 +104,7 @@ class HuggingFaceInference_LLMs implements INode { if (topP) obj.topP = parseInt(topP, 10) if (hfTopK) obj.topK = parseInt(hfTopK, 10) if (frequencyPenalty) obj.frequencyPenalty = parseInt(frequencyPenalty, 10) + if (endpoint) obj.endpoint = endpoint const huggingFace = new HuggingFaceInference(obj) return huggingFace diff --git a/packages/components/nodes/llms/HuggingFaceInference/core.ts b/packages/components/nodes/llms/HuggingFaceInference/core.ts new file mode 100644 index 00000000..958e9072 --- /dev/null +++ b/packages/components/nodes/llms/HuggingFaceInference/core.ts @@ -0,0 +1,109 @@ +import { getEnvironmentVariable } from '../../../src/utils' +import { LLM, BaseLLMParams } from 'langchain/llms/base' + +export interface HFInput { + /** Model to use */ + model: string + + /** Sampling temperature to use */ + temperature?: number + + /** + * Maximum number of tokens to generate in the completion. + */ + maxTokens?: number + + /** Total probability mass of tokens to consider at each step */ + topP?: number + + /** Integer to define the top tokens considered within the sample operation to create new text. */ + topK?: number + + /** Penalizes repeated tokens according to frequency */ + frequencyPenalty?: number + + /** API key to use. */ + apiKey?: string + + /** Private endpoint to use. */ + endpoint?: string +} + +export class HuggingFaceInference extends LLM implements HFInput { + get lc_secrets(): { [key: string]: string } | undefined { + return { + apiKey: 'HUGGINGFACEHUB_API_KEY' + } + } + + model = 'gpt2' + + temperature: number | undefined = undefined + + maxTokens: number | undefined = undefined + + topP: number | undefined = undefined + + topK: number | undefined = undefined + + frequencyPenalty: number | undefined = undefined + + apiKey: string | undefined = undefined + + endpoint: string | undefined = undefined + + constructor(fields?: Partial & BaseLLMParams) { + super(fields ?? {}) + + this.model = fields?.model ?? this.model + this.temperature = fields?.temperature ?? this.temperature + this.maxTokens = fields?.maxTokens ?? this.maxTokens + this.topP = fields?.topP ?? this.topP + this.topK = fields?.topK ?? this.topK + this.frequencyPenalty = fields?.frequencyPenalty ?? this.frequencyPenalty + this.endpoint = fields?.endpoint ?? '' + this.apiKey = fields?.apiKey ?? getEnvironmentVariable('HUGGINGFACEHUB_API_KEY') + if (!this.apiKey) { + throw new Error( + 'Please set an API key for HuggingFace Hub in the environment variable HUGGINGFACEHUB_API_KEY or in the apiKey field of the HuggingFaceInference constructor.' + ) + } + } + + _llmType() { + return 'hf' + } + + /** @ignore */ + async _call(prompt: string, options: this['ParsedCallOptions']): Promise { + const { HfInference } = await HuggingFaceInference.imports() + const hf = new HfInference(this.apiKey) + if (this.endpoint) hf.endpoint(this.endpoint) + const res = await this.caller.callWithOptions({ signal: options.signal }, hf.textGeneration.bind(hf), { + model: this.model, + parameters: { + // make it behave similar to openai, returning only the generated text + return_full_text: false, + temperature: this.temperature, + max_new_tokens: this.maxTokens, + top_p: this.topP, + top_k: this.topK, + repetition_penalty: this.frequencyPenalty + }, + inputs: prompt + }) + return res.generated_text + } + + /** @ignore */ + static async imports(): Promise<{ + HfInference: typeof import('@huggingface/inference').HfInference + }> { + try { + const { HfInference } = await import('@huggingface/inference') + return { HfInference } + } catch (e) { + throw new Error('Please install huggingface as a dependency with, e.g. `yarn add @huggingface/inference`') + } + } +} diff --git a/packages/components/package.json b/packages/components/package.json index a5f03e10..df0589e7 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -19,7 +19,7 @@ "@aws-sdk/client-dynamodb": "^3.360.0", "@dqbd/tiktoken": "^1.0.7", "@getzep/zep-js": "^0.3.1", - "@huggingface/inference": "1", + "@huggingface/inference": "^2.6.1", "@pinecone-database/pinecone": "^0.0.12", "@qdrant/js-client-rest": "^1.2.2", "@supabase/supabase-js": "^2.21.0", diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index c247ebc2..c87b0831 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -201,6 +201,20 @@ export const getAvailableURLs = async (url: string, limit: number) => { } } +/** + * Get env variables + * @param {string} url + * @param {number} limit + * @returns {string[]} + */ +export const getEnvironmentVariable = (name: string): string | undefined => { + try { + return typeof process !== 'undefined' ? process.env?.[name] : undefined + } catch (e) { + return undefined + } +} + /** * Custom chain handler class */ From 90d7f4472dae028e5eb9dab3cf05f3add4d6142b Mon Sep 17 00:00:00 2001 From: atilgner Date: Fri, 7 Jul 2023 12:39:30 -0700 Subject: [PATCH 061/183] fix: docker should install chromium and puppeteer should be no sandbox --- docker/Dockerfile | 5 +++++ .../nodes/documentloaders/Puppeteer/Puppeteer.ts | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 2203af11..1ad1bf5e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,7 +6,12 @@ RUN apk add --no-cache git RUN apk add --no-cache python3 py3-pip make g++ # needed for pdfjs-dist RUN apk add --no-cache build-base cairo-dev pango-dev + +# Install Chromium +RUN apk add --no-cache chromium + ENV PUPPETEER_SKIP_DOWNLOAD=true +ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser # You can install a specific version like: flowise@1.0.0 RUN npm install -g flowise diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index 1331c736..bc1bc9ed 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -73,7 +73,12 @@ class Puppeteer_DocumentLoaders implements INode { const puppeteerLoader = async (url: string): Promise => { let docs = [] - const loader = new PuppeteerWebBaseLoader(url) + const loader = new PuppeteerWebBaseLoader(url, { + launchOptions: { + args: ['--no-sandbox'], + headless: 'new' + } + }) if (textSplitter) { docs = await loader.loadAndSplit(textSplitter) } else { From 7e076028dfdb3fe728648fbadab95dd6b3df59f7 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 8 Jul 2023 01:55:44 +0100 Subject: [PATCH 062/183] add warning description --- .../nodes/documentloaders/Cheerio/Cheerio.ts | 3 ++- .../documentloaders/Playwright/Playwright.ts | 3 ++- .../documentloaders/Puppeteer/Puppeteer.ts | 3 ++- packages/components/src/Interface.ts | 1 + .../ui/src/views/canvas/NodeInputHandler.js | 18 +++++++++++++++++- 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts index 2106b86f..2521b039 100644 --- a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts +++ b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts @@ -58,7 +58,8 @@ class Cheerio_DocumentLoaders implements INode { default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl/scrape all relative links' + description: 'Set 0 to crawl/scrape all relative links', + warning: `Scraping all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc) ` }, { label: 'Metadata', diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts index c02ab442..2301b4e9 100644 --- a/packages/components/nodes/documentloaders/Playwright/Playwright.ts +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -58,7 +58,8 @@ class Playwright_DocumentLoaders implements INode { default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl/scrape all relative links' + description: 'Set 0 to crawl/scrape all relative links', + warning: `Scraping all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc) ` }, { label: 'Metadata', diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index 101a41ea..bf0920bb 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -58,7 +58,8 @@ class Puppeteer_DocumentLoaders implements INode { default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl/scrape all relative links' + description: 'Set 0 to crawl/scrape all relative links', + warning: `Scraping all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc) ` }, { label: 'Metadata', diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index f8a6fd58..d9233e49 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -57,6 +57,7 @@ export interface INodeParams { type: NodeParamsType | string default?: CommonType | ICommonObject | ICommonObject[] description?: string + warning?: string options?: Array optional?: boolean | INodeDisplay rows?: number diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 2d96bcb5..ba72a4ce 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -7,7 +7,7 @@ import { useSelector } from 'react-redux' import { useTheme, styled } from '@mui/material/styles' import { Box, Typography, Tooltip, IconButton, Button } from '@mui/material' import { tooltipClasses } from '@mui/material/Tooltip' -import { IconArrowsMaximize, IconEdit } from '@tabler/icons' +import { IconArrowsMaximize, IconEdit, IconAlertTriangle } from '@tabler/icons' // project import import { Dropdown } from 'ui-component/dropdown/Dropdown' @@ -210,6 +210,22 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA )}

+ {inputParam.warning && ( +
+ + {inputParam.warning} +
+ )} {inputParam.type === 'file' && ( Date: Sat, 8 Jul 2023 15:00:47 +0530 Subject: [PATCH 063/183] bug/chat input issue --- packages/ui/src/views/chatmessage/ChatMessage.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.css b/packages/ui/src/views/chatmessage/ChatMessage.css index ef7f1e93..3b006c1d 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.css +++ b/packages/ui/src/views/chatmessage/ChatMessage.css @@ -127,7 +127,8 @@ .cloud-dialog { width: 100%; - height: calc(100vh - 230px); + height: 100vh; + overflow-y: scroll; border-radius: 0.5rem; display: flex; justify-content: center; From 6f77461921605bf011e30dbc5460714fbbc1ac93 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 8 Jul 2023 11:36:31 +0100 Subject: [PATCH 064/183] update error message --- .../components/nodes/documentloaders/Playwright/Playwright.ts | 2 +- .../components/nodes/documentloaders/Puppeteer/Puppeteer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts index 2301b4e9..273536ae 100644 --- a/packages/components/nodes/documentloaders/Playwright/Playwright.ts +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -94,7 +94,7 @@ class Playwright_DocumentLoaders implements INode { } return docs } catch (err) { - if (process.env.DEBUG === 'true') console.error(`error in CheerioWebBaseLoader: ${err.message}, on page: ${url}`) + if (process.env.DEBUG === 'true') console.error(`error in PlaywrightWebBaseLoader: ${err.message}, on page: ${url}`) } } diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index bf0920bb..86a9477d 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -94,7 +94,7 @@ class Puppeteer_DocumentLoaders implements INode { } return docs } catch (err) { - if (process.env.DEBUG === 'true') console.error(`error in CheerioWebBaseLoader: ${err.message}, on page: ${url}`) + if (process.env.DEBUG === 'true') console.error(`error in PuppeteerWebBaseLoader: ${err.message}, on page: ${url}`) } } From 41346594c60185711e2eb3048e2538b5c44f69e3 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Sat, 8 Jul 2023 19:01:11 +0800 Subject: [PATCH 065/183] copy paste chrome fix to root docker file --- Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Dockerfile b/Dockerfile index fe01ed8d..e485cd3e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,12 @@ FROM node:18-alpine RUN apk add --update libc6-compat python3 make g++ # needed for pdfjs-dist RUN apk add --no-cache build-base cairo-dev pango-dev + +# Install Chromium +RUN apk add --no-cache chromium + ENV PUPPETEER_SKIP_DOWNLOAD=true +ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser WORKDIR /usr/src/packages From 0d46c0226e6987d34b407a2d695b19e455e01190 Mon Sep 17 00:00:00 2001 From: vjsai Date: Sat, 8 Jul 2023 18:01:51 +0530 Subject: [PATCH 066/183] fixed header parsing issue --- packages/components/nodes/chains/ApiChain/OpenAPIChain.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts index b5970bb8..2e54d237 100644 --- a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -60,7 +60,7 @@ class OpenApiChain_Chains implements INode { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as ChatOpenAI - const headers = nodeData.inputs?.headers as Record + const headers = nodeData.inputs?.headers as string const yamlFileBase64 = nodeData.inputs?.yamlFile as string const splitDataURI = yamlFileBase64.split(',') splitDataURI.pop() @@ -68,7 +68,7 @@ class OpenApiChain_Chains implements INode { const utf8String = bf.toString('utf-8') const chain = await createOpenAPIChain(utf8String, { llm: model, - headers + headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {} }) if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) From 9cda188c73124901467b58108f335e3562e09b07 Mon Sep 17 00:00:00 2001 From: vjsai Date: Sat, 8 Jul 2023 18:45:18 +0530 Subject: [PATCH 067/183] fixed the issue in init as well --- packages/components/nodes/chains/ApiChain/OpenAPIChain.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts index 2e54d237..a7e8441a 100644 --- a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -45,7 +45,7 @@ class OpenApiChain_Chains implements INode { async init(nodeData: INodeData): Promise { const model = nodeData.inputs?.model as ChatOpenAI - const headers = nodeData.inputs?.headers as Record + const headers = nodeData.inputs?.headers as string const yamlFileBase64 = nodeData.inputs?.yamlFile as string const splitDataURI = yamlFileBase64.split(',') splitDataURI.pop() @@ -53,7 +53,7 @@ class OpenApiChain_Chains implements INode { const utf8String = bf.toString('utf-8') const chain = await createOpenAPIChain(utf8String, { llm: model, - headers + headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {} }) return chain } From 3cda348e95762a09dbc0abc89b92eae86c57a9dc Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 8 Jul 2023 21:31:43 +0100 Subject: [PATCH 068/183] add fix to HF endpoint --- .../nodes/chatmodels/ChatHuggingFace/core.ts | 12 ++++++++---- .../embeddings/HuggingFaceInferenceEmbedding/core.ts | 11 +++++------ .../nodes/llms/HuggingFaceInference/core.ts | 12 ++++++++---- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts b/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts index 958e9072..416567f0 100644 --- a/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/core.ts @@ -78,9 +78,7 @@ export class HuggingFaceInference extends LLM implements HFInput { async _call(prompt: string, options: this['ParsedCallOptions']): Promise { const { HfInference } = await HuggingFaceInference.imports() const hf = new HfInference(this.apiKey) - if (this.endpoint) hf.endpoint(this.endpoint) - const res = await this.caller.callWithOptions({ signal: options.signal }, hf.textGeneration.bind(hf), { - model: this.model, + const obj: any = { parameters: { // make it behave similar to openai, returning only the generated text return_full_text: false, @@ -91,7 +89,13 @@ export class HuggingFaceInference extends LLM implements HFInput { repetition_penalty: this.frequencyPenalty }, inputs: prompt - }) + } + if (this.endpoint) { + hf.endpoint(this.endpoint) + } else { + obj.model = this.model + } + const res = await this.caller.callWithOptions({ signal: options.signal }, hf.textGeneration.bind(hf), obj) return res.generated_text } diff --git a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts index b8d89ebe..41e63aa4 100644 --- a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts +++ b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/core.ts @@ -30,12 +30,11 @@ export class HuggingFaceInferenceEmbeddings extends Embeddings implements Huggin async _embed(texts: string[]): Promise { // replace newlines, which can negatively affect performance. const clean = texts.map((text) => text.replace(/\n/g, ' ')) - return this.caller.call(() => - this.client.featureExtraction({ - model: this.model, - inputs: clean - }) - ) as Promise + const obj: any = { + inputs: clean + } + if (!this.endpoint) obj.model = this.model + return this.caller.call(() => this.client.featureExtraction(obj)) as Promise } embedQuery(document: string): Promise { diff --git a/packages/components/nodes/llms/HuggingFaceInference/core.ts b/packages/components/nodes/llms/HuggingFaceInference/core.ts index 958e9072..416567f0 100644 --- a/packages/components/nodes/llms/HuggingFaceInference/core.ts +++ b/packages/components/nodes/llms/HuggingFaceInference/core.ts @@ -78,9 +78,7 @@ export class HuggingFaceInference extends LLM implements HFInput { async _call(prompt: string, options: this['ParsedCallOptions']): Promise { const { HfInference } = await HuggingFaceInference.imports() const hf = new HfInference(this.apiKey) - if (this.endpoint) hf.endpoint(this.endpoint) - const res = await this.caller.callWithOptions({ signal: options.signal }, hf.textGeneration.bind(hf), { - model: this.model, + const obj: any = { parameters: { // make it behave similar to openai, returning only the generated text return_full_text: false, @@ -91,7 +89,13 @@ export class HuggingFaceInference extends LLM implements HFInput { repetition_penalty: this.frequencyPenalty }, inputs: prompt - }) + } + if (this.endpoint) { + hf.endpoint(this.endpoint) + } else { + obj.model = this.model + } + const res = await this.caller.callWithOptions({ signal: options.signal }, hf.textGeneration.bind(hf), obj) return res.generated_text } From 45792665df1e8971bbfa89f2fb1558c350af9bbb Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sat, 8 Jul 2023 22:34:46 +0100 Subject: [PATCH 069/183] Update OpenAPIChain.ts --- .../nodes/chains/ApiChain/OpenAPIChain.ts | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts index a7e8441a..ae1ae3c0 100644 --- a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -27,11 +27,19 @@ class OpenApiChain_Chains implements INode { name: 'model', type: 'ChatOpenAI' }, + { + label: 'YAML Link', + name: 'yamlLink', + type: 'string', + placeholder: 'https://api.speak.com/openapi.yaml', + description: 'If YAML link is provided, uploaded YAML File will be ignored and YAML link will be used instead' + }, { label: 'YAML File', name: 'yamlFile', type: 'file', - fileType: '.yaml' + fileType: '.yaml', + description: 'If YAML link is provided, uploaded YAML File will be ignored and YAML link will be used instead' }, { label: 'Headers', @@ -44,34 +52,13 @@ class OpenApiChain_Chains implements INode { } async init(nodeData: INodeData): Promise { - const model = nodeData.inputs?.model as ChatOpenAI - const headers = nodeData.inputs?.headers as string - const yamlFileBase64 = nodeData.inputs?.yamlFile as string - const splitDataURI = yamlFileBase64.split(',') - splitDataURI.pop() - const bf = Buffer.from(splitDataURI.pop() || '', 'base64') - const utf8String = bf.toString('utf-8') - const chain = await createOpenAPIChain(utf8String, { - llm: model, - headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {} - }) - return chain + return await initChain(nodeData) } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const model = nodeData.inputs?.model as ChatOpenAI - const headers = nodeData.inputs?.headers as string - const yamlFileBase64 = nodeData.inputs?.yamlFile as string - const splitDataURI = yamlFileBase64.split(',') - splitDataURI.pop() - const bf = Buffer.from(splitDataURI.pop() || '', 'base64') - const utf8String = bf.toString('utf-8') - const chain = await createOpenAPIChain(utf8String, { - llm: model, - headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {} - }) + const chain = await initChain(nodeData) if (options.socketIO && options.socketIOClientId) { - const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) + const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) const res = await chain.run(input, [handler]) return res } else { @@ -81,4 +68,28 @@ class OpenApiChain_Chains implements INode { } } +const initChain = async (nodeData: INodeData) => { + const model = nodeData.inputs?.model as ChatOpenAI + const headers = nodeData.inputs?.headers as string + const yamlLink = nodeData.inputs?.yamlLink as string + const yamlFileBase64 = nodeData.inputs?.yamlFile as string + + let yamlString = '' + + if (yamlLink) { + yamlString = yamlLink + } else { + const splitDataURI = yamlFileBase64.split(',') + splitDataURI.pop() + const bf = Buffer.from(splitDataURI.pop() || '', 'base64') + yamlString = bf.toString('utf-8') + } + + return await createOpenAPIChain(yamlString, { + llm: model, + headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {}, + verbose: process.env.DEBUG === 'true' ? true : false + }) +} + module.exports = { nodeClass: OpenApiChain_Chains } From 83a62011cbe2f2e552c5dd3725764d55957ef20f Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sat, 8 Jul 2023 22:35:25 +0100 Subject: [PATCH 070/183] Update package.json --- 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 cca3e47a..775d6e94 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -35,7 +35,7 @@ "form-data": "^4.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "^0.0.103", + "langchain": "^0.0.104", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3", From e66c07d336dcaac9695862e8eb946b40e72ad044 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sat, 8 Jul 2023 22:38:56 +0100 Subject: [PATCH 071/183] Update utils isFlowValidForStream Some chains/agents streaming are not working atm, disabling streaming for those chains/agents --- packages/server/src/utils/index.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 005f4a4b..d32faa8b 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -680,10 +680,16 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod } } - return ( - isChatOrLLMsExist && - (endingNodeData.category === 'Chains' || endingNodeData.name === 'openAIFunctionAgent') && - !isVectorStoreFaiss(endingNodeData) && - process.env.EXECUTION_MODE !== 'child' - ) + let isValidChainOrAgent = false + if (endingNodeData.category === 'Chains') { + // Chains that are not available to stream + const blacklistChains = ['openApiChain'] + isValidChainOrAgent = !blacklistChains.includes(endingNodeData.name) + } else if (endingNodeData.category === 'Agents') { + // Agent that are available to stream + const whitelistAgents = ['openAIFunctionAgent'] + isValidChainOrAgent = whitelistAgents.includes(endingNodeData.name) + } + + return isChatOrLLMsExist && isValidChainOrAgent && !isVectorStoreFaiss(endingNodeData) && process.env.EXECUTION_MODE !== 'child' } From 21552776d6f939593f4d8956fe723f5bf2c26d85 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 8 Jul 2023 23:17:01 +0100 Subject: [PATCH 072/183] update HF marketplace template --- .../marketplaces/chatflows/HuggingFace LLM Chain.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json b/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json index d46f9d64..63a04b03 100644 --- a/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json +++ b/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json @@ -156,6 +156,16 @@ "optional": true, "additionalParams": true, "id": "huggingFaceInference_LLMs_0-input-frequencyPenalty-number" + }, + { + "label": "Endpoint", + "name": "endpoint", + "type": "string", + "placeholder": "https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2", + "description": "Using your own inference endpoint", + "optional": true, + "additionalParams": true, + "id": "huggingFaceInference_LLMs_0-input-endpoint-string" } ], "inputAnchors": [], From f83f1c64d3009f0b22cadd63592ac32447eacc2d Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Sat, 8 Jul 2023 23:26:28 +0100 Subject: [PATCH 073/183] allow logger by default --- packages/server/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 280d870b..38917023 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -61,7 +61,7 @@ export class App { this.app = express() // Add the expressRequestLogger middleware to log all requests - if (process.env.DEBUG === 'true') this.app.use(expressRequestLogger) + this.app.use(expressRequestLogger) } async initDatabase() { From d53522a0a800c11f209379210b1a27b9569dcd59 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Sun, 9 Jul 2023 10:32:05 +0800 Subject: [PATCH 074/183] add description and modify default limit to 10 if empty --- .../nodes/documentloaders/Cheerio/Cheerio.ts | 17 ++++++++++------- .../documentloaders/Playwright/Playwright.ts | 17 ++++++++++------- .../documentloaders/Puppeteer/Puppeteer.ts | 17 ++++++++++------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts index 2521b039..b93a8685 100644 --- a/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts +++ b/packages/components/nodes/documentloaders/Cheerio/Cheerio.ts @@ -38,28 +38,31 @@ class Cheerio_DocumentLoaders implements INode { label: 'Get Relative Links Method', name: 'relativeLinksMethod', type: 'options', + description: 'Select a method to retrieve relative links', options: [ { label: 'Web Crawl', - name: 'webCrawl' + name: 'webCrawl', + description: 'Crawl relative links from HTML URL' }, { label: 'Scrape XML Sitemap', - name: 'scrapeXMLSitemap' + name: 'scrapeXMLSitemap', + description: 'Scrape relative links from XML sitemap URL' } ], optional: true, additionalParams: true }, { - label: 'Crawl/Scrape Links Limit', + label: 'Get Relative Links Limit', name: 'limit', type: 'number', - default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl/scrape all relative links', - warning: `Scraping all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc) ` + description: + 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', + warning: `Retreiving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` }, { label: 'Metadata', @@ -101,7 +104,7 @@ class Cheerio_DocumentLoaders implements INode { let docs = [] if (relativeLinksMethod) { if (process.env.DEBUG === 'true') console.info(`Start ${relativeLinksMethod}`) - if (!limit) throw new Error('Please set a limit to crawl/scrape') + if (!limit) limit = '10' else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') const pages: string[] = relativeLinksMethod === 'webCrawl' ? await webCrawl(url, parseInt(limit)) : await xmlScrape(url, parseInt(limit)) diff --git a/packages/components/nodes/documentloaders/Playwright/Playwright.ts b/packages/components/nodes/documentloaders/Playwright/Playwright.ts index 273536ae..73a3e290 100644 --- a/packages/components/nodes/documentloaders/Playwright/Playwright.ts +++ b/packages/components/nodes/documentloaders/Playwright/Playwright.ts @@ -38,28 +38,31 @@ class Playwright_DocumentLoaders implements INode { label: 'Get Relative Links Method', name: 'relativeLinksMethod', type: 'options', + description: 'Select a method to retrieve relative links', options: [ { label: 'Web Crawl', - name: 'webCrawl' + name: 'webCrawl', + description: 'Crawl relative links from HTML URL' }, { label: 'Scrape XML Sitemap', - name: 'scrapeXMLSitemap' + name: 'scrapeXMLSitemap', + description: 'Scrape relative links from XML sitemap URL' } ], optional: true, additionalParams: true }, { - label: 'Crawl/Scrape Links Limit', + label: 'Get Relative Links Limit', name: 'limit', type: 'number', - default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl/scrape all relative links', - warning: `Scraping all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc) ` + description: + 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', + warning: `Retreiving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` }, { label: 'Metadata', @@ -101,7 +104,7 @@ class Playwright_DocumentLoaders implements INode { let docs = [] if (relativeLinksMethod) { if (process.env.DEBUG === 'true') console.info(`Start ${relativeLinksMethod}`) - if (!limit) throw new Error('Please set a limit to crawl/scrape') + if (!limit) limit = '10' else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') const pages: string[] = relativeLinksMethod === 'webCrawl' ? await webCrawl(url, parseInt(limit)) : await xmlScrape(url, parseInt(limit)) diff --git a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts index 618d110b..014845d2 100644 --- a/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts +++ b/packages/components/nodes/documentloaders/Puppeteer/Puppeteer.ts @@ -38,28 +38,31 @@ class Puppeteer_DocumentLoaders implements INode { label: 'Get Relative Links Method', name: 'relativeLinksMethod', type: 'options', + description: 'Select a method to retrieve relative links', options: [ { label: 'Web Crawl', - name: 'webCrawl' + name: 'webCrawl', + description: 'Crawl relative links from HTML URL' }, { label: 'Scrape XML Sitemap', - name: 'scrapeXMLSitemap' + name: 'scrapeXMLSitemap', + description: 'Scrape relative links from XML sitemap URL' } ], optional: true, additionalParams: true }, { - label: 'Crawl/Scrape Links Limit', + label: 'Get Relative Links Limit', name: 'limit', type: 'number', - default: 10, optional: true, additionalParams: true, - description: 'Set 0 to crawl/scrape all relative links', - warning: `Scraping all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc) ` + description: + 'Only used when "Get Relative Links Method" is selected. Set 0 to retrieve all relative links, default limit is 10.', + warning: `Retreiving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)` }, { label: 'Metadata', @@ -106,7 +109,7 @@ class Puppeteer_DocumentLoaders implements INode { let docs = [] if (relativeLinksMethod) { if (process.env.DEBUG === 'true') console.info(`Start ${relativeLinksMethod}`) - if (!limit) throw new Error('Please set a limit to crawl/scrape') + if (!limit) limit = '10' else if (parseInt(limit) < 0) throw new Error('Limit cannot be less than 0') const pages: string[] = relativeLinksMethod === 'webCrawl' ? await webCrawl(url, parseInt(limit)) : await xmlScrape(url, parseInt(limit)) From f13c16569ffc35fa2a48df1cd23a8972114f152b Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Sun, 9 Jul 2023 18:18:04 +0800 Subject: [PATCH 075/183] modify options api config --- packages/server/src/utils/index.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index d32faa8b..9e8c5c32 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -632,7 +632,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => { for (const flowNode of reactFlowNodes) { for (const inputParam of flowNode.data.inputParams) { let obj: IOverrideConfig - if (inputParam.type === 'password' || inputParam.type === 'options') { + if (inputParam.type === 'password') { continue } else if (inputParam.type === 'file') { obj = { @@ -641,6 +641,19 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => { name: 'files', type: inputParam.fileType ?? inputParam.type } + } else if (inputParam.type === 'options') { + obj = { + node: flowNode.data.label, + label: inputParam.label, + name: inputParam.name, + type: inputParam.options + ? inputParam.options + ?.map((option) => { + return option.name + }) + .join(', ') + : 'string' + } } else { obj = { node: flowNode.data.label, From fb28f61f8b966163c6d8d653a0e270a894e04723 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 9 Jul 2023 16:11:42 +0100 Subject: [PATCH 076/183] add memory option --- .../ConversationalRetrievalQAChain.ts | 49 ++++++++++++++++--- .../nodes/memory/DynamoDb/DynamoDb.ts | 5 +- packages/ui/src/utils/genericHelper.js | 6 ++- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 3b7e1413..4872717f 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -2,7 +2,7 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { ConversationalRetrievalQAChain } from 'langchain/chains' -import { AIChatMessage, BaseRetriever, HumanChatMessage } from 'langchain/schema' +import { AIMessage, BaseRetriever, HumanMessage } from 'langchain/schema' import { BaseChatMemory, BufferMemory, ChatMessageHistory } from 'langchain/memory' import { PromptTemplate } from 'langchain/prompts' @@ -20,6 +20,20 @@ const qa_template = `Use the following pieces of context to answer the question Question: {question} Helpful Answer:` +const CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT = `Given the following conversation and a follow up question, return the conversation history excerpt that includes any relevant context to the question if it exists and rephrase the follow up question to be a standalone question. +Chat History: +{chat_history} +Follow Up Input: {question} +Your answer should follow the following format: +\`\`\` +Use the following pieces of context to answer the users question. +If you don't know the answer, just say that you don't know, don't try to make up an answer. +---------------- + +Standalone question: +\`\`\` +Your answer:` + class ConversationalRetrievalQAChain_Chains implements INode { label: string name: string @@ -49,6 +63,13 @@ class ConversationalRetrievalQAChain_Chains implements INode { name: 'vectorStoreRetriever', type: 'BaseRetriever' }, + { + label: 'Memory', + name: 'memory', + type: 'DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory', + optional: true, + description: 'If no memory connected, default BufferMemory will be used' + }, { label: 'Return Source Documents', name: 'returnSourceDocuments', @@ -99,6 +120,7 @@ class ConversationalRetrievalQAChain_Chains implements INode { const systemMessagePrompt = nodeData.inputs?.systemMessagePrompt as string const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean const chainOption = nodeData.inputs?.chainOption as string + const memory = nodeData.inputs?.memory const obj: any = { verbose: process.env.DEBUG === 'true' ? true : false, @@ -106,15 +128,25 @@ class ConversationalRetrievalQAChain_Chains implements INode { type: 'stuff', prompt: PromptTemplate.fromTemplate(systemMessagePrompt ? `${systemMessagePrompt}\n${qa_template}` : default_qa_template) }, - memory: new BufferMemory({ + questionGeneratorChainOptions: { + template: CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT + } + } + if (returnSourceDocuments) obj.returnSourceDocuments = returnSourceDocuments + if (chainOption) obj.qaChainOptions = { ...obj.qaChainOptions, type: chainOption } + if (memory) { + memory.inputKey = 'question' + memory.outputKey = 'text' + memory.memoryKey = 'chat_history' + obj.memory = memory + } else { + obj.memory = new BufferMemory({ memoryKey: 'chat_history', inputKey: 'question', outputKey: 'text', returnMessages: true }) } - if (returnSourceDocuments) obj.returnSourceDocuments = returnSourceDocuments - if (chainOption) obj.qaChainOptions = { ...obj.qaChainOptions, type: chainOption } const chain = ConversationalRetrievalQAChain.fromLLM(model, vectorStoreRetriever, obj) return chain @@ -123,6 +155,8 @@ class ConversationalRetrievalQAChain_Chains implements INode { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = nodeData.instance as ConversationalRetrievalQAChain const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean + const memory = nodeData.inputs?.memory + let model = nodeData.inputs?.model // Temporary fix: https://github.com/hwchase17/langchainjs/issues/754 @@ -131,16 +165,17 @@ class ConversationalRetrievalQAChain_Chains implements INode { const obj = { question: input } - if (chain.memory && options && options.chatHistory) { + // If external memory like Zep, Redis is being used, ignore below + if (!memory && chain.memory && options && options.chatHistory) { const chatHistory = [] const histories: IMessage[] = options.chatHistory const memory = chain.memory as BaseChatMemory for (const message of histories) { if (message.type === 'apiMessage') { - chatHistory.push(new AIChatMessage(message.message)) + chatHistory.push(new AIMessage(message.message)) } else if (message.type === 'userMessage') { - chatHistory.push(new HumanChatMessage(message.message)) + chatHistory.push(new HumanMessage(message.message)) } } memory.chatHistory = new ChatMessageHistory(chatHistory) diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index b1368044..49d15cb6 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -13,8 +13,9 @@ class DynamoDb_Memory implements INode { inputs: INodeParams[] constructor() { - this.label = 'DynamoDB Memory' - this.name = 'DynamoDbMemory' + this.label = 'DynamoDB Chat Memory' + this.name = 'DynamoDBChatMemory' + this.type = 'DynamoDBChatMemory' this.icon = 'dynamodb.svg' this.category = 'Memory' this.description = 'Stores the conversation in dynamo db table' diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index 42a63057..305326f7 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -168,8 +168,10 @@ export const isValidConnection = (connection, reactFlowInstance) => { //sourceHandle: "llmChain_0-output-llmChain-BaseChain" //targetHandle: "mrlkAgentLLM_0-input-model-BaseLanguageModel" - const sourceTypes = sourceHandle.split('-')[sourceHandle.split('-').length - 1].split('|') - const targetTypes = targetHandle.split('-')[targetHandle.split('-').length - 1].split('|') + let sourceTypes = sourceHandle.split('-')[sourceHandle.split('-').length - 1].split('|') + sourceTypes = sourceTypes.map((s) => s.trim()) + let targetTypes = targetHandle.split('-')[targetHandle.split('-').length - 1].split('|') + targetTypes = targetTypes.map((t) => t.trim()) if (targetTypes.some((t) => sourceTypes.includes(t))) { let targetNode = reactFlowInstance.getNode(target) From 050a972da818bfcbf3b380ce45c9742fbbdf0968 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 9 Jul 2023 16:16:13 +0100 Subject: [PATCH 077/183] update marketplace --- .../chatflows/Conversational Retrieval QA Chain.json | 8 ++++++++ .../server/marketplaces/chatflows/Github Repo QnA.json | 8 ++++++++ packages/server/marketplaces/chatflows/Local QnA.json | 8 ++++++++ .../marketplaces/chatflows/Metadata Filter Load.json | 8 ++++++++ .../marketplaces/chatflows/Metadata Filter Upsert.json | 8 ++++++++ 5 files changed, 40 insertions(+) diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json index ed190cdc..e420fefe 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json @@ -539,6 +539,14 @@ "name": "vectorStoreRetriever", "type": "BaseRetriever", "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + }, + { + "label": "Memory", + "name": "memory", + "type": "DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory", + "optional": true, + "description": "If no memory connected, default BufferMemory will be used", + "id": "conversationalRetrievalQAChain_0-input-memory-DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory" } ], "inputs": { diff --git a/packages/server/marketplaces/chatflows/Github Repo QnA.json b/packages/server/marketplaces/chatflows/Github Repo QnA.json index 92867957..92ad0967 100644 --- a/packages/server/marketplaces/chatflows/Github Repo QnA.json +++ b/packages/server/marketplaces/chatflows/Github Repo QnA.json @@ -556,6 +556,14 @@ "name": "vectorStoreRetriever", "type": "BaseRetriever", "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + }, + { + "label": "Memory", + "name": "memory", + "type": "DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory", + "optional": true, + "description": "If no memory connected, default BufferMemory will be used", + "id": "conversationalRetrievalQAChain_0-input-memory-DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory" } ], "inputs": { diff --git a/packages/server/marketplaces/chatflows/Local QnA.json b/packages/server/marketplaces/chatflows/Local QnA.json index 9cfba954..5265c428 100644 --- a/packages/server/marketplaces/chatflows/Local QnA.json +++ b/packages/server/marketplaces/chatflows/Local QnA.json @@ -131,6 +131,14 @@ "name": "vectorStoreRetriever", "type": "BaseRetriever", "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + }, + { + "label": "Memory", + "name": "memory", + "type": "DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory", + "optional": true, + "description": "If no memory connected, default BufferMemory will be used", + "id": "conversationalRetrievalQAChain_0-input-memory-DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory" } ], "inputs": { diff --git a/packages/server/marketplaces/chatflows/Metadata Filter Load.json b/packages/server/marketplaces/chatflows/Metadata Filter Load.json index dfc6d6fb..61dad1c4 100644 --- a/packages/server/marketplaces/chatflows/Metadata Filter Load.json +++ b/packages/server/marketplaces/chatflows/Metadata Filter Load.json @@ -421,6 +421,14 @@ "name": "vectorStoreRetriever", "type": "BaseRetriever", "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + }, + { + "label": "Memory", + "name": "memory", + "type": "DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory", + "optional": true, + "description": "If no memory connected, default BufferMemory will be used", + "id": "conversationalRetrievalQAChain_0-input-memory-DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory" } ], "inputs": { diff --git a/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json b/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json index 87336654..f2273825 100644 --- a/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json +++ b/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json @@ -625,6 +625,14 @@ "name": "vectorStoreRetriever", "type": "BaseRetriever", "id": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever" + }, + { + "label": "Memory", + "name": "memory", + "type": "DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory", + "optional": true, + "description": "If no memory connected, default BufferMemory will be used", + "id": "conversationalRetrievalQAChain_0-input-memory-DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory" } ], "inputs": { From aeb143a04e95ec18928d83cf89e2ed5dd1a813f7 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 9 Jul 2023 16:30:19 +0100 Subject: [PATCH 078/183] update zep icon --- .../nodes/memory/ZepMemory/ZepMemory.ts | 2 +- .../nodes/memory/ZepMemory/memory.svg | 8 -------- .../components/nodes/memory/ZepMemory/zep.png | Bin 0 -> 17169 bytes 3 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 packages/components/nodes/memory/ZepMemory/memory.svg create mode 100644 packages/components/nodes/memory/ZepMemory/zep.png diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 2e7ba001..23691e30 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -18,7 +18,7 @@ class ZepMemory_Memory implements INode { this.label = 'Zep Memory' this.name = 'ZepMemory' this.type = 'ZepMemory' - this.icon = 'memory.svg' + this.icon = 'zep.png' this.category = 'Memory' this.description = 'Summarizes the conversation and stores the memory in zep server' this.baseClasses = [this.type, ...getBaseClasses(ZepMemory)] diff --git a/packages/components/nodes/memory/ZepMemory/memory.svg b/packages/components/nodes/memory/ZepMemory/memory.svg deleted file mode 100644 index ca8e17da..00000000 --- a/packages/components/nodes/memory/ZepMemory/memory.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/packages/components/nodes/memory/ZepMemory/zep.png b/packages/components/nodes/memory/ZepMemory/zep.png new file mode 100644 index 0000000000000000000000000000000000000000..293be6f6de9a238631d52059b16c90aa8e395fdb GIT binary patch literal 17169 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 Y|Gacjls{jx3;+NC07*qoM6N<$f`z|uM*si- literal 0 HcmV?d00001 From 46a1e43dc3438b51e142f7bcd5557c6493ac4fea Mon Sep 17 00:00:00 2001 From: vjsai Date: Mon, 10 Jul 2023 03:34:42 +0530 Subject: [PATCH 079/183] Added support for OpenSearch --- .../OpenSearch_existing.ts | 95 +++++++++++++++ .../OpenSearch_Existing/opensearch.png | Bin 0 -> 5216 bytes .../OpenSearch_Upsert/OpenSearch_Upsert.ts | 110 ++++++++++++++++++ .../OpenSearch_Upsert/opensearch.png | Bin 0 -> 5216 bytes packages/components/package.json | 1 + 5 files changed, 206 insertions(+) create mode 100644 packages/components/nodes/vectorstores/OpenSearch_Existing/OpenSearch_existing.ts create mode 100644 packages/components/nodes/vectorstores/OpenSearch_Existing/opensearch.png create mode 100644 packages/components/nodes/vectorstores/OpenSearch_Upsert/OpenSearch_Upsert.ts create mode 100644 packages/components/nodes/vectorstores/OpenSearch_Upsert/opensearch.png diff --git a/packages/components/nodes/vectorstores/OpenSearch_Existing/OpenSearch_existing.ts b/packages/components/nodes/vectorstores/OpenSearch_Existing/OpenSearch_existing.ts new file mode 100644 index 00000000..7aeac919 --- /dev/null +++ b/packages/components/nodes/vectorstores/OpenSearch_Existing/OpenSearch_existing.ts @@ -0,0 +1,95 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { OpenSearchVectorStore } from 'langchain/vectorstores/opensearch' +import { Embeddings } from 'langchain/embeddings/base' +import { Client } from '@opensearch-project/opensearch' +import { getBaseClasses } from '../../../src/utils' + +class OpenSearch_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 = 'OpenSearch Load Existing Index' + this.name = 'openSearchExistingIndex' + this.type = 'OpenSearch' + this.icon = 'opensearch.png' + this.category = 'Vector Stores' + this.description = 'Load existing index from OpenSearch (i.e: Document has been upserted)' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'OpenSearch URL', + name: 'opensearchURL', + type: 'string', + placeholder: 'http://127.0.0.1:9200' + }, + { + label: 'Index Name', + name: 'indexName', + type: 'string' + }, + { + 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: 'OpenSearch Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'OpenSearch Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(OpenSearchVectorStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const embeddings = nodeData.inputs?.embeddings as Embeddings + const opensearchURL = nodeData.inputs?.opensearchURL as string + const indexName = nodeData.inputs?.indexName as string + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 + + const client = new Client({ + nodes: [opensearchURL] + }) + + const vectorStore = new OpenSearchVectorStore(embeddings, { + client, + indexName + }) + + 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: OpenSearch_Existing_VectorStores } diff --git a/packages/components/nodes/vectorstores/OpenSearch_Existing/opensearch.png b/packages/components/nodes/vectorstores/OpenSearch_Existing/opensearch.png new file mode 100644 index 0000000000000000000000000000000000000000..3fdcfd3f09ed02c1478f690884ecafe844cb32ae GIT binary patch literal 5216 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 { + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const opensearchURL = nodeData.inputs?.opensearchURL as string + const indexName = nodeData.inputs?.indexName as string + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseInt(topK, 10) : 4 + + 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 client = new Client({ + nodes: [opensearchURL] + }) + + const vectorStore = await OpenSearchVectorStore.fromDocuments(finalDocs, embeddings, { + client, + indexName: indexName + }) + + 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: OpenSearchUpsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/OpenSearch_Upsert/opensearch.png b/packages/components/nodes/vectorstores/OpenSearch_Upsert/opensearch.png new file mode 100644 index 0000000000000000000000000000000000000000..3fdcfd3f09ed02c1478f690884ecafe844cb32ae GIT binary patch literal 5216 zcmcgwS5y-~vkpy=5~`pOy7b-ys0f56y@o0sX-Wtkq)6`&dIt?vq=&9_fl!pDAT0=? zO7955pa1bb+{b&*p4mD3&6(Yu**UYb8>gqEMnTF*3IG5oG}IvmIF9-+k>KO{#yW%} zjuF06)K&xl8dAt^Y>9AVHhXmgZ2%yU8vqE41OWcxps+0fz(*7S*s%csVPtE_FT+hti>ComH1eO{D|CTb80SS} z1kh=M2X3F`ll>ayrBi_#S~O`Bo^o0=_)CTNNEvwEs_y)oyP@YNoP{UN+3{qGGt1 z1q^{vN}i{`zvzOl<5c%}?3z@YuHI@~Z6|OcY12_LyhH+e$uZZ{f3?fb zM{s|QPvZoO9#YnIr#)ogRI~;SmDOqU&vlOK*0^r(`hr0yHIZimYQRi`t4`=kEnST) zlwl^^&}1-qQD|>AkjLYpb0{%K6C4W;__XrAaG}bZnGxh|a{;U|S(;gHP6M(FK$0^C z+Mw3{s_zeXvVJhpk-iC5HQ(PjgYM+N5)#1jJ49&}E1X>Fm!;jBiW{V1ZYyS`9bZiV zAs~}J1ZVK;sV0T=QHpjG8S*-)}izSw5Z%tPfksPGK!$j)Go(2K|wk#`lq%*B2Z|5?vQxUwMiY z@9GU~uFs|Bs>%~WK|%hQ>6AN)J>DlcP+eQ6gtoV=o8z9es`E2qEQ9(%2B zWTy^pu7^!)$m9EI#HR%R6|?SWKMRoEB_l&AQ}rPJU11wjAw!$%qPeeJV%Px5Yau6# zwCsxSPz0W2#o6}m@OWR6J`zfbfSpNB6cUERke#8p6~bk6x;eSTihSut*G}Lw&8TUh zq9|$9>Q#EmRB~Su*oAmS2)dIu*3{+wt6})(nc0Gu^mMY_qqi@U1FCE4+Y){0iVm?z zHHTX*9VOXS0}`p5(Bfi=huTvVkjDlzYjEk2KpiW7S#QPR5w4k*0N?X*h3-^}mLttZVCnF4jc27!&-l?qfO{iAZK5k+jnL(L*-hJx^Qhw;0=X z^5#1Qy-sAA8XZG8x|x&LGWZ?Wf83jYCz$kDE%}-}fzW4yHON1N(y*P^CKt=kFj&ulvcnZ3A09rxHm2Jw1el#|mV zLK`(dNrV5b3pC5s_`X&uxcM$lMn<;&HxPIFc($_m;SCprbfkbBMiq~02LhbAj#55l z#(_2Fw5&`*3(7KKQu@>t-x$y^W-eP<_%R|U4U)k{{kZXIeND=k~5 zUybZrcSIj<`P*CY?D6hLmx7L}<{D3~S)1i%vU(IHWQLx&x$Nz<>zR9YR65xBJJ|M8 zKVsR~RJ%*GZ(0z^G@edgeWM;ClItf7SpyH$r5`u;6Wv3$*Oz{-FSRT)T24pgwK^&< z51#R9%&;I|m6gFjJgxGAc4|PKcfl#Wy1_#tHD2O~HF{u$m$zMGOVydT{&wz%siJr9 ziDEd#1&#K2T)6A!zt@$%5NRE$7{HpQ`wz)FB2r;9R?&vO+3N{orZ?Je?K{2=*FMv`DAa=l#Bt6%8c2Ip(0r{#5a=QTaGCiL=4=HhP_PlRUVTw&bgn(LL>`6(^ zl88P|*V;^jPl^1NA-u(aK`!Kl203-DtPP;zG?(a1x;B>r=$y`r)MszZ{;zKZ=$; zyv>sYe}?Uy?Ym8dt~H&IEtKMLJgLI%ro8XuR29yo&#BO=zGP+aQLDg)aCt}Vw(ddb z=iP;S(W-=!JF97-I;c`Aakzi6`WNo|L)LxoE2!vI9xJ(AeLl>e;{WpJ;*^3+V*@5; zCdy;3NGc9Dg`~;&aO!l$_kU+oof>afKRma--RVeHFD}tyg{lGBXN85?Ib*h?owYqd z2K&8n*`B-5;W>P+e-1X^bEF?)u2v+vph8|=$nf}lqL`yDNr8Ens4>6SdGy`mmqr5> zeRiq~#@;_|QJ5J1RAG2m+wSn~Q%&J&57z^@TqHRUIQ)X^sOzn`8t{aIV;ujGa^R;G z_(;4ckhzmp(UKI4x*3qtsW?H_eG|D#TN!Z@E>}$YkO|HB=Mt4tLgl_3wf=`fu#LEt>!w&dxH%~qo{K^5azYVk-0x!fA}aCm4iFZ*#r31{6%4d$ zK$RU%Oe(sVkR`D75t)gHQFsE^Og{PQ*q1fTPZzt6;qfWe9RSCn#b&gBH}(s4H9fwg z_LhDgdfyO9qb4fmyT$xO5VCBs4HW%oAL?0Xn#tYPGF(vjrD=0u;PtAY@Ka3Yfxc7pKO>-N;Pw(dHjwW=MMiS@= z@|Q?^w!K3FO;*ea0qC0_>$MlLOgtL0f9Gv{1inW-1I6rG7Tm}`)GI0EMfv>Zk&a_mi&7z@h8nV(J_8jP_?2JsskE zZ`FYSAN;zO%6?D(mHJpsg)B(L5BjXE3(EV&>BB454sugGcHBc8k?T1R>8)PzT~4MT zIuMp8x8~zPEJm)gMC?VO(Z~vwQ!u*1^`#K|a?~Yk&Z}>!%&d=3kdO9lIzF5HcVkzf zcM{+L*}n^7TpOrD$mO5mLQ7of@Z`$Wm*qec`R}m_b9#3FHsq`9>X%fTf5EjsaSsxu zkJTzng^-W-nvM7?=Ct>o{rpBll27%vwjqxWr)FE*? z3=A5}Y1DM_?zekKTGLyvIcJ(8;ihA4m+UKsd}{;yPDHSS5Ga|rt0uazuFNWJ9+NCr zJWpVv7=(VKURvEh_&I6|ZGPOFU7YnSv@a&0q{p~9YLIk}WJ5qhIA$rj>A-H8!>7B5 z-8C@MhGxoge05<%U(@$IchJ_$mHZV3*?AP-8QZ#VBuYx;%j$U+-C;1rH=XgVilFdv zgI3Op%q}p|e0G5aLU@;i@&Psvj51GmuDI4l-_kTMFGc>mNBJHx95noc>7z*eZP;y$ z-WNs@`3Tp52i{%zii|;)Fw)(iMPsXGrnSm!4*lr!8uN8oqx*ad0$eBbi4(j%g{^+M zA62uvPwRK`o;#1?m3*W!jzUG4#($F|g^r79wE1;c%bd2P4gWEtc{GeyvV$N0EN!Ds z5pb6Feb&VXd`SkC@Q@aWSD>9J@f%hK0>+ImcVCZ@R#mEHxFs1_C&59$BFpky*p(Tz zfS_Q4I~D-@!ob=U*MK+g2_Pe*qsj0EnR5p_v@uZ>85v(Z>|4kr`zjZc`RU1oT$27IaB*YpUP86hCM` z71Nq3)T2lI!WBgls*+HGo;(Q2l3O2Bx$4n+Xs=Ud?NA#X-4lXYi%Uu9`*QV;)Gp;( zH~=OmIv4em@zRCr5s!*(8``$uZ+a=^0>|3n@X6>5$3xb>Ul)}#JZTM}1k=sQ>G0Zn zTj97XLl3cR^7ic2{f*LY9yz>}CzLhcegp|hfX`S_EWpv1$tr>pTc6Y{DCl=d66gcI zhAqW6*6IBvB79T4_P~>GG{2!fOyVxd(={l^Ygk^j+JjR6V`>A@wnb{r{%b}13fk9F zl}|)`&U%~8{4de#bbhO=G0qQ6g|&>{>LpuTRv|Lte3(+}fr|JSwc?K^s??ODGQ3{M z{HkZZQp@hvuj4Phok3@>w=pLve4wR%kP<63LDZ`NxP5m>8P<x9OGj@6jLB6yZQ*3Xp>eWk3-#|xM0eb`Ey z9vM^oJe8^1reo*m%zEACUJhvBH`sl-U5n;QXub}&*JE_8BRuu7B+WI8pPxeGAe=6g zJKcVeLklR*OE-)j3MO9c{N?*$2mhnU$OyTpY;0<3^pcw8dNSr)AHHxf#P3-IC3j3^ zBxLm+KHk~Dh7;^uq*3T)AC@YvL=TI#@At(^Z&_0Yp9n{pfm?0i{@TuTxFWxh$Gy68 z!<8cPU|l;XAsAHQOkC3B;B z;%WEaAUKJ0Z{@;9pgnm&F)Jjv3yq(pfiG6$Sh`_r5Zv`NX`g$Z;3U$RJEoYCl@aG6O&xfFDjp7r#P67?Y6eB<<0g$uW~X@`kH;;S*5yMzZY=(Ac7ZT1xg|@yXV= z*p(nk^fx7fS4x5Z3P5=LF;mB5789oL=4o=>4R^SzpC>N{8|2vLsaoC7`xs{eOV2T8 zQyZiYPm%|1xu|Gz1und83~ALgc?ZH{hvYFcJy&}=f7S)f`t=$ImAgc$#!qKVze9wa zN+O%^?j2poAeeA-o>#YdbVG%-Qf%9Z|1ocQT%0jcDSpD2b&bbG>6_YRj&eRN%ONap z?S)-wHoVF_@KbWx{TC157i<#5z0`m=%X+4pq6);LTO`_#|DFY zIJvkxvUvx<9NAo8-u3{1f5F-@If Date: Mon, 10 Jul 2023 00:36:52 -0700 Subject: [PATCH 080/183] fix lint error and update apikey and clientid attributes --- .../nodes/memory/MotorheadMemory/MotorheadMemory.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 383ad613..d50b7064 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -48,12 +48,16 @@ class MotorMemory_Memory implements INode { label: 'API Key', name: 'apiKey', type: 'string', + description: 'Only needed when using hosted solution - https://getmetal.io', + additionalParams: true, optional: true }, { label: 'Client ID', name: 'clientId', type: 'string', + description: 'Only needed when using hosted solution - https://getmetal.io', + additionalParams: true, optional: true } ] @@ -68,8 +72,6 @@ class MotorMemory_Memory implements INode { const chatId = options?.chatId as string - console.log(chatId) - let obj: MotorheadMemoryInput = { returnMessages: true, sessionId: sessionId ? sessionId : chatId, From 9dd19178ff30ababe681cf4e200f8a981f370d27 Mon Sep 17 00:00:00 2001 From: chungyau97 Date: Mon, 10 Jul 2023 21:19:22 +0800 Subject: [PATCH 081/183] modify password api config --- packages/server/src/utils/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 9e8c5c32..9bcf0f71 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -632,9 +632,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => { for (const flowNode of reactFlowNodes) { for (const inputParam of flowNode.data.inputParams) { let obj: IOverrideConfig - if (inputParam.type === 'password') { - continue - } else if (inputParam.type === 'file') { + if (inputParam.type === 'file') { obj = { node: flowNode.data.label, label: inputParam.label, @@ -659,7 +657,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => { node: flowNode.data.label, label: inputParam.label, name: inputParam.name, - type: inputParam.type + type: inputParam.type === 'password' ? 'string' : inputParam.type } } if (!configs.some((config) => JSON.stringify(config) === JSON.stringify(obj))) { From cf3bd72a982dea17b641f380c496d5ee563ed732 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 10 Jul 2023 16:51:36 +0100 Subject: [PATCH 082/183] update message schema --- .../ConversationalAgent/ConversationalAgent.ts | 6 +++--- .../OpenAIFunctionAgent/OpenAIFunctionAgent.ts | 6 +++--- .../ConversationChain/ConversationChain.ts | 6 +++--- .../ConversationalRetrievalQAChain.ts | 17 +++++------------ .../nodes/memory/ZepMemory/ZepMemory.ts | 4 ++-- packages/server/src/index.ts | 8 ++++---- 6 files changed, 20 insertions(+), 27 deletions(-) diff --git a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts index 363b3907..568ced0b 100644 --- a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts +++ b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts @@ -3,7 +3,7 @@ import { initializeAgentExecutorWithOptions, AgentExecutor, InitializeAgentExecu import { Tool } from 'langchain/tools' import { BaseChatMemory, ChatMessageHistory } from 'langchain/memory' import { getBaseClasses } from '../../../src/utils' -import { AIChatMessage, HumanChatMessage } from 'langchain/schema' +import { AIMessage, HumanMessage } from 'langchain/schema' import { BaseLanguageModel } from 'langchain/base_language' import { flatten } from 'lodash' @@ -99,9 +99,9 @@ class ConversationalAgent_Agents implements INode { for (const message of histories) { if (message.type === 'apiMessage') { - chatHistory.push(new AIChatMessage(message.message)) + chatHistory.push(new AIMessage(message.message)) } else if (message.type === 'userMessage') { - chatHistory.push(new HumanChatMessage(message.message)) + chatHistory.push(new HumanMessage(message.message)) } } memory.chatHistory = new ChatMessageHistory(chatHistory) diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index 1cbcb547..43de74cb 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -4,7 +4,7 @@ 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' +import { AIMessage, HumanMessage } from 'langchain/schema' class OpenAIFunctionAgent_Agents implements INode { label: string @@ -84,9 +84,9 @@ class OpenAIFunctionAgent_Agents implements INode { for (const message of histories) { if (message.type === 'apiMessage') { - chatHistory.push(new AIChatMessage(message.message)) + chatHistory.push(new AIMessage(message.message)) } else if (message.type === 'userMessage') { - chatHistory.push(new HumanChatMessage(message.message)) + chatHistory.push(new HumanMessage(message.message)) } } memory.chatHistory = new ChatMessageHistory(chatHistory) diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index 843e05fc..27af5b8e 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -4,7 +4,7 @@ import { CustomChainHandler, getBaseClasses } from '../../../src/utils' import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts' import { BufferMemory, ChatMessageHistory } from 'langchain/memory' import { BaseChatModel } from 'langchain/chat_models/base' -import { AIChatMessage, HumanChatMessage } from 'langchain/schema' +import { AIMessage, HumanMessage } from 'langchain/schema' const systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.` @@ -81,9 +81,9 @@ class ConversationChain_Chains implements INode { for (const message of histories) { if (message.type === 'apiMessage') { - chatHistory.push(new AIChatMessage(message.message)) + chatHistory.push(new AIMessage(message.message)) } else if (message.type === 'userMessage') { - chatHistory.push(new HumanChatMessage(message.message)) + chatHistory.push(new HumanMessage(message.message)) } } memory.chatHistory = new ChatMessageHistory(chatHistory) diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 4872717f..2dccd012 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -6,33 +6,26 @@ import { AIMessage, BaseRetriever, HumanMessage } from 'langchain/schema' import { BaseChatMemory, BufferMemory, ChatMessageHistory } from 'langchain/memory' import { PromptTemplate } from 'langchain/prompts' -const default_qa_template = `Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. +const default_qa_template = `Use the following pieces of context to answer the question at the end, in its original language. If you don't know the answer, just say that you don't know in its original language, don't try to make up an answer. {context} Question: {question} Helpful Answer:` -const qa_template = `Use the following pieces of context to answer the question at the end. +const qa_template = `Use the following pieces of context to answer the question at the end, in its original language. {context} Question: {question} Helpful Answer:` -const CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT = `Given the following conversation and a follow up question, return the conversation history excerpt that includes any relevant context to the question if it exists and rephrase the follow up question to be a standalone question. +const CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language. include it in the standalone question. + Chat History: {chat_history} Follow Up Input: {question} -Your answer should follow the following format: -\`\`\` -Use the following pieces of context to answer the users question. -If you don't know the answer, just say that you don't know, don't try to make up an answer. ----------------- - -Standalone question: -\`\`\` -Your answer:` +Standalone question:` class ConversationalRetrievalQAChain_Chains implements INode { label: string diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 23691e30..89d07b8f 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -1,4 +1,4 @@ -import { SystemChatMessage } from 'langchain/schema' +import { SystemMessage } from 'langchain/schema' import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' @@ -123,7 +123,7 @@ class ZepMemory_Memory implements INode { let summary = autoSummaryTemplate.replace(/{summary}/g, memory.summary.content) // eslint-disable-next-line no-console console.log('[ZepMemory] auto summary:', summary) - data[zep.memoryKey].unshift(new SystemChatMessage(summary)) + data[zep.memoryKey].unshift(new SystemMessage(summary)) } } // for langchain zep memory compatibility, or we will get "Missing value for input variable chat_history" diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index cd4978a0..ef9bc42d 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -687,13 +687,13 @@ export class App { } } - /* Don't rebuild the flow (to avoid duplicated upsert, recomputation) when all these conditions met: + /* Reuse the flow without having to rebuild (to avoid duplicated upsert, recomputation) when all these conditions met: * - Node Data already exists in pool * - Still in sync (i.e the flow has not been modified since) * - Existing overrideConfig and new overrideConfig are the same * - Flow doesn't start with nodes that depend on incomingInput.question ***/ - const isRebuildNeeded = () => { + const isFlowReusable = () => { return ( Object.prototype.hasOwnProperty.call(this.chatflowPool.activeChatflows, chatflowid) && this.chatflowPool.activeChatflows[chatflowid].inSync && @@ -707,7 +707,7 @@ export class App { } if (process.env.EXECUTION_MODE === 'child') { - if (isRebuildNeeded()) { + if (isFlowReusable()) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData try { const result = await this.startChildProcess(chatflow, chatId, incomingInput, nodeToExecuteData) @@ -731,7 +731,7 @@ export class App { const nodes = parsedFlowData.nodes const edges = parsedFlowData.edges - if (isRebuildNeeded()) { + if (isFlowReusable()) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData isStreamValid = isFlowValidForStream(nodes, nodeToExecuteData) } else { From eb19c206cfddc9d3f62e98312a6bee7641ed84ce Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 01:53:22 +0100 Subject: [PATCH 083/183] add logs to component chains/agents --- .../nodes/agents/AutoGPT/AutoGPT.ts | 1 - .../OpenAIFunctionAgent.ts | 9 +- .../nodes/chains/ApiChain/GETApiChain.ts | 9 +- .../nodes/chains/ApiChain/OpenAPIChain.ts | 9 +- .../nodes/chains/ApiChain/POSTApiChain.ts | 9 +- .../ConversationChain/ConversationChain.ts | 9 +- .../ConversationalRetrievalQAChain.ts | 9 +- .../nodes/chains/LLMChain/LLMChain.ts | 40 ++-- .../MultiPromptChain/MultiPromptChain.ts | 9 +- .../MultiRetrievalQAChain.ts | 8 +- .../RetrievalQAChain/RetrievalQAChain.ts | 8 +- .../SqlDatabaseChain/SqlDatabaseChain.ts | 9 +- .../chains/VectorDBQAChain/VectorDBQAChain.ts | 9 +- packages/components/src/handler.ts | 180 ++++++++++++++++++ packages/components/src/utils.ts | 48 +---- packages/server/.env.example | 3 +- packages/server/src/ChatflowPool.ts | 4 + packages/server/src/ChildProcess.ts | 176 +++++++++-------- packages/server/src/commands/start.ts | 2 + packages/server/src/index.ts | 36 +++- packages/server/src/utils/config.ts | 4 +- packages/server/src/utils/index.ts | 8 +- packages/server/src/utils/logger.ts | 14 +- 23 files changed, 414 insertions(+), 199 deletions(-) create mode 100644 packages/components/src/handler.ts diff --git a/packages/components/nodes/agents/AutoGPT/AutoGPT.ts b/packages/components/nodes/agents/AutoGPT/AutoGPT.ts index ca118500..044b6f7b 100644 --- a/packages/components/nodes/agents/AutoGPT/AutoGPT.ts +++ b/packages/components/nodes/agents/AutoGPT/AutoGPT.ts @@ -90,7 +90,6 @@ class AutoGPT_Agents implements INode { const res = await executor.run([input]) return res || 'I have completed all my tasks.' } catch (e) { - console.error(e) throw new Error(e) } } diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index 43de74cb..f4d065d9 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -1,10 +1,11 @@ import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { flatten } from 'lodash' import { BaseChatMemory, ChatMessageHistory } from 'langchain/memory' import { AIMessage, HumanMessage } from 'langchain/schema' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class OpenAIFunctionAgent_Agents implements INode { label: string @@ -93,12 +94,14 @@ class OpenAIFunctionAgent_Agents implements INode { executor.memory = memory } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const result = await executor.run(input, [handler]) + const result = await executor.run(input, [loggerHandler, handler]) return result } else { - const result = await executor.run(input) + const result = await executor.run(input, [loggerHandler]) return result } } diff --git a/packages/components/nodes/chains/ApiChain/GETApiChain.ts b/packages/components/nodes/chains/ApiChain/GETApiChain.ts index 8e657749..373d0462 100644 --- a/packages/components/nodes/chains/ApiChain/GETApiChain.ts +++ b/packages/components/nodes/chains/ApiChain/GETApiChain.ts @@ -1,8 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { APIChain } from 'langchain/chains' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { PromptTemplate } from 'langchain/prompts' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' export const API_URL_RAW_PROMPT_TEMPLATE = `You are given the below API Documentation: {api_docs} @@ -95,12 +96,14 @@ class GETApiChain_Chains implements INode { const ansPrompt = nodeData.inputs?.ansPrompt as string const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt) + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts index ae1ae3c0..a231e80a 100644 --- a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -1,7 +1,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { APIChain, createOpenAPIChain } from 'langchain/chains' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { ChatOpenAI } from 'langchain/chat_models/openai' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class OpenApiChain_Chains implements INode { label: string @@ -57,12 +58,14 @@ class OpenApiChain_Chains implements INode { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = await initChain(nodeData) + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/ApiChain/POSTApiChain.ts b/packages/components/nodes/chains/ApiChain/POSTApiChain.ts index 3c6ea677..7189f1ad 100644 --- a/packages/components/nodes/chains/ApiChain/POSTApiChain.ts +++ b/packages/components/nodes/chains/ApiChain/POSTApiChain.ts @@ -1,8 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' import { PromptTemplate } from 'langchain/prompts' import { API_RESPONSE_RAW_PROMPT_TEMPLATE, API_URL_RAW_PROMPT_TEMPLATE, APIChain } from './postCore' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class POSTApiChain_Chains implements INode { label: string @@ -84,12 +85,14 @@ class POSTApiChain_Chains implements INode { const ansPrompt = nodeData.inputs?.ansPrompt as string const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt) + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index 27af5b8e..f1df0183 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -1,10 +1,11 @@ import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { ConversationChain } from 'langchain/chains' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts' import { BufferMemory, ChatMessageHistory } from 'langchain/memory' import { BaseChatModel } from 'langchain/chat_models/base' import { AIMessage, HumanMessage } from 'langchain/schema' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' const systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.` @@ -90,12 +91,14 @@ class ConversationChain_Chains implements INode { chain.memory = memory } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.call({ input }, [handler]) + const res = await chain.call({ input }, [loggerHandler, handler]) return res?.response } else { - const res = await chain.call({ input }) + const res = await chain.call({ input }, [loggerHandler]) return res?.response } } diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 2dccd012..24b40d48 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -1,10 +1,11 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { ConversationalRetrievalQAChain } from 'langchain/chains' import { AIMessage, BaseRetriever, HumanMessage } from 'langchain/schema' import { BaseChatMemory, BufferMemory, ChatMessageHistory } from 'langchain/memory' import { PromptTemplate } from 'langchain/prompts' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' const default_qa_template = `Use the following pieces of context to answer the question at the end, in its original language. If you don't know the answer, just say that you don't know in its original language, don't try to make up an answer. @@ -175,13 +176,15 @@ class ConversationalRetrievalQAChain_Chains implements INode { chain.memory = memory } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, undefined, returnSourceDocuments) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) if (res.text && res.sourceDocuments) return res return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) if (res.text && res.sourceDocuments) return res return res?.text } diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index 67c21ce4..1d0ccb92 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -1,7 +1,8 @@ import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { LLMChain } from 'langchain/chains' import { BaseLanguageModel } from 'langchain/base_language' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class LLMChain_Chains implements INode { label: string @@ -55,7 +56,7 @@ class LLMChain_Chains implements INode { ] } - async init(nodeData: INodeData, input: string): Promise { + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as BaseLanguageModel const prompt = nodeData.inputs?.prompt const output = nodeData.outputs?.output as string @@ -67,7 +68,7 @@ class LLMChain_Chains implements INode { } else if (output === 'outputPrediction') { const chain = new LLMChain({ llm: model, prompt, verbose: process.env.DEBUG === 'true' ? true : false }) const inputVariables = chain.prompt.inputVariables as string[] // ["product"] - const res = await runPrediction(inputVariables, chain, input, promptValues) + const res = await runPrediction(inputVariables, chain, input, promptValues, options) // eslint-disable-next-line no-console console.log('\x1b[92m\x1b[1m\n*****OUTPUT PREDICTION*****\n\x1b[0m\x1b[0m') // eslint-disable-next-line no-console @@ -81,9 +82,7 @@ class LLMChain_Chains implements INode { const chain = nodeData.instance as LLMChain const promptValues = nodeData.inputs?.prompt.promptValues as ICommonObject - const res = options.socketIO - ? await runPrediction(inputVariables, chain, input, promptValues, true, options.socketIO, options.socketIOClientId) - : await runPrediction(inputVariables, chain, input, promptValues) + const res = await runPrediction(inputVariables, chain, input, promptValues, options) // eslint-disable-next-line no-console console.log('\x1b[93m\x1b[1m\n*****FINAL RESULT*****\n\x1b[0m\x1b[0m') // eslint-disable-next-line no-console @@ -97,17 +96,20 @@ const runPrediction = async ( chain: LLMChain, input: string, promptValues: ICommonObject, - isStreaming?: boolean, - socketIO?: any, - socketIOClientId = '' + options: ICommonObject ) => { + const loggerHandler = new ConsoleCallbackHandler(options.logger) + const isStreaming = options.socketIO && options.socketIOClientId + const socketIO = isStreaming ? options.socketIO : undefined + const socketIOClientId = isStreaming ? options.socketIOClientId : '' + if (inputVariables.length === 1) { if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } else if (inputVariables.length > 1) { @@ -122,15 +124,13 @@ const runPrediction = async ( if (seen.length === 0) { // All inputVariables have fixed values specified - const options = { - ...promptValues - } + const options = { ...promptValues } if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.call(options, [handler]) + const res = await chain.call(options, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(options) + const res = await chain.call(options, [loggerHandler]) return res?.text } } else if (seen.length === 1) { @@ -143,10 +143,10 @@ const runPrediction = async ( } if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.call(options, [handler]) + const res = await chain.call(options, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(options) + const res = await chain.call(options, [loggerHandler]) return res?.text } } else { @@ -155,10 +155,10 @@ const runPrediction = async ( } else { if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts index 189f41f7..e9783639 100644 --- a/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts +++ b/packages/components/nodes/chains/MultiPromptChain/MultiPromptChain.ts @@ -1,7 +1,8 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, INode, INodeData, INodeParams, PromptRetriever } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { MultiPromptChain } from 'langchain/chains' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class MultiPromptChain_Chains implements INode { label: string @@ -63,12 +64,14 @@ class MultiPromptChain_Chains implements INode { const chain = nodeData.instance as MultiPromptChain const obj = { input } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) return res?.text } } diff --git a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts index b3575a93..a1947faa 100644 --- a/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts +++ b/packages/components/nodes/chains/MultiRetrievalQAChain/MultiRetrievalQAChain.ts @@ -1,7 +1,8 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, INode, INodeData, INodeParams, VectorStoreRetriever } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { MultiRetrievalQAChain } from 'langchain/chains' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class MultiRetrievalQAChain_Chains implements INode { label: string @@ -71,14 +72,15 @@ class MultiRetrievalQAChain_Chains implements INode { const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean const obj = { input } + const loggerHandler = new ConsoleCallbackHandler(options.logger) if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2, returnSourceDocuments) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) if (res.text && res.sourceDocuments) return res return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) if (res.text && res.sourceDocuments) return res return res?.text } diff --git a/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts b/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts index 97fa51a1..eaf5a0a9 100644 --- a/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts +++ b/packages/components/nodes/chains/RetrievalQAChain/RetrievalQAChain.ts @@ -1,8 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { RetrievalQAChain } from 'langchain/chains' import { BaseRetriever } from 'langchain/schema' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { BaseLanguageModel } from 'langchain/base_language' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class RetrievalQAChain_Chains implements INode { label: string @@ -49,13 +50,14 @@ class RetrievalQAChain_Chains implements INode { const obj = { query: input } + const loggerHandler = new ConsoleCallbackHandler(options.logger) if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) return res?.text } } diff --git a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts index 08d3eee5..5817264d 100644 --- a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts +++ b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts @@ -1,9 +1,10 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { SqlDatabaseChain, SqlDatabaseChainInput } from 'langchain/chains/sql_db' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { DataSource } from 'typeorm' import { SqlDatabase } from 'langchain/sql_db' import { BaseLanguageModel } from 'langchain/base_language' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class SqlDatabaseChain_Chains implements INode { label: string @@ -65,12 +66,14 @@ class SqlDatabaseChain_Chains implements INode { const dbFilePath = nodeData.inputs?.dbFilePath const chain = await getSQLDBChain(databaseType, dbFilePath, model) + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2) - const res = await chain.run(input, [handler]) + const res = await chain.run(input, [loggerHandler, handler]) return res } else { - const res = await chain.run(input) + const res = await chain.run(input, [loggerHandler]) return res } } diff --git a/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts b/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts index 5850ed7b..abe7aab3 100644 --- a/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts +++ b/packages/components/nodes/chains/VectorDBQAChain/VectorDBQAChain.ts @@ -1,8 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { CustomChainHandler, getBaseClasses } from '../../../src/utils' +import { getBaseClasses } from '../../../src/utils' import { VectorDBQAChain } from 'langchain/chains' import { BaseLanguageModel } from 'langchain/base_language' import { VectorStore } from 'langchain/vectorstores' +import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' class VectorDBQAChain_Chains implements INode { label: string @@ -53,12 +54,14 @@ class VectorDBQAChain_Chains implements INode { query: input } + const loggerHandler = new ConsoleCallbackHandler(options.logger) + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const res = await chain.call(obj, [handler]) + const res = await chain.call(obj, [loggerHandler, handler]) return res?.text } else { - const res = await chain.call(obj) + const res = await chain.call(obj, [loggerHandler]) return res?.text } } diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts new file mode 100644 index 00000000..8e363361 --- /dev/null +++ b/packages/components/src/handler.ts @@ -0,0 +1,180 @@ +import { BaseTracer, Run, BaseCallbackHandler } from 'langchain/callbacks' +import { AgentAction, ChainValues } from 'langchain/schema' +import { Logger } from 'winston' +import { Server } from 'socket.io' + +interface AgentRun extends Run { + actions: AgentAction[] +} + +function tryJsonStringify(obj: unknown, fallback: string) { + try { + return JSON.stringify(obj, null, 2) + } catch (err) { + return fallback + } +} + +function elapsed(run: Run): string { + if (!run.end_time) return '' + const elapsed = run.end_time - run.start_time + if (elapsed < 1000) { + return `${elapsed}ms` + } + return `${(elapsed / 1000).toFixed(2)}s` +} + +export class ConsoleCallbackHandler extends BaseTracer { + name = 'console_callback_handler' as const + logger: Logger + + protected persistRun(_run: Run) { + return Promise.resolve() + } + + constructor(logger: Logger) { + super() + this.logger = logger + } + + // utility methods + + getParents(run: Run) { + const parents: Run[] = [] + let currentRun = run + while (currentRun.parent_run_id) { + const parent = this.runMap.get(currentRun.parent_run_id) + if (parent) { + parents.push(parent) + currentRun = parent + } else { + break + } + } + return parents + } + + getBreadcrumbs(run: Run) { + const parents = this.getParents(run).reverse() + const string = [...parents, run] + .map((parent) => { + const name = `${parent.execution_order}:${parent.run_type}:${parent.name}` + return name + }) + .join(' > ') + return string + } + + // logging methods + + onChainStart(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose(`[chain/start] [${crumbs}] Entering Chain run with input: ${tryJsonStringify(run.inputs, '[inputs]')}`) + } + + onChainEnd(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[chain/end] [${crumbs}] [${elapsed(run)}] Exiting Chain run with output: ${tryJsonStringify(run.outputs, '[outputs]')}` + ) + } + + onChainError(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[chain/error] [${crumbs}] [${elapsed(run)}] Chain run errored with error: ${tryJsonStringify(run.error, '[error]')}` + ) + } + + onLLMStart(run: Run) { + const crumbs = this.getBreadcrumbs(run) + const inputs = 'prompts' in run.inputs ? { prompts: (run.inputs.prompts as string[]).map((p) => p.trim()) } : run.inputs + this.logger.verbose(`[llm/start] [${crumbs}] Entering LLM run with input: ${tryJsonStringify(inputs, '[inputs]')}`) + } + + onLLMEnd(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[llm/end] [${crumbs}] [${elapsed(run)}] Exiting LLM run with output: ${tryJsonStringify(run.outputs, '[response]')}` + ) + } + + onLLMError(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[llm/error] [${crumbs}] [${elapsed(run)}] LLM run errored with error: ${tryJsonStringify(run.error, '[error]')}` + ) + } + + onToolStart(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose(`[tool/start] [${crumbs}] Entering Tool run with input: "${run.inputs.input?.trim()}"`) + } + + onToolEnd(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose(`[tool/end] [${crumbs}] [${elapsed(run)}] Exiting Tool run with output: "${run.outputs?.output?.trim()}"`) + } + + onToolError(run: Run) { + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[tool/error] [${crumbs}] [${elapsed(run)}] Tool run errored with error: ${tryJsonStringify(run.error, '[error]')}` + ) + } + + onAgentAction(run: Run) { + const agentRun = run as AgentRun + const crumbs = this.getBreadcrumbs(run) + this.logger.verbose( + `[agent/action] [${crumbs}] Agent selected action: ${tryJsonStringify( + agentRun.actions[agentRun.actions.length - 1], + '[action]' + )}` + ) + } +} + +/** + * Custom chain handler class + */ +export class CustomChainHandler extends BaseCallbackHandler { + name = 'custom_chain_handler' + isLLMStarted = false + socketIO: Server + socketIOClientId = '' + skipK = 0 // Skip streaming for first K numbers of handleLLMStart + returnSourceDocuments = false + + constructor(socketIO: Server, socketIOClientId: string, skipK?: number, returnSourceDocuments?: boolean) { + super() + this.socketIO = socketIO + this.socketIOClientId = socketIOClientId + this.skipK = skipK ?? this.skipK + this.returnSourceDocuments = returnSourceDocuments ?? this.returnSourceDocuments + } + + handleLLMStart() { + if (this.skipK > 0) this.skipK -= 1 + } + + handleLLMNewToken(token: string) { + if (this.skipK === 0) { + if (!this.isLLMStarted) { + this.isLLMStarted = true + this.socketIO.to(this.socketIOClientId).emit('start', token) + } + this.socketIO.to(this.socketIOClientId).emit('token', token) + } + } + + handleLLMEnd() { + this.socketIO.to(this.socketIOClientId).emit('end') + } + + handleChainEnd(outputs: ChainValues): void | Promise { + if (this.returnSourceDocuments) { + this.socketIO.to(this.socketIOClientId).emit('sourceDocuments', outputs?.sourceDocuments) + } + } +} diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index e1399404..027ec8db 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -3,9 +3,6 @@ import { load } from 'cheerio' import * as fs from 'fs' import * as path from 'path' import { JSDOM } from 'jsdom' -import { BaseCallbackHandler } from 'langchain/callbacks' -import { Server } from 'socket.io' -import { ChainValues } from 'langchain/dist/schema' export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}} export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank @@ -350,50 +347,9 @@ export const getEnvironmentVariable = (name: string): string | undefined => { } } -/** - * Custom chain handler class +/* + * List of dependencies allowed to be import in vm2 */ -export class CustomChainHandler extends BaseCallbackHandler { - name = 'custom_chain_handler' - isLLMStarted = false - socketIO: Server - socketIOClientId = '' - skipK = 0 // Skip streaming for first K numbers of handleLLMStart - returnSourceDocuments = false - - constructor(socketIO: Server, socketIOClientId: string, skipK?: number, returnSourceDocuments?: boolean) { - super() - this.socketIO = socketIO - this.socketIOClientId = socketIOClientId - this.skipK = skipK ?? this.skipK - this.returnSourceDocuments = returnSourceDocuments ?? this.returnSourceDocuments - } - - handleLLMStart() { - if (this.skipK > 0) this.skipK -= 1 - } - - handleLLMNewToken(token: string) { - if (this.skipK === 0) { - if (!this.isLLMStarted) { - this.isLLMStarted = true - this.socketIO.to(this.socketIOClientId).emit('start', token) - } - this.socketIO.to(this.socketIOClientId).emit('token', token) - } - } - - handleLLMEnd() { - this.socketIO.to(this.socketIOClientId).emit('end') - } - - handleChainEnd(outputs: ChainValues): void | Promise { - if (this.returnSourceDocuments) { - this.socketIO.to(this.socketIOClientId).emit('sourceDocuments', outputs?.sourceDocuments) - } - } -} - export const availableDependencies = [ '@dqbd/tiktoken', '@getzep/zep-js', diff --git a/packages/server/.env.example b/packages/server/.env.example index 262e08a6..d9b2da76 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -5,4 +5,5 @@ PORT=3000 # DATABASE_PATH=/your_database_path/.flowise # APIKEY_PATH=/your_api_key_path/.flowise # LOG_PATH=/your_log_path/logs -# EXECUTION_MODE=child or main +# LOG_LEVEL=debug (error | warn | info | verbose | debug) +# EXECUTION_MODE=main (child | main) diff --git a/packages/server/src/ChatflowPool.ts b/packages/server/src/ChatflowPool.ts index 35b0d947..d296dcfe 100644 --- a/packages/server/src/ChatflowPool.ts +++ b/packages/server/src/ChatflowPool.ts @@ -1,5 +1,6 @@ import { ICommonObject } from 'flowise-components' import { IActiveChatflows, INodeData, IReactFlowNode } from './Interface' +import logger from './utils/logger' /** * This pool is to keep track of active chatflow pools @@ -22,6 +23,7 @@ export class ChatflowPool { inSync: true } if (overrideConfig) this.activeChatflows[chatflowid].overrideConfig = overrideConfig + logger.info(`[server]: Chatflow ${chatflowid} added into ChatflowPool`) } /** @@ -32,6 +34,7 @@ export class ChatflowPool { updateInSync(chatflowid: string, inSync: boolean) { if (Object.prototype.hasOwnProperty.call(this.activeChatflows, chatflowid)) { this.activeChatflows[chatflowid].inSync = inSync + logger.info(`[server]: Chatflow ${chatflowid} updated inSync=${inSync} in ChatflowPool`) } } @@ -42,6 +45,7 @@ export class ChatflowPool { async remove(chatflowid: string) { if (Object.prototype.hasOwnProperty.call(this.activeChatflows, chatflowid)) { delete this.activeChatflows[chatflowid] + logger.info(`[server]: Chatflow ${chatflowid} removed from ChatflowPool`) } } } diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index e8aeaff2..27629480 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -5,6 +5,7 @@ import { DataSource } from 'typeorm' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' import { Tool } from './entity/Tool' +import logger from './utils/logger' export class ChildProcess { /** @@ -27,99 +28,112 @@ export class ChildProcess { await sendToParentProcess('start', '_') - const childAppDataSource = await initDB() + try { + const childAppDataSource = await initDB() - // Create a Queue and add our initial node in it - const { endingNodeData, chatflow, chatId, incomingInput, componentNodes } = messageValue + // Create a Queue and add our initial node in it + const { endingNodeData, chatflow, chatId, incomingInput, componentNodes } = messageValue - let nodeToExecuteData: INodeData - let addToChatFlowPool: any = {} + let nodeToExecuteData: INodeData + let addToChatFlowPool: any = {} - /* Don't rebuild the flow (to avoid duplicated upsert, recomputation) when all these conditions met: - * - Node Data already exists in pool - * - Still in sync (i.e the flow has not been modified since) - * - Existing overrideConfig and new overrideConfig are the same - * - Flow doesn't start with nodes that depend on incomingInput.question - ***/ - if (endingNodeData) { - nodeToExecuteData = endingNodeData - } else { - /*** Get chatflows and prepare data ***/ - const flowData = chatflow.flowData - const parsedFlowData: IReactFlowObject = JSON.parse(flowData) - const nodes = parsedFlowData.nodes - const edges = parsedFlowData.edges + /* Don't rebuild the flow (to avoid duplicated upsert, recomputation) when all these conditions met: + * - Node Data already exists in pool + * - Still in sync (i.e the flow has not been modified since) + * - Existing overrideConfig and new overrideConfig are the same + * - Flow doesn't start with nodes that depend on incomingInput.question + ***/ + if (endingNodeData) { + nodeToExecuteData = endingNodeData + } else { + /*** Get chatflows and prepare data ***/ + const flowData = chatflow.flowData + const parsedFlowData: IReactFlowObject = JSON.parse(flowData) + const nodes = parsedFlowData.nodes + const edges = parsedFlowData.edges - /*** Get Ending Node with Directed Graph ***/ - const { graph, nodeDependencies } = constructGraphs(nodes, edges) - const directedGraph = graph - const endingNodeId = getEndingNode(nodeDependencies, directedGraph) - if (!endingNodeId) { - await sendToParentProcess('error', `Ending node must be either a Chain or Agent`) - return - } + /*** Get Ending Node with Directed Graph ***/ + const { graph, nodeDependencies } = constructGraphs(nodes, edges) + const directedGraph = graph + const endingNodeId = getEndingNode(nodeDependencies, directedGraph) + if (!endingNodeId) { + await sendToParentProcess('error', `Ending node ${endingNodeId} not found`) + return + } - const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data - if (!endingNodeData) { - await sendToParentProcess('error', `Ending node must be either a Chain or Agent`) - return - } + const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data + if (!endingNodeData) { + await sendToParentProcess('error', `Ending node ${endingNodeId} data not found`) + return + } - if ( - endingNodeData.outputs && - Object.keys(endingNodeData.outputs).length && - !Object.values(endingNodeData.outputs).includes(endingNodeData.name) - ) { - await sendToParentProcess( - 'error', - `Output of ${endingNodeData.label} (${endingNodeData.id}) must be ${endingNodeData.label}, can't be an Output Prediction` + if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { + await sendToParentProcess('error', `Ending node must be either a Chain or Agent`) + return + } + + if ( + endingNodeData.outputs && + Object.keys(endingNodeData.outputs).length && + !Object.values(endingNodeData.outputs).includes(endingNodeData.name) + ) { + await sendToParentProcess( + 'error', + `Output of ${endingNodeData.label} (${endingNodeData.id}) must be ${endingNodeData.label}, can't be an Output Prediction` + ) + return + } + + /*** Get Starting Nodes with Non-Directed Graph ***/ + const constructedObj = constructGraphs(nodes, edges, true) + const nonDirectedGraph = constructedObj.graph + const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) + + logger.debug(`[server] [mode:child]: Start building chatflow ${chatflow.id}`) + /*** BFS to traverse from Starting Nodes to Ending Node ***/ + const reactFlowNodes = await buildLangchain( + startingNodeIds, + nodes, + graph, + depthQueue, + componentNodes, + incomingInput.question, + chatId, + childAppDataSource, + incomingInput?.overrideConfig ) - return + + const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId) + if (!nodeToExecute) { + await sendToParentProcess('error', `Node ${endingNodeId} not found`) + return + } + + const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question) + nodeToExecuteData = reactFlowNodeData + + const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id)) + addToChatFlowPool = { + chatflowid: chatflow.id, + nodeToExecuteData, + startingNodes, + overrideConfig: incomingInput?.overrideConfig + } } - /*** Get Starting Nodes with Non-Directed Graph ***/ - const constructedObj = constructGraphs(nodes, edges, true) - const nonDirectedGraph = constructedObj.graph - const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) + const nodeInstanceFilePath = componentNodes[nodeToExecuteData.name].filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const nodeInstance = new nodeModule.nodeClass() - /*** BFS to traverse from Starting Nodes to Ending Node ***/ - const reactFlowNodes = await buildLangchain( - startingNodeIds, - nodes, - graph, - depthQueue, - componentNodes, - incomingInput.question, - chatId, - childAppDataSource, - incomingInput?.overrideConfig - ) + logger.debug(`[server] [mode:child]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) + const result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history }) + logger.debug(`[server] [mode:child]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) - const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId) - if (!nodeToExecute) { - await sendToParentProcess('error', `Node ${endingNodeId} not found`) - return - } - - const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question) - nodeToExecuteData = reactFlowNodeData - - const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id)) - addToChatFlowPool = { - chatflowid: chatflow.id, - nodeToExecuteData, - startingNodes, - overrideConfig: incomingInput?.overrideConfig - } + await sendToParentProcess('finish', { result, addToChatFlowPool }) + } catch (e: any) { + await sendToParentProcess('error', e.message) + logger.error('[server] [mode:child]: Error:', e) } - - const nodeInstanceFilePath = componentNodes[nodeToExecuteData.name].filePath as string - const nodeModule = await import(nodeInstanceFilePath) - const nodeInstance = new nodeModule.nodeClass() - - const result = await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history }) - - await sendToParentProcess('finish', { result, addToChatFlowPool }) } } diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index c05c042a..276a3036 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -23,6 +23,7 @@ export default class Start extends Command { DATABASE_PATH: Flags.string(), APIKEY_PATH: Flags.string(), LOG_PATH: Flags.string(), + LOG_LEVEL: Flags.string(), EXECUTION_MODE: Flags.string() } @@ -61,6 +62,7 @@ export default class Start extends Command { if (flags.DATABASE_PATH) process.env.DATABASE_PATH = flags.DATABASE_PATH if (flags.APIKEY_PATH) process.env.APIKEY_PATH = flags.APIKEY_PATH if (flags.LOG_PATH) process.env.LOG_PATH = flags.LOG_PATH + if (flags.LOG_LEVEL) process.env.LOG_LEVEL = flags.LOG_LEVEL if (flags.EXECUTION_MODE) process.env.EXECUTION_MODE = flags.EXECUTION_MODE if (flags.DEBUG) process.env.DEBUG = flags.DEBUG diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 0f87aeba..74c4d07e 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -283,10 +283,16 @@ export class App { const nodes = parsedFlowData.nodes const edges = parsedFlowData.edges const { graph, nodeDependencies } = constructGraphs(nodes, edges) + const endingNodeId = getEndingNode(nodeDependencies, graph) - if (!endingNodeId) return res.status(500).send(`Ending node must be either a Chain or Agent`) + if (!endingNodeId) return res.status(500).send(`Ending node ${endingNodeId} not found`) + const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data - if (!endingNodeData) return res.status(500).send(`Ending node must be either a Chain or Agent`) + if (!endingNodeData) return res.status(500).send(`Ending node ${endingNodeId} data not found`) + + if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { + return res.status(500).send(`Ending node must be either a Chain or Agent`) + } const obj = { isStreaming: isFlowValidForStream(nodes, endingNodeData) @@ -638,7 +644,7 @@ export class App { }) }) } catch (err) { - logger.error(err) + logger.error('[server] [mode:child]: Error:', err) } } @@ -714,9 +720,11 @@ export class App { if (process.env.EXECUTION_MODE === 'child') { if (isFlowReusable()) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData + logger.debug( + `[server] [mode:child]: Reuse existing chatflow ${chatflowid} with ending node ${nodeToExecuteData.label} (${nodeToExecuteData.id})` + ) try { const result = await this.startChildProcess(chatflow, chatId, incomingInput, nodeToExecuteData) - return res.json(result) } catch (error) { return res.status(500).send(error) @@ -739,15 +747,22 @@ export class App { if (isFlowReusable()) { nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData isStreamValid = isFlowValidForStream(nodes, nodeToExecuteData) + logger.debug( + `[server]: Reuse existing chatflow ${chatflowid} with ending node ${nodeToExecuteData.label} (${nodeToExecuteData.id})` + ) } else { /*** Get Ending Node with Directed Graph ***/ const { graph, nodeDependencies } = constructGraphs(nodes, edges) const directedGraph = graph const endingNodeId = getEndingNode(nodeDependencies, directedGraph) - if (!endingNodeId) return res.status(500).send(`Ending node must be either a Chain or Agent`) + if (!endingNodeId) return res.status(500).send(`Ending node ${endingNodeId} not found`) const endingNodeData = nodes.find((nd) => nd.id === endingNodeId)?.data - if (!endingNodeData) return res.status(500).send(`Ending node must be either a Chain or Agent`) + if (!endingNodeData) return res.status(500).send(`Ending node ${endingNodeId} data not found`) + + if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { + return res.status(500).send(`Ending node must be either a Chain or Agent`) + } if ( endingNodeData.outputs && @@ -768,6 +783,7 @@ export class App { const nonDirectedGraph = constructedObj.graph const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId) + logger.debug(`[server]: Start building chatflow ${chatflowid}`) /*** BFS to traverse from Starting Nodes to Ending Node ***/ const reactFlowNodes = await buildLangchain( startingNodeIds, @@ -796,17 +812,21 @@ export class App { const nodeInstance = new nodeModule.nodeClass() isStreamValid = isStreamValid && !isVectorStoreFaiss(nodeToExecuteData) + logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) const result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, socketIO, - socketIOClientId: incomingInput.socketIOClientId + socketIOClientId: incomingInput.socketIOClientId, + logger }) - : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history }) + : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, logger }) + logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) return res.json(result) } } catch (e: any) { + logger.error('[server]: Error:', e) return res.status(500).send(e.message) } } diff --git a/packages/server/src/utils/config.ts b/packages/server/src/utils/config.ts index c38d5a0c..8540b3b1 100644 --- a/packages/server/src/utils/config.ts +++ b/packages/server/src/utils/config.ts @@ -9,12 +9,12 @@ dotenv.config({ path: path.join(__dirname, '..', '..', '.env'), override: true } const loggingConfig = { dir: process.env.LOG_PATH ?? path.join(__dirname, '..', '..', '..', '..', 'logs'), server: { - level: 'info', + level: process.env.LOG_LEVEL ?? 'info', filename: 'server.log', errorFilename: 'server-error.log' }, express: { - level: 'info', + level: process.env.LOG_LEVEL ?? 'info', format: 'jsonl', // can't be changed currently filename: 'server-requests.log.jsonl' // should end with .jsonl } diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index bc4d8188..b67f2796 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -180,6 +180,9 @@ export const getEndingNode = (nodeDependencies: INodeDependencies, graph: INodeD * @param {IDepthQueue} depthQueue * @param {IComponentNodes} componentNodes * @param {string} question + * @param {string} chatId + * @param {DataSource} appDataSource + * @param {ICommonObject} overrideConfig */ export const buildLangchain = async ( startingNodeIds: string[], @@ -222,11 +225,14 @@ export const buildLangchain = async ( if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig) const reactFlowNodeData: INodeData = resolveVariables(flowNodeData, flowNodes, question) + logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, { chatId, appDataSource, - databaseEntities + databaseEntities, + logger }) + logger.debug(`[server]: Finished initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) } catch (e: any) { logger.error(e) throw new Error(e) diff --git a/packages/server/src/utils/logger.ts b/packages/server/src/utils/logger.ts index 1c28b173..5d7ffedf 100644 --- a/packages/server/src/utils/logger.ts +++ b/packages/server/src/utils/logger.ts @@ -4,7 +4,7 @@ import config from './config' // should be replaced by node-config or similar import { createLogger, transports, format } from 'winston' import { NextFunction, Request, Response } from 'express' -const { combine, timestamp, printf } = format +const { combine, timestamp, printf, errors } = format // expect the log dir be relative to the projects root const logDir = config.logging.dir @@ -18,9 +18,11 @@ const logger = createLogger({ format: combine( timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.json(), - printf(({ level, message, timestamp }) => { - return `${timestamp} [${level.toUpperCase()}]: ${message}` - }) + printf(({ level, message, timestamp, stack }) => { + const text = `${timestamp} [${level.toUpperCase()}]: ${message}` + return stack ? text + '\n' + stack : text + }), + errors({ stack: true }) ), defaultMeta: { package: 'server' @@ -56,7 +58,7 @@ const logger = createLogger({ */ export function expressRequestLogger(req: Request, res: Response, next: NextFunction): void { const fileLogger = createLogger({ - format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.json()), + format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.json(), errors({ stack: true })), defaultMeta: { package: 'server', request: { @@ -71,7 +73,7 @@ export function expressRequestLogger(req: Request, res: Response, next: NextFunc transports: [ new transports.File({ filename: path.join(logDir, config.logging.express.filename ?? 'server-requests.log.jsonl'), - level: 'debug' + level: config.logging.express.level ?? 'debug' }) ] }) From 8ad870ba261c3c8a3493f2cc43e6e0ee9f5e4afe Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 02:12:54 +0100 Subject: [PATCH 084/183] update README --- README.md | 22 ++++++++++++++++++++++ packages/server/README.md | 22 ++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4613e19f..7792b969 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,28 @@ FLOWISE_USERNAME=user FLOWISE_PASSWORD=1234 ``` +## 🌱 Env Variables + +Flowise support different environment variables to configure your instance. You can specify the following variables in the `.env` file inside `packages/server` folder. + +| Variable | Description | Type | Default | +| ---------------- | ---------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------- | +| PORT | The HTTP port Flowise runs on | Number | 3000 | +| FLOWISE_USERNAME | Username to login | String | +| FLOWISE_PASSWORD | Password to login | String | +| DEBUG | Print logs from components | Boolean | +| LOG_PATH | Location where log files are stored | String | `your-path/Flowise/logs` | +| LOG_LEVEL | Different levels of logs | Enum String: `error`, `info`, `verbose`, `debug` | `info` | +| DATABASE_PATH | Location where database is saved | String | `your-home-dir/.flowise` | +| APIKEY_PATH | Location where api keys are saved | String | `your-path/Flowise/packages/server` | +| EXECUTION_MODE | Whether predictions run in their own process or the main process | Enum String: `child`, `main` | `main` | + +You can also specify the env variables when using `npx`. For example: + +``` +npx flowise start --PORT=3000 --DEBUG=true +``` + ## 📖 Documentation [Flowise Docs](https://docs.flowiseai.com/) diff --git a/packages/server/README.md b/packages/server/README.md index 7895bd90..fb3a0c12 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -29,9 +29,27 @@ FLOWISE_USERNAME=user FLOWISE_PASSWORD=1234 ``` -## 🔎 Debugging +## 🌱 Env Variables -You can set `DEBUG=true` to the `.env` file. Refer [here](https://docs.flowiseai.com/environment-variables) for full list of env variables +Flowise support different environment variables to configure your instance. You can specify the following variables in the `.env` file inside `packages/server` folder. + +| Variable | Description | Type | Default | +| ---------------- | ---------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------- | +| PORT | The HTTP port Flowise runs on | Number | 3000 | +| FLOWISE_USERNAME | Username to login | String | +| FLOWISE_PASSWORD | Password to login | String | +| DEBUG | Print logs from components | Boolean | +| LOG_PATH | Location where log files are stored | String | `your-path/Flowise/logs` | +| LOG_LEVEL | Different levels of logs | Enum String: `error`, `info`, `verbose`, `debug` | `info` | +| DATABASE_PATH | Location where database is saved | String | `your-home-dir/.flowise` | +| APIKEY_PATH | Location where api keys are saved | String | `your-path/Flowise/packages/server` | +| EXECUTION_MODE | Whether predictions run in their own process or the main process | Enum String: `child`, `main` | `main` | + +You can also specify the env variables when using `npx`. For example: + +``` +npx flowise start --PORT=3000 --DEBUG=true +``` ## 📖 Documentation From 81641f99836b1717fd0d027d5f38f8915eef07d7 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 12:07:43 +0100 Subject: [PATCH 085/183] update Motorhead Memory icon --- .../memory/MotorheadMemory/MotorheadMemory.ts | 2 +- .../nodes/memory/MotorheadMemory/memory.svg | 8 -------- .../nodes/memory/MotorheadMemory/motorhead.png | Bin 0 -> 9923 bytes 3 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 packages/components/nodes/memory/MotorheadMemory/memory.svg create mode 100644 packages/components/nodes/memory/MotorheadMemory/motorhead.png diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index d50b7064..01d57614 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -17,7 +17,7 @@ class MotorMemory_Memory implements INode { this.label = 'Motorhead Memory' this.name = 'motorheadMemory' this.type = 'MotorheadMemory' - this.icon = 'memory.svg' + this.icon = 'motorhead.png' this.category = 'Memory' this.description = 'Remembers previous conversational back and forths directly' this.baseClasses = [this.type, ...getBaseClasses(MotorheadMemory)] diff --git a/packages/components/nodes/memory/MotorheadMemory/memory.svg b/packages/components/nodes/memory/MotorheadMemory/memory.svg deleted file mode 100644 index ca8e17da..00000000 --- a/packages/components/nodes/memory/MotorheadMemory/memory.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/packages/components/nodes/memory/MotorheadMemory/motorhead.png b/packages/components/nodes/memory/MotorheadMemory/motorhead.png new file mode 100644 index 0000000000000000000000000000000000000000..e1dfbde08872a555e544b353b0ec096071a0dd4c GIT binary patch literal 9923 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 B5P|>z literal 0 HcmV?d00001 From b69ba48ad4604cec2118e331f67f750bd5f60d4b Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 12:29:32 +0100 Subject: [PATCH 086/183] update GithubLoader marketplace --- .../server/marketplaces/chatflows/Github Repo QnA.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/server/marketplaces/chatflows/Github Repo QnA.json b/packages/server/marketplaces/chatflows/Github Repo QnA.json index 92ad0967..73fbf304 100644 --- a/packages/server/marketplaces/chatflows/Github Repo QnA.json +++ b/packages/server/marketplaces/chatflows/Github Repo QnA.json @@ -98,6 +98,13 @@ "optional": true, "id": "github_1-input-accessToken-password" }, + { + "label": "Recursive", + "name": "recursive", + "type": "boolean", + "optional": true, + "id": "github_1-input-recursive-boolean" + }, { "label": "Metadata", "name": "metadata", From cf6ad5355962c32b3b719da3f69a5aaca554e80f Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 12:52:32 +0100 Subject: [PATCH 087/183] update Zep memory node --- .../nodes/memory/ZepMemory/ZepMemory.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 89d07b8f..6e1a14bd 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -44,6 +44,20 @@ class ZepMemory_Memory implements INode { additionalParams: true, optional: true }, + { + label: 'API Key', + name: 'apiKey', + type: 'string', + additionalParams: true, + optional: true + }, + { + label: 'Size', + name: 'k', + type: 'number', + default: '10', + description: 'Window of size k to surface the last k back-and-forths to use as memory.' + }, { label: 'Auto Summary Template', name: 'autoSummaryTemplate', @@ -98,6 +112,8 @@ class ZepMemory_Memory implements INode { const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string const autoSummary = nodeData.inputs?.autoSummary as boolean const sessionId = nodeData.inputs?.sessionId as string + const apiKey = nodeData.inputs?.apiKey as string + const k = nodeData.inputs?.k as string const chatId = options?.chatId as string @@ -110,6 +126,7 @@ class ZepMemory_Memory implements INode { memoryKey, inputKey } + if (apiKey) obj.apiKey = apiKey let zep = new ZepMemory(obj) @@ -118,7 +135,7 @@ class ZepMemory_Memory implements INode { zep.loadMemoryVariables = async (values) => { let data = await tmpFunc.bind(zep, values)() if (autoSummary && zep.returnMessages && data[zep.memoryKey] && data[zep.memoryKey].length) { - const memory = await zep.zepClient.getMemory(zep.sessionId, 10) + const memory = await zep.zepClient.getMemory(zep.sessionId, parseInt(k, 10) ?? 10) if (memory?.summary) { let summary = autoSummaryTemplate.replace(/{summary}/g, memory.summary.content) // eslint-disable-next-line no-console From 6827a13e37a0f7acbf5a67a7a6192b66944a9a15 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 16:29:30 +0100 Subject: [PATCH 088/183] clear session memory --- .../nodes/memory/DynamoDb/DynamoDb.ts | 66 +++++++++++-------- .../memory/MotorheadMemory/MotorheadMemory.ts | 63 ++++++++++-------- .../RedisBackedChatMemory.ts | 55 +++++++++------- .../nodes/memory/ZepMemory/ZepMemory.ts | 53 +++++++++------ packages/components/src/Interface.ts | 1 + packages/server/src/index.ts | 18 ++++- packages/server/src/utils/index.ts | 23 +++++++ 7 files changed, 178 insertions(+), 101 deletions(-) diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index 49d15cb6..2ea7ba04 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -65,37 +65,47 @@ class DynamoDb_Memory implements INode { } ] } + 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 memoryKey = nodeData.inputs?.memoryKey as string + return initalizeDynamoDB(nodeData, options) + } - const chatId = options.chatId - - const dynamoDb = new DynamoDBChatMessageHistory({ - tableName, - partitionKey, - sessionId: sessionId ? sessionId : chatId, - config: { - region, - credentials: { - accessKeyId: accessKey, - secretAccessKey - } - } - }) - - const memory = new BufferMemory({ - memoryKey, - chatHistory: dynamoDb, - returnMessages: true - }) - return memory + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const dynamodbMemory = initalizeDynamoDB(nodeData, options) + dynamodbMemory.clear() } } +const initalizeDynamoDB = (nodeData: INodeData, options: ICommonObject): BufferMemory => { + const tableName = nodeData.inputs?.tableName as string + const partitionKey = nodeData.inputs?.partitionKey as string + const sessionId = nodeData.inputs?.sessionId as string + const region = nodeData.inputs?.region as string + const accessKey = nodeData.inputs?.accessKey as string + const secretAccessKey = nodeData.inputs?.secretAccessKey as string + const memoryKey = nodeData.inputs?.memoryKey as string + + const chatId = options.chatId + + const dynamoDb = new DynamoDBChatMessageHistory({ + tableName, + partitionKey, + sessionId: sessionId ? sessionId : chatId, + config: { + region, + credentials: { + accessKeyId: accessKey, + secretAccessKey + } + } + }) + + const memory = new BufferMemory({ + memoryKey, + chatHistory: dynamoDb, + returnMessages: true + }) + return memory +} + module.exports = { nodeClass: DynamoDb_Memory } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 01d57614..b710b30a 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -64,35 +64,44 @@ class MotorMemory_Memory implements INode { } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const baseURL = nodeData.inputs?.baseURL as string - const sessionId = nodeData.inputs?.sessionId as string - const apiKey = nodeData.inputs?.apiKey as string - const clientId = nodeData.inputs?.clientId as string + return initalizeMotorhead(nodeData, options) + } - const chatId = options?.chatId as string - - let obj: MotorheadMemoryInput = { - returnMessages: true, - sessionId: sessionId ? sessionId : chatId, - memoryKey - } - - if (baseURL) { - obj = { - ...obj, - url: baseURL - } - } else { - obj = { - ...obj, - apiKey, - clientId - } - } - - return new MotorheadMemory(obj) + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const motorhead = initalizeMotorhead(nodeData, options) + motorhead.clear() } } +const initalizeMotorhead = (nodeData: INodeData, options: ICommonObject): MotorheadMemory => { + const memoryKey = nodeData.inputs?.memoryKey as string + const baseURL = nodeData.inputs?.baseURL as string + const sessionId = nodeData.inputs?.sessionId as string + const apiKey = nodeData.inputs?.apiKey as string + const clientId = nodeData.inputs?.clientId as string + + const chatId = options?.chatId as string + + let obj: MotorheadMemoryInput = { + returnMessages: true, + sessionId: sessionId ? sessionId : chatId, + memoryKey + } + + if (baseURL) { + obj = { + ...obj, + url: baseURL + } + } else { + obj = { + ...obj, + apiKey, + clientId + } + } + + return new MotorheadMemory(obj) +} + module.exports = { nodeClass: MotorMemory_Memory } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 2b4e51c2..cd406f59 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -56,31 +56,40 @@ class RedisBackedChatMemory_Memory implements INode { } 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 + return initalizeRedis(nodeData, options) + } - 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 + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const redis = initalizeRedis(nodeData, options) + redis.clear() } } +const initalizeRedis = (nodeData: INodeData, options: ICommonObject): BufferMemory => { + 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/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 6e1a14bd..d9bac948 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -104,31 +104,11 @@ class ZepMemory_Memory implements INode { } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const baseURL = nodeData.inputs?.baseURL as string - const aiPrefix = nodeData.inputs?.aiPrefix as string - const humanPrefix = nodeData.inputs?.humanPrefix as string - const memoryKey = nodeData.inputs?.memoryKey as string - const inputKey = nodeData.inputs?.inputKey as string const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string const autoSummary = nodeData.inputs?.autoSummary as boolean - const sessionId = nodeData.inputs?.sessionId as string - const apiKey = nodeData.inputs?.apiKey as string const k = nodeData.inputs?.k as string - const chatId = options?.chatId as string - - const obj: ZepMemoryInput = { - baseURL, - sessionId: sessionId ? sessionId : chatId, - aiPrefix, - humanPrefix, - returnMessages: true, - memoryKey, - inputKey - } - if (apiKey) obj.apiKey = apiKey - - let zep = new ZepMemory(obj) + let zep = initalizeZep(nodeData, options) // hack to support summary let tmpFunc = zep.loadMemoryVariables @@ -153,6 +133,37 @@ class ZepMemory_Memory implements INode { } return zep } + + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const zep = initalizeZep(nodeData, options) + zep.clear() + } +} + +const initalizeZep = (nodeData: INodeData, options: ICommonObject) => { + const baseURL = nodeData.inputs?.baseURL as string + const aiPrefix = nodeData.inputs?.aiPrefix as string + const humanPrefix = nodeData.inputs?.humanPrefix as string + const memoryKey = nodeData.inputs?.memoryKey as string + const inputKey = nodeData.inputs?.inputKey as string + + const sessionId = nodeData.inputs?.sessionId as string + const apiKey = nodeData.inputs?.apiKey as string + + const chatId = options?.chatId as string + + const obj: ZepMemoryInput = { + baseURL, + sessionId: sessionId ? sessionId : chatId, + aiPrefix, + humanPrefix, + returnMessages: true, + memoryKey, + inputKey + } + if (apiKey) obj.apiKey = apiKey + + return new ZepMemory(obj) } module.exports = { nodeClass: ZepMemory_Memory } diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index d9233e49..862b81ac 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -96,6 +96,7 @@ export interface INode extends INodeProperties { } init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise + clearSessionMemory?(nodeData: INodeData, options?: ICommonObject): Promise } export interface INodeData extends INodeProperties { diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 0f87aeba..e11fa283 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -39,7 +39,8 @@ import { isFlowValidForStream, isVectorStoreFaiss, databaseEntities, - getApiKey + getApiKey, + clearSessionMemory } from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' @@ -320,6 +321,19 @@ export class App { // Delete all chatmessages from chatflowid this.app.delete('/api/v1/chatmessage/:id', async (req: Request, res: Response) => { + const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ + id: req.params.id + }) + if (!chatflow) { + res.status(404).send(`Chatflow ${req.params.id} not found`) + return + } + const flowData = chatflow.flowData + const parsedFlowData: IReactFlowObject = JSON.parse(flowData) + const nodes = parsedFlowData.nodes + let chatId = await getChatId(chatflow.id) + if (!chatId) chatId = chatflow.id + clearSessionMemory(nodes, this.nodesPool.componentNodes, chatId, req.query.sessionId as string) const results = await this.AppDataSource.getRepository(ChatMessage).delete({ chatflowid: req.params.id }) return res.json(results) }) @@ -662,7 +676,7 @@ export class App { if (!chatflow) return res.status(404).send(`Chatflow ${chatflowid} not found`) let chatId = await getChatId(chatflow.id) - if (!chatId) chatId = Date.now().toString() + if (!chatId) chatId = chatflowid if (!isInternal) { await this.validateKey(req, res, chatflow) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 3ee7a25b..e13bca97 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -267,6 +267,29 @@ export const buildLangchain = async ( return flowNodes } +/** + * Clear memory + * @param {IReactFlowNode[]} reactFlowNodes + * @param {IComponentNodes} componentNodes + * @param {string} chatId + * @param {string} sessionId + */ +export const clearSessionMemory = async ( + reactFlowNodes: IReactFlowNode[], + componentNodes: IComponentNodes, + chatId: string, + sessionId?: string +) => { + for (const node of reactFlowNodes) { + if (node.data.category !== 'Memory') continue + const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const newNodeInstance = new nodeModule.nodeClass() + if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId + if (newNodeInstance.clearSessionMemory) await newNodeInstance?.clearSessionMemory(node.data, { chatId }) + } +} + /** * Get variable value from outputResponses.output * @param {string} paramValue From aee0a51f7397e23b516ff5cbb6bc6f7d757e5ac2 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Jul 2023 17:31:47 +0100 Subject: [PATCH 089/183] update zep and motorhead api key as password --- .../components/nodes/memory/MotorheadMemory/MotorheadMemory.ts | 2 +- packages/components/nodes/memory/ZepMemory/ZepMemory.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 01d57614..8a160223 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -47,7 +47,7 @@ class MotorMemory_Memory implements INode { { label: 'API Key', name: 'apiKey', - type: 'string', + type: 'password', description: 'Only needed when using hosted solution - https://getmetal.io', additionalParams: true, optional: true diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 6e1a14bd..5ca1310d 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -47,7 +47,7 @@ class ZepMemory_Memory implements INode { { label: 'API Key', name: 'apiKey', - type: 'string', + type: 'password', additionalParams: true, optional: true }, From 4c47beabbcaffba93a1c9c873cb59899531f0292 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 01:11:02 +0100 Subject: [PATCH 090/183] update claude v2 --- .../ConversationChain/ConversationChain.ts | 26 ++++++++++++++++++- .../chatmodels/ChatAnthropic/ChatAnthropic.ts | 10 +++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index f1df0183..7b6f002d 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -6,8 +6,10 @@ import { BufferMemory, ChatMessageHistory } from 'langchain/memory' import { BaseChatModel } from 'langchain/chat_models/base' import { AIMessage, HumanMessage } from 'langchain/schema' import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' +import { flatten } from 'lodash' +import { Document } from 'langchain/document' -const systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.` +let systemMessage = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.` class ConversationChain_Chains implements INode { label: string @@ -38,6 +40,14 @@ class ConversationChain_Chains implements INode { name: 'memory', type: 'BaseMemory' }, + { + label: 'Document', + name: 'document', + type: 'Document', + description: 'Include whole document into the context window', + optional: true, + list: true + }, { label: 'System Message', name: 'systemMessagePrompt', @@ -54,6 +64,20 @@ class ConversationChain_Chains implements INode { const model = nodeData.inputs?.model as BaseChatModel const memory = nodeData.inputs?.memory as BufferMemory const prompt = nodeData.inputs?.systemMessagePrompt as string + const docs = nodeData.inputs?.document as Document[] + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + finalDocs.push(new Document(flattenDocs[i])) + } + + let finalText = '' + for (let i = 0; i < finalDocs.length; i += 1) { + finalText += finalDocs[i].pageContent + } + + if (finalText) systemMessage = `${systemMessage}\nThe AI has the following context:\n${finalText}` const obj: any = { llm: model, diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts index 3d861d24..b65c7bd8 100644 --- a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts +++ b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts @@ -31,6 +31,16 @@ class ChatAnthropic_ChatModels implements INode { name: 'modelName', type: 'options', options: [ + { + label: 'claude-2', + name: 'claude-2', + description: 'Claude 2 latest major version, automatically get updates to the model as they are released' + }, + { + label: 'claude-instant-1', + name: 'claude-instant-1', + description: 'Claude Instant latest major version, automatically get updates to the model as they are released' + }, { label: 'claude-v1', name: 'claude-v1' From f558c0374473d55e023aaf2b8b74dd5c7f684020 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 01:31:01 +0100 Subject: [PATCH 091/183] update marketplace --- .../chatflows/Simple Conversation Chain.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json index f7e654db..04009123 100644 --- a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json +++ b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json @@ -249,6 +249,15 @@ "name": "memory", "type": "BaseMemory", "id": "conversationChain_0-input-memory-BaseMemory" + }, + { + "label": "Document", + "name": "document", + "type": "Document", + "description": "Include whole document into the context window", + "optional": true, + "list": true, + "id": "conversationChain_0-input-document-Document" } ], "inputs": { From d023042158a26bd8b2c07d37f14d020b57c9f12b Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 02:38:59 +0100 Subject: [PATCH 092/183] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.2.1?= =?UTF-8?q?6=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 32a84610..3459a372 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.15", + "version": "1.2.16", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From 59a3f7ab66190f5d4dfbdff2d8110ebf865558b8 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 02:40:46 +0100 Subject: [PATCH 093/183] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.2.14=20rele?= =?UTF-8?q?ase?= 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 2cadc89d..0df27569 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.13", + "version": "1.2.14", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From f8945c7fabd8a11668b105f3e28eaa432458489b Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Jul 2023 02:41:09 +0100 Subject: [PATCH 094/183] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.2.15=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 61ea436b..9d0d9d1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.14", + "version": "1.2.15", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index 44b7e991..daed16d8 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.14", + "version": "1.2.15", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From ab0534169ce01c406217b9fb22f9b7f2dada5e3f Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Thu, 13 Jul 2023 19:03:36 +0100 Subject: [PATCH 095/183] update README Railway Deploy --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7792b969..5023a1b7 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ npx flowise start --PORT=3000 --DEBUG=true ### [Railway](https://docs.flowiseai.com/deployment/railway) -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/YK7J0v) +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/pn4G8S?referralCode=WVNPD9) ### [Render](https://docs.flowiseai.com/deployment/render) From 880754358db6a32030f1ab820140c7bdebc6709f Mon Sep 17 00:00:00 2001 From: apeng-singlestore <127370261+apeng-singlestore@users.noreply.github.com> Date: Thu, 13 Jul 2023 16:54:48 -0700 Subject: [PATCH 096/183] Add singlestore upsert and existing --- package.json | 5 +- .../Singlestore_Existing.ts | 138 ++++++++++++++++ .../Singlestore_Existing/singlestore.svg | 20 +++ .../Singlestore_Upsert/Singlestore_Upsert.ts | 154 ++++++++++++++++++ .../Singlestore_Upsert/singlestore.svg | 20 +++ 5 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts create mode 100644 packages/components/nodes/vectorstores/Singlestore_Existing/singlestore.svg create mode 100644 packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts create mode 100644 packages/components/nodes/vectorstores/Singlestore_Upsert/singlestore.svg diff --git a/package.json b/package.json index 61ea436b..8f9004ba 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "*.{js,jsx,ts,tsx,json,md}": "eslint --fix" }, "devDependencies": { - "turbo": "1.7.4", "@babel/preset-env": "^7.19.4", "@babel/preset-typescript": "7.18.6", "@types/express": "^4.17.13", @@ -48,9 +47,13 @@ "pretty-quick": "^3.1.3", "rimraf": "^3.0.2", "run-script-os": "^1.1.6", + "turbo": "1.7.4", "typescript": "^4.8.4" }, "engines": { "node": ">=18.15.0" + }, + "dependencies": { + "mysql2": "^3.5.1" } } diff --git a/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts b/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts new file mode 100644 index 00000000..61ae84f6 --- /dev/null +++ b/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts @@ -0,0 +1,138 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams, INodeOptionsValue } from '../../../src/Interface' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { getBaseClasses } from '../../../src/utils' +import { ConnectionOptions, SingleStoreVectorStore, SingleStoreVectorStoreConfig } from 'langchain/vectorstores/singlestore' +import { flatten } from 'lodash' +import { integer } from '@opensearch-project/opensearch/api/types' + +class SingleStoreExisting_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'SingleStore Load Existing Table' + this.name = 'singlestoreExisting' + this.type = 'SingleStore' + this.icon = 'singlestore.svg' + this.category = 'Vector Stores' + this.description = 'Load existing document from SingleStore' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Host', + name: 'host', + type: 'string' + }, + { + label: 'Port', + name: 'port', + type: 'string' + }, + { + label: 'User', + name: 'user', + type: 'string' + }, + { + label: 'Password', + name: 'password', + type: 'password' + }, + { + label: 'Database', + name: 'database', + type: 'string' + }, + { + label: 'Table Name', + name: 'tableName', + type: 'string' + }, + { + label: 'Content Column Name', + name: 'contentColumnName', + type: 'string' + }, + { + label: 'Vector Column Name', + name: 'vectorColumnName', + type: 'string' + }, + { + label: 'Metadata Column Name', + name: 'metadataColumnName', + type: 'string' + }, + { + 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: 'SingleStore Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'SingleStore Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(SingleStoreVectorStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const singleStoreConnectionConfig = { + connectionOptions: { + host: nodeData.inputs?.host as string, + port: nodeData.inputs?.port as integer, + user: nodeData.inputs?.user as string, + password: nodeData.inputs?.password as string, + database: nodeData.inputs?.database as string + }, + tableName: nodeData.inputs?.tableName as string, + contentColumnName: nodeData.inputs?.contentColumnName as string, + vectorColumnName: nodeData.inputs?.vectorColumnName as string, + metadataColumnName: nodeData.inputs?.metadataColumnName as string + } as SingleStoreVectorStoreConfig + + 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 + + let vectorStore: SingleStoreVectorStore + + vectorStore = new SingleStoreVectorStore(embeddings, singleStoreConnectionConfig) + + 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: SingleStoreExisting_VectorStores } diff --git a/packages/components/nodes/vectorstores/Singlestore_Existing/singlestore.svg b/packages/components/nodes/vectorstores/Singlestore_Existing/singlestore.svg new file mode 100644 index 00000000..bd8dc817 --- /dev/null +++ b/packages/components/nodes/vectorstores/Singlestore_Existing/singlestore.svg @@ -0,0 +1,20 @@ + + + SingleStore + + + + + + + + + + + + + + + + + diff --git a/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts b/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts new file mode 100644 index 00000000..b5f873a5 --- /dev/null +++ b/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts @@ -0,0 +1,154 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams, INodeOptionsValue } from '../../../src/Interface' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { getBaseClasses } from '../../../src/utils' +import { ConnectionOptions, SingleStoreVectorStore, SingleStoreVectorStoreConfig } from 'langchain/vectorstores/singlestore' +import { flatten } from 'lodash' +import { integer } from '@opensearch-project/opensearch/api/types' +import { MemoryVectorStore } from 'langchain/vectorstores/memory' + +class SingleStoreUpsert_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'SingleStore Upsert Document' + this.name = 'singlestoreUpsert' + this.type = 'SingleStore' + this.icon = 'singlestore.svg' + this.category = 'Vector Stores' + this.description = 'Upsert documents to SingleStore' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Document', + name: 'document', + type: 'Document', + list: true + }, + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Host', + name: 'host', + type: 'string' + }, + { + label: 'Port', + name: 'port', + type: 'string' + }, + { + label: 'User', + name: 'user', + type: 'string' + }, + { + label: 'Password', + name: 'password', + type: 'password' + }, + { + label: 'Database', + name: 'database', + type: 'string' + }, + { + label: 'Table Name', + name: 'tableName', + type: 'string' + }, + { + label: 'Content Column Name', + name: 'contentColumnName', + type: 'string' + }, + { + label: 'Vector Column Name', + name: 'vectorColumnName', + type: 'string' + }, + { + label: 'Metadata Column Name', + name: 'metadataColumnName', + type: 'string' + }, + { + 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: 'SingleStore Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'SingleStore Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(SingleStoreVectorStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const singleStoreConnectionConfig = { + connectionOptions: { + host: nodeData.inputs?.host as string, + port: nodeData.inputs?.port as integer, + user: nodeData.inputs?.user as string, + password: nodeData.inputs?.password as string, + database: nodeData.inputs?.database as string + }, + tableName: nodeData.inputs?.tableName as string, + contentColumnName: nodeData.inputs?.contentColumnName as string, + vectorColumnName: nodeData.inputs?.vectorColumnName as string, + metadataColumnName: nodeData.inputs?.metadataColumnName as string + } as SingleStoreVectorStoreConfig + + 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 + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + finalDocs.push(new Document(flattenDocs[i])) + } + + let vectorStore: SingleStoreVectorStore | MemoryVectorStore + + vectorStore = new SingleStoreVectorStore(embeddings, singleStoreConnectionConfig) + vectorStore.addDocuments.bind(vectorStore)(finalDocs) + + + 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: SingleStoreUpsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/Singlestore_Upsert/singlestore.svg b/packages/components/nodes/vectorstores/Singlestore_Upsert/singlestore.svg new file mode 100644 index 00000000..bd8dc817 --- /dev/null +++ b/packages/components/nodes/vectorstores/Singlestore_Upsert/singlestore.svg @@ -0,0 +1,20 @@ + + + SingleStore + + + + + + + + + + + + + + + + + From f893edcc02f9b830a5d51a26e2d77166f863ad48 Mon Sep 17 00:00:00 2001 From: Atish Amte Date: Sat, 15 Jul 2023 00:49:31 +0530 Subject: [PATCH 097/183] ChatMessage Order Fixed --- packages/server/src/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 74c4d07e..ff7d375d 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -306,8 +306,13 @@ export class App { // Get all chatmessages from chatflowid this.app.get('/api/v1/chatmessage/:id', async (req: Request, res: Response) => { - const chatmessages = await this.AppDataSource.getRepository(ChatMessage).findBy({ - chatflowid: req.params.id + const chatmessages = await this.AppDataSource.getRepository(ChatMessage).find({ + where: { + chatflowid: req.params.id + }, + order: { + createdDate: 'ASC' + }, }) return res.json(chatmessages) }) From 5ce082e34f8a9499cdf195dbc15ce0339a1d935d Mon Sep 17 00:00:00 2001 From: apeng-singlestore <127370261+apeng-singlestore@users.noreply.github.com> Date: Fri, 14 Jul 2023 15:24:24 -0700 Subject: [PATCH 098/183] remove redundant imports and move some options to additional --- .../Singlestore_Existing.ts | 43 +++++++++--------- .../Singlestore_Upsert/Singlestore_Upsert.ts | 44 ++++++++++--------- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts b/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts index 61ae84f6..3b880b24 100644 --- a/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts +++ b/packages/components/nodes/vectorstores/Singlestore_Existing/Singlestore_Existing.ts @@ -1,10 +1,7 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams, INodeOptionsValue } from '../../../src/Interface' +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { Embeddings } from 'langchain/embeddings/base' -import { Document } from 'langchain/document' import { getBaseClasses } from '../../../src/utils' -import { ConnectionOptions, SingleStoreVectorStore, SingleStoreVectorStoreConfig } from 'langchain/vectorstores/singlestore' -import { flatten } from 'lodash' -import { integer } from '@opensearch-project/opensearch/api/types' +import { SingleStoreVectorStore, SingleStoreVectorStoreConfig } from 'langchain/vectorstores/singlestore' class SingleStoreExisting_VectorStores implements INode { label: string @@ -36,11 +33,6 @@ class SingleStoreExisting_VectorStores implements INode { name: 'host', type: 'string' }, - { - label: 'Port', - name: 'port', - type: 'string' - }, { label: 'User', name: 'user', @@ -59,27 +51,38 @@ class SingleStoreExisting_VectorStores implements INode { { label: 'Table Name', name: 'tableName', - type: 'string' + type: 'string', + placeholder: 'embeddings', + additionalParams: true, + optional: true }, { label: 'Content Column Name', name: 'contentColumnName', - type: 'string' + type: 'string', + placeholder: 'content', + additionalParams: true, + optional: true }, { label: 'Vector Column Name', name: 'vectorColumnName', - type: 'string' + type: 'string', + placeholder: 'vector', + additionalParams: true, + optional: true }, { label: 'Metadata Column Name', name: 'metadataColumnName', - type: 'string' + type: 'string', + placeholder: 'metadata', + additionalParams: true, + optional: true }, { label: 'Top K', name: 'topK', - description: 'Number of top results to fetch. Default to 4', placeholder: '4', type: 'number', additionalParams: true, @@ -104,15 +107,15 @@ class SingleStoreExisting_VectorStores implements INode { const singleStoreConnectionConfig = { connectionOptions: { host: nodeData.inputs?.host as string, - port: nodeData.inputs?.port as integer, + port: 3306, user: nodeData.inputs?.user as string, password: nodeData.inputs?.password as string, database: nodeData.inputs?.database as string }, - tableName: nodeData.inputs?.tableName as string, - contentColumnName: nodeData.inputs?.contentColumnName as string, - vectorColumnName: nodeData.inputs?.vectorColumnName as string, - metadataColumnName: nodeData.inputs?.metadataColumnName as string + ...(nodeData.inputs?.tableName ? { tableName: nodeData.inputs.tableName as string } : {}), + ...(nodeData.inputs?.contentColumnName ? { contentColumnName: nodeData.inputs.contentColumnName as string } : {}), + ...(nodeData.inputs?.vectorColumnName ? { vectorColumnName: nodeData.inputs.vectorColumnName as string } : {}), + ...(nodeData.inputs?.metadataColumnName ? { metadataColumnName: nodeData.inputs.metadataColumnName as string } : {}) } as SingleStoreVectorStoreConfig const embeddings = nodeData.inputs?.embeddings as Embeddings diff --git a/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts b/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts index b5f873a5..dc59edb6 100644 --- a/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts +++ b/packages/components/nodes/vectorstores/Singlestore_Upsert/Singlestore_Upsert.ts @@ -2,9 +2,8 @@ import { INode, INodeData, INodeOutputsValue, INodeParams, INodeOptionsValue } f import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' import { getBaseClasses } from '../../../src/utils' -import { ConnectionOptions, SingleStoreVectorStore, SingleStoreVectorStoreConfig } from 'langchain/vectorstores/singlestore' +import { SingleStoreVectorStore, SingleStoreVectorStoreConfig } from 'langchain/vectorstores/singlestore' import { flatten } from 'lodash' -import { integer } from '@opensearch-project/opensearch/api/types' import { MemoryVectorStore } from 'langchain/vectorstores/memory' class SingleStoreUpsert_VectorStores implements INode { @@ -43,11 +42,6 @@ class SingleStoreUpsert_VectorStores implements INode { name: 'host', type: 'string' }, - { - label: 'Port', - name: 'port', - type: 'string' - }, { label: 'User', name: 'user', @@ -66,27 +60,38 @@ class SingleStoreUpsert_VectorStores implements INode { { label: 'Table Name', name: 'tableName', - type: 'string' + type: 'string', + placeholder: 'embeddings', + additionalParams: true, + optional: true }, { label: 'Content Column Name', name: 'contentColumnName', - type: 'string' + type: 'string', + placeholder: 'content', + additionalParams: true, + optional: true }, { label: 'Vector Column Name', name: 'vectorColumnName', - type: 'string' + type: 'string', + placeholder: 'vector', + additionalParams: true, + optional: true }, { label: 'Metadata Column Name', name: 'metadataColumnName', - type: 'string' + type: 'string', + placeholder: 'metadata', + additionalParams: true, + optional: true }, { label: 'Top K', name: 'topK', - description: 'Number of top results to fetch. Default to 4', placeholder: '4', type: 'number', additionalParams: true, @@ -111,15 +116,15 @@ class SingleStoreUpsert_VectorStores implements INode { const singleStoreConnectionConfig = { connectionOptions: { host: nodeData.inputs?.host as string, - port: nodeData.inputs?.port as integer, + port: 3306, user: nodeData.inputs?.user as string, password: nodeData.inputs?.password as string, database: nodeData.inputs?.database as string }, - tableName: nodeData.inputs?.tableName as string, - contentColumnName: nodeData.inputs?.contentColumnName as string, - vectorColumnName: nodeData.inputs?.vectorColumnName as string, - metadataColumnName: nodeData.inputs?.metadataColumnName as string + ...(nodeData.inputs?.tableName ? { tableName: nodeData.inputs.tableName as string } : {}), + ...(nodeData.inputs?.contentColumnName ? { contentColumnName: nodeData.inputs.contentColumnName as string } : {}), + ...(nodeData.inputs?.vectorColumnName ? { vectorColumnName: nodeData.inputs.vectorColumnName as string } : {}), + ...(nodeData.inputs?.metadataColumnName ? { metadataColumnName: nodeData.inputs.metadataColumnName as string } : {}) } as SingleStoreVectorStoreConfig const docs = nodeData.inputs?.document as Document[] @@ -134,11 +139,10 @@ class SingleStoreUpsert_VectorStores implements INode { finalDocs.push(new Document(flattenDocs[i])) } - let vectorStore: SingleStoreVectorStore | MemoryVectorStore - + let vectorStore: SingleStoreVectorStore + vectorStore = new SingleStoreVectorStore(embeddings, singleStoreConnectionConfig) vectorStore.addDocuments.bind(vectorStore)(finalDocs) - if (output === 'retriever') { const retriever = vectorStore.asRetriever(k) From 647bb1f8649d11f27686b827ae67d40e9644fae4 Mon Sep 17 00:00:00 2001 From: denchi Date: Sat, 15 Jul 2023 13:34:08 +0100 Subject: [PATCH 099/183] Added BraveSearch Api Tool --- .../tools/BraveSearchAPI/BraveSearchAPI.ts | 38 +++++++++++++++ .../nodes/tools/BraveSearchAPI/brave-logo.svg | 46 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts create mode 100644 packages/components/nodes/tools/BraveSearchAPI/brave-logo.svg diff --git a/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts b/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts new file mode 100644 index 00000000..75d0d5c5 --- /dev/null +++ b/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts @@ -0,0 +1,38 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { BraveSearch } from 'langchain/tools' + +class BraveSearchAPI_Tools implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'BraveSearch API' + this.name = 'braveSearchAPI' + this.type = 'BraveSearchAPI' + this.icon = 'brave-logo.svg' + this.category = 'Tools' + this.description = 'Wrapper around BraveSearch API - a real-time API to access Brave search results' + this.inputs = [ + { + label: 'BraveSearch API Key', + name: 'apiKey', + type: 'password' + } + ] + this.baseClasses = [this.type, ...getBaseClasses(BraveSearch)] + } + + async init(nodeData: INodeData): Promise { + const apiKey = nodeData.inputs?.apiKey as string + return new BraveSearch({apiKey}) + } +} + +module.exports = { nodeClass: BraveSearchAPI_Tools } diff --git a/packages/components/nodes/tools/BraveSearchAPI/brave-logo.svg b/packages/components/nodes/tools/BraveSearchAPI/brave-logo.svg new file mode 100644 index 00000000..686b5c41 --- /dev/null +++ b/packages/components/nodes/tools/BraveSearchAPI/brave-logo.svg @@ -0,0 +1,46 @@ + + + + Logotypes/bat/logo-dark@1x + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 413d654493df40924332666e6dd8a55587beb17c Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 15 Jul 2023 14:25:52 +0100 Subject: [PATCH 100/183] add credentials --- .../AzureChatOpenAI/AzureChatOpenAI.ts | 48 +-- .../AzureOpenAIApi.credential.ts | 45 +++ .../ChatAnthropic/AnthropicApi.credential.ts | 21 ++ .../chatmodels/ChatAnthropic/ChatAnthropic.ts | 22 +- .../nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts | 22 +- .../ChatOpenAI/OpenAIApi.credential.ts | 21 ++ .../components/nodes/llms/OpenAI/OpenAI.ts | 22 +- .../memory/MotorheadMemory/MotorheadMemory.ts | 33 +-- .../MotorheadMemoryApi.credential.ts | 29 ++ .../nodes/memory/ZepMemory/ZepMemory.ts | 23 +- .../ZepMemory/ZepMemoryApi.credential.ts | 24 ++ packages/components/src/Interface.ts | 10 + packages/components/src/utils.ts | 86 ++++++ packages/server/package.json | 2 + packages/server/src/ChildProcess.ts | 3 +- packages/server/src/DataSource.ts | 3 +- packages/server/src/Interface.ts | 27 ++ packages/server/src/NodesPool.ts | 48 ++- packages/server/src/entity/Credential.ts | 24 ++ packages/server/src/index.ts | 155 +++++++++- packages/server/src/utils/index.ts | 156 +++++++++- packages/server/src/utils/logger.ts | 3 +- packages/ui/src/api/credentials.js | 28 ++ .../ui/src/assets/images/credential_empty.svg | 1 + packages/ui/src/menu-items/dashboard.js | 12 +- packages/ui/src/routes/MainRoutes.js | 9 +- packages/ui/src/store/constant.js | 1 + .../ui-component/dropdown/AsyncDropdown.js | 35 ++- packages/ui/src/ui-component/input/Input.js | 2 +- packages/ui/src/utils/genericHelper.js | 19 +- .../views/canvas/CredentialInputHandler.js | 149 ++++++++++ .../ui/src/views/canvas/NodeInputHandler.js | 16 + packages/ui/src/views/canvas/index.js | 26 +- .../credentials/AddEditCredentialDialog.js | 276 ++++++++++++++++++ .../credentials/CredentialInputHandler.js | 137 +++++++++ .../views/credentials/CredentialListDialog.js | 172 +++++++++++ packages/ui/src/views/credentials/index.js | 274 +++++++++++++++++ 37 files changed, 1858 insertions(+), 126 deletions(-) create mode 100644 packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts create mode 100644 packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts create mode 100644 packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts create mode 100644 packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts create mode 100644 packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts create mode 100644 packages/server/src/entity/Credential.ts create mode 100644 packages/ui/src/api/credentials.js create mode 100644 packages/ui/src/assets/images/credential_empty.svg create mode 100644 packages/ui/src/views/canvas/CredentialInputHandler.js create mode 100644 packages/ui/src/views/credentials/AddEditCredentialDialog.js create mode 100644 packages/ui/src/views/credentials/CredentialInputHandler.js create mode 100644 packages/ui/src/views/credentials/CredentialListDialog.js create mode 100644 packages/ui/src/views/credentials/index.js diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts index 2cdb505d..0bff883f 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts @@ -1,6 +1,6 @@ import { OpenAIBaseInput } from 'langchain/dist/types/openai-types' -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { AzureOpenAIInput, ChatOpenAI } from 'langchain/chat_models/openai' class AzureChatOpenAI_ChatModels implements INode { @@ -11,6 +11,7 @@ class AzureChatOpenAI_ChatModels implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -21,12 +22,13 @@ class AzureChatOpenAI_ChatModels implements INode { this.category = 'Chat Models' this.description = 'Wrapper around Azure OpenAI large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatOpenAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['azureOpenAIApi'] + } this.inputs = [ - { - label: 'Azure OpenAI Api Key', - name: 'azureOpenAIApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -59,26 +61,6 @@ class AzureChatOpenAI_ChatModels implements INode { default: 0.9, optional: true }, - { - label: 'Azure OpenAI Api Instance Name', - name: 'azureOpenAIApiInstanceName', - type: 'string', - placeholder: 'YOUR-INSTANCE-NAME' - }, - { - label: 'Azure OpenAI Api Deployment Name', - name: 'azureOpenAIApiDeploymentName', - type: 'string', - placeholder: 'YOUR-DEPLOYMENT-NAME' - }, - { - label: 'Azure OpenAI Api Version', - name: 'azureOpenAIApiVersion', - type: 'string', - placeholder: '2023-06-01-preview', - description: - 'Description of Supported API Versions. Please refer examples' - }, { label: 'Max Tokens', name: 'maxTokens', @@ -110,19 +92,21 @@ class AzureChatOpenAI_ChatModels implements INode { ] } - async init(nodeData: INodeData): Promise { - const azureOpenAIApiKey = nodeData.inputs?.azureOpenAIApiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const modelName = nodeData.inputs?.modelName as string const temperature = nodeData.inputs?.temperature as string - const azureOpenAIApiInstanceName = nodeData.inputs?.azureOpenAIApiInstanceName as string - const azureOpenAIApiDeploymentName = nodeData.inputs?.azureOpenAIApiDeploymentName as string - const azureOpenAIApiVersion = nodeData.inputs?.azureOpenAIApiVersion as string const maxTokens = nodeData.inputs?.maxTokens as string const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string const presencePenalty = nodeData.inputs?.presencePenalty as string const timeout = nodeData.inputs?.timeout as string const streaming = nodeData.inputs?.streaming as boolean + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const azureOpenAIApiKey = getCredentialParam('azureOpenAIApiKey', credentialData, nodeData) + const azureOpenAIApiInstanceName = getCredentialParam('azureOpenAIApiInstanceName', credentialData, nodeData) + const azureOpenAIApiDeploymentName = getCredentialParam('azureOpenAIApiDeploymentName', credentialData, nodeData) + const azureOpenAIApiVersion = getCredentialParam('azureOpenAIApiVersion', credentialData, nodeData) + const obj: Partial & Partial = { temperature: parseFloat(temperature), modelName, diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts new file mode 100644 index 00000000..d48e0c88 --- /dev/null +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts @@ -0,0 +1,45 @@ +import { INodeParams, INodeCredential } from '../../../src/Interface' + +class AzureOpenAIApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Azure OpenAI API' + this.name = 'azureOpenAIApi' + this.description = + 'Refer to official guide of how to use Azure OpenAI service' + this.inputs = [ + { + label: 'Azure OpenAI Api Key', + name: 'azureOpenAIApiKey', + type: 'password', + description: `Refer to official guide on how to create API key on Azure OpenAI` + }, + { + label: 'Azure OpenAI Api Instance Name', + name: 'azureOpenAIApiInstanceName', + type: 'string', + placeholder: 'YOUR-INSTANCE-NAME' + }, + { + label: 'Azure OpenAI Api Deployment Name', + name: 'azureOpenAIApiDeploymentName', + type: 'string', + placeholder: 'YOUR-DEPLOYMENT-NAME' + }, + { + label: 'Azure OpenAI Api Version', + name: 'azureOpenAIApiVersion', + type: 'string', + placeholder: '2023-06-01-preview', + description: + 'Description of Supported API Versions. Please refer examples' + } + ] + } +} + +module.exports = { credClass: AzureOpenAIApi } diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts b/packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts new file mode 100644 index 00000000..607fa625 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts @@ -0,0 +1,21 @@ +import { INodeParams, INodeCredential } from '../../../src/Interface' + +class AnthropicApi implements INodeCredential { + label: string + name: string + inputs: INodeParams[] + + constructor() { + this.label = 'Anthropic API' + this.name = 'anthropicApi' + this.inputs = [ + { + label: 'Anthropic Api Key', + name: 'anthropicApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: AnthropicApi } diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts index 3d861d24..6581475e 100644 --- a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts +++ b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { AnthropicInput, ChatAnthropic } from 'langchain/chat_models/anthropic' class ChatAnthropic_ChatModels implements INode { @@ -10,6 +10,7 @@ class ChatAnthropic_ChatModels implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class ChatAnthropic_ChatModels implements INode { this.category = 'Chat Models' this.description = 'Wrapper around ChatAnthropic large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatAnthropic)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['anthropicApi'] + } this.inputs = [ - { - label: 'ChatAnthropic Api Key', - name: 'anthropicApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -110,15 +112,17 @@ class ChatAnthropic_ChatModels implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const temperature = nodeData.inputs?.temperature as string const modelName = nodeData.inputs?.modelName as string - const anthropicApiKey = nodeData.inputs?.anthropicApiKey as string const maxTokensToSample = nodeData.inputs?.maxTokensToSample as string const topP = nodeData.inputs?.topP as string const topK = nodeData.inputs?.topK as string const streaming = nodeData.inputs?.streaming as boolean + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const anthropicApiKey = getCredentialParam('anthropicApiKey', credentialData, nodeData) + const obj: Partial & { anthropicApiKey?: string } = { temperature: parseFloat(temperature), modelName, diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts index 955563ff..1339d1fe 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ChatOpenAI, OpenAIChatInput } from 'langchain/chat_models/openai' class ChatOpenAI_ChatModels implements INode { @@ -10,6 +10,7 @@ class ChatOpenAI_ChatModels implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class ChatOpenAI_ChatModels implements INode { this.category = 'Chat Models' this.description = 'Wrapper around OpenAI large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatOpenAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['openAIApi'] + } this.inputs = [ - { - label: 'OpenAI Api Key', - name: 'openAIApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -119,10 +121,9 @@ class ChatOpenAI_ChatModels implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const temperature = nodeData.inputs?.temperature as string const modelName = nodeData.inputs?.modelName as string - const openAIApiKey = nodeData.inputs?.openAIApiKey as string const maxTokens = nodeData.inputs?.maxTokens as string const topP = nodeData.inputs?.topP as string const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string @@ -131,6 +132,9 @@ class ChatOpenAI_ChatModels implements INode { const streaming = nodeData.inputs?.streaming as boolean const basePath = nodeData.inputs?.basepath as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) + const obj: Partial & { openAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts b/packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts new file mode 100644 index 00000000..96209a35 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts @@ -0,0 +1,21 @@ +import { INodeParams, INodeCredential } from '../../../src/Interface' + +class OpenAIApi implements INodeCredential { + label: string + name: string + inputs: INodeParams[] + + constructor() { + this.label = 'OpenAI API' + this.name = 'openAIApi' + this.inputs = [ + { + label: 'OpenAI Api Key', + name: 'openAIApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: OpenAIApi } diff --git a/packages/components/nodes/llms/OpenAI/OpenAI.ts b/packages/components/nodes/llms/OpenAI/OpenAI.ts index b0af867d..50aa1c60 100644 --- a/packages/components/nodes/llms/OpenAI/OpenAI.ts +++ b/packages/components/nodes/llms/OpenAI/OpenAI.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { OpenAI, OpenAIInput } from 'langchain/llms/openai' class OpenAI_LLMs implements INode { @@ -10,6 +10,7 @@ class OpenAI_LLMs implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class OpenAI_LLMs implements INode { this.category = 'LLMs' this.description = 'Wrapper around OpenAI large language models' this.baseClasses = [this.type, ...getBaseClasses(OpenAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['openAIApi'] + } this.inputs = [ - { - label: 'OpenAI Api Key', - name: 'openAIApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -117,10 +119,9 @@ class OpenAI_LLMs implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const temperature = nodeData.inputs?.temperature as string const modelName = nodeData.inputs?.modelName as string - const openAIApiKey = nodeData.inputs?.openAIApiKey as string const maxTokens = nodeData.inputs?.maxTokens as string const topP = nodeData.inputs?.topP as string const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string @@ -131,6 +132,9 @@ class OpenAI_LLMs implements INode { const streaming = nodeData.inputs?.streaming as boolean const basePath = nodeData.inputs?.basepath as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) + const obj: Partial & { openAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 8a160223..9caf604c 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -1,5 +1,5 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ICommonObject } from '../../../src' import { MotorheadMemory, MotorheadMemoryInput } from 'langchain/memory' @@ -11,6 +11,7 @@ class MotorMemory_Memory implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -21,6 +22,14 @@ class MotorMemory_Memory implements INode { this.category = 'Memory' this.description = 'Remembers previous conversational back and forths directly' this.baseClasses = [this.type, ...getBaseClasses(MotorheadMemory)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: true, + description: 'Only needed when using hosted solution - https://getmetal.io', + credentialNames: ['motorheadMemoryApi'] + } this.inputs = [ { label: 'Base URL', @@ -43,22 +52,6 @@ class MotorMemory_Memory implements INode { default: '', additionalParams: true, optional: true - }, - { - label: 'API Key', - name: 'apiKey', - type: 'password', - description: 'Only needed when using hosted solution - https://getmetal.io', - additionalParams: true, - optional: true - }, - { - label: 'Client ID', - name: 'clientId', - type: 'string', - description: 'Only needed when using hosted solution - https://getmetal.io', - additionalParams: true, - optional: true } ] } @@ -67,11 +60,13 @@ class MotorMemory_Memory implements INode { const memoryKey = nodeData.inputs?.memoryKey as string const baseURL = nodeData.inputs?.baseURL as string const sessionId = nodeData.inputs?.sessionId as string - const apiKey = nodeData.inputs?.apiKey as string - const clientId = nodeData.inputs?.clientId as string const chatId = options?.chatId as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + const clientId = getCredentialParam('clientId', credentialData, nodeData) + let obj: MotorheadMemoryInput = { returnMessages: true, sessionId: sessionId ? sessionId : chatId, diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts new file mode 100644 index 00000000..4563cda2 --- /dev/null +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts @@ -0,0 +1,29 @@ +import { INodeParams, INodeCredential } from '../../../src/Interface' + +class MotorheadMemoryApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Motorhead Memory API' + this.name = 'motorheadMemoryApi' + this.description = + 'Refer to official guide on how to create API key and Client ID on Motorhead Memory' + this.inputs = [ + { + label: 'Client ID', + name: 'clientId', + type: 'string' + }, + { + label: 'API Key', + name: 'apiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: MotorheadMemoryApi } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 5ca1310d..211f60be 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -1,6 +1,6 @@ import { SystemMessage } from 'langchain/schema' import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' import { ICommonObject } from '../../../src' @@ -12,6 +12,7 @@ class ZepMemory_Memory implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -22,6 +23,14 @@ class ZepMemory_Memory implements INode { this.category = 'Memory' this.description = 'Summarizes the conversation and stores the memory in zep server' this.baseClasses = [this.type, ...getBaseClasses(ZepMemory)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: true, + description: 'Configure JWT authentication on your Zep instance (Optional)', + credentialNames: ['zepMemoryApi'] + } this.inputs = [ { label: 'Base URL', @@ -44,18 +53,12 @@ class ZepMemory_Memory implements INode { additionalParams: true, optional: true }, - { - label: 'API Key', - name: 'apiKey', - type: 'password', - additionalParams: true, - optional: true - }, { label: 'Size', name: 'k', type: 'number', default: '10', + step: 1, description: 'Window of size k to surface the last k back-and-forths to use as memory.' }, { @@ -112,11 +115,13 @@ class ZepMemory_Memory implements INode { const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string const autoSummary = nodeData.inputs?.autoSummary as boolean const sessionId = nodeData.inputs?.sessionId as string - const apiKey = nodeData.inputs?.apiKey as string const k = nodeData.inputs?.k as string const chatId = options?.chatId as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + const obj: ZepMemoryInput = { baseURL, sessionId: sessionId ? sessionId : chatId, diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts b/packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts new file mode 100644 index 00000000..5e92ef5d --- /dev/null +++ b/packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts @@ -0,0 +1,24 @@ +import { INodeParams, INodeCredential } from '../../../src/Interface' + +class ZepMemoryApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Zep Memory Api' + this.name = 'zepMemoryApi' + this.description = + 'Refer to official guide on how to create API key on Zep' + this.inputs = [ + { + label: 'API Key', + name: 'apiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: ZepMemoryApi } diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index d9233e49..4a69a9a0 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -59,7 +59,9 @@ export interface INodeParams { description?: string warning?: string options?: Array + credentialNames?: Array optional?: boolean | INodeDisplay + step?: number rows?: number list?: boolean acceptVariable?: boolean @@ -102,10 +104,18 @@ export interface INodeData extends INodeProperties { id: string inputs?: ICommonObject outputs?: ICommonObject + credential?: string instance?: any loadMethod?: string // method to load async options } +export interface INodeCredential { + label: string + name: string + description?: string + inputs?: INodeParams[] +} + export interface IMessage { message: string type: MessageType diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index e1399404..3244422f 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -6,6 +6,9 @@ import { JSDOM } from 'jsdom' import { BaseCallbackHandler } from 'langchain/callbacks' import { Server } from 'socket.io' import { ChainValues } from 'langchain/dist/schema' +import { DataSource } from 'typeorm' +import { ICommonObject, IDatabaseEntity, INodeData } from './Interface' +import { AES, enc } from 'crypto-js' export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}} export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank @@ -350,6 +353,89 @@ export const getEnvironmentVariable = (name: string): string | undefined => { } } +/** + * Returns the path of encryption key + * @returns {string} + */ +const getEncryptionKeyFilePath = (): string => { + const checkPaths = [ + path.join(__dirname, '..', '..', 'server', 'encryption.key'), + path.join(__dirname, '..', '..', '..', 'server', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', 'server', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', '..', 'server', 'encryption.key') + ] + for (const checkPath of checkPaths) { + if (fs.existsSync(checkPath)) { + return checkPath + } + } + return '' +} + +const getEncryptionKeyPath = (): string => { + return process.env.SECRETKEY_PATH ? path.join(process.env.SECRETKEY_PATH, 'encryption.key') : getEncryptionKeyFilePath() +} + +/** + * Returns the encryption key + * @returns {Promise} + */ +const getEncryptionKey = async (): Promise => { + try { + return await fs.promises.readFile(getEncryptionKeyPath(), 'utf8') + } catch (error) { + throw new Error(error) + } +} + +/** + * Decrypt credential data + * @param {string} encryptedData + * @param {string} componentCredentialName + * @param {IComponentCredentials} componentCredentials + * @returns {Promise} + */ +const decryptCredentialData = async (encryptedData: string): Promise => { + const encryptKey = await getEncryptionKey() + const decryptedData = AES.decrypt(encryptedData, encryptKey) + try { + return JSON.parse(decryptedData.toString(enc.Utf8)) + } catch (e) { + console.error(e) + throw new Error('Credentials could not be decrypted.') + } +} + +/** + * Get credential data + * @param {string} selectedCredentialId + * @param {ICommonObject} options + * @returns {Promise} + */ +export const getCredentialData = async (selectedCredentialId: string, options: ICommonObject): Promise => { + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + + try { + const credential = await appDataSource.getRepository(databaseEntities['Credential']).findOneBy({ + id: selectedCredentialId + }) + + if (!credential) throw new Error(`Credential ${selectedCredentialId} not found`) + + // Decrpyt credentialData + const decryptedCredentialData = await decryptCredentialData(credential.encryptedData) + + return decryptedCredentialData + } catch (e) { + throw new Error(e) + } +} + +export const getCredentialParam = (paramName: string, credentialData: ICommonObject, nodeData: INodeData): any => { + return (nodeData.inputs as ICommonObject)[paramName] ?? credentialData[paramName] +} + /** * Custom chain handler class */ diff --git a/packages/server/package.json b/packages/server/package.json index 44b7e991..d04bcab2 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -48,6 +48,7 @@ "@oclif/core": "^1.13.10", "axios": "^0.27.2", "cors": "^2.8.5", + "crypto-js": "^4.1.1", "dotenv": "^16.0.0", "express": "^4.17.3", "express-basic-auth": "^1.2.1", @@ -63,6 +64,7 @@ }, "devDependencies": { "@types/cors": "^2.8.12", + "@types/crypto-js": "^4.1.1", "@types/multer": "^1.4.7", "concurrently": "^7.1.0", "nodemon": "^2.0.15", diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index e8aeaff2..1ac80557 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -5,6 +5,7 @@ import { DataSource } from 'typeorm' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' import { Tool } from './entity/Tool' +import { Credential } from './entity/Credential' export class ChildProcess { /** @@ -133,7 +134,7 @@ async function initDB() { type: 'sqlite', database: path.resolve(homePath, 'database.sqlite'), synchronize: true, - entities: [ChatFlow, ChatMessage, Tool], + entities: [ChatFlow, ChatMessage, Tool, Credential], migrations: [] }) return await childAppDataSource.initialize() diff --git a/packages/server/src/DataSource.ts b/packages/server/src/DataSource.ts index 03b9d5ce..fdf0c924 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 { Credential } from './entity/Credential' import { Tool } from './entity/Tool' import { getUserHome } from './utils' @@ -15,7 +16,7 @@ export const init = async (): Promise => { type: 'sqlite', database: path.resolve(homePath, 'database.sqlite'), synchronize: true, - entities: [ChatFlow, ChatMessage, Tool], + entities: [ChatFlow, ChatMessage, Tool, Credential], migrations: [] }) } diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 0c630490..49d61036 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -38,10 +38,23 @@ export interface ITool { createdDate: Date } +export interface ICredential { + id: string + name: string + credentialName: string + encryptedData: string + updatedDate: Date + createdDate: Date +} + export interface IComponentNodes { [key: string]: INode } +export interface IComponentCredentials { + [key: string]: INode +} + export interface IVariableDict { [key: string]: string } @@ -167,3 +180,17 @@ export interface IChildProcessMessage { key: string value?: any } + +export type ICredentialDataDecrypted = ICommonObject + +// Plain credential object sent to server +export interface ICredentialReqBody { + name: string + credentialName: string + plainDataObj: ICredentialDataDecrypted +} + +// Decrypted credential object sent back to client +export interface ICredentialReturnResponse extends ICredential { + plainDataObj: ICredentialDataDecrypted +} diff --git a/packages/server/src/NodesPool.ts b/packages/server/src/NodesPool.ts index 1ee506ea..e339f592 100644 --- a/packages/server/src/NodesPool.ts +++ b/packages/server/src/NodesPool.ts @@ -1,23 +1,33 @@ -import { IComponentNodes } from './Interface' - +import { IComponentNodes, IComponentCredentials } from './Interface' import path from 'path' import { Dirent } from 'fs' import { getNodeModulesPackagePath } from './utils' import { promises } from 'fs' +import { ICommonObject } from 'flowise-components' export class NodesPool { componentNodes: IComponentNodes = {} + componentCredentials: IComponentCredentials = {} + private credentialIconPath: ICommonObject = {} /** - * Initialize to get all nodes + * Initialize to get all nodes & credentials */ async initialize() { + await this.initializeNodes() + await this.initializeCrdentials() + } + + /** + * Initialize nodes + */ + private async initializeNodes() { const packagePath = getNodeModulesPackagePath('flowise-components') const nodesPath = path.join(packagePath, 'dist', 'nodes') const nodeFiles = await this.getFiles(nodesPath) return Promise.all( nodeFiles.map(async (file) => { - if (file.endsWith('.js')) { + if (file.endsWith('.js') && !file.endsWith('.credential.js')) { const nodeModule = await require(file) if (nodeModule.nodeClass) { @@ -37,6 +47,13 @@ export class NodesPool { filePath.pop() const nodeIconAbsolutePath = `${filePath.join('/')}/${newNodeInstance.icon}` this.componentNodes[newNodeInstance.name].icon = nodeIconAbsolutePath + + // Store icon path for componentCredentials + if (newNodeInstance.credential) { + for (const credName of newNodeInstance.credential.credentialNames) { + this.credentialIconPath[credName] = nodeIconAbsolutePath + } + } } } } @@ -44,12 +61,33 @@ export class NodesPool { ) } + /** + * Initialize credentials + */ + private async initializeCrdentials() { + const packagePath = getNodeModulesPackagePath('flowise-components') + const nodesPath = path.join(packagePath, 'dist', 'nodes') + const nodeFiles = await this.getFiles(nodesPath) + return Promise.all( + nodeFiles.map(async (file) => { + if (file.endsWith('.credential.js')) { + const credentialModule = await require(file) + if (credentialModule.credClass) { + const newCredInstance = new credentialModule.credClass() + newCredInstance.icon = this.credentialIconPath[newCredInstance.name] ?? '' + this.componentCredentials[newCredInstance.name] = newCredInstance + } + } + }) + ) + } + /** * Recursive function to get node files * @param {string} dir * @returns {string[]} */ - async getFiles(dir: string): Promise { + private async getFiles(dir: string): Promise { const dirents = await promises.readdir(dir, { withFileTypes: true }) const files = await Promise.all( dirents.map((dirent: Dirent) => { diff --git a/packages/server/src/entity/Credential.ts b/packages/server/src/entity/Credential.ts new file mode 100644 index 00000000..b724eed6 --- /dev/null +++ b/packages/server/src/entity/Credential.ts @@ -0,0 +1,24 @@ +/* eslint-disable */ +import { Entity, Column, PrimaryGeneratedColumn, Index, CreateDateColumn, UpdateDateColumn } from 'typeorm' +import { ICredential } from '../Interface' + +@Entity() +export class Credential implements ICredential { + @PrimaryGeneratedColumn('uuid') + id: string + + @Column() + name: string + + @Column() + credentialName: string + + @Column() + encryptedData: string + + @CreateDateColumn() + createdDate: Date + + @UpdateDateColumn() + updatedDate: Date +} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 0f87aeba..b4316b69 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -17,7 +17,8 @@ import { INodeData, IDatabaseExport, IRunChatflowMessageValue, - IChildProcessMessage + IChildProcessMessage, + ICredentialReturnResponse } from './Interface' import { getNodeModulesPackagePath, @@ -39,17 +40,20 @@ import { isFlowValidForStream, isVectorStoreFaiss, databaseEntities, - getApiKey + getApiKey, + transformToCredentialEntity, + decryptCredentialData } from './utils' -import { cloneDeep } from 'lodash' +import { cloneDeep, omit } from 'lodash' import { getDataSource } from './DataSource' import { NodesPool } from './NodesPool' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' +import { Credential } from './entity/Credential' +import { Tool } from './entity/Tool' import { ChatflowPool } from './ChatflowPool' import { ICommonObject, INodeOptionsValue } from 'flowise-components' import { fork } from 'child_process' -import { Tool } from './entity/Tool' export class App { app: express.Application @@ -70,10 +74,11 @@ export class App { .then(async () => { logger.info('📦 [server]: Data Source has been initialized!') - // Initialize pools + // Initialize nodes pool this.nodesPool = new NodesPool() await this.nodesPool.initialize() + // Initialize chatflow pool this.chatflowPool = new ChatflowPool() // Initialize API keys @@ -104,6 +109,7 @@ export class App { '/api/v1/public-chatflows', '/api/v1/prediction/', '/api/v1/node-icon/', + '/api/v1/components-credentials-icon/', '/api/v1/chatflows-streaming' ] this.app.use((req, res, next) => { @@ -116,7 +122,7 @@ export class App { const upload = multer({ dest: `${path.join(__dirname, '..', 'uploads')}/` }) // ---------------------------------------- - // Nodes + // Components // ---------------------------------------- // Get all component nodes @@ -129,6 +135,16 @@ export class App { return res.json(returnData) }) + // Get all component credentials + this.app.get('/api/v1/components-credentials', async (req: Request, res: Response) => { + const returnData = [] + for (const credName in this.nodesPool.componentCredentials) { + const clonedCred = cloneDeep(this.nodesPool.componentCredentials[credName]) + returnData.push(clonedCred) + } + return res.json(returnData) + }) + // Get specific component node via name this.app.get('/api/v1/nodes/:name', (req: Request, res: Response) => { if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, req.params.name)) { @@ -138,6 +154,27 @@ export class App { } }) + // Get component credential via name + this.app.get('/api/v1/components-credentials/:name', (req: Request, res: Response) => { + if (!req.params.name.includes('&')) { + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, req.params.name)) { + return res.json(this.nodesPool.componentCredentials[req.params.name]) + } else { + throw new Error(`Credential ${req.params.name} not found`) + } + } else { + const returnResponse = [] + for (const name of req.params.name.split('&')) { + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, name)) { + returnResponse.push(this.nodesPool.componentCredentials[name]) + } else { + throw new Error(`Credential ${name} not found`) + } + } + return res.json(returnResponse) + } + }) + // Returns specific component node icon via name this.app.get('/api/v1/node-icon/:name', (req: Request, res: Response) => { if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, req.params.name)) { @@ -157,6 +194,25 @@ export class App { } }) + // Returns specific component credential icon via name + this.app.get('/api/v1/components-credentials-icon/:name', (req: Request, res: Response) => { + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, req.params.name)) { + const credInstance = this.nodesPool.componentCredentials[req.params.name] + if (credInstance.icon === undefined) { + throw new Error(`Credential ${req.params.name} icon not found`) + } + + if (credInstance.icon.endsWith('.svg') || credInstance.icon.endsWith('.png') || credInstance.icon.endsWith('.jpg')) { + const filepath = credInstance.icon + res.sendFile(filepath) + } else { + throw new Error(`Credential ${req.params.name} icon is missing icon`) + } + } else { + throw new Error(`Credential ${req.params.name} not found`) + } + }) + // load async options this.app.post('/api/v1/node-load-method/:name', async (req: Request, res: Response) => { const nodeData: INodeData = req.body @@ -324,6 +380,91 @@ export class App { return res.json(results) }) + // ---------------------------------------- + // Credentials + // ---------------------------------------- + + // Create new credential + this.app.post('/api/v1/credentials', async (req: Request, res: Response) => { + const body = req.body + const newCredential = await transformToCredentialEntity(body) + const credential = this.AppDataSource.getRepository(Credential).create(newCredential) + const results = await this.AppDataSource.getRepository(Credential).save(credential) + return res.json(results) + }) + + // Get all credentials + this.app.get('/api/v1/credentials', async (req: Request, res: Response) => { + if (req.query.credentialName) { + let returnCredentials = [] + if (Array.isArray(req.query.credentialName)) { + for (let i = 0; i < req.query.credentialName.length; i += 1) { + const name = req.query.credentialName[i] as string + const credentials = await this.AppDataSource.getRepository(Credential).findBy({ + credentialName: name + }) + returnCredentials.push(...credentials) + } + } else { + const credentials = await this.AppDataSource.getRepository(Credential).findBy({ + credentialName: req.query.credentialName as string + }) + returnCredentials = [...credentials] + } + return res.json(returnCredentials) + } else { + const credentials = await this.AppDataSource.getRepository(Credential).find() + const returnCredentials = [] + for (const credential of credentials) { + returnCredentials.push(omit(credential, ['encryptedData'])) + } + return res.json(returnCredentials) + } + }) + + // Get specific credential + this.app.get('/api/v1/credentials/:id', async (req: Request, res: Response) => { + const credential = await this.AppDataSource.getRepository(Credential).findOneBy({ + id: req.params.id + }) + + if (!credential) return res.status(404).send(`Credential ${req.params.id} not found`) + + // Decrpyt credentialData + const decryptedCredentialData = await decryptCredentialData( + credential.encryptedData, + credential.credentialName, + this.nodesPool.componentCredentials + ) + const returnCredential: ICredentialReturnResponse = { + ...credential, + plainDataObj: decryptedCredentialData + } + return res.json(omit(returnCredential, ['encryptedData'])) + }) + + // Update credential + this.app.put('/api/v1/credentials/:id', async (req: Request, res: Response) => { + const credential = await this.AppDataSource.getRepository(Credential).findOneBy({ + id: req.params.id + }) + + if (!credential) return res.status(404).send(`Credential ${req.params.id} not found`) + + const body = req.body + const updateCredential = await transformToCredentialEntity(body) + this.AppDataSource.getRepository(Credential).merge(credential, updateCredential) + const result = await this.AppDataSource.getRepository(Credential).save(credential) + + return res.json(result) + }) + + // Delete all chatmessages from chatflowid + this.app.delete('/api/v1/credentials/:id', async (req: Request, res: Response) => { + const results = await this.AppDataSource.getRepository(Credential).delete({ id: req.params.id }) + return res.json(results) + }) + // ---------------------------------------- // Tools // ---------------------------------------- @@ -393,7 +534,7 @@ export class App { const flowData = chatflow.flowData const parsedFlowData: IReactFlowObject = JSON.parse(flowData) const nodes = parsedFlowData.nodes - const availableConfigs = findAvailableConfigs(nodes) + const availableConfigs = findAvailableConfigs(nodes, this.nodesPool.componentCredentials) return res.json(availableConfigs) }) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 3ee7a25b..77b047cc 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -13,18 +13,26 @@ import { IReactFlowNode, IVariableDict, INodeData, - IOverrideConfig + IOverrideConfig, + ICredentialDataDecrypted, + IComponentCredentials, + ICredentialReqBody } from '../Interface' import { cloneDeep, get, omit, merge } from 'lodash' import { ICommonObject, getInputVariables, IDatabaseEntity } from 'flowise-components' import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' +import { lib, PBKDF2, AES, enc } from 'crypto-js' + import { ChatFlow } from '../entity/ChatFlow' import { ChatMessage } from '../entity/ChatMessage' +import { Credential } from '../entity/Credential' import { Tool } from '../entity/Tool' import { DataSource } from 'typeorm' const QUESTION_VAR_PREFIX = 'question' -export const databaseEntities: IDatabaseEntity = { ChatFlow: ChatFlow, ChatMessage: ChatMessage, Tool: Tool } +const REDACTED_CREDENTIAL_VALUE = '_FLOWISE_BLANK_07167752-1a71-43b1-bf8f-4f32252165db' + +export const databaseEntities: IDatabaseEntity = { ChatFlow: ChatFlow, ChatMessage: ChatMessage, Tool: Tool, Credential: Credential } /** * Returns the home folder path of the user if @@ -399,9 +407,8 @@ export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig: const types = 'inputs' const getParamValues = (paramsObj: ICommonObject) => { - for (const key in paramsObj) { - const paramValue: string = paramsObj[key] - paramsObj[key] = overrideConfig[key] ?? paramValue + for (const config in overrideConfig) { + paramsObj[config] = overrideConfig[config] } } @@ -623,11 +630,12 @@ export const mapMimeTypeToInputField = (mimeType: string) => { } /** - * Find all available inpur params config + * Find all available input params config * @param {IReactFlowNode[]} reactFlowNodes - * @returns {Promise} + * @param {IComponentCredentials} componentCredentials + * @returns {IOverrideConfig[]} */ -export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => { +export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], componentCredentials: IComponentCredentials) => { const configs: IOverrideConfig[] = [] for (const flowNode of reactFlowNodes) { @@ -653,6 +661,23 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => { .join(', ') : 'string' } + } else if (inputParam.type === 'credential') { + // get component credential inputs + for (const name of inputParam.credentialNames ?? []) { + if (Object.prototype.hasOwnProperty.call(componentCredentials, name)) { + const inputs = componentCredentials[name]?.inputs ?? [] + for (const input of inputs) { + obj = { + node: flowNode.data.label, + label: input.label, + name: input.name, + type: input.type === 'password' ? 'string' : input.type + } + configs.push(obj) + } + } + } + continue } else { obj = { node: flowNode.data.label, @@ -705,3 +730,118 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod return isChatOrLLMsExist && isValidChainOrAgent && !isVectorStoreFaiss(endingNodeData) && process.env.EXECUTION_MODE !== 'child' } + +/** + * Returns the path of encryption key + * @returns {string} + */ +export const getEncryptionKeyPath = (): string => { + return process.env.SECRETKEY_PATH + ? path.join(process.env.SECRETKEY_PATH, 'encryption.key') + : path.join(__dirname, '..', '..', 'encryption.key') +} + +/** + * Generate an encryption key + * @returns {string} + */ +export const generateEncryptKey = (): string => { + const salt = lib.WordArray.random(128 / 8) + const key256Bits = PBKDF2(process.env.PASSPHRASE || 'MYPASSPHRASE', salt, { + keySize: 256 / 32, + iterations: 1000 + }) + return key256Bits.toString() +} + +/** + * Returns the encryption key + * @returns {Promise} + */ +export const getEncryptionKey = async (): Promise => { + try { + return await fs.promises.readFile(getEncryptionKeyPath(), 'utf8') + } catch (error) { + const encryptKey = generateEncryptKey() + await fs.promises.writeFile(getEncryptionKeyPath(), encryptKey) + return encryptKey + } +} + +/** + * Encrypt credential data + * @param {ICredentialDataDecrypted} plainDataObj + * @returns {Promise} + */ +export const encryptCredentialData = async (plainDataObj: ICredentialDataDecrypted): Promise => { + const encryptKey = await getEncryptionKey() + return AES.encrypt(JSON.stringify(plainDataObj), encryptKey).toString() +} + +/** + * Decrypt credential data + * @param {string} encryptedData + * @param {string} componentCredentialName + * @param {IComponentCredentials} componentCredentials + * @returns {Promise} + */ +export const decryptCredentialData = async ( + encryptedData: string, + componentCredentialName?: string, + componentCredentials?: IComponentCredentials +): Promise => { + const encryptKey = await getEncryptionKey() + const decryptedData = AES.decrypt(encryptedData, encryptKey) + try { + if (componentCredentialName && componentCredentials) { + const plainDataObj = JSON.parse(decryptedData.toString(enc.Utf8)) + return redactCredentialWithPasswordType(componentCredentialName, plainDataObj, componentCredentials) + } + return JSON.parse(decryptedData.toString(enc.Utf8)) + } catch (e) { + console.error(e) + throw new Error('Credentials could not be decrypted.') + } +} + +/** + * Transform ICredentialBody from req to Credential entity + * @param {ICredentialReqBody} body + * @returns {Credential} + */ +export const transformToCredentialEntity = async (body: ICredentialReqBody): Promise => { + const encryptedData = await encryptCredentialData(body.plainDataObj) + + const credentialBody = { + name: body.name, + credentialName: body.credentialName, + encryptedData + } + + const newCredential = new Credential() + Object.assign(newCredential, credentialBody) + + return newCredential +} + +/** + * Redact values that are of password type to avoid sending back to client + * @param {string} componentCredentialName + * @param {ICredentialDataDecrypted} decryptedCredentialObj + * @param {IComponentCredentials} componentCredentials + * @returns {ICredentialDataDecrypted} + */ +export const redactCredentialWithPasswordType = ( + componentCredentialName: string, + decryptedCredentialObj: ICredentialDataDecrypted, + componentCredentials: IComponentCredentials +): ICredentialDataDecrypted => { + const plainDataObj = cloneDeep(decryptedCredentialObj) + for (const cred in plainDataObj) { + const inputParam = componentCredentials[componentCredentialName].inputs?.find((inp) => inp.type === 'password' && inp.name === cred) + if (inputParam) { + plainDataObj[cred] = REDACTED_CREDENTIAL_VALUE + } + } + return plainDataObj +} diff --git a/packages/server/src/utils/logger.ts b/packages/server/src/utils/logger.ts index 1c28b173..c5ff26b0 100644 --- a/packages/server/src/utils/logger.ts +++ b/packages/server/src/utils/logger.ts @@ -81,7 +81,8 @@ export function expressRequestLogger(req: Request, res: Response, next: NextFunc GET: '⬇️', POST: '⬆️', PUT: '🖊', - DELETE: '❌' + DELETE: '❌', + OPTION: '🔗' } return requetsEmojis[method] || '?' diff --git a/packages/ui/src/api/credentials.js b/packages/ui/src/api/credentials.js new file mode 100644 index 00000000..9dbdcf7a --- /dev/null +++ b/packages/ui/src/api/credentials.js @@ -0,0 +1,28 @@ +import client from './client' + +const getAllCredentials = () => client.get('/credentials') + +const getCredentialsByName = (componentCredentialName) => client.get(`/credentials?credentialName=${componentCredentialName}`) + +const getAllComponentsCredentials = () => client.get('/components-credentials') + +const getSpecificCredential = (id) => client.get(`/credentials/${id}`) + +const getSpecificComponentCredential = (name) => client.get(`/components-credentials/${name}`) + +const createCredential = (body) => client.post(`/credentials`, body) + +const updateCredential = (id, body) => client.put(`/credentials/${id}`, body) + +const deleteCredential = (id) => client.delete(`/credentials/${id}`) + +export default { + getAllCredentials, + getCredentialsByName, + getAllComponentsCredentials, + getSpecificCredential, + getSpecificComponentCredential, + createCredential, + updateCredential, + deleteCredential +} diff --git a/packages/ui/src/assets/images/credential_empty.svg b/packages/ui/src/assets/images/credential_empty.svg new file mode 100644 index 00000000..0951ee07 --- /dev/null +++ b/packages/ui/src/assets/images/credential_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 948b4e4a..87ef88f9 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, IconTool } from '@tabler/icons' +import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock } from '@tabler/icons' // constant -const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool } +const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock } // ==============================|| DASHBOARD MENU ITEMS ||============================== // @@ -35,6 +35,14 @@ const dashboard = { icon: icons.IconTool, breadcrumbs: true }, + { + id: 'credentials', + title: 'Credentials', + type: 'item', + url: '/credentials', + icon: icons.IconLock, + breadcrumbs: true + }, { id: 'apikey', title: 'API Keys', diff --git a/packages/ui/src/routes/MainRoutes.js b/packages/ui/src/routes/MainRoutes.js index 28e60287..9a1c29af 100644 --- a/packages/ui/src/routes/MainRoutes.js +++ b/packages/ui/src/routes/MainRoutes.js @@ -13,9 +13,12 @@ const Marketplaces = Loadable(lazy(() => import('views/marketplaces'))) // apikey routing const APIKey = Loadable(lazy(() => import('views/apikey'))) -// apikey routing +// tools routing const Tools = Loadable(lazy(() => import('views/tools'))) +// credentials routing +const Credentials = Loadable(lazy(() => import('views/credentials'))) + // ==============================|| MAIN ROUTING ||============================== // const MainRoutes = { @@ -41,6 +44,10 @@ const MainRoutes = { { path: '/tools', element: + }, + { + path: '/credentials', + element: } ] } diff --git a/packages/ui/src/store/constant.js b/packages/ui/src/store/constant.js index c3138257..c0fce49d 100644 --- a/packages/ui/src/store/constant.js +++ b/packages/ui/src/store/constant.js @@ -5,3 +5,4 @@ export const appDrawerWidth = 320 export const maxScroll = 100000 export const baseURL = process.env.NODE_ENV === 'production' ? window.location.origin : window.location.origin.replace(':8080', ':3000') export const uiBaseURL = window.location.origin +export const FLOWISE_CREDENTIAL_ID = 'FLOWISE_CREDENTIAL_ID' diff --git a/packages/ui/src/ui-component/dropdown/AsyncDropdown.js b/packages/ui/src/ui-component/dropdown/AsyncDropdown.js index 8dfd782d..b24fa02b 100644 --- a/packages/ui/src/ui-component/dropdown/AsyncDropdown.js +++ b/packages/ui/src/ui-component/dropdown/AsyncDropdown.js @@ -1,13 +1,17 @@ import { useState, useEffect, Fragment } from 'react' import { useSelector } from 'react-redux' - import PropTypes from 'prop-types' import axios from 'axios' +// Material import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete' import { Popper, CircularProgress, TextField, Box, Typography } from '@mui/material' import { styled } from '@mui/material/styles' +// API +import credentialsApi from 'api/credentials' + +// const import { baseURL } from 'store/constant' const StyledPopper = styled(Popper)({ @@ -49,6 +53,7 @@ export const AsyncDropdown = ({ onSelect, isCreateNewOption, onCreateNew, + credentialNames = [], disabled = false, disableClearable = false }) => { @@ -62,11 +67,36 @@ export const AsyncDropdown = ({ const addNewOption = [{ label: '- Create New -', name: '-create-' }] let [internalValue, setInternalValue] = useState(value ?? 'choose an option') + const fetchCredentialList = async () => { + try { + let names = '' + if (credentialNames.length > 1) { + names = credentialNames.join('&credentialName=') + } else { + names = credentialNames[0] + } + const resp = await credentialsApi.getCredentialsByName(names) + if (resp.data) { + const returnList = [] + for (let i = 0; i < resp.data.length; i += 1) { + const data = { + label: resp.data[i].name, + name: resp.data[i].id + } + returnList.push(data) + } + return returnList + } + } catch (error) { + console.error(error) + } + } + useEffect(() => { setLoading(true) ;(async () => { const fetchData = async () => { - let response = await fetchList({ name, nodeData }) + let response = credentialNames.length ? await fetchCredentialList() : await fetchList({ name, nodeData }) if (isCreateNewOption) setOptions([...response, ...addNewOption]) else setOptions([...response]) setLoading(false) @@ -142,6 +172,7 @@ AsyncDropdown.propTypes = { onSelect: PropTypes.func, onCreateNew: PropTypes.func, disabled: PropTypes.bool, + credentialNames: PropTypes.array, disableClearable: PropTypes.bool, isCreateNewOption: PropTypes.bool } diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index e7744764..8f2d55e0 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -37,7 +37,7 @@ export const Input = ({ inputParam, value, onChange, disabled = false, showDialo onChange(e.target.value) }} inputProps={{ - step: 0.1, + step: inputParam.step ?? 0.1, style: { height: inputParam.rows ? '90px' : 'inherit' } diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index 305326f7..eb3b08bc 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -41,6 +41,7 @@ export const initNode = (nodeData, newNodeId) => { const whitelistTypes = ['asyncOptions', 'options', 'string', 'number', 'boolean', 'password', 'json', 'code', 'date', 'file', 'folder'] + // Inputs for (let i = 0; i < incoming; i += 1) { const newInput = { ...nodeData.inputs[i], @@ -53,6 +54,16 @@ export const initNode = (nodeData, newNodeId) => { } } + // Credential + if (nodeData.credential) { + const newInput = { + ...nodeData.credential, + id: `${newNodeId}-input-${nodeData.credential.name}-${nodeData.credential.type}` + } + inputParams.unshift(newInput) + } + + // Outputs const outputAnchors = [] for (let i = 0; i < outgoing; i += 1) { if (nodeData.outputs && nodeData.outputs.length) { @@ -129,6 +140,8 @@ export const initNode = (nodeData, newNodeId) => { } ] */ + + // Inputs if (nodeData.inputs) { nodeData.inputAnchors = inputAnchors nodeData.inputParams = inputParams @@ -139,13 +152,17 @@ export const initNode = (nodeData, newNodeId) => { nodeData.inputs = {} } + // Outputs if (nodeData.outputs) { nodeData.outputs = initializeDefaultNodeData(outputAnchors) } else { nodeData.outputs = {} } - nodeData.outputAnchors = outputAnchors + + // Credential + if (nodeData.credential) nodeData.credential = '' + nodeData.id = newNodeId return nodeData diff --git a/packages/ui/src/views/canvas/CredentialInputHandler.js b/packages/ui/src/views/canvas/CredentialInputHandler.js new file mode 100644 index 00000000..4f874719 --- /dev/null +++ b/packages/ui/src/views/canvas/CredentialInputHandler.js @@ -0,0 +1,149 @@ +import PropTypes from 'prop-types' +import { useRef, useState } from 'react' + +// material-ui +import { IconButton } from '@mui/material' +import { IconEdit } from '@tabler/icons' + +// project import +import { AsyncDropdown } from 'ui-component/dropdown/AsyncDropdown' +import AddEditCredentialDialog from 'views/credentials/AddEditCredentialDialog' +import CredentialListDialog from 'views/credentials/CredentialListDialog' + +// API +import credentialsApi from 'api/credentials' + +// ===========================|| CredentialInputHandler ||=========================== // + +const CredentialInputHandler = ({ inputParam, data, onSelect, disabled = false }) => { + const ref = useRef(null) + const [credentialId, setCredentialId] = useState(data?.credential ?? '') + const [showCredentialListDialog, setShowCredentialListDialog] = useState(false) + const [credentialListDialogProps, setCredentialListDialogProps] = useState({}) + const [showSpecificCredentialDialog, setShowSpecificCredentialDialog] = useState(false) + const [specificCredentialDialogProps, setSpecificCredentialDialogProps] = useState({}) + const [reloadTimestamp, setReloadTimestamp] = useState(Date.now().toString()) + + const editCredential = (credentialId) => { + const dialogProp = { + type: 'EDIT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Save', + credentialId + } + setSpecificCredentialDialogProps(dialogProp) + setShowSpecificCredentialDialog(true) + } + + const addAsyncOption = async () => { + try { + let names = '' + if (inputParam.credentialNames.length > 1) { + names = inputParam.credentialNames.join('&') + } else { + names = inputParam.credentialNames[0] + } + const componentCredentialsResp = await credentialsApi.getSpecificComponentCredential(names) + if (componentCredentialsResp.data) { + if (Array.isArray(componentCredentialsResp.data)) { + const dialogProp = { + title: 'Add New Credential', + componentsCredentials: componentCredentialsResp.data + } + setCredentialListDialogProps(dialogProp) + setShowCredentialListDialog(true) + } else { + const dialogProp = { + type: 'ADD', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add', + credentialComponent: componentCredentialsResp.data + } + setSpecificCredentialDialogProps(dialogProp) + setShowSpecificCredentialDialog(true) + } + } + } catch (error) { + console.error(error) + } + } + + const onConfirmAsyncOption = (selectedCredentialId = '') => { + setCredentialId(selectedCredentialId) + setReloadTimestamp(Date.now().toString()) + setSpecificCredentialDialogProps({}) + setShowSpecificCredentialDialog(false) + onSelect(selectedCredentialId) + } + + const onCredentialSelected = (credentialComponent) => { + setShowCredentialListDialog(false) + const dialogProp = { + type: 'ADD', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add', + credentialComponent + } + setSpecificCredentialDialogProps(dialogProp) + setShowSpecificCredentialDialog(true) + } + + return ( +
+ {inputParam && ( + <> + {inputParam.type === 'credential' && ( + <> +
+
+ { + setCredentialId(newValue) + onSelect(newValue) + }} + onCreateNew={() => addAsyncOption(inputParam.name)} + /> + {credentialId && ( + editCredential(credentialId)}> + + + )} +
+ + )} + + )} + {showSpecificCredentialDialog && ( + setShowSpecificCredentialDialog(false)} + onConfirm={onConfirmAsyncOption} + > + )} + {showCredentialListDialog && ( + setShowCredentialListDialog(false)} + onCredentialSelected={onCredentialSelected} + > + )} +
+ ) +} + +CredentialInputHandler.propTypes = { + inputParam: PropTypes.object, + data: PropTypes.object, + onSelect: PropTypes.func, + disabled: PropTypes.bool +} + +export default CredentialInputHandler diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index ba72a4ce..176df52f 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -21,9 +21,14 @@ 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 CredentialInputHandler from './CredentialInputHandler' +// utils import { getInputVariables } from 'utils/genericHelper' +// const +import { FLOWISE_CREDENTIAL_ID } from 'store/constant' + const EDITABLE_TOOLS = ['selectedTool'] const CustomWidthTooltip = styled(({ className, ...props }) => )({ @@ -226,6 +231,17 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA {inputParam.warning}
)} + {inputParam.type === 'credential' && ( + { + data.credential = newValue + data.inputs[FLOWISE_CREDENTIAL_ID] = newValue // in case data.credential is not updated + }} + /> + )} {inputParam.type === 'file' && ( { const handleSaveFlow = (chatflowName) => { if (reactFlowInstance) { - setNodes((nds) => - nds.map((node) => { - node.data = { - ...node.data, - selected: false - } - return node - }) - ) + const nodes = reactFlowInstance.getNodes().map((node) => { + const nodeData = cloneDeep(node.data) + if (Object.prototype.hasOwnProperty.call(nodeData.inputs, FLOWISE_CREDENTIAL_ID)) { + nodeData.credential = nodeData.inputs[FLOWISE_CREDENTIAL_ID] + nodeData.inputs = omit(nodeData.inputs, [FLOWISE_CREDENTIAL_ID]) + } + node.data = { + ...nodeData, + selected: false + } + return node + }) const rfInstanceObject = reactFlowInstance.toObject() + rfInstanceObject.nodes = nodes const flowData = JSON.stringify(rfInstanceObject) if (!chatflow.id) { diff --git a/packages/ui/src/views/credentials/AddEditCredentialDialog.js b/packages/ui/src/views/credentials/AddEditCredentialDialog.js new file mode 100644 index 00000000..6a5c9568 --- /dev/null +++ b/packages/ui/src/views/credentials/AddEditCredentialDialog.js @@ -0,0 +1,276 @@ +import { createPortal } from 'react-dom' +import PropTypes from 'prop-types' +import { useState, useEffect } from 'react' +import { useDispatch } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import parser from 'html-react-parser' + +// Material +import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Box, Stack, OutlinedInput, Typography } from '@mui/material' + +// Project imports +import { StyledButton } from 'ui-component/button/StyledButton' +import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' +import CredentialInputHandler from './CredentialInputHandler' + +// Icons +import { IconX } from '@tabler/icons' + +// API +import credentialsApi from 'api/credentials' + +// Hooks +import useApi from 'hooks/useApi' + +// utils +import useNotifier from 'utils/useNotifier' + +// const +import { baseURL } from 'store/constant' + +const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const portalElement = document.getElementById('portal') + + const dispatch = useDispatch() + + // ==============================|| Snackbar ||============================== // + + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const getSpecificCredentialApi = useApi(credentialsApi.getSpecificCredential) + const getSpecificComponentCredentialApi = useApi(credentialsApi.getSpecificComponentCredential) + + const [credential, setCredential] = useState({}) + const [name, setName] = useState('') + const [credentialData, setCredentialData] = useState({}) + const [componentCredential, setComponentCredential] = useState({}) + + useEffect(() => { + if (getSpecificCredentialApi.data) { + setCredential(getSpecificCredentialApi.data) + if (getSpecificCredentialApi.data.name) { + setName(getSpecificCredentialApi.data.name) + } + if (getSpecificCredentialApi.data.plainDataObj) { + setCredentialData(getSpecificCredentialApi.data.plainDataObj) + } + getSpecificComponentCredentialApi.request(getSpecificCredentialApi.data.credentialName) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getSpecificCredentialApi.data]) + + useEffect(() => { + if (getSpecificComponentCredentialApi.data) { + setComponentCredential(getSpecificComponentCredentialApi.data) + } + }, [getSpecificComponentCredentialApi.data]) + + useEffect(() => { + if (dialogProps.type === 'EDIT' && dialogProps.data) { + // When credential dialog is opened from Credentials dashboard + getSpecificCredentialApi.request(dialogProps.data.id) + } else if (dialogProps.type === 'EDIT' && dialogProps.credentialId) { + // When credential dialog is opened from node in canvas + getSpecificCredentialApi.request(dialogProps.credentialId) + } else if (dialogProps.type === 'ADD' && dialogProps.credentialComponent) { + // When credential dialog is to add a new credential + setName('') + setCredential({}) + setCredentialData({}) + setComponentCredential(dialogProps.credentialComponent) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dialogProps]) + + const addNewCredential = async () => { + try { + const obj = { + name, + credentialName: componentCredential.name, + plainDataObj: credentialData + } + const createResp = await credentialsApi.createCredential(obj) + if (createResp.data) { + enqueueSnackbar({ + message: 'New Credential 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 Credential: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + + const saveCredential = async () => { + try { + const saveResp = await credentialsApi.updateCredential(credential.id, { + name, + credentialName: componentCredential.name, + plainDataObj: credentialData + }) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Credential saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm(saveResp.data.id) + } + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to save Credential: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + + const component = show ? ( + + + {componentCredential && componentCredential.label && ( +
+
+ {componentCredential.name} +
+ {componentCredential.label} +
+ )} +
+ + {componentCredential && componentCredential.description && ( + +
+ {parser(componentCredential.description)} +
+
+ )} + {componentCredential && componentCredential.label && ( + + + + Credential Name +  * + + + setName(e.target.value)} + /> + + )} + {componentCredential && + componentCredential.inputs && + componentCredential.inputs.map((inputParam, index) => ( + + ))} +
+ + (dialogProps.type === 'ADD' ? addNewCredential() : saveCredential())} + > + {dialogProps.confirmButtonName} + + + +
+ ) : null + + return createPortal(component, portalElement) +} + +AddEditCredentialDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func, + onConfirm: PropTypes.func +} + +export default AddEditCredentialDialog diff --git a/packages/ui/src/views/credentials/CredentialInputHandler.js b/packages/ui/src/views/credentials/CredentialInputHandler.js new file mode 100644 index 00000000..30cc5746 --- /dev/null +++ b/packages/ui/src/views/credentials/CredentialInputHandler.js @@ -0,0 +1,137 @@ +import PropTypes from 'prop-types' +import { useRef, useState } from 'react' +import { useSelector } from 'react-redux' + +// material-ui +import { Box, Typography, IconButton } from '@mui/material' +import { IconArrowsMaximize, IconAlertTriangle } from '@tabler/icons' + +// project import +import { Dropdown } from 'ui-component/dropdown/Dropdown' +import { Input } from 'ui-component/input/Input' +import { SwitchInput } from 'ui-component/switch/Switch' +import { JsonEditorInput } from 'ui-component/json/JsonEditor' +import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' + +// ===========================|| NodeInputHandler ||=========================== // + +const CredentialInputHandler = ({ inputParam, data, disabled = false }) => { + const customization = useSelector((state) => state.customization) + const ref = useRef(null) + + const [showExpandDialog, setShowExpandDialog] = useState(false) + const [expandDialogProps, setExpandDialogProps] = useState({}) + + const onExpandDialogClicked = (value, inputParam) => { + const dialogProp = { + value, + inputParam, + disabled, + confirmButtonName: 'Save', + cancelButtonName: 'Cancel' + } + setExpandDialogProps(dialogProp) + setShowExpandDialog(true) + } + + const onExpandDialogSave = (newValue, inputParamName) => { + setShowExpandDialog(false) + data[inputParamName] = newValue + } + + return ( +
+ {inputParam && ( + <> + +
+ + {inputParam.label} + {!inputParam.optional &&  *} + {inputParam.description && } + +
+ {inputParam.type === 'string' && inputParam.rows && ( + onExpandDialogClicked(data[inputParam.name] ?? inputParam.default ?? '', inputParam)} + > + + + )} +
+ {inputParam.warning && ( +
+ + {inputParam.warning} +
+ )} + + {inputParam.type === 'boolean' && ( + (data[inputParam.name] = newValue)} + value={data[inputParam.name] ?? inputParam.default ?? false} + /> + )} + {(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && ( + (data[inputParam.name] = newValue)} + value={data[inputParam.name] ?? inputParam.default ?? ''} + showDialog={showExpandDialog} + dialogProps={expandDialogProps} + onDialogCancel={() => setShowExpandDialog(false)} + onDialogConfirm={(newValue, inputParamName) => onExpandDialogSave(newValue, inputParamName)} + /> + )} + {inputParam.type === 'json' && ( + (data[inputParam.name] = newValue)} + value={data[inputParam.name] ?? inputParam.default ?? ''} + isDarkMode={customization.isDarkMode} + /> + )} + {inputParam.type === 'options' && ( + (data[inputParam.name] = newValue)} + value={data[inputParam.name] ?? inputParam.default ?? 'choose an option'} + /> + )} +
+ + )} +
+ ) +} + +CredentialInputHandler.propTypes = { + inputAnchor: PropTypes.object, + inputParam: PropTypes.object, + data: PropTypes.object, + disabled: PropTypes.bool +} + +export default CredentialInputHandler diff --git a/packages/ui/src/views/credentials/CredentialListDialog.js b/packages/ui/src/views/credentials/CredentialListDialog.js new file mode 100644 index 00000000..9333db67 --- /dev/null +++ b/packages/ui/src/views/credentials/CredentialListDialog.js @@ -0,0 +1,172 @@ +import { useState, useEffect } from 'react' +import { createPortal } from 'react-dom' +import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' +import { + List, + ListItemButton, + ListItem, + ListItemAvatar, + ListItemText, + Dialog, + DialogContent, + DialogTitle, + Box, + OutlinedInput, + InputAdornment +} from '@mui/material' +import { useTheme } from '@mui/material/styles' +import { IconSearch, IconX } from '@tabler/icons' + +// const +import { baseURL } from 'store/constant' + +const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelected }) => { + const portalElement = document.getElementById('portal') + const customization = useSelector((state) => state.customization) + + const theme = useTheme() + const [searchValue, setSearchValue] = useState('') + const [componentsCredentials, setComponentsCredentials] = useState([]) + + const filterSearch = (value) => { + setSearchValue(value) + setTimeout(() => { + if (value) { + const searchData = dialogProps.componentsCredentials.filter((crd) => crd.name.toLowerCase().includes(value.toLowerCase())) + setComponentsCredentials(searchData) + } else if (value === '') { + setComponentsCredentials(dialogProps.componentsCredentials) + } + // scrollTop() + }, 500) + } + + useEffect(() => { + if (show && dialogProps.componentsCredentials) { + setComponentsCredentials(dialogProps.componentsCredentials) + } + }, [show, dialogProps]) + + const component = show ? ( + + + {dialogProps.title} + + filterSearch(e.target.value)} + placeholder='Search credential' + startAdornment={ + + + + } + endAdornment={ + + filterSearch('')} + style={{ + cursor: 'pointer' + }} + /> + + } + aria-describedby='search-helper-text' + inputProps={{ + 'aria-label': 'weight' + }} + /> + + + + + {[...componentsCredentials].map((componentCredential) => ( +
+ onCredentialSelected(componentCredential)} + sx={{ p: 0, borderRadius: `${customization.borderRadius}px` }} + > + + +
+ {componentCredential.name} +
+
+ +
+
+
+ ))} +
+
+
+ ) : null + + return createPortal(component, portalElement) +} + +CredentialListDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func, + onCredentialSelected: PropTypes.func +} + +export default CredentialListDialog diff --git a/packages/ui/src/views/credentials/index.js b/packages/ui/src/views/credentials/index.js new file mode 100644 index 00000000..bcd641ed --- /dev/null +++ b/packages/ui/src/views/credentials/index.js @@ -0,0 +1,274 @@ +import { useEffect, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import moment from 'moment' + +// material-ui +import { Button, Box, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, IconButton } from '@mui/material' +import { useTheme } from '@mui/material/styles' + +// project imports +import MainCard from 'ui-component/cards/MainCard' +import { StyledButton } from 'ui-component/button/StyledButton' +import CredentialListDialog from './CredentialListDialog' +import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' +import AddEditCredentialDialog from './AddEditCredentialDialog' + +// API +import credentialsApi from 'api/credentials' + +// Hooks +import useApi from 'hooks/useApi' +import useConfirm from 'hooks/useConfirm' + +// utils +import useNotifier from 'utils/useNotifier' + +// Icons +import { IconTrash, IconEdit, IconX, IconPlus } from '@tabler/icons' +import CredentialEmptySVG from 'assets/images/credential_empty.svg' + +// const +import { baseURL } from 'store/constant' + +// ==============================|| Credentials ||============================== // + +const Credentials = () => { + const theme = useTheme() + const customization = useSelector((state) => state.customization) + + const dispatch = useDispatch() + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [showCredentialListDialog, setShowCredentialListDialog] = useState(false) + const [credentialListDialogProps, setCredentialListDialogProps] = useState({}) + const [showSpecificCredentialDialog, setShowSpecificCredentialDialog] = useState(false) + const [specificCredentialDialogProps, setSpecificCredentialDialogProps] = useState({}) + const [credentials, setCredentials] = useState([]) + const [componentsCredentials, setComponentsCredentials] = useState([]) + + const { confirm } = useConfirm() + + const getAllCredentialsApi = useApi(credentialsApi.getAllCredentials) + const getAllComponentsCredentialsApi = useApi(credentialsApi.getAllComponentsCredentials) + + const listCredential = () => { + const dialogProp = { + title: 'Add New Credential', + componentsCredentials + } + setCredentialListDialogProps(dialogProp) + setShowCredentialListDialog(true) + } + + const addNew = (credentialComponent) => { + const dialogProp = { + type: 'ADD', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add', + credentialComponent + } + setSpecificCredentialDialogProps(dialogProp) + setShowSpecificCredentialDialog(true) + } + + const edit = (credential) => { + const dialogProp = { + type: 'EDIT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Save', + data: credential + } + setSpecificCredentialDialogProps(dialogProp) + setShowSpecificCredentialDialog(true) + } + + const deleteCredential = async (credential) => { + const confirmPayload = { + title: `Delete`, + description: `Delete credential ${credential.name}?`, + confirmButtonName: 'Delete', + cancelButtonName: 'Cancel' + } + const isConfirmed = await confirm(confirmPayload) + + if (isConfirmed) { + try { + const deleteResp = await credentialsApi.deleteCredential(credential.id) + if (deleteResp.data) { + enqueueSnackbar({ + message: 'Credential 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 Credential: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + } + + const onCredentialSelected = (credentialComponent) => { + setShowCredentialListDialog(false) + addNew(credentialComponent) + } + + const onConfirm = () => { + setShowCredentialListDialog(false) + setShowSpecificCredentialDialog(false) + getAllCredentialsApi.request() + } + + useEffect(() => { + getAllCredentialsApi.request() + getAllComponentsCredentialsApi.request() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (getAllCredentialsApi.data) { + setCredentials(getAllCredentialsApi.data) + } + }, [getAllCredentialsApi.data]) + + useEffect(() => { + if (getAllComponentsCredentialsApi.data) { + setComponentsCredentials(getAllComponentsCredentialsApi.data) + } + }, [getAllComponentsCredentialsApi.data]) + + return ( + <> + + +

Credentials 

+ + + } + > + Add Credential + +
+ {credentials.length <= 0 && ( + + + CredentialEmptySVG + +
No Credentials Yet
+
+ )} + {credentials.length > 0 && ( + + + + + Name + Last Updated + Created + + + + + + {credentials.map((credential, index) => ( + + +
+
+ {credential.credentialName} +
+ {credential.name} +
+
+ {moment(credential.updatedDate).format('DD-MMM-YY')} + {moment(credential.createdDate).format('DD-MMM-YY')} + + edit(credential)}> + + + + + deleteCredential(credential)}> + + + +
+ ))} +
+
+
+ )} +
+ setShowCredentialListDialog(false)} + onCredentialSelected={onCredentialSelected} + > + setShowSpecificCredentialDialog(false)} + onConfirm={onConfirm} + > + + + ) +} + +export default Credentials From c4e4e92fb213d5a208474b60ca057306b84200d7 Mon Sep 17 00:00:00 2001 From: denchi Date: Sat, 15 Jul 2023 15:56:52 +0100 Subject: [PATCH 101/183] Fix linting error --- .../components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts b/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts index 75d0d5c5..76629392 100644 --- a/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts +++ b/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts @@ -31,7 +31,7 @@ class BraveSearchAPI_Tools implements INode { async init(nodeData: INodeData): Promise { const apiKey = nodeData.inputs?.apiKey as string - return new BraveSearch({apiKey}) + return new BraveSearch({ apiKey }) } } From ff755c3d1b3b58d58b87de2cbd6bab84473737c5 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 15 Jul 2023 19:43:09 +0100 Subject: [PATCH 102/183] update credentials --- .../AnthropicApi.credential.ts | 2 +- .../AzureOpenAIApi.credential.ts | 2 +- .../credentials/CohereApi.credential.ts | 21 ++++ .../credentials/ConfluenceApi.credential.ts | 31 ++++++ .../DynamodbMemoryApi.credential.ts | 27 +++++ .../credentials/FigmaApi.credential.ts | 25 +++++ .../credentials/GithubApi.credential.ts | 25 +++++ .../credentials/HuggingFaceApi.credential.ts | 21 ++++ .../MotorheadMemoryApi.credential.ts | 2 +- .../credentials/NotionApi.credential.ts | 24 +++++ .../OpenAIApi.credential.ts | 2 +- .../credentials/OpenAPIAuth.credential.ts | 23 ++++ .../credentials/PineconeApi.credential.ts | 27 +++++ .../credentials/QdrantApi.credential.ts | 22 ++++ .../credentials/SerpApi.credential.ts | 22 ++++ .../credentials/SerperApi.credential.ts | 22 ++++ .../credentials/SupabaseApi.credential.ts | 22 ++++ .../credentials/WeaviateApi.credential.ts | 22 ++++ .../credentials/ZapierNLAApi.credential.ts | 22 ++++ .../ZepMemoryApi.credential.ts | 4 +- .../OpenAIFunctionAgent.ts | 2 +- .../agents/OpenAIFunctionAgent/openai.png | Bin 3991 -> 0 bytes .../agents/OpenAIFunctionAgent/openai.svg | 1 + .../nodes/chains/ApiChain/OpenAPIChain.ts | 2 +- .../chatmodels/AzureChatOpenAI/Azure.svg | 6 +- .../ChatHuggingFace/ChatHuggingFace.ts | 41 ++++---- .../nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts | 2 +- .../nodes/chatmodels/ChatOpenAI/openai.png | Bin 3991 -> 0 bytes .../nodes/chatmodels/ChatOpenAI/openai.svg | 1 + .../documentloaders/Confluence/Confluence.ts | 38 +++---- .../nodes/documentloaders/Figma/Figma.ts | 38 ++++--- .../nodes/documentloaders/Figma/figma.png | Bin 176029 -> 0 bytes .../nodes/documentloaders/Figma/figma.svg | 1 + .../nodes/documentloaders/Github/Github.ts | 31 +++--- .../documentloaders/NotionDB/NotionDB.ts | 46 ++++---- .../documentloaders/NotionPage/NotionPage.ts | 99 ++++++++++++++++++ .../documentloaders/NotionPage/notion.png | Bin 0 -> 11406 bytes .../embeddings/AzureOpenAIEmbedding/Azure.svg | 6 +- .../AzureOpenAIEmbedding.ts | 48 +++------ .../CohereEmbedding/CohereEmbedding.ts | 24 +++-- .../HuggingFaceInferenceEmbedding.ts | 24 +++-- .../OpenAIEmbedding/OpenAIEmbedding.ts | 24 +++-- .../embeddings/OpenAIEmbedding/openai.png | Bin 3991 -> 0 bytes .../embeddings/OpenAIEmbedding/openai.svg | 1 + .../nodes/llms/Azure OpenAI/Azure.svg | 6 +- .../nodes/llms/Azure OpenAI/AzureOpenAI.ts | 48 +++------ .../components/nodes/llms/Cohere/Cohere.ts | 24 +++-- .../HuggingFaceInference.ts | 41 ++++---- .../components/nodes/llms/OpenAI/OpenAI.ts | 2 +- .../components/nodes/llms/OpenAI/openai.png | Bin 3991 -> 0 bytes .../components/nodes/llms/OpenAI/openai.svg | 1 + .../nodes/memory/DynamoDb/DynamoDb.ts | 42 ++++---- .../memory/MotorheadMemory/MotorheadMemory.ts | 15 +-- .../RedisBackedChatMemory.ts | 4 +- .../tools/OpenAPIToolkit/OpenAPIToolkit.ts | 29 +++-- .../components/nodes/tools/SerpAPI/SerpAPI.ts | 26 ++--- .../components/nodes/tools/Serper/Serper.ts | 26 ++--- .../nodes/tools/ZapierNLA/ZapierNLA.ts | 27 ++--- .../nodes/tools/ZapierNLA/zapier.png | Bin 502 -> 0 bytes .../nodes/tools/ZapierNLA/zapier.svg | 8 ++ .../Pinecone_Existing/Pinecone_Existing.ts | 29 +++-- .../Pinecone_Upsert/Pinecone_Upsert.ts | 29 +++-- .../Qdrant_Existing/Qdrant_Existing.ts | 28 ++--- .../vectorstores/Qdrant_Existing/qdrant.png | Bin 0 -> 11663 bytes .../Qdrant_Existing/qdrant_logo.svg | 27 ----- .../Qdrant_Upsert/Qdrant_Upsert.ts | 28 ++--- .../vectorstores/Qdrant_Upsert/qdrant.png | Bin 0 -> 11663 bytes .../Qdrant_Upsert/qdrant_logo.svg | 27 ----- .../Supabase_Existing/Supabase_Exisiting.ts | 22 ++-- .../Supabase_Upsert/Supabase_Upsert.ts | 22 ++-- .../Weaviate_Existing/Weaviate_Existing.ts | 25 +++-- .../Weaviate_Upsert/Weaviate_Upsert.ts | 25 +++-- packages/components/package.json | 2 + packages/components/tsconfig.json | 2 +- packages/server/src/NodesPool.ts | 4 +- 75 files changed, 911 insertions(+), 461 deletions(-) rename packages/components/{nodes/chatmodels/ChatAnthropic => credentials}/AnthropicApi.credential.ts (86%) rename packages/components/{nodes/chatmodels/AzureChatOpenAI => credentials}/AzureOpenAIApi.credential.ts (96%) create mode 100644 packages/components/credentials/CohereApi.credential.ts create mode 100644 packages/components/credentials/ConfluenceApi.credential.ts create mode 100644 packages/components/credentials/DynamodbMemoryApi.credential.ts create mode 100644 packages/components/credentials/FigmaApi.credential.ts create mode 100644 packages/components/credentials/GithubApi.credential.ts create mode 100644 packages/components/credentials/HuggingFaceApi.credential.ts rename packages/components/{nodes/memory/MotorheadMemory => credentials}/MotorheadMemoryApi.credential.ts (91%) create mode 100644 packages/components/credentials/NotionApi.credential.ts rename packages/components/{nodes/chatmodels/ChatOpenAI => credentials}/OpenAIApi.credential.ts (85%) create mode 100644 packages/components/credentials/OpenAPIAuth.credential.ts create mode 100644 packages/components/credentials/PineconeApi.credential.ts create mode 100644 packages/components/credentials/QdrantApi.credential.ts create mode 100644 packages/components/credentials/SerpApi.credential.ts create mode 100644 packages/components/credentials/SerperApi.credential.ts create mode 100644 packages/components/credentials/SupabaseApi.credential.ts create mode 100644 packages/components/credentials/WeaviateApi.credential.ts create mode 100644 packages/components/credentials/ZapierNLAApi.credential.ts rename packages/components/{nodes/memory/ZepMemory => credentials}/ZepMemoryApi.credential.ts (84%) delete mode 100644 packages/components/nodes/agents/OpenAIFunctionAgent/openai.png create mode 100644 packages/components/nodes/agents/OpenAIFunctionAgent/openai.svg delete mode 100644 packages/components/nodes/chatmodels/ChatOpenAI/openai.png create mode 100644 packages/components/nodes/chatmodels/ChatOpenAI/openai.svg delete mode 100644 packages/components/nodes/documentloaders/Figma/figma.png create mode 100644 packages/components/nodes/documentloaders/Figma/figma.svg create mode 100644 packages/components/nodes/documentloaders/NotionPage/NotionPage.ts create mode 100644 packages/components/nodes/documentloaders/NotionPage/notion.png delete mode 100644 packages/components/nodes/embeddings/OpenAIEmbedding/openai.png create mode 100644 packages/components/nodes/embeddings/OpenAIEmbedding/openai.svg delete mode 100644 packages/components/nodes/llms/OpenAI/openai.png create mode 100644 packages/components/nodes/llms/OpenAI/openai.svg delete mode 100644 packages/components/nodes/tools/ZapierNLA/zapier.png create mode 100644 packages/components/nodes/tools/ZapierNLA/zapier.svg create mode 100644 packages/components/nodes/vectorstores/Qdrant_Existing/qdrant.png delete mode 100644 packages/components/nodes/vectorstores/Qdrant_Existing/qdrant_logo.svg create mode 100644 packages/components/nodes/vectorstores/Qdrant_Upsert/qdrant.png delete mode 100644 packages/components/nodes/vectorstores/Qdrant_Upsert/qdrant_logo.svg diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts b/packages/components/credentials/AnthropicApi.credential.ts similarity index 86% rename from packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts rename to packages/components/credentials/AnthropicApi.credential.ts index 607fa625..448128f1 100644 --- a/packages/components/nodes/chatmodels/ChatAnthropic/AnthropicApi.credential.ts +++ b/packages/components/credentials/AnthropicApi.credential.ts @@ -1,4 +1,4 @@ -import { INodeParams, INodeCredential } from '../../../src/Interface' +import { INodeParams, INodeCredential } from '../src/Interface' class AnthropicApi implements INodeCredential { label: string diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts b/packages/components/credentials/AzureOpenAIApi.credential.ts similarity index 96% rename from packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts rename to packages/components/credentials/AzureOpenAIApi.credential.ts index d48e0c88..e880c91c 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureOpenAIApi.credential.ts +++ b/packages/components/credentials/AzureOpenAIApi.credential.ts @@ -1,4 +1,4 @@ -import { INodeParams, INodeCredential } from '../../../src/Interface' +import { INodeParams, INodeCredential } from '../src/Interface' class AzureOpenAIApi implements INodeCredential { label: string diff --git a/packages/components/credentials/CohereApi.credential.ts b/packages/components/credentials/CohereApi.credential.ts new file mode 100644 index 00000000..488644a2 --- /dev/null +++ b/packages/components/credentials/CohereApi.credential.ts @@ -0,0 +1,21 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class CohereApi implements INodeCredential { + label: string + name: string + inputs: INodeParams[] + + constructor() { + this.label = 'Cohere API' + this.name = 'cohereApi' + this.inputs = [ + { + label: 'Cohere Api Key', + name: 'cohereApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: CohereApi } diff --git a/packages/components/credentials/ConfluenceApi.credential.ts b/packages/components/credentials/ConfluenceApi.credential.ts new file mode 100644 index 00000000..75ea1d88 --- /dev/null +++ b/packages/components/credentials/ConfluenceApi.credential.ts @@ -0,0 +1,31 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class ConfluenceApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Confluence API' + this.name = 'confluenceApi' + this.description = + 'Refer to official guide on how to get accessToken on Confluence' + this.inputs = [ + { + label: 'Access Token', + name: 'accessToken', + type: 'password', + placeholder: '' + }, + { + label: 'Username', + name: 'username', + type: 'string', + placeholder: '' + } + ] + } +} + +module.exports = { credClass: ConfluenceApi } diff --git a/packages/components/credentials/DynamodbMemoryApi.credential.ts b/packages/components/credentials/DynamodbMemoryApi.credential.ts new file mode 100644 index 00000000..5bdfce37 --- /dev/null +++ b/packages/components/credentials/DynamodbMemoryApi.credential.ts @@ -0,0 +1,27 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class DynamodbMemoryApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'DynamodbMemory API' + this.name = 'dynamodbMemoryApi' + this.inputs = [ + { + label: 'Access Key', + name: 'accessKey', + type: 'password' + }, + { + label: 'Secret Access Key', + name: 'secretAccessKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: DynamodbMemoryApi } diff --git a/packages/components/credentials/FigmaApi.credential.ts b/packages/components/credentials/FigmaApi.credential.ts new file mode 100644 index 00000000..49638885 --- /dev/null +++ b/packages/components/credentials/FigmaApi.credential.ts @@ -0,0 +1,25 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class FigmaApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Figma API' + this.name = 'figmaApi' + this.description = + 'Refer to official guide on how to get accessToken on Figma' + this.inputs = [ + { + label: 'Access Token', + name: 'accessToken', + type: 'password', + placeholder: '' + } + ] + } +} + +module.exports = { credClass: FigmaApi } diff --git a/packages/components/credentials/GithubApi.credential.ts b/packages/components/credentials/GithubApi.credential.ts new file mode 100644 index 00000000..ffbe4739 --- /dev/null +++ b/packages/components/credentials/GithubApi.credential.ts @@ -0,0 +1,25 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class GithubApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Github API' + this.name = 'githubApi' + this.description = + 'Refer to official guide on how to get accessToken on Github' + this.inputs = [ + { + label: 'Access Token', + name: 'accessToken', + type: 'password', + placeholder: '' + } + ] + } +} + +module.exports = { credClass: GithubApi } diff --git a/packages/components/credentials/HuggingFaceApi.credential.ts b/packages/components/credentials/HuggingFaceApi.credential.ts new file mode 100644 index 00000000..2dae4319 --- /dev/null +++ b/packages/components/credentials/HuggingFaceApi.credential.ts @@ -0,0 +1,21 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class HuggingFaceApi implements INodeCredential { + label: string + name: string + inputs: INodeParams[] + + constructor() { + this.label = 'HuggingFace API' + this.name = 'huggingFaceApi' + this.inputs = [ + { + label: 'HuggingFace Api Key', + name: 'huggingFaceApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: HuggingFaceApi } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts b/packages/components/credentials/MotorheadMemoryApi.credential.ts similarity index 91% rename from packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts rename to packages/components/credentials/MotorheadMemoryApi.credential.ts index 4563cda2..937a9402 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemoryApi.credential.ts +++ b/packages/components/credentials/MotorheadMemoryApi.credential.ts @@ -1,4 +1,4 @@ -import { INodeParams, INodeCredential } from '../../../src/Interface' +import { INodeParams, INodeCredential } from '../src/Interface' class MotorheadMemoryApi implements INodeCredential { label: string diff --git a/packages/components/credentials/NotionApi.credential.ts b/packages/components/credentials/NotionApi.credential.ts new file mode 100644 index 00000000..47d03f3e --- /dev/null +++ b/packages/components/credentials/NotionApi.credential.ts @@ -0,0 +1,24 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class NotionApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Notion API' + this.name = 'notionApi' + this.description = + 'You can find integration token here' + this.inputs = [ + { + label: 'Notion Integration Token', + name: 'notionIntegrationToken', + type: 'password' + } + ] + } +} + +module.exports = { credClass: NotionApi } diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts b/packages/components/credentials/OpenAIApi.credential.ts similarity index 85% rename from packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts rename to packages/components/credentials/OpenAIApi.credential.ts index 96209a35..9aebf049 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/OpenAIApi.credential.ts +++ b/packages/components/credentials/OpenAIApi.credential.ts @@ -1,4 +1,4 @@ -import { INodeParams, INodeCredential } from '../../../src/Interface' +import { INodeParams, INodeCredential } from '../src/Interface' class OpenAIApi implements INodeCredential { label: string diff --git a/packages/components/credentials/OpenAPIAuth.credential.ts b/packages/components/credentials/OpenAPIAuth.credential.ts new file mode 100644 index 00000000..7cc2d318 --- /dev/null +++ b/packages/components/credentials/OpenAPIAuth.credential.ts @@ -0,0 +1,23 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class OpenAPIAuth implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'OpenAPI Auth Token' + this.name = 'openAPIAuth' + this.inputs = [ + { + label: 'OpenAPI Token', + name: 'openAPIToken', + type: 'password', + description: 'Auth Token. For example: Bearer ' + } + ] + } +} + +module.exports = { credClass: OpenAPIAuth } diff --git a/packages/components/credentials/PineconeApi.credential.ts b/packages/components/credentials/PineconeApi.credential.ts new file mode 100644 index 00000000..393bfd46 --- /dev/null +++ b/packages/components/credentials/PineconeApi.credential.ts @@ -0,0 +1,27 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class PineconeApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Pinecone API' + this.name = 'pineconeApi' + this.inputs = [ + { + label: 'Pinecone Api Key', + name: 'pineconeApiKey', + type: 'password' + }, + { + label: 'Pinecone Environment', + name: 'pineconeEnv', + type: 'string' + } + ] + } +} + +module.exports = { credClass: PineconeApi } diff --git a/packages/components/credentials/QdrantApi.credential.ts b/packages/components/credentials/QdrantApi.credential.ts new file mode 100644 index 00000000..1738cc45 --- /dev/null +++ b/packages/components/credentials/QdrantApi.credential.ts @@ -0,0 +1,22 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class QdrantApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Qdrant API' + this.name = 'qdrantApi' + this.inputs = [ + { + label: 'Qdrant API Key', + name: 'qdrantApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: QdrantApi } diff --git a/packages/components/credentials/SerpApi.credential.ts b/packages/components/credentials/SerpApi.credential.ts new file mode 100644 index 00000000..0c18b103 --- /dev/null +++ b/packages/components/credentials/SerpApi.credential.ts @@ -0,0 +1,22 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class SerpApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Serp API' + this.name = 'serpApi' + this.inputs = [ + { + label: 'Serp Api Key', + name: 'serpApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: SerpApi } diff --git a/packages/components/credentials/SerperApi.credential.ts b/packages/components/credentials/SerperApi.credential.ts new file mode 100644 index 00000000..71e61b32 --- /dev/null +++ b/packages/components/credentials/SerperApi.credential.ts @@ -0,0 +1,22 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class SerperApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Serper API' + this.name = 'serperApi' + this.inputs = [ + { + label: 'Serper Api Key', + name: 'serperApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: SerperApi } diff --git a/packages/components/credentials/SupabaseApi.credential.ts b/packages/components/credentials/SupabaseApi.credential.ts new file mode 100644 index 00000000..d485e401 --- /dev/null +++ b/packages/components/credentials/SupabaseApi.credential.ts @@ -0,0 +1,22 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class SupabaseApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Supabase API' + this.name = 'supabaseApi' + this.inputs = [ + { + label: 'Supabase API Key', + name: 'supabaseApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: SupabaseApi } diff --git a/packages/components/credentials/WeaviateApi.credential.ts b/packages/components/credentials/WeaviateApi.credential.ts new file mode 100644 index 00000000..3d5dd5b9 --- /dev/null +++ b/packages/components/credentials/WeaviateApi.credential.ts @@ -0,0 +1,22 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class WeaviateApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Weaviate API' + this.name = 'weaviateApi' + this.inputs = [ + { + label: 'Weaviate API Key', + name: 'weaviateApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: WeaviateApi } diff --git a/packages/components/credentials/ZapierNLAApi.credential.ts b/packages/components/credentials/ZapierNLAApi.credential.ts new file mode 100644 index 00000000..03cb01b8 --- /dev/null +++ b/packages/components/credentials/ZapierNLAApi.credential.ts @@ -0,0 +1,22 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class ZapierNLAApi implements INodeCredential { + label: string + name: string + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Zapier NLA API' + this.name = 'zapierNLAApi' + this.inputs = [ + { + label: 'Zapier NLA Api Key', + name: 'zapierNLAApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: ZapierNLAApi } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts b/packages/components/credentials/ZepMemoryApi.credential.ts similarity index 84% rename from packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts rename to packages/components/credentials/ZepMemoryApi.credential.ts index 5e92ef5d..d886328b 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemoryApi.credential.ts +++ b/packages/components/credentials/ZepMemoryApi.credential.ts @@ -1,4 +1,4 @@ -import { INodeParams, INodeCredential } from '../../../src/Interface' +import { INodeParams, INodeCredential } from '../src/Interface' class ZepMemoryApi implements INodeCredential { label: string @@ -7,7 +7,7 @@ class ZepMemoryApi implements INodeCredential { inputs: INodeParams[] constructor() { - this.label = 'Zep Memory Api' + this.label = 'Zep Memory API' this.name = 'zepMemoryApi' this.description = 'Refer to official guide on how to create API key on Zep' diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index f4d065d9..9faf83dd 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -22,7 +22,7 @@ class OpenAIFunctionAgent_Agents implements INode { this.name = 'openAIFunctionAgent' this.type = 'AgentExecutor' this.category = 'Agents' - this.icon = 'openai.png' + this.icon = 'openai.svg' this.description = `An agent that uses OpenAI's Function Calling functionality to pick the tool and args to call` this.baseClasses = [this.type, ...getBaseClasses(AgentExecutor)] this.inputs = [ diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/openai.png b/packages/components/nodes/agents/OpenAIFunctionAgent/openai.png deleted file mode 100644 index de08a05b28979826c4cc669c4899789763a938a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3991 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 diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/openai.svg b/packages/components/nodes/agents/OpenAIFunctionAgent/openai.svg new file mode 100644 index 00000000..9d3261dd --- /dev/null +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/openai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts index a231e80a..583ca1f4 100644 --- a/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts +++ b/packages/components/nodes/chains/ApiChain/OpenAPIChain.ts @@ -20,7 +20,7 @@ class OpenApiChain_Chains implements INode { this.type = 'openApiChain' this.icon = 'openapi.png' this.category = 'Chains' - this.description = 'Chain to run queries against OpenAPI' + this.description = 'Chain that automatically select and call APIs based only on an OpenAPI spec' this.baseClasses = [this.type, ...getBaseClasses(APIChain)] this.inputs = [ { diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/Azure.svg b/packages/components/nodes/chatmodels/AzureChatOpenAI/Azure.svg index 51eb6253..47ad8c44 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/Azure.svg +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/Azure.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts index d92dd1e0..f192fefd 100644 --- a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { HFInput, HuggingFaceInference } from './core' class ChatHuggingFace_ChatModels implements INode { @@ -10,6 +10,7 @@ class ChatHuggingFace_ChatModels implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,17 +21,28 @@ class ChatHuggingFace_ChatModels implements INode { this.category = 'Chat Models' this.description = 'Wrapper around HuggingFace large language models' this.baseClasses = [this.type, 'BaseChatModel', ...getBaseClasses(HuggingFaceInference)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['huggingFaceApi'] + } this.inputs = [ { label: 'Model', name: 'model', type: 'string', - placeholder: 'gpt2' + description: 'If using own inference endpoint, leave this blank', + placeholder: 'gpt2', + optional: true }, { - label: 'HuggingFace Api Key', - name: 'apiKey', - type: 'password' + label: 'Endpoint', + name: 'endpoint', + type: 'string', + placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2', + description: 'Using your own inference endpoint', + optional: true }, { label: 'Temperature', @@ -71,22 +83,12 @@ class ChatHuggingFace_ChatModels implements INode { description: 'Frequency Penalty parameter may not apply to certain model. Please check available model parameters', optional: true, additionalParams: true - }, - { - label: 'Endpoint', - name: 'endpoint', - type: 'string', - placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2', - description: 'Using your own inference endpoint', - optional: true, - additionalParams: true } ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as string - const apiKey = nodeData.inputs?.apiKey as string const temperature = nodeData.inputs?.temperature as string const maxTokens = nodeData.inputs?.maxTokens as string const topP = nodeData.inputs?.topP as string @@ -94,9 +96,12 @@ class ChatHuggingFace_ChatModels implements INode { const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string const endpoint = nodeData.inputs?.endpoint as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const huggingFaceApiKey = getCredentialParam('huggingFaceApiKey', credentialData, nodeData) + const obj: Partial = { model, - apiKey + apiKey: huggingFaceApiKey } if (temperature) obj.temperature = parseFloat(temperature) diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts index 1339d1fe..13262b5f 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts @@ -17,7 +17,7 @@ class ChatOpenAI_ChatModels implements INode { this.label = 'ChatOpenAI' this.name = 'chatOpenAI' this.type = 'ChatOpenAI' - this.icon = 'openai.png' + this.icon = 'openai.svg' this.category = 'Chat Models' this.description = 'Wrapper around OpenAI large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatOpenAI)] diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/openai.png b/packages/components/nodes/chatmodels/ChatOpenAI/openai.png deleted file mode 100644 index de08a05b28979826c4cc669c4899789763a938a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3991 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 diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/openai.svg b/packages/components/nodes/chatmodels/ChatOpenAI/openai.svg new file mode 100644 index 00000000..9d3261dd --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatOpenAI/openai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/documentloaders/Confluence/Confluence.ts b/packages/components/nodes/documentloaders/Confluence/Confluence.ts index 9a69be14..db992310 100644 --- a/packages/components/nodes/documentloaders/Confluence/Confluence.ts +++ b/packages/components/nodes/documentloaders/Confluence/Confluence.ts @@ -1,6 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { ConfluencePagesLoader, ConfluencePagesLoaderParams } from 'langchain/document_loaders/web/confluence' +import { getCredentialData, getCredentialParam } from '../../../src' class Confluence_DocumentLoaders implements INode { label: string @@ -10,6 +11,7 @@ class Confluence_DocumentLoaders implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,6 +22,12 @@ class Confluence_DocumentLoaders implements INode { this.category = 'Document Loaders' this.description = `Load data from a Confluence Document` this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['confluenceApi'] + } this.inputs = [ { label: 'Text Splitter', @@ -27,18 +35,6 @@ class Confluence_DocumentLoaders implements INode { type: 'TextSplitter', optional: true }, - { - label: 'Username', - name: 'username', - type: 'string', - placeholder: '' - }, - { - label: 'Access Token', - name: 'accessToken', - type: 'password', - placeholder: '' - }, { label: 'Base URL', name: 'baseUrl', @@ -49,7 +45,9 @@ class Confluence_DocumentLoaders implements INode { label: 'Space Key', name: 'spaceKey', type: 'string', - placeholder: '~EXAMPLE362906de5d343d49dcdbae5dEXAMPLE' + placeholder: '~EXAMPLE362906de5d343d49dcdbae5dEXAMPLE', + description: + 'Refer to official guide on how to get Confluence Space Key' }, { label: 'Limit', @@ -68,16 +66,18 @@ class Confluence_DocumentLoaders implements INode { ] } - async init(nodeData: INodeData): Promise { - const username = nodeData.inputs?.username as string - const accessToken = nodeData.inputs?.accessToken as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const spaceKey = nodeData.inputs?.spaceKey as string const baseUrl = nodeData.inputs?.baseUrl as string const limit = nodeData.inputs?.limit as number const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const options: ConfluencePagesLoaderParams = { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const accessToken = getCredentialParam('accessToken', credentialData, nodeData) + const username = getCredentialParam('username', credentialData, nodeData) + + const confluenceOptions: ConfluencePagesLoaderParams = { username, accessToken, baseUrl, @@ -85,7 +85,7 @@ class Confluence_DocumentLoaders implements INode { limit } - const loader = new ConfluencePagesLoader(options) + const loader = new ConfluencePagesLoader(confluenceOptions) let docs = [] diff --git a/packages/components/nodes/documentloaders/Figma/Figma.ts b/packages/components/nodes/documentloaders/Figma/Figma.ts index 388c4ee0..e570490e 100644 --- a/packages/components/nodes/documentloaders/Figma/Figma.ts +++ b/packages/components/nodes/documentloaders/Figma/Figma.ts @@ -1,4 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getCredentialData, getCredentialParam } from '../../../src' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { FigmaFileLoader, FigmaLoaderParams } from 'langchain/document_loaders/web/figma' class Figma_DocumentLoaders implements INode { @@ -9,34 +10,39 @@ class Figma_DocumentLoaders implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { this.label = 'Figma' this.name = 'figma' this.type = 'Document' - this.icon = 'figma.png' + this.icon = 'figma.svg' this.category = 'Document Loaders' this.description = 'Load data from a Figma file' this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['figmaApi'] + } this.inputs = [ - { - label: 'Access Token', - name: 'accessToken', - type: 'password', - placeholder: '' - }, { label: 'File Key', name: 'fileKey', type: 'string', - placeholder: 'key' + placeholder: 'key', + description: + 'The file key can be read from any Figma file URL: https://www.figma.com/file/:key/:title. For example, in https://www.figma.com/file/12345/Website, the file key is 12345' }, { label: 'Node IDs', name: 'nodeIds', type: 'string', - placeholder: '0, 1, 2' + placeholder: '0, 1, 2', + description: + 'A list of Node IDs, seperated by comma. Refer to official guide on how to get Node IDs' }, { label: 'Recursive', @@ -60,18 +66,20 @@ class Figma_DocumentLoaders implements INode { ] } - async init(nodeData: INodeData): Promise { - const accessToken = nodeData.inputs?.accessToken as string - const nodeIds = (nodeData.inputs?.nodeIds as string)?.split(',') || [] + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const nodeIds = (nodeData.inputs?.nodeIds as string)?.trim().split(',') || [] const fileKey = nodeData.inputs?.fileKey as string - const options: FigmaLoaderParams = { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const accessToken = getCredentialParam('accessToken', credentialData, nodeData) + + const figmaOptions: FigmaLoaderParams = { accessToken, nodeIds, fileKey } - const loader = new FigmaFileLoader(options) + const loader = new FigmaFileLoader(figmaOptions) const docs = await loader.load() return docs diff --git a/packages/components/nodes/documentloaders/Figma/figma.png b/packages/components/nodes/documentloaders/Figma/figma.png deleted file mode 100644 index 72372ddff0e6eec27448d24b10c46c987edf317d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176029 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

?1A{3edV5r?d-vCR~^xf@Hu&y&T1 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 V5p|!MT;T}c;(o \ No newline at end of file diff --git a/packages/components/nodes/documentloaders/Github/Github.ts b/packages/components/nodes/documentloaders/Github/Github.ts index bbaad3cb..4a968403 100644 --- a/packages/components/nodes/documentloaders/Github/Github.ts +++ b/packages/components/nodes/documentloaders/Github/Github.ts @@ -1,6 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { GithubRepoLoader, GithubRepoLoaderParams } from 'langchain/document_loaders/web/github' +import { getCredentialData, getCredentialParam } from '../../../src' class Github_DocumentLoaders implements INode { label: string @@ -10,6 +11,7 @@ class Github_DocumentLoaders implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,6 +22,14 @@ class Github_DocumentLoaders implements INode { this.category = 'Document Loaders' this.description = `Load data from a GitHub repository` this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + description: 'Only needed when accessing private repo', + optional: true, + credentialNames: ['githubApi'] + } this.inputs = [ { label: 'Repo Link', @@ -33,13 +43,6 @@ class Github_DocumentLoaders implements INode { type: 'string', default: 'main' }, - { - label: 'Access Token', - name: 'accessToken', - type: 'password', - placeholder: '', - optional: true - }, { label: 'Recursive', name: 'recursive', @@ -62,23 +65,25 @@ class Github_DocumentLoaders implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const repoLink = nodeData.inputs?.repoLink as string const branch = nodeData.inputs?.branch as string const recursive = nodeData.inputs?.recursive as boolean - const accessToken = nodeData.inputs?.accessToken as string const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const metadata = nodeData.inputs?.metadata - const options: GithubRepoLoaderParams = { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const accessToken = getCredentialParam('accessToken', credentialData, nodeData) + + const githubOptions: GithubRepoLoaderParams = { branch, recursive, unknown: 'warn' } - if (accessToken) options.accessToken = accessToken + if (accessToken) githubOptions.accessToken = accessToken - const loader = new GithubRepoLoader(repoLink, options) + const loader = new GithubRepoLoader(repoLink, githubOptions) const docs = textSplitter ? await loader.loadAndSplit(textSplitter) : await loader.load() if (metadata) { diff --git a/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts b/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts index 71e5e507..5fec6083 100644 --- a/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts +++ b/packages/components/nodes/documentloaders/NotionDB/NotionDB.ts @@ -1,6 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' -import { NotionDBLoader, NotionDBLoaderParams } from 'langchain/document_loaders/web/notiondb' +import { NotionAPILoader, NotionAPILoaderOptions } from 'langchain/document_loaders/web/notionapi' +import { getCredentialData, getCredentialParam } from '../../../src' class NotionDB_DocumentLoaders implements INode { label: string @@ -10,6 +11,7 @@ class NotionDB_DocumentLoaders implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -18,8 +20,14 @@ class NotionDB_DocumentLoaders implements INode { this.type = 'Document' this.icon = 'notion.png' this.category = 'Document Loaders' - this.description = 'Load data from Notion Database ID' + this.description = 'Load data from Notion Database (each row is a separate document with all properties as metadata)' this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['notionApi'] + } this.inputs = [ { label: 'Text Splitter', @@ -34,19 +42,6 @@ class NotionDB_DocumentLoaders implements INode { description: 'If your URL looks like - https://www.notion.so/?v=, then is the database ID' }, - { - label: 'Notion Integration Token', - name: 'notionIntegrationToken', - type: 'password', - description: - 'You can find integration token here' - }, - { - label: 'Page Size Limit', - name: 'pageSizeLimit', - type: 'number', - default: 10 - }, { label: 'Metadata', name: 'metadata', @@ -57,19 +52,22 @@ class NotionDB_DocumentLoaders implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const databaseId = nodeData.inputs?.databaseId as string - const notionIntegrationToken = nodeData.inputs?.notionIntegrationToken as string - const pageSizeLimit = nodeData.inputs?.pageSizeLimit as string const metadata = nodeData.inputs?.metadata - const obj: NotionDBLoaderParams = { - pageSizeLimit: pageSizeLimit ? parseInt(pageSizeLimit, 10) : 10, - databaseId, - notionIntegrationToken + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const notionIntegrationToken = getCredentialParam('notionIntegrationToken', credentialData, nodeData) + + const obj: NotionAPILoaderOptions = { + clientOptions: { + auth: notionIntegrationToken + }, + id: databaseId, + type: 'database' } - const loader = new NotionDBLoader(obj) + const loader = new NotionAPILoader(obj) let docs = [] if (textSplitter) { diff --git a/packages/components/nodes/documentloaders/NotionPage/NotionPage.ts b/packages/components/nodes/documentloaders/NotionPage/NotionPage.ts new file mode 100644 index 00000000..57da8aaa --- /dev/null +++ b/packages/components/nodes/documentloaders/NotionPage/NotionPage.ts @@ -0,0 +1,99 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { TextSplitter } from 'langchain/text_splitter' +import { NotionAPILoader, NotionAPILoaderOptions } from 'langchain/document_loaders/web/notionapi' +import { getCredentialData, getCredentialParam } from '../../../src' + +class NotionPage_DocumentLoaders implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'Notion Page' + this.name = 'notionPage' + this.type = 'Document' + this.icon = 'notion.png' + this.category = 'Document Loaders' + this.description = 'Load data from Notion Page (including child pages all as separate documents)' + this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['notionApi'] + } + this.inputs = [ + { + label: 'Text Splitter', + name: 'textSplitter', + type: 'TextSplitter', + optional: true + }, + { + label: 'Notion Page Id', + name: 'pageId', + type: 'string', + description: + 'The last The 32 char hex in the url path. For example: https://www.notion.so/skarard/LangChain-Notion-API-b34ca03f219c4420a6046fc4bdfdf7b4, b34ca03f219c4420a6046fc4bdfdf7b4 is the Page ID' + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const textSplitter = nodeData.inputs?.textSplitter as TextSplitter + const pageId = nodeData.inputs?.pageId as string + const metadata = nodeData.inputs?.metadata + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const notionIntegrationToken = getCredentialParam('notionIntegrationToken', credentialData, nodeData) + + const obj: NotionAPILoaderOptions = { + clientOptions: { + auth: notionIntegrationToken + }, + id: pageId, + type: 'page' + } + const loader = new NotionAPILoader(obj) + + let docs = [] + if (textSplitter) { + docs = await loader.loadAndSplit(textSplitter) + } else { + docs = await loader.load() + } + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + let finaldocs = [] + for (const doc of docs) { + const newdoc = { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + finaldocs.push(newdoc) + } + return finaldocs + } + + return docs + } +} + +module.exports = { nodeClass: NotionPage_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/NotionPage/notion.png b/packages/components/nodes/documentloaders/NotionPage/notion.png new file mode 100644 index 0000000000000000000000000000000000000000..391051679c8cc33e7e52891593147283bf93dcb0 GIT binary patch literal 11406 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 - - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts index 4133539e..15c9f460 100644 --- a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts +++ b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/AzureOpenAIEmbedding.ts @@ -1,6 +1,6 @@ import { AzureOpenAIInput } from 'langchain/chat_models/openai' -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { OpenAIEmbeddings, OpenAIEmbeddingsParams } from 'langchain/embeddings/openai' class AzureOpenAIEmbedding_Embeddings implements INode { @@ -11,6 +11,7 @@ class AzureOpenAIEmbedding_Embeddings implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -21,32 +22,13 @@ class AzureOpenAIEmbedding_Embeddings implements INode { this.category = 'Embeddings' this.description = 'Azure OpenAI API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(OpenAIEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['azureOpenAIApi'] + } this.inputs = [ - { - label: 'Azure OpenAI Api Key', - name: 'azureOpenAIApiKey', - type: 'password' - }, - { - label: 'Azure OpenAI Api Instance Name', - name: 'azureOpenAIApiInstanceName', - type: 'string', - placeholder: 'YOUR-INSTANCE-NAME' - }, - { - label: 'Azure OpenAI Api Deployment Name', - name: 'azureOpenAIApiDeploymentName', - type: 'string', - placeholder: 'YOUR-DEPLOYMENT-NAME' - }, - { - label: 'Azure OpenAI Api Version', - name: 'azureOpenAIApiVersion', - type: 'string', - placeholder: '2023-03-15-preview', - description: - 'Description of Supported API Versions. Please refer examples' - }, { label: 'Batch Size', name: 'batchSize', @@ -65,14 +47,16 @@ class AzureOpenAIEmbedding_Embeddings implements INode { ] } - async init(nodeData: INodeData): Promise { - const azureOpenAIApiKey = nodeData.inputs?.azureOpenAIApiKey as string - const azureOpenAIApiInstanceName = nodeData.inputs?.azureOpenAIApiInstanceName as string - const azureOpenAIApiDeploymentName = nodeData.inputs?.azureOpenAIApiDeploymentName as string - const azureOpenAIApiVersion = nodeData.inputs?.azureOpenAIApiVersion as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const batchSize = nodeData.inputs?.batchSize as string const timeout = nodeData.inputs?.timeout as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const azureOpenAIApiKey = getCredentialParam('azureOpenAIApiKey', credentialData, nodeData) + const azureOpenAIApiInstanceName = getCredentialParam('azureOpenAIApiInstanceName', credentialData, nodeData) + const azureOpenAIApiDeploymentName = getCredentialParam('azureOpenAIApiDeploymentName', credentialData, nodeData) + const azureOpenAIApiVersion = getCredentialParam('azureOpenAIApiVersion', credentialData, nodeData) + const obj: Partial & Partial = { azureOpenAIApiKey, azureOpenAIApiInstanceName, diff --git a/packages/components/nodes/embeddings/CohereEmbedding/CohereEmbedding.ts b/packages/components/nodes/embeddings/CohereEmbedding/CohereEmbedding.ts index 344713a4..914d643d 100644 --- a/packages/components/nodes/embeddings/CohereEmbedding/CohereEmbedding.ts +++ b/packages/components/nodes/embeddings/CohereEmbedding/CohereEmbedding.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { CohereEmbeddings, CohereEmbeddingsParams } from 'langchain/embeddings/cohere' class CohereEmbedding_Embeddings implements INode { @@ -10,6 +10,7 @@ class CohereEmbedding_Embeddings implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class CohereEmbedding_Embeddings implements INode { this.category = 'Embeddings' this.description = 'Cohere API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(CohereEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['cohereApi'] + } this.inputs = [ - { - label: 'Cohere API Key', - name: 'cohereApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -50,12 +52,14 @@ class CohereEmbedding_Embeddings implements INode { ] } - async init(nodeData: INodeData): Promise { - const apiKey = nodeData.inputs?.cohereApiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const modelName = nodeData.inputs?.modelName as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const cohereApiKey = getCredentialParam('cohereApiKey', credentialData, nodeData) + const obj: Partial & { apiKey?: string } = { - apiKey + apiKey: cohereApiKey } if (modelName) obj.modelName = modelName diff --git a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts index d77d623f..bfbb93ed 100644 --- a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts +++ b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { HuggingFaceInferenceEmbeddings, HuggingFaceInferenceEmbeddingsParams } from './core' class HuggingFaceInferenceEmbedding_Embeddings implements INode { @@ -10,6 +10,7 @@ class HuggingFaceInferenceEmbedding_Embeddings implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class HuggingFaceInferenceEmbedding_Embeddings implements INode { this.category = 'Embeddings' this.description = 'HuggingFace Inference API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(HuggingFaceInferenceEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['huggingFaceApi'] + } this.inputs = [ - { - label: 'HuggingFace Api Key', - name: 'apiKey', - type: 'password' - }, { label: 'Model', name: 'modelName', @@ -43,13 +45,15 @@ class HuggingFaceInferenceEmbedding_Embeddings implements INode { ] } - async init(nodeData: INodeData): Promise { - const apiKey = nodeData.inputs?.apiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const modelName = nodeData.inputs?.modelName as string const endpoint = nodeData.inputs?.endpoint as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const huggingFaceApiKey = getCredentialParam('huggingFaceApiKey', credentialData, nodeData) + const obj: Partial = { - apiKey + apiKey: huggingFaceApiKey } if (modelName) obj.model = modelName diff --git a/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding.ts b/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding.ts index 0fd08973..f2f5eb43 100644 --- a/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding.ts +++ b/packages/components/nodes/embeddings/OpenAIEmbedding/OpenAIEmbedding.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { OpenAIEmbeddings, OpenAIEmbeddingsParams } from 'langchain/embeddings/openai' class OpenAIEmbedding_Embeddings implements INode { @@ -10,22 +10,24 @@ class OpenAIEmbedding_Embeddings implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { this.label = 'OpenAI Embeddings' this.name = 'openAIEmbeddings' this.type = 'OpenAIEmbeddings' - this.icon = 'openai.png' + this.icon = 'openai.svg' this.category = 'Embeddings' this.description = 'OpenAI API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(OpenAIEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['openAIApi'] + } this.inputs = [ - { - label: 'OpenAI Api Key', - name: 'openAIApiKey', - type: 'password' - }, { label: 'Strip New Lines', name: 'stripNewLines', @@ -57,13 +59,15 @@ class OpenAIEmbedding_Embeddings implements INode { ] } - async init(nodeData: INodeData): Promise { - const openAIApiKey = nodeData.inputs?.openAIApiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const stripNewLines = nodeData.inputs?.stripNewLines as boolean const batchSize = nodeData.inputs?.batchSize as string const timeout = nodeData.inputs?.timeout as string const basePath = nodeData.inputs?.basepath as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) + const obj: Partial & { openAIApiKey?: string } = { openAIApiKey } diff --git a/packages/components/nodes/embeddings/OpenAIEmbedding/openai.png b/packages/components/nodes/embeddings/OpenAIEmbedding/openai.png deleted file mode 100644 index de08a05b28979826c4cc669c4899789763a938a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3991 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 diff --git a/packages/components/nodes/embeddings/OpenAIEmbedding/openai.svg b/packages/components/nodes/embeddings/OpenAIEmbedding/openai.svg new file mode 100644 index 00000000..9d3261dd --- /dev/null +++ b/packages/components/nodes/embeddings/OpenAIEmbedding/openai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/llms/Azure OpenAI/Azure.svg b/packages/components/nodes/llms/Azure OpenAI/Azure.svg index 51eb6253..47ad8c44 100644 --- a/packages/components/nodes/llms/Azure OpenAI/Azure.svg +++ b/packages/components/nodes/llms/Azure OpenAI/Azure.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts index 130eed33..9729dd41 100644 --- a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts +++ b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { AzureOpenAIInput, OpenAI, OpenAIInput } from 'langchain/llms/openai' class AzureOpenAI_LLMs implements INode { @@ -10,6 +10,7 @@ class AzureOpenAI_LLMs implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class AzureOpenAI_LLMs implements INode { this.category = 'LLMs' this.description = 'Wrapper around Azure OpenAI large language models' this.baseClasses = [this.type, ...getBaseClasses(OpenAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['azureOpenAIApi'] + } this.inputs = [ - { - label: 'Azure OpenAI Api Key', - name: 'azureOpenAIApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -90,26 +92,6 @@ class AzureOpenAI_LLMs implements INode { default: 0.9, optional: true }, - { - label: 'Azure OpenAI Api Instance Name', - name: 'azureOpenAIApiInstanceName', - type: 'string', - placeholder: 'YOUR-INSTANCE-NAME' - }, - { - label: 'Azure OpenAI Api Deployment Name', - name: 'azureOpenAIApiDeploymentName', - type: 'string', - placeholder: 'YOUR-DEPLOYMENT-NAME' - }, - { - label: 'Azure OpenAI Api Version', - name: 'azureOpenAIApiVersion', - type: 'string', - placeholder: '2023-06-01-preview', - description: - 'Description of Supported API Versions. Please refer examples' - }, { label: 'Max Tokens', name: 'maxTokens', @@ -155,13 +137,9 @@ class AzureOpenAI_LLMs implements INode { ] } - async init(nodeData: INodeData): Promise { - const azureOpenAIApiKey = nodeData.inputs?.azureOpenAIApiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const temperature = nodeData.inputs?.temperature as string const modelName = nodeData.inputs?.modelName as string - const azureOpenAIApiInstanceName = nodeData.inputs?.azureOpenAIApiInstanceName as string - const azureOpenAIApiDeploymentName = nodeData.inputs?.azureOpenAIApiDeploymentName as string - const azureOpenAIApiVersion = nodeData.inputs?.azureOpenAIApiVersion as string const maxTokens = nodeData.inputs?.maxTokens as string const topP = nodeData.inputs?.topP as string const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string @@ -170,6 +148,12 @@ class AzureOpenAI_LLMs implements INode { const bestOf = nodeData.inputs?.bestOf as string const streaming = nodeData.inputs?.streaming as boolean + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const azureOpenAIApiKey = getCredentialParam('azureOpenAIApiKey', credentialData, nodeData) + const azureOpenAIApiInstanceName = getCredentialParam('azureOpenAIApiInstanceName', credentialData, nodeData) + const azureOpenAIApiDeploymentName = getCredentialParam('azureOpenAIApiDeploymentName', credentialData, nodeData) + const azureOpenAIApiVersion = getCredentialParam('azureOpenAIApiVersion', credentialData, nodeData) + const obj: Partial & Partial = { temperature: parseFloat(temperature), modelName, diff --git a/packages/components/nodes/llms/Cohere/Cohere.ts b/packages/components/nodes/llms/Cohere/Cohere.ts index 75151571..36bc077a 100644 --- a/packages/components/nodes/llms/Cohere/Cohere.ts +++ b/packages/components/nodes/llms/Cohere/Cohere.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { Cohere, CohereInput } from './core' class Cohere_LLMs implements INode { @@ -10,6 +10,7 @@ class Cohere_LLMs implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,12 +21,13 @@ class Cohere_LLMs implements INode { this.category = 'LLMs' this.description = 'Wrapper around Cohere large language models' this.baseClasses = [this.type, ...getBaseClasses(Cohere)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['cohereApi'] + } this.inputs = [ - { - label: 'Cohere Api Key', - name: 'cohereApiKey', - type: 'password' - }, { label: 'Model Name', name: 'modelName', @@ -75,14 +77,16 @@ class Cohere_LLMs implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const temperature = nodeData.inputs?.temperature as string const modelName = nodeData.inputs?.modelName as string - const apiKey = nodeData.inputs?.cohereApiKey as string const maxTokens = nodeData.inputs?.maxTokens as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const cohereApiKey = getCredentialParam('cohereApiKey', credentialData, nodeData) + const obj: CohereInput = { - apiKey + apiKey: cohereApiKey } if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) diff --git a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts index 92eb46d5..fae1525f 100644 --- a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts +++ b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { HFInput, HuggingFaceInference } from './core' class HuggingFaceInference_LLMs implements INode { @@ -10,6 +10,7 @@ class HuggingFaceInference_LLMs implements INode { category: string description: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,17 +21,28 @@ class HuggingFaceInference_LLMs implements INode { this.category = 'LLMs' this.description = 'Wrapper around HuggingFace large language models' this.baseClasses = [this.type, ...getBaseClasses(HuggingFaceInference)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['huggingFaceApi'] + } this.inputs = [ { label: 'Model', name: 'model', type: 'string', - placeholder: 'gpt2' + description: 'If using own inference endpoint, leave this blank', + placeholder: 'gpt2', + optional: true }, { - label: 'HuggingFace Api Key', - name: 'apiKey', - type: 'password' + label: 'Endpoint', + name: 'endpoint', + type: 'string', + placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2', + description: 'Using your own inference endpoint', + optional: true }, { label: 'Temperature', @@ -71,22 +83,12 @@ class HuggingFaceInference_LLMs implements INode { description: 'Frequency Penalty parameter may not apply to certain model. Please check available model parameters', optional: true, additionalParams: true - }, - { - label: 'Endpoint', - name: 'endpoint', - type: 'string', - placeholder: 'https://xyz.eu-west-1.aws.endpoints.huggingface.cloud/gpt2', - description: 'Using your own inference endpoint', - optional: true, - additionalParams: true } ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as string - const apiKey = nodeData.inputs?.apiKey as string const temperature = nodeData.inputs?.temperature as string const maxTokens = nodeData.inputs?.maxTokens as string const topP = nodeData.inputs?.topP as string @@ -94,9 +96,12 @@ class HuggingFaceInference_LLMs implements INode { const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string const endpoint = nodeData.inputs?.endpoint as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const huggingFaceApiKey = getCredentialParam('huggingFaceApiKey', credentialData, nodeData) + const obj: Partial = { model, - apiKey + apiKey: huggingFaceApiKey } if (temperature) obj.temperature = parseFloat(temperature) diff --git a/packages/components/nodes/llms/OpenAI/OpenAI.ts b/packages/components/nodes/llms/OpenAI/OpenAI.ts index 50aa1c60..884cf63f 100644 --- a/packages/components/nodes/llms/OpenAI/OpenAI.ts +++ b/packages/components/nodes/llms/OpenAI/OpenAI.ts @@ -17,7 +17,7 @@ class OpenAI_LLMs implements INode { this.label = 'OpenAI' this.name = 'openAI' this.type = 'OpenAI' - this.icon = 'openai.png' + this.icon = 'openai.svg' this.category = 'LLMs' this.description = 'Wrapper around OpenAI large language models' this.baseClasses = [this.type, ...getBaseClasses(OpenAI)] diff --git a/packages/components/nodes/llms/OpenAI/openai.png b/packages/components/nodes/llms/OpenAI/openai.png deleted file mode 100644 index de08a05b28979826c4cc669c4899789763a938a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3991 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 diff --git a/packages/components/nodes/llms/OpenAI/openai.svg b/packages/components/nodes/llms/OpenAI/openai.svg new file mode 100644 index 00000000..9d3261dd --- /dev/null +++ b/packages/components/nodes/llms/OpenAI/openai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index 49d15cb6..d9fc75d0 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -1,4 +1,4 @@ -import { ICommonObject, INode, INodeData, INodeParams, getBaseClasses } from '../../../src' +import { ICommonObject, INode, INodeData, INodeParams, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src' import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' import { BufferMemory } from 'langchain/memory' @@ -10,6 +10,7 @@ class DynamoDb_Memory implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -20,6 +21,12 @@ class DynamoDb_Memory implements INode { this.category = 'Memory' this.description = 'Stores the conversation in dynamo db table' this.baseClasses = [this.type, ...getBaseClasses(BufferMemory)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['dynamodbMemoryApi'] + } this.inputs = [ { label: 'Table Name', @@ -31,6 +38,13 @@ class DynamoDb_Memory implements INode { name: 'partitionKey', type: 'string' }, + { + label: 'Region', + name: 'region', + type: 'string', + description: 'The aws region in which table is located', + placeholder: 'us-east-1' + }, { label: 'Session ID', name: 'sessionId', @@ -40,28 +54,12 @@ class DynamoDb_Memory implements INode { additionalParams: true, optional: true }, - { - label: 'Region', - name: 'region', - type: 'string', - description: 'The aws region in which table is located', - placeholder: 'us-east-1' - }, - { - label: 'Access Key', - name: 'accessKey', - type: 'password' - }, - { - label: 'Secret Access Key', - name: 'secretAccessKey', - type: 'password' - }, { label: 'Memory Key', name: 'memoryKey', type: 'string', - default: 'chat_history' + default: 'chat_history', + additionalParams: true } ] } @@ -70,12 +68,14 @@ class DynamoDb_Memory implements INode { const partitionKey = nodeData.inputs?.partitionKey as string const sessionId = nodeData.inputs?.sessionId as string const region = nodeData.inputs?.region as string - const accessKey = nodeData.inputs?.accessKey as string - const secretAccessKey = nodeData.inputs?.secretAccessKey as string const memoryKey = nodeData.inputs?.memoryKey as string const chatId = options.chatId + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const accessKey = getCredentialParam('accessKey', credentialData, nodeData) + const secretAccessKey = getCredentialParam('secretAccessKey', credentialData, nodeData) + const dynamoDb = new DynamoDBChatMessageHistory({ tableName, partitionKey, diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 9caf604c..904bd672 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -20,7 +20,7 @@ class MotorMemory_Memory implements INode { this.type = 'MotorheadMemory' this.icon = 'motorhead.png' this.category = 'Memory' - this.description = 'Remembers previous conversational back and forths directly' + this.description = 'Use Motorhead Memory to store chat conversations' this.baseClasses = [this.type, ...getBaseClasses(MotorheadMemory)] this.credential = { label: 'Connect Credential', @@ -38,12 +38,6 @@ class MotorMemory_Memory implements INode { optional: true, description: 'To use the online version, leave the URL blank. More details at https://getmetal.io.' }, - { - label: 'Memory Key', - name: 'memoryKey', - type: 'string', - default: 'chat_history' - }, { label: 'Session Id', name: 'sessionId', @@ -52,6 +46,13 @@ class MotorMemory_Memory implements INode { default: '', additionalParams: true, optional: true + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history', + additionalParams: true } ] } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 2b4e51c2..37f1cbe2 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -44,13 +44,15 @@ class RedisBackedChatMemory_Memory implements INode { name: 'sessionTTL', type: 'number', description: 'Omit this parameter to make sessions never expire', + additionalParams: true, optional: true }, { label: 'Memory Key', name: 'memoryKey', type: 'string', - default: 'chat_history' + default: 'chat_history', + additionalParams: true } ] } diff --git a/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts b/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts index d6168061..0de7151b 100644 --- a/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts +++ b/packages/components/nodes/tools/OpenAPIToolkit/OpenAPIToolkit.ts @@ -1,8 +1,9 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { OpenApiToolkit } from 'langchain/agents' import { JsonSpec, JsonObject } from 'langchain/tools' import { BaseLanguageModel } from 'langchain/base_language' import { load } from 'js-yaml' +import { getCredentialData, getCredentialParam } from '../../../src' class OpenAPIToolkit_Tools implements INode { label: string @@ -12,6 +13,7 @@ class OpenAPIToolkit_Tools implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -21,12 +23,15 @@ class OpenAPIToolkit_Tools implements INode { this.icon = 'openapi.png' this.category = 'Tools' this.description = 'Load OpenAPI specification' + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + description: 'Only needed if the YAML OpenAPI Spec requires authentication', + optional: true, + credentialNames: ['openAPIAuth'] + } this.inputs = [ - { - label: 'OpenAI API Key', - name: 'openAIApiKey', - type: 'password' - }, { label: 'Language Model', name: 'model', @@ -42,11 +47,13 @@ class OpenAPIToolkit_Tools implements INode { this.baseClasses = [this.type, 'Tool'] } - async init(nodeData: INodeData): Promise { - const openAIApiKey = nodeData.inputs?.openAIApiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as BaseLanguageModel const yamlFileBase64 = nodeData.inputs?.yamlFile as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const openAPIToken = getCredentialParam('openAPIToken', credentialData, nodeData) + const splitDataURI = yamlFileBase64.split(',') splitDataURI.pop() const bf = Buffer.from(splitDataURI.pop() || '', 'base64') @@ -56,10 +63,10 @@ class OpenAPIToolkit_Tools implements INode { throw new Error('Failed to load OpenAPI spec') } - const headers = { - 'Content-Type': 'application/json', - Authorization: `Bearer ${openAIApiKey}` + const headers: ICommonObject = { + 'Content-Type': 'application/json' } + if (openAPIToken) headers.Authorization = `Bearer ${openAPIToken}` const toolkit = new OpenApiToolkit(new JsonSpec(data), model, headers) return toolkit.tools diff --git a/packages/components/nodes/tools/SerpAPI/SerpAPI.ts b/packages/components/nodes/tools/SerpAPI/SerpAPI.ts index 69432408..7e87e9c1 100644 --- a/packages/components/nodes/tools/SerpAPI/SerpAPI.ts +++ b/packages/components/nodes/tools/SerpAPI/SerpAPI.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { SerpAPI } from 'langchain/tools' class SerpAPI_Tools implements INode { @@ -10,6 +10,7 @@ class SerpAPI_Tools implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -19,19 +20,20 @@ class SerpAPI_Tools implements INode { this.icon = 'serp.png' this.category = 'Tools' this.description = 'Wrapper around SerpAPI - a real-time API to access Google search results' - this.inputs = [ - { - label: 'Serp Api Key', - name: 'apiKey', - type: 'password' - } - ] + this.inputs = [] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['serpApi'] + } this.baseClasses = [this.type, ...getBaseClasses(SerpAPI)] } - async init(nodeData: INodeData): Promise { - const apiKey = nodeData.inputs?.apiKey as string - return new SerpAPI(apiKey) + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const serpApiKey = getCredentialParam('serpApiKey', credentialData, nodeData) + return new SerpAPI(serpApiKey) } } diff --git a/packages/components/nodes/tools/Serper/Serper.ts b/packages/components/nodes/tools/Serper/Serper.ts index 65dff57c..495ac8af 100644 --- a/packages/components/nodes/tools/Serper/Serper.ts +++ b/packages/components/nodes/tools/Serper/Serper.ts @@ -1,5 +1,5 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { Serper } from 'langchain/tools' class Serper_Tools implements INode { @@ -10,6 +10,7 @@ class Serper_Tools implements INode { icon: string category: string baseClasses: string[] + credential: INodeParams inputs: INodeParams[] constructor() { @@ -19,19 +20,20 @@ class Serper_Tools implements INode { this.icon = 'serper.png' this.category = 'Tools' this.description = 'Wrapper around Serper.dev - Google Search API' - this.inputs = [ - { - label: 'Serper Api Key', - name: 'apiKey', - type: 'password' - } - ] + this.inputs = [] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['serperApi'] + } this.baseClasses = [this.type, ...getBaseClasses(Serper)] } - async init(nodeData: INodeData): Promise { - const apiKey = nodeData.inputs?.apiKey as string - return new Serper(apiKey) + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const serperApiKey = getCredentialParam('serperApiKey', credentialData, nodeData) + return new Serper(serperApiKey) } } diff --git a/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts b/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts index d16e32e6..06d3dc5a 100644 --- a/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts +++ b/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts @@ -1,6 +1,7 @@ import { ZapierNLAWrapper, ZapierNLAWrapperParams } from 'langchain/tools' -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { ZapierToolKit } from 'langchain/agents' +import { getCredentialData, getCredentialParam } from '../../../src' class ZapierNLA_Tools implements INode { label: string @@ -11,29 +12,31 @@ class ZapierNLA_Tools implements INode { category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams constructor() { this.label = 'Zapier NLA' this.name = 'zapierNLA' this.type = 'ZapierNLA' - this.icon = 'zapier.png' + this.icon = 'zapier.svg' this.category = 'Tools' this.description = "Access to apps and actions on Zapier's platform through a natural language API interface" - this.inputs = [ - { - label: 'Zapier NLA Api Key', - name: 'apiKey', - type: 'password' - } - ] + this.inputs = [] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['zapierNLAApi'] + } this.baseClasses = [this.type, 'Tool'] } - async init(nodeData: INodeData): Promise { - const apiKey = nodeData.inputs?.apiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const zapierNLAApiKey = getCredentialParam('zapierNLAApiKey', credentialData, nodeData) const obj: Partial = { - apiKey + apiKey: zapierNLAApiKey } const zapier = new ZapierNLAWrapper(obj) const toolkit = await ZapierToolKit.fromZapierNLAWrapper(zapier) diff --git a/packages/components/nodes/tools/ZapierNLA/zapier.png b/packages/components/nodes/tools/ZapierNLA/zapier.png deleted file mode 100644 index 769716faaadca77fcd3e7ac06c22ba570d37d2e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 502 zcmeAS@N?(olHy`uVBq!ia0vp^?I6s-1SB`N-i-xPjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucL7@`Yh?3y^w370~qErU=qSVy9;*9)~xKIwD z7RFpp7srr_xVIM-MVTELj$9~>2wc9vtS{`rwEm`s`Qk!ziZ33Xs + + + + + + + \ No newline at end of file diff --git a/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts b/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts index e57da396..3d37d61e 100644 --- a/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts +++ b/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts @@ -1,8 +1,8 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { PineconeClient } from '@pinecone-database/pinecone' import { PineconeLibArgs, PineconeStore } from 'langchain/vectorstores/pinecone' import { Embeddings } from 'langchain/embeddings/base' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' class Pinecone_Existing_VectorStores implements INode { label: string @@ -13,6 +13,7 @@ class Pinecone_Existing_VectorStores implements INode { category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams outputs: INodeOutputsValue[] constructor() { @@ -23,22 +24,18 @@ class Pinecone_Existing_VectorStores implements INode { this.category = 'Vector Stores' this.description = 'Load existing index from Pinecone (i.e: Document has been upserted)' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['pineconeApi'] + } this.inputs = [ { label: 'Embeddings', name: 'embeddings', type: 'Embeddings' }, - { - label: 'Pinecone Api Key', - name: 'pineconeApiKey', - type: 'password' - }, - { - label: 'Pinecone Environment', - name: 'pineconeEnv', - type: 'string' - }, { label: 'Pinecone Index', name: 'pineconeIndex', @@ -83,9 +80,7 @@ class Pinecone_Existing_VectorStores implements INode { ] } - async init(nodeData: INodeData): Promise { - const pineconeApiKey = nodeData.inputs?.pineconeApiKey as string - const pineconeEnv = nodeData.inputs?.pineconeEnv as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const index = nodeData.inputs?.pineconeIndex as string const pineconeNamespace = nodeData.inputs?.pineconeNamespace as string const pineconeMetadataFilter = nodeData.inputs?.pineconeMetadataFilter @@ -94,6 +89,10 @@ class Pinecone_Existing_VectorStores implements INode { const topK = nodeData.inputs?.topK as string const k = topK ? parseInt(topK, 10) : 4 + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData) + const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData) + const client = new PineconeClient() await client.init({ apiKey: pineconeApiKey, diff --git a/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts b/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts index ad1767c2..a3cc9094 100644 --- a/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts +++ b/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts @@ -1,9 +1,9 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { PineconeClient } from '@pinecone-database/pinecone' import { PineconeLibArgs, PineconeStore } from 'langchain/vectorstores/pinecone' import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { flatten } from 'lodash' class PineconeUpsert_VectorStores implements INode { @@ -15,6 +15,7 @@ class PineconeUpsert_VectorStores implements INode { category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams outputs: INodeOutputsValue[] constructor() { @@ -25,6 +26,12 @@ class PineconeUpsert_VectorStores implements INode { this.category = 'Vector Stores' this.description = 'Upsert documents to Pinecone' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['pineconeApi'] + } this.inputs = [ { label: 'Document', @@ -37,16 +44,6 @@ class PineconeUpsert_VectorStores implements INode { name: 'embeddings', type: 'Embeddings' }, - { - label: 'Pinecone Api Key', - name: 'pineconeApiKey', - type: 'password' - }, - { - label: 'Pinecone Environment', - name: 'pineconeEnv', - type: 'string' - }, { label: 'Pinecone Index', name: 'pineconeIndex', @@ -84,9 +81,7 @@ class PineconeUpsert_VectorStores implements INode { ] } - async init(nodeData: INodeData): Promise { - const pineconeApiKey = nodeData.inputs?.pineconeApiKey as string - const pineconeEnv = nodeData.inputs?.pineconeEnv as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const index = nodeData.inputs?.pineconeIndex as string const pineconeNamespace = nodeData.inputs?.pineconeNamespace as string const docs = nodeData.inputs?.document as Document[] @@ -95,6 +90,10 @@ class PineconeUpsert_VectorStores implements INode { const topK = nodeData.inputs?.topK as string const k = topK ? parseInt(topK, 10) : 4 + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData) + const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData) + const client = new PineconeClient() await client.init({ apiKey: pineconeApiKey, diff --git a/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts index f1eef8f9..bb4ac6ed 100644 --- a/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts +++ b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts @@ -1,8 +1,8 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ICommonObject, 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' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' class Qdrant_Existing_VectorStores implements INode { label: string @@ -13,16 +13,25 @@ class Qdrant_Existing_VectorStores implements INode { category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams outputs: INodeOutputsValue[] constructor() { this.label = 'Qdrant Load Existing Index' this.name = 'qdrantExistingIndex' this.type = 'Qdrant' - this.icon = 'qdrant_logo.svg' + this.icon = 'qdrant.png' this.category = 'Vector Stores' this.description = 'Load existing index from Qdrant (i.e., documents have been upserted)' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + description: 'Only needed when using Qdrant cloud hosted', + optional: true, + credentialNames: ['qdrantApi'] + } this.inputs = [ { label: 'Embeddings', @@ -40,12 +49,6 @@ class Qdrant_Existing_VectorStores implements INode { name: 'qdrantCollection', type: 'string' }, - { - label: 'Qdrant API Key', - name: 'qdrantApiKey', - type: 'password', - optional: true - }, { label: 'Qdrant Collection Cofiguration', name: 'qdrantCollectionCofiguration', @@ -77,17 +80,18 @@ class Qdrant_Existing_VectorStores implements INode { ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): 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 credentialData = await getCredentialData(nodeData.credential ?? '', options) + const qdrantApiKey = getCredentialParam('qdrantApiKey', credentialData, nodeData) + const client = new QdrantClient({ url: qdrantServerUrl, apiKey: qdrantApiKey diff --git a/packages/components/nodes/vectorstores/Qdrant_Existing/qdrant.png b/packages/components/nodes/vectorstores/Qdrant_Existing/qdrant.png new file mode 100644 index 0000000000000000000000000000000000000000..ecb2a56d55fbaf6ccdc3640a3d8d3d6587dd701a GIT binary patch literal 11663 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

- )} - - - - Output - - - + } + placement='right-start' + > + +
+ +
+ Notification +
+
+ + + {data.label} + + + {warningMessage && ( + <> +
+ {warningMessage}} placement='top'> + + + + + + )} +
+ {(data.inputAnchors.length > 0 || data.inputParams.length > 0) && ( + <> + + + + Inputs + + + + + )} + {data.inputAnchors.map((inputAnchor, index) => ( + + ))} + {data.inputParams.map((inputParam, index) => ( + + ))} + {data.inputParams.find((param) => param.additionalParams) && ( +
param.additionalParams).length === + data.inputParams.length + data.inputAnchors.length + ? 20 + : 0 + }} + > + +
+ )} + + + + Output + + + - {data.outputAnchors.map((outputAnchor, index) => ( - - ))} -
+ {data.outputAnchors.map((outputAnchor, index) => ( + + ))} + + setShowDialog(false)} > + setShowInfoDialog(false)}> ) } diff --git a/packages/ui/src/views/chatmessage/ChatMessage.js b/packages/ui/src/views/chatmessage/ChatMessage.js index e58fdd00..b89af7bb 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.js +++ b/packages/ui/src/views/chatmessage/ChatMessage.js @@ -28,6 +28,9 @@ import useApi from 'hooks/useApi' // Const import { baseURL, maxScroll } from 'store/constant' +import robotPNG from 'assets/images/robot.png' +import userPNG from 'assets/images/account.png' + export const ChatMessage = ({ open, chatflowid, isDialog }) => { const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -281,21 +284,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => { > {/* Display the correct icon depending on the message type */} {message.type === 'apiMessage' ? ( - AI + AI ) : ( - Me + Me )}
diff --git a/packages/ui/src/views/credentials/AddEditCredentialDialog.js b/packages/ui/src/views/credentials/AddEditCredentialDialog.js index 6a5c9568..65b72a5f 100644 --- a/packages/ui/src/views/credentials/AddEditCredentialDialog.js +++ b/packages/ui/src/views/credentials/AddEditCredentialDialog.js @@ -27,6 +27,7 @@ import useNotifier from 'utils/useNotifier' // const import { baseURL } from 'store/constant' +import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const portalElement = document.getElementById('portal') @@ -87,6 +88,12 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) => // eslint-disable-next-line react-hooks/exhaustive-deps }, [dialogProps]) + useEffect(() => { + if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) + else dispatch({ type: HIDE_CANVAS_DIALOG }) + return () => dispatch({ type: HIDE_CANVAS_DIALOG }) + }, [show, dispatch]) + const addNewCredential = async () => { try { const obj = { diff --git a/packages/ui/src/views/credentials/CredentialListDialog.js b/packages/ui/src/views/credentials/CredentialListDialog.js index 9333db67..e0a3e08d 100644 --- a/packages/ui/src/views/credentials/CredentialListDialog.js +++ b/packages/ui/src/views/credentials/CredentialListDialog.js @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react' import { createPortal } from 'react-dom' -import { useSelector } from 'react-redux' +import { useSelector, useDispatch } from 'react-redux' import PropTypes from 'prop-types' import { List, @@ -20,11 +20,12 @@ import { IconSearch, IconX } from '@tabler/icons' // const import { baseURL } from 'store/constant' +import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelected }) => { const portalElement = document.getElementById('portal') const customization = useSelector((state) => state.customization) - + const dispatch = useDispatch() const theme = useTheme() const [searchValue, setSearchValue] = useState('') const [componentsCredentials, setComponentsCredentials] = useState([]) @@ -43,10 +44,16 @@ const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelecte } useEffect(() => { - if (show && dialogProps.componentsCredentials) { + if (dialogProps.componentsCredentials) { setComponentsCredentials(dialogProps.componentsCredentials) } - }, [show, dialogProps]) + }, [dialogProps]) + + useEffect(() => { + if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) + else dispatch({ type: HIDE_CANVAS_DIALOG }) + return () => dispatch({ type: HIDE_CANVAS_DIALOG }) + }, [show, dispatch]) const component = show ? ( { useEffect(() => { if (getAllComponentsCredentialsApi.data) { setComponentsCredentials(getAllComponentsCredentialsApi.data) + dispatch({ type: SET_COMPONENT_CREDENTIALS, componentsCredentials: getAllComponentsCredentialsApi.data }) } - }, [getAllComponentsCredentialsApi.data]) + }, [getAllComponentsCredentialsApi.data, dispatch]) return ( <> diff --git a/packages/ui/src/views/tools/ToolDialog.js b/packages/ui/src/views/tools/ToolDialog.js index 77ef770d..5e286789 100644 --- a/packages/ui/src/views/tools/ToolDialog.js +++ b/packages/ui/src/views/tools/ToolDialog.js @@ -29,6 +29,7 @@ import useApi from 'hooks/useApi' // utils import useNotifier from 'utils/useNotifier' import { generateRandomGradient } from 'utils/genericHelper' +import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' const exampleAPIFunc = `/* * You can use any libraries imported in Flowise @@ -155,6 +156,12 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) = } } + useEffect(() => { + if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) + else dispatch({ type: HIDE_CANVAS_DIALOG }) + return () => dispatch({ type: HIDE_CANVAS_DIALOG }) + }, [show, dispatch]) + useEffect(() => { if (getSpecificToolApi.data) { setToolId(getSpecificToolApi.data.id) From 5af9b5f63ba14998fe88d89c631a1dcc6e3c0e41 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 28 Jul 2023 17:49:54 +0100 Subject: [PATCH 164/183] =?UTF-8?q?=F0=9F=A5=B3=20flowise-components@1.3.0?= =?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 01738a7c..e41e5add 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.2.17", + "version": "1.3.0", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From d47835d383b7713c364179291d0b0c17514b0cb7 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 28 Jul 2023 17:57:50 +0100 Subject: [PATCH 165/183] =?UTF-8?q?=F0=9F=A5=B3=20flowise-ui@1.3.0=20relea?= =?UTF-8?q?se?= 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 bdb49846..a2eeec6f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.2.15", + "version": "1.3.0", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { From 3ffc9050b24ef71f352976e0320eb14bbc81eb92 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 28 Jul 2023 17:58:13 +0100 Subject: [PATCH 166/183] =?UTF-8?q?=F0=9F=A5=B3=20flowise@1.3.0=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 27c5358e..220d5807 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.16", + "version": "1.3.0", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index b8b5fe37..56e47fe3 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.2.16", + "version": "1.3.0", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 722223a87ef4d5fabdcfe63dea7b6823a846fd44 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 28 Jul 2023 19:16:42 +0100 Subject: [PATCH 167/183] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13da9420..65249147 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Download and Install [NodeJS](https://nodejs.org/en/download) >= 18.15.0 ### Docker Compose 1. Go to `docker` folder at the root of the project -2. Create `.env` file and specify the `PORT` (refer to `.env.example`) +2. Copy `.env.example` file, paste it into the same location, and rename to `.env` 3. `docker-compose up -d` 4. Open [http://localhost:3000](http://localhost:3000) 5. You can bring the containers down by `docker-compose stop` From 2a2fc02c75265499009a8670337c788b73299f7e Mon Sep 17 00:00:00 2001 From: Rafael Reis Date: Fri, 28 Jul 2023 20:18:35 -0300 Subject: [PATCH 168/183] Update CONTRIBUTING.md fixed DATABASE_USER(NAME) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7c2222a7..524db215 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -135,7 +135,7 @@ Flowise support different environment variables to configure your instance. You | DATABASE_PATH | Location where database is saved (When DATABASE_TYPE is sqlite) | String | `your-home-dir/.flowise` | | DATABASE_HOST | Host URL or IP address (When DATABASE_TYPE is not sqlite) | String | | | DATABASE_PORT | Database port (When DATABASE_TYPE is not sqlite) | String | | -| DATABASE_USERNAME | Database username (When DATABASE_TYPE is not sqlite) | String | | +| DATABASE_USER | Database username (When DATABASE_TYPE is not sqlite) | String | | | DATABASE_PASSWORD | Database password (When DATABASE_TYPE is not sqlite) | String | | | DATABASE_NAME | Database name (When DATABASE_TYPE is not sqlite) | String | | | PASSPHRASE | Passphrase used to create encryption key | String | `MYPASSPHRASE` | From 6378e08f279ad8fccc335dbd8f2ea04c6755517a Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 29 Jul 2023 01:46:51 +0100 Subject: [PATCH 169/183] fix default encryption key file path - Error: Error: Error: ENOENT: no such file or directory, open '' --- packages/components/src/utils.ts | 8 ++++---- packages/server/src/index.ts | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 8167a350..bada7cc0 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -355,10 +355,10 @@ export const getEnvironmentVariable = (name: string): string | undefined => { */ const getEncryptionKeyFilePath = (): string => { const checkPaths = [ - path.join(__dirname, '..', '..', 'server', 'encryption.key'), - path.join(__dirname, '..', '..', '..', 'server', 'encryption.key'), - path.join(__dirname, '..', '..', '..', '..', 'server', 'encryption.key'), - path.join(__dirname, '..', '..', '..', '..', '..', 'server', 'encryption.key') + path.join(__dirname, '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', '..', 'encryption.key') ] for (const checkPath of checkPaths) { if (fs.existsSync(checkPath)) { diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index e1ac4724..34357ea9 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -44,7 +44,8 @@ import { transformToCredentialEntity, decryptCredentialData, clearSessionMemory, - replaceInputsWithConfig + replaceInputsWithConfig, + getEncryptionKey } from './utils' import { cloneDeep, omit } from 'lodash' import { getDataSource } from './DataSource' @@ -82,6 +83,9 @@ export class App { // Initialize API keys await getAPIKeys() + + // Initialize encryption key + await getEncryptionKey() }) .catch((err) => { logger.error('❌ [server]: Error during Data Source initialization:', err) From cec026a8895efdf7b58e6bc954cfcaf1eb644aff Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 29 Jul 2023 02:01:18 +0100 Subject: [PATCH 170/183] add check file paths --- packages/components/src/utils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index bada7cc0..363dd026 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -356,9 +356,13 @@ export const getEnvironmentVariable = (name: string): string | undefined => { const getEncryptionKeyFilePath = (): string => { const checkPaths = [ path.join(__dirname, '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', 'server', 'encryption.key'), path.join(__dirname, '..', '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', '..', 'server', 'encryption.key'), path.join(__dirname, '..', '..', '..', '..', 'encryption.key'), - path.join(__dirname, '..', '..', '..', '..', '..', 'encryption.key') + path.join(__dirname, '..', '..', '..', '..', 'server', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', '..', 'encryption.key'), + path.join(__dirname, '..', '..', '..', '..', '..', 'server', 'encryption.key') ] for (const checkPath of checkPaths) { if (fs.existsSync(checkPath)) { From 7511d82c83ad22a842b759710df27c0a0f012c38 Mon Sep 17 00:00:00 2001 From: denchi Date: Sat, 29 Jul 2023 10:10:58 +0100 Subject: [PATCH 171/183] Added new GoogleCustomSearch tool --- .../tools/GoogleSearchAPI/GoogleSearchAPI.ts | 43 ++++++++++++++++++ .../nodes/tools/GoogleSearchAPI/google.png | Bin 0 -> 16690 bytes 2 files changed, 43 insertions(+) create mode 100644 packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts create mode 100644 packages/components/nodes/tools/GoogleSearchAPI/google.png diff --git a/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts b/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts new file mode 100644 index 00000000..4fbf7e36 --- /dev/null +++ b/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts @@ -0,0 +1,43 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { GoogleCustomSearch } from 'langchain/tools' + + +class GoogleSearchAPI_Tools implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'Google Custom Search' + this.name = 'googleSearchAPI' + this.version = 1.0 + this.type = 'GoogleSearchAPI' + this.icon = 'google.png' + this.category = 'Tools' + this.description = 'Wrapper around Google Custom Search API - a real-time API to access Google search results' + this.inputs = [] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['googleSearchApi'] + } + this.baseClasses = [this.type, ...getBaseClasses(GoogleCustomSearch)] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const googleApiKey = getCredentialParam('googleApiKey', credentialData, nodeData) + return new GoogleCustomSearch({ apiKey: googleApiKey }) + } +} + +module.exports = { nodeClass: GoogleSearchAPI_Tools } diff --git a/packages/components/nodes/tools/GoogleSearchAPI/google.png b/packages/components/nodes/tools/GoogleSearchAPI/google.png new file mode 100644 index 0000000000000000000000000000000000000000..c7cd4ca111a53f1085db2a6070c718ad3e17cdd6 GIT binary patch literal 16690 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 IRP3Mp5020|NdN!< literal 0 HcmV?d00001 From 2fcb2ea88edf21ee561ad67df9bd67a48e7f644f Mon Sep 17 00:00:00 2001 From: denchi Date: Sat, 29 Jul 2023 11:20:00 +0100 Subject: [PATCH 172/183] Added google custom search credential --- .../credentials/GoogleSearchApi.credential.ts | 29 +++++++++++++++++++ .../tools/GoogleSearchAPI/GoogleSearchAPI.ts | 15 +++++----- 2 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 packages/components/credentials/GoogleSearchApi.credential.ts diff --git a/packages/components/credentials/GoogleSearchApi.credential.ts b/packages/components/credentials/GoogleSearchApi.credential.ts new file mode 100644 index 00000000..ab6d709e --- /dev/null +++ b/packages/components/credentials/GoogleSearchApi.credential.ts @@ -0,0 +1,29 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class GoogleSearchApi implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Google Custom Search API' + this.name = 'googleCustomSearchApi' + this.version = 1.0 + this.inputs = [ + { + label: 'Google Custom Search Api Key', + name: 'googleCustomSearchApiKey', + type: 'password' + }, + { + label: 'Programmable Search Engine ID', + name: 'googleCustomSearchApiId', + type: 'string' + } + ] + } +} + +module.exports = { credClass: GoogleSearchApi } diff --git a/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts b/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts index 4fbf7e36..b510b37b 100644 --- a/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts +++ b/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts @@ -3,7 +3,7 @@ import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../ import { GoogleCustomSearch } from 'langchain/tools' -class GoogleSearchAPI_Tools implements INode { +class GoogleCustomSearchAPI_Tools implements INode { label: string name: string version: number @@ -17,9 +17,9 @@ class GoogleSearchAPI_Tools implements INode { constructor() { this.label = 'Google Custom Search' - this.name = 'googleSearchAPI' + this.name = 'googleCustomSearch' this.version = 1.0 - this.type = 'GoogleSearchAPI' + this.type = 'GoogleCustomSearchAPI' this.icon = 'google.png' this.category = 'Tools' this.description = 'Wrapper around Google Custom Search API - a real-time API to access Google search results' @@ -28,16 +28,17 @@ class GoogleSearchAPI_Tools implements INode { label: 'Connect Credential', name: 'credential', type: 'credential', - credentialNames: ['googleSearchApi'] + credentialNames: ['googleCustomSearchApi'] } this.baseClasses = [this.type, ...getBaseClasses(GoogleCustomSearch)] } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const credentialData = await getCredentialData(nodeData.credential ?? '', options) - const googleApiKey = getCredentialParam('googleApiKey', credentialData, nodeData) - return new GoogleCustomSearch({ apiKey: googleApiKey }) + const googleApiKey = getCredentialParam('googleCustomSearchApiKey', credentialData, nodeData) + const googleCseId = getCredentialParam('googleCustomSearchApiId', credentialData, nodeData) + return new GoogleCustomSearch({ apiKey: googleApiKey, googleCSEId: googleCseId }) } } -module.exports = { nodeClass: GoogleSearchAPI_Tools } +module.exports = { nodeClass: GoogleCustomSearchAPI_Tools } From 56c5c0dace036035524c1837fc38d095374ad2f4 Mon Sep 17 00:00:00 2001 From: denchi Date: Sat, 29 Jul 2023 12:00:59 +0100 Subject: [PATCH 173/183] Removed new line --- .../components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts b/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts index b510b37b..29ebae8b 100644 --- a/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts +++ b/packages/components/nodes/tools/GoogleSearchAPI/GoogleSearchAPI.ts @@ -2,7 +2,6 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Inter import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { GoogleCustomSearch } from 'langchain/tools' - class GoogleCustomSearchAPI_Tools implements INode { label: string name: string From b2b924a38ab731f53d4b09123f0f46773de316bb Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 29 Jul 2023 12:44:52 +0100 Subject: [PATCH 174/183] minor patch bug fix to 1.3.1 --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 220d5807..b5c7c5fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.0", + "version": "1.3.1", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index 56e47fe3..a8cf1e65 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.0", + "version": "1.3.1", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From 79cae6962b9afed1862f517b3880b3d330f830e7 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 29 Jul 2023 12:45:31 +0100 Subject: [PATCH 175/183] minor patch bug fix to 1.3.1 --- 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 e41e5add..8a3eca6e 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.3.0", + "version": "1.3.1", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From 1832eecde2a4d8cdced394ce04e3fe1ac186a198 Mon Sep 17 00:00:00 2001 From: Seif Date: Sat, 29 Jul 2023 12:35:31 -0700 Subject: [PATCH 176/183] Add credentials file --- .../credentials/VectaraApi.credential.ts | 34 +++++++++++++++++ .../Vectara_Existing/Vectara_Existing.ts | 38 +++++++++---------- .../Vectara_Upsert/Vectara_Upsert.ts | 38 +++++++++---------- 3 files changed, 68 insertions(+), 42 deletions(-) create mode 100644 packages/components/credentials/VectaraApi.credential.ts diff --git a/packages/components/credentials/VectaraApi.credential.ts b/packages/components/credentials/VectaraApi.credential.ts new file mode 100644 index 00000000..96ad29a6 --- /dev/null +++ b/packages/components/credentials/VectaraApi.credential.ts @@ -0,0 +1,34 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class VectaraAPI implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Vectara API' + this.name = 'vectaraApi' + this.version = 1.0 + this.inputs = [ + { + label: 'Vectara Customer ID', + name: 'customerID', + type: 'string' + }, + { + label: 'Vectara Corpus ID', + name: 'corpusID', + type: 'string' + }, + { + label: 'Vectara API Key', + name: 'apiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: VectaraAPI } diff --git a/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts b/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts index fcf0b1aa..725a9e32 100644 --- a/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts +++ b/packages/components/nodes/vectorstores/Vectara_Existing/Vectara_Existing.ts @@ -1,42 +1,36 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { VectaraStore, VectaraLibArgs, VectaraFilter } from 'langchain/vectorstores/vectara' class VectaraExisting_VectorStores implements INode { label: string name: string + version: number description: string type: string icon: string category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams outputs: INodeOutputsValue[] constructor() { this.label = 'Vectara Load Existing Index' this.name = 'vectaraExistingIndex' + this.version = 1.0 this.type = 'Vectara' this.icon = 'vectara.png' this.category = 'Vector Stores' this.description = 'Load existing index from Vectara (i.e: Document has been upserted)' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['vectaraApi'] + } this.inputs = [ - { - label: 'Vectara Customer ID', - name: 'customerID', - type: 'string' - }, - { - label: 'Vectara Corpus ID', - name: 'corpusID', - type: 'string' - }, - { - label: 'Vectara API Key', - name: 'apiKey', - type: 'password' - }, { label: 'Vectara Metadata Filter', name: 'filter', @@ -74,10 +68,12 @@ class VectaraExisting_VectorStores implements INode { } ] } - async init(nodeData: INodeData): Promise { - const customerId = nodeData.inputs?.customerID as number - const corpusId = nodeData.inputs?.corpusID as number - const apiKey = nodeData.inputs?.apiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + const customerId = getCredentialParam('customerID', credentialData, nodeData) + const corpusId = getCredentialParam('corpusID', credentialData, nodeData) + const vectaraMetadatafilter = nodeData.inputs?.filter as VectaraFilter const lambda = nodeData.inputs?.lambda as number const output = nodeData.outputs?.output as string diff --git a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts index ea44f3c9..bc236060 100644 --- a/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts +++ b/packages/components/nodes/vectorstores/Vectara_Upsert/Vectara_Upsert.ts @@ -1,6 +1,6 @@ -import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { Embeddings } from 'langchain/embeddings/base' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { VectaraStore, VectaraLibArgs, VectaraFilter } from 'langchain/vectorstores/vectara' import { Document } from 'langchain/document' import { flatten } from 'lodash' @@ -8,38 +8,32 @@ import { flatten } from 'lodash' class VectaraExisting_VectorStores implements INode { label: string name: string + version: number description: string type: string icon: string category: string baseClasses: string[] inputs: INodeParams[] + credential: INodeParams outputs: INodeOutputsValue[] constructor() { this.label = 'Vectara Upsert Document' this.name = 'vectaraExisting' + this.version = 1.0 this.type = 'Vectara' this.icon = 'vectara.png' this.category = 'Vector Stores' this.description = 'Upsert documents to Vectara' this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['vectaraApi'] + } this.inputs = [ - { - label: 'Vectara Customer ID', - name: 'customerID', - type: 'string' - }, - { - label: 'Vectara Corpus ID', - name: 'corpusID', - type: 'string' - }, - { - label: 'Vectara API Key', - name: 'apiKey', - type: 'password' - }, { label: 'Document', name: 'document', @@ -83,10 +77,12 @@ class VectaraExisting_VectorStores implements INode { } ] } - async init(nodeData: INodeData): Promise { - const customerId = nodeData.inputs?.customerID as number - const corpusId = nodeData.inputs?.corpusID as number - const apiKey = nodeData.inputs?.apiKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + const customerId = getCredentialParam('customerID', credentialData, nodeData) + const corpusId = getCredentialParam('corpusID', credentialData, nodeData) + const docs = nodeData.inputs?.document as Document[] const embeddings = {} as Embeddings const vectaraMetadatafilter = nodeData.inputs?.filter as VectaraFilter From 6cedcc352a036b9d88ff092f667890693e41fc36 Mon Sep 17 00:00:00 2001 From: denchi Date: Sat, 29 Jul 2023 21:31:10 +0100 Subject: [PATCH 177/183] Added credentials description --- packages/components/credentials/GoogleSearchApi.credential.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/components/credentials/GoogleSearchApi.credential.ts b/packages/components/credentials/GoogleSearchApi.credential.ts index ab6d709e..cb82b25a 100644 --- a/packages/components/credentials/GoogleSearchApi.credential.ts +++ b/packages/components/credentials/GoogleSearchApi.credential.ts @@ -11,6 +11,8 @@ class GoogleSearchApi implements INodeCredential { this.label = 'Google Custom Search API' this.name = 'googleCustomSearchApi' this.version = 1.0 + this.description = + 'Please refer to the Google Cloud Console for instructions on how to create an API key, and visit the Search Engine Creation page to learn how to generate your Search Engine ID.' this.inputs = [ { label: 'Google Custom Search Api Key', From 27660b8ed30a89582ca8bd95d9e19aacb95cf89b Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 30 Jul 2023 14:03:15 +0100 Subject: [PATCH 178/183] add fix --- packages/components/nodes/chains/LLMChain/LLMChain.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index cf9e4bc9..c9e49165 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -116,16 +116,7 @@ const runPrediction = async ( */ const promptValues = handleEscapeCharacters(promptValuesRaw, true) - if (inputVariables.length === 1) { - if (isStreaming) { - const handler = new CustomChainHandler(socketIO, socketIOClientId) - const res = await chain.run(input, [loggerHandler, handler]) - return res - } else { - const res = await chain.run(input, [loggerHandler]) - return res - } - } else if (inputVariables.length > 1) { + if (inputVariables.length > 0) { let seen: string[] = [] for (const variable of inputVariables) { From b5c9345b2b83e5cf3d1d295787628ec71a45a899 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 30 Jul 2023 17:41:30 +0100 Subject: [PATCH 179/183] add clearSessionMemory fix --- .../nodes/memory/DynamoDb/DynamoDb.ts | 4 ++++ .../memory/MotorheadMemory/MotorheadMemory.ts | 4 ++++ .../RedisBackedChatMemory.ts | 6 +++++- .../nodes/memory/ZepMemory/ZepMemory.ts | 4 ++++ packages/server/src/index.ts | 2 +- packages/server/src/utils/index.ts | 21 +++++++++++++++---- 6 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index a1c0fb1f..6926912f 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -72,7 +72,11 @@ class DynamoDb_Memory implements INode { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { const dynamodbMemory = await initalizeDynamoDB(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing DynamoDb memory session ${sessionId ? sessionId : chatId}`) await dynamodbMemory.clear() + options.logger.info(`Successfully cleared DynamoDb memory session ${sessionId ? sessionId : chatId}`) } } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 790c753c..bb23de9d 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -65,7 +65,11 @@ class MotorMemory_Memory implements INode { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { const motorhead = await initalizeMotorhead(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Motorhead memory session ${sessionId ? sessionId : chatId}`) await motorhead.clear() + options.logger.info(`Successfully cleared Motorhead memory session ${sessionId ? sessionId : chatId}`) } } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index e9c963dd..7b3e2cb5 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -65,7 +65,11 @@ class RedisBackedChatMemory_Memory implements INode { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { const redis = initalizeRedis(nodeData, options) - redis.clear() + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Redis memory session ${sessionId ? sessionId : chatId}`) + await redis.clear() + options.logger.info(`Successfully cleared Redis memory session ${sessionId ? sessionId : chatId}`) } } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index e64740b8..3faa29b9 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -141,7 +141,11 @@ class ZepMemory_Memory implements INode { async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { const zep = await initalizeZep(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Zep memory session ${sessionId ? sessionId : chatId}`) await zep.clear() + options.logger.info(`Successfully cleared Zep memory session ${sessionId ? sessionId : chatId}`) } } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 34357ea9..545c75a7 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -405,7 +405,7 @@ export class App { const nodes = parsedFlowData.nodes let chatId = await getChatId(chatflow.id) if (!chatId) chatId = chatflow.id - clearSessionMemory(nodes, this.nodesPool.componentNodes, chatId, req.query.sessionId as string) + clearSessionMemory(nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, req.query.sessionId as string) const results = await this.AppDataSource.getRepository(ChatMessage).delete({ chatflowid: req.params.id }) return res.json(results) }) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index f92cc6dc..2a68be47 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -18,7 +18,7 @@ import { IComponentCredentials, ICredentialReqBody } from '../Interface' -import { cloneDeep, get, omit, merge } from 'lodash' +import { cloneDeep, get, omit, merge, isEqual } from 'lodash' import { ICommonObject, getInputVariables, IDatabaseEntity, handleEscapeCharacters } from 'flowise-components' import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' import { lib, PBKDF2, AES, enc } from 'crypto-js' @@ -182,7 +182,7 @@ export const getEndingNode = (nodeDependencies: INodeDependencies, graph: INodeD /** * Build langchain from start to end - * @param {string} startingNodeId + * @param {string[]} startingNodeIds * @param {IReactFlowNode[]} reactFlowNodes * @param {INodeDirectedGraph} graph * @param {IDepthQueue} depthQueue @@ -286,12 +286,14 @@ export const buildLangchain = async ( * @param {IReactFlowNode[]} reactFlowNodes * @param {IComponentNodes} componentNodes * @param {string} chatId + * @param {DataSource} appDataSource * @param {string} sessionId */ export const clearSessionMemory = async ( reactFlowNodes: IReactFlowNode[], componentNodes: IComponentNodes, chatId: string, + appDataSource: DataSource, sessionId?: string ) => { for (const node of reactFlowNodes) { @@ -300,7 +302,8 @@ export const clearSessionMemory = async ( const nodeModule = await import(nodeInstanceFilePath) const newNodeInstance = new nodeModule.nodeClass() if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId - if (newNodeInstance.clearSessionMemory) await newNodeInstance?.clearSessionMemory(node.data, { chatId }) + if (newNodeInstance.clearSessionMemory) + await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) } } @@ -495,7 +498,7 @@ export const isSameOverrideConfig = ( Object.keys(existingOverrideConfig).length && newOverrideConfig && Object.keys(newOverrideConfig).length && - JSON.stringify(existingOverrideConfig) === JSON.stringify(newOverrideConfig) + isEqual(existingOverrideConfig, newOverrideConfig) ) { return true } @@ -660,8 +663,18 @@ export const mapMimeTypeToInputField = (mimeType: string) => { return 'jsonFile' case 'text/csv': return 'csvFile' + case 'application/json-lines': + case 'application/jsonl': + case 'text/jsonl': + return 'jsonlinesFile' case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': return 'docxFile' + case 'application/vnd.yaml': + case 'application/x-yaml': + case 'text/vnd.yaml': + case 'text/x-yaml': + case 'text/yaml': + return 'yamlFile' default: return '' } From 8237ce1d2a7dcfc63a81a4046af56cc0331e7fa5 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 30 Jul 2023 18:28:23 +0100 Subject: [PATCH 180/183] minor patch bug fix to 1.3.2 --- 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 8a3eca6e..85267d38 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.3.1", + "version": "1.3.2", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From ae3d7e2e2d41f63874e9c70ebff9ffec4dbe92d7 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 30 Jul 2023 18:28:53 +0100 Subject: [PATCH 181/183] minor patch bug fix to 1.3.2 --- package.json | 2 +- packages/server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b5c7c5fe..650671f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.1", + "version": "1.3.2", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/server/package.json b/packages/server/package.json index a8cf1e65..0aac4ac5 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.1", + "version": "1.3.2", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", From d2313a30f9fa4feebef7dd3d835bb0d7b116595f Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 30 Jul 2023 23:52:41 +0100 Subject: [PATCH 182/183] fix python API --- .../ui/src/views/chatflows/APICodeDialog.js | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/views/chatflows/APICodeDialog.js b/packages/ui/src/views/chatflows/APICodeDialog.js index 994f7552..6ca605cd 100644 --- a/packages/ui/src/views/chatflows/APICodeDialog.js +++ b/packages/ui/src/views/chatflows/APICodeDialog.js @@ -97,7 +97,7 @@ const getConfigExamplesForPython = (configData, bodyType) => { if (config.type === 'string') exampleVal = `"example"` else if (config.type === 'boolean') exampleVal = `true` else if (config.type === 'number') exampleVal = `1` - else if (config.name === 'files') exampleVal = `('example${config.type}', open('example${config.type}', 'rb'))` + else if (config.name === 'files') continue finalStr += bodyType === 'json' ? `\n "${config.name}": ${exampleVal},` : `\n "${config.name}": ${exampleVal},` if (i === loop - 1 && bodyType !== 'json') finalStr += `\n "question": "Hey, how are you?"\n` } @@ -289,15 +289,20 @@ query({"question": "Hey, how are you?"}).then((response) => { const getConfigCodeWithFormData = (codeLang, configData) => { if (codeLang === 'Python') { + configData = unshiftFiles(configData) + const fileType = configData[0].type return `import requests API_URL = "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}" # use form data to upload files -form_data = {${getConfigExamplesForPython(configData, 'formData')}} +form_data = { + "files": ${`('example${fileType}', open('example${fileType}', 'rb'))`} +} +body_data = {${getConfigExamplesForPython(configData, 'formData')}} def query(form_data): - response = requests.post(API_URL, files=form_data) + response = requests.post(API_URL, files=form_data, data=body_data) return response.json() output = query(form_data) @@ -334,16 +339,21 @@ query(formData).then((response) => { const getConfigCodeWithFormDataWithAuth = (codeLang, configData) => { if (codeLang === 'Python') { + configData = unshiftFiles(configData) + const fileType = configData[0].type return `import requests API_URL = "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}" headers = {"Authorization": "Bearer ${selectedApiKey?.apiKey}"} # use form data to upload files -form_data = {${getConfigExamplesForPython(configData, 'formData')}} +form_data = { + "files": ${`('example${fileType}', open('example${fileType}', 'rb'))`} +} +body_data = {${getConfigExamplesForPython(configData, 'formData')}} def query(form_data): - response = requests.post(API_URL, headers=headers, files=form_data) + response = requests.post(API_URL, headers=headers, files=form_data, data=body_data) return response.json() output = query(form_data) From 36d3709371a43d405764d1cb0b1869fc5fd8686d Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 31 Jul 2023 00:08:37 +0100 Subject: [PATCH 183/183] minor fix when promptValues is undefined --- packages/components/nodes/chains/LLMChain/LLMChain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index c9e49165..5088b34d 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -116,7 +116,7 @@ const runPrediction = async ( */ const promptValues = handleEscapeCharacters(promptValuesRaw, true) - if (inputVariables.length > 0) { + if (promptValues && inputVariables.length > 0) { let seen: string[] = [] for (const variable of inputVariables) {