diff --git a/README.md b/README.md index 5023a1b7..b4b7fe76 100644 --- a/README.md +++ b/README.md @@ -128,19 +128,21 @@ 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. +Flowise support different environment variables to configure your instance. You can specify the following variables in the `.env` file inside `packages/server` folder. Read [more](https://docs.flowiseai.com/environment-variables) -| 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` | +| 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 onto terminal/console | Boolean | +| LOG_PATH | Location where log files are stored | String | `your-path/Flowise/packages/server` | +| LOG_LEVEL | Different log levels for loggers to be saved | 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` | +| TOOL_FUNCTION_BUILTIN_DEP | NodeJS built-in modules to be used for Tool Function | String | | +| TOOL_FUNCTION_EXTERNAL_DEP | External modules to be used for Tool Function | String | | You can also specify the env variables when using `npx`. For example: diff --git a/docker/.env.example b/docker/.env.example index 262e08a6..42dc696d 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -1,8 +1,11 @@ PORT=3000 +DATABASE_PATH=/root/.flowise +APIKEY_PATH=/root/.flowise +LOG_PATH=/root/.flowise/logs # FLOWISE_USERNAME=user # FLOWISE_PASSWORD=1234 # DEBUG=true -# DATABASE_PATH=/your_database_path/.flowise -# APIKEY_PATH=/your_api_key_path/.flowise -# LOG_PATH=/your_log_path/logs +# LOG_LEVEL=debug (error | warn | info | verbose | debug) # EXECUTION_MODE=child or main +# TOOL_FUNCTION_BUILTIN_DEP=crypto,fs +# TOOL_FUNCTION_EXTERNAL_DEP=moment,lodash \ No newline at end of file diff --git a/docker/README.md b/docker/README.md index 7f991a04..d0ce5a7a 100644 --- a/docker/README.md +++ b/docker/README.md @@ -9,7 +9,7 @@ Starts Flowise from [DockerHub Image](https://hub.docker.com/repository/docker/f 3. Open [http://localhost:3000](http://localhost:3000) 4. You can bring the containers down by `docker-compose stop` -## With Authrorization +## 🔒 Authrorization 1. Create `.env` file and specify the `PORT`, `FLOWISE_USERNAME`, and `FLOWISE_PASSWORD` (refer to `.env.example`) 2. Pass `FLOWISE_USERNAME` and `FLOWISE_PASSWORD` to the `docker-compose.yml` file: @@ -22,3 +22,27 @@ Starts Flowise from [DockerHub Image](https://hub.docker.com/repository/docker/f 3. `docker-compose up -d` 4. Open [http://localhost:3000](http://localhost:3000) 5. You can bring the containers down by `docker-compose stop` + +## 🌱 Env Variables + +If you like to persist your data (flows, logs, apikeys), set these variables in the `.env` file inside `docker` folder: + +- DATABASE_PATH=/root/.flowise +- APIKEY_PATH=/root/.flowise +- LOG_PATH=/root/.flowise/logs + +Flowise also support different environment variables to configure your instance. Read [more](https://docs.flowiseai.com/environment-variables) + +| 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 onto terminal/console | Boolean | +| LOG_PATH | Location where log files are stored | String | `your-path/Flowise/packages/server` | +| LOG_LEVEL | Different log levels for loggers to be saved | 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` | +| TOOL_FUNCTION_BUILTIN_DEP | NodeJS built-in modules to be used for Tool Function | String | | +| TOOL_FUNCTION_EXTERNAL_DEP | External modules to be used for Tool Function | String | | diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 3077c43d..7f616fa5 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -8,11 +8,12 @@ services: - PORT=${PORT} - FLOWISE_USERNAME=${FLOWISE_USERNAME} - FLOWISE_PASSWORD=${FLOWISE_PASSWORD} + - DEBUG=${DEBUG} - DATABASE_PATH=${DATABASE_PATH} - APIKEY_PATH=${APIKEY_PATH} - LOG_PATH=${LOG_PATH} + - LOG_LEVEL=${LOG_LEVEL} - EXECUTION_MODE=${EXECUTION_MODE} - - DEBUG=${DEBUG} ports: - '${PORT}:${PORT}' volumes: diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 24b40d48..eeebe1ec 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -7,14 +7,14 @@ import { BaseChatMemory, BufferMemory, ChatMessageHistory } from 'langchain/memo 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. +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. {context} Question: {question} Helpful Answer:` -const qa_template = `Use the following pieces of context to answer the question at the end, in its original language. +const qa_template = `Use the following pieces of context to answer the question at the end. {context} 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 8a160223..ece0e655 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 5ca1310d..77db7dac 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/nodes/textsplitters/HtmlToMarkdownTextSplitter/HtmlToMarkdownTextSplitter.ts b/packages/components/nodes/textsplitters/HtmlToMarkdownTextSplitter/HtmlToMarkdownTextSplitter.ts new file mode 100644 index 00000000..161cb89e --- /dev/null +++ b/packages/components/nodes/textsplitters/HtmlToMarkdownTextSplitter/HtmlToMarkdownTextSplitter.ts @@ -0,0 +1,70 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { MarkdownTextSplitter, MarkdownTextSplitterParams } from 'langchain/text_splitter' +import { NodeHtmlMarkdown } from 'node-html-markdown' + +class HtmlToMarkdownTextSplitter_TextSplitters implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'HtmlToMarkdown Text Splitter' + this.name = 'htmlToMarkdownTextSplitter' + this.type = 'HtmlToMarkdownTextSplitter' + this.icon = 'htmlToMarkdownTextSplitter.svg' + this.category = 'Text Splitters' + this.description = `Converts Html to Markdown and then split your content into documents based on the Markdown headers` + this.baseClasses = [this.type, ...getBaseClasses(HtmlToMarkdownTextSplitter)] + this.inputs = [ + { + label: 'Chunk Size', + name: 'chunkSize', + type: 'number', + default: 1000, + optional: true + }, + { + label: 'Chunk Overlap', + name: 'chunkOverlap', + type: 'number', + optional: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const chunkSize = nodeData.inputs?.chunkSize as string + const chunkOverlap = nodeData.inputs?.chunkOverlap as string + + const obj = {} as MarkdownTextSplitterParams + + if (chunkSize) obj.chunkSize = parseInt(chunkSize, 10) + if (chunkOverlap) obj.chunkOverlap = parseInt(chunkOverlap, 10) + + const splitter = new HtmlToMarkdownTextSplitter(obj) + + return splitter + } +} +class HtmlToMarkdownTextSplitter extends MarkdownTextSplitter implements MarkdownTextSplitterParams { + constructor(fields?: Partial) { + { + super(fields) + } + } + splitText(text: string): Promise { + return new Promise((resolve) => { + const markdown = NodeHtmlMarkdown.translate(text) + super.splitText(markdown).then((result) => { + resolve(result) + }) + }) + } +} +module.exports = { nodeClass: HtmlToMarkdownTextSplitter_TextSplitters } diff --git a/packages/components/nodes/textsplitters/HtmlToMarkdownTextSplitter/htmlToMarkdownTextSplitter.svg b/packages/components/nodes/textsplitters/HtmlToMarkdownTextSplitter/htmlToMarkdownTextSplitter.svg new file mode 100644 index 00000000..f7d45d60 --- /dev/null +++ b/packages/components/nodes/textsplitters/HtmlToMarkdownTextSplitter/htmlToMarkdownTextSplitter.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts b/packages/components/nodes/tools/BraveSearchAPI/BraveSearchAPI.ts new file mode 100644 index 00000000..b84aaccd --- /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.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.svg b/packages/components/nodes/tools/BraveSearchAPI/brave.svg new file mode 100644 index 00000000..0c0c0e86 --- /dev/null +++ b/packages/components/nodes/tools/BraveSearchAPI/brave.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/tools/CustomTool/core.ts b/packages/components/nodes/tools/CustomTool/core.ts index 0d3d7bcd..2aa06b54 100644 --- a/packages/components/nodes/tools/CustomTool/core.ts +++ b/packages/components/nodes/tools/CustomTool/core.ts @@ -51,25 +51,37 @@ export class DynamicStructuredTool< } } + const defaultAllowBuiltInDep = [ + 'assert', + 'buffer', + 'crypto', + 'events', + 'http', + 'https', + 'net', + 'path', + 'querystring', + 'timers', + 'tls', + 'url', + 'zlib' + ] + + const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP + ? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(',')) + : defaultAllowBuiltInDep + const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : [] + const deps = availableDependencies.concat(externalDeps) + const options = { console: 'inherit', sandbox, require: { - external: false as boolean | { modules: string[] }, - builtin: ['*'] + external: { modules: deps }, + builtin: builtinDeps } } as any - const external = JSON.stringify(availableDependencies) - if (external) { - const deps = JSON.parse(external) - if (deps && deps.length) { - options.require.external = { - modules: deps - } - } - } - const vm = new NodeVM(options) const response = await vm.run(`module.exports = async function() {${this.code}}()`, __dirname) diff --git a/packages/components/package.json b/packages/components/package.json index 3459a372..8cc02235 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -18,7 +18,7 @@ "dependencies": { "@aws-sdk/client-dynamodb": "^3.360.0", "@dqbd/tiktoken": "^1.0.7", - "@getzep/zep-js": "^0.3.1", + "@getzep/zep-js": "^0.4.1", "@huggingface/inference": "^2.6.1", "@opensearch-project/opensearch": "^1.2.0", "@pinecone-database/pinecone": "^0.0.12", @@ -42,6 +42,7 @@ "mammoth": "^1.5.1", "moment": "^2.29.3", "node-fetch": "^2.6.11", + "node-html-markdown": "^1.3.0", "pdf-parse": "^1.1.1", "pdfjs-dist": "^3.7.107", "playwright": "^1.35.0", 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/components/src/utils.ts b/packages/components/src/utils.ts index 027ec8db..78486643 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -320,7 +320,7 @@ export async function xmlScrape(currentURL: string, limit: number): Promise { try { diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 74c4d07e..aece8b6f 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' @@ -59,9 +60,6 @@ export class App { constructor() { this.app = express() - - // Add the expressRequestLogger middleware to log all requests - this.app.use(expressRequestLogger) } async initDatabase() { @@ -92,6 +90,9 @@ export class App { // Allow access from * this.app.use(cors()) + // Add the expressRequestLogger middleware to log all requests + this.app.use(expressRequestLogger) + if (process.env.FLOWISE_USERNAME && process.env.FLOWISE_PASSWORD) { const username = process.env.FLOWISE_USERNAME const password = process.env.FLOWISE_PASSWORD @@ -306,8 +307,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) }) @@ -326,6 +332,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) }) @@ -668,7 +687,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/config.ts b/packages/server/src/utils/config.ts index 8540b3b1..b5f5884e 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: process.env.LOG_LEVEL ?? 'info', filename: 'server.log', diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index eb901f4d..203cee4f 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -273,6 +273,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 diff --git a/packages/server/src/utils/logger.ts b/packages/server/src/utils/logger.ts index 5d7ffedf..57d5f64a 100644 --- a/packages/server/src/utils/logger.ts +++ b/packages/server/src/utils/logger.ts @@ -57,43 +57,46 @@ const logger = createLogger({ * 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(), errors({ stack: true })), - 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: config.logging.express.level ?? 'debug' - }) - ] - }) + const unwantedLogURLs = ['/api/v1/node-icon/'] + if (req.url.includes('/api/v1/') && !unwantedLogURLs.some((url) => req.url.includes(url))) { + const fileLogger = createLogger({ + format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.json(), errors({ stack: true })), + 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: config.logging.express.level ?? 'debug' + }) + ] + }) - const getRequestEmoji = (method: string) => { - const requetsEmojis: Record = { - GET: '⬇️', - POST: '⬆️', - PUT: '🖊', - DELETE: '❌' + const getRequestEmoji = (method: string) => { + const requetsEmojis: Record = { + GET: '⬇️', + POST: '⬆️', + PUT: '🖊', + DELETE: '❌' + } + + return requetsEmojis[method] || '?' } - 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}`) + 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() diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index e7744764..5a7f45b7 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -61,7 +61,7 @@ export const Input = ({ inputParam, value, onChange, disabled = false, showDialo Input.propTypes = { inputParam: PropTypes.object, - value: PropTypes.string, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), onChange: PropTypes.func, disabled: PropTypes.bool, showDialog: PropTypes.bool, diff --git a/packages/ui/src/views/chatflows/APICodeDialog.js b/packages/ui/src/views/chatflows/APICodeDialog.js index 5e32c1d4..716ac3ff 100644 --- a/packages/ui/src/views/chatflows/APICodeDialog.js +++ b/packages/ui/src/views/chatflows/APICodeDialog.js @@ -190,7 +190,10 @@ output = query({ "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}", { method: "POST", - body: data + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(data) } ); const result = await response.json(); @@ -204,7 +207,8 @@ query({"question": "Hey, how are you?"}).then((response) => { } else if (codeLang === 'cURL') { return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\ -X POST \\ - -d '{"question": "Hey, how are you?"}'` + -d '{"question": "Hey, how are you?"}' \\ + -H "Content-Type: application/json"` } return '' } @@ -229,9 +233,12 @@ output = query({ const response = await fetch( "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}", { - headers: { Authorization: "Bearer ${selectedApiKey?.apiKey}" }, + headers: { + Authorization: "Bearer ${selectedApiKey?.apiKey}", + "Content-Type": "application/json" + }, method: "POST", - body: data + body: JSON.stringify(data) } ); const result = await response.json(); @@ -246,6 +253,7 @@ query({"question": "Hey, how are you?"}).then((response) => { return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\ -X POST \\ -d '{"question": "Hey, how are you?"}' \\ + -H "Content-Type: application/json" \\ -H "Authorization: Bearer ${selectedApiKey?.apiKey}"` } return '' @@ -316,7 +324,8 @@ query(formData).then((response) => { ` } else if (codeLang === 'cURL') { return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\ - -X POST \\${getConfigExamplesForCurl(configData, 'formData')}` + -X POST \\${getConfigExamplesForCurl(configData, 'formData')} \\ + -H "Content-Type: multipart/form-data"` } return '' } @@ -363,6 +372,7 @@ query(formData).then((response) => { } else if (codeLang === 'cURL') { return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\ -X POST \\${getConfigExamplesForCurl(configData, 'formData')} \\ + -H "Content-Type: multipart/form-data" \\ -H "Authorization: Bearer ${selectedApiKey?.apiKey}"` } return '' @@ -392,7 +402,10 @@ output = query({ "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}", { method: "POST", - body: data + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(data) } ); const result = await response.json(); @@ -410,7 +423,8 @@ query({ } else if (codeLang === 'cURL') { return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\ -X POST \\ - -d '{"question": "Hey, how are you?", "overrideConfig": {${getConfigExamplesForCurl(configData, 'json')}}'` + -d '{"question": "Hey, how are you?", "overrideConfig": {${getConfigExamplesForCurl(configData, 'json')}}' \\ + -H "Content-Type: application/json"` } return '' } @@ -439,9 +453,12 @@ output = query({ const response = await fetch( "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}", { - headers: { Authorization: "Bearer ${selectedApiKey?.apiKey}" }, + headers: { + Authorization: "Bearer ${selectedApiKey?.apiKey}", + "Content-Type": "application/json" + }, method: "POST", - body: data + body: JSON.stringify(data) } ); const result = await response.json(); @@ -460,6 +477,7 @@ query({ return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\ -X POST \\ -d '{"question": "Hey, how are you?", "overrideConfig": {${getConfigExamplesForCurl(configData, 'json')}}' \\ + -H "Content-Type: application/json" \\ -H "Authorization: Bearer ${selectedApiKey?.apiKey}"` } return ''