From 292b90ccb10b298b3519f3631e48d0d989e66806 Mon Sep 17 00:00:00 2001 From: Avin Date: Thu, 23 Nov 2023 11:37:49 +0530 Subject: [PATCH 1/9] refactor: move api key utilities to seperate file --- packages/server/src/index.ts | 8 +- packages/server/src/utils/apiKey.ts | 147 +++++++++++++++++++++++++ packages/server/src/utils/index.ts | 164 ++-------------------------- 3 files changed, 159 insertions(+), 160 deletions(-) create mode 100644 packages/server/src/utils/apiKey.ts diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 2f7d31e2..7e10c817 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -31,18 +31,11 @@ import { constructGraphs, resolveVariables, isStartNodeDependOnInput, - getAPIKeys, - addAPIKey, - updateAPIKey, - deleteAPIKey, - compareKeys, mapMimeTypeToInputField, findAvailableConfigs, isSameOverrideConfig, - replaceAllAPIKeys, isFlowValidForStream, databaseEntities, - getApiKey, transformToCredentialEntity, decryptCredentialData, clearAllSessionMemory, @@ -64,6 +57,7 @@ import { ChatflowPool } from './ChatflowPool' import { CachePool } from './CachePool' import { ICommonObject, INodeOptionsValue } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' +import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, replaceAllAPIKeys, updateAPIKey } from './utils/apiKey' export class App { app: express.Application diff --git a/packages/server/src/utils/apiKey.ts b/packages/server/src/utils/apiKey.ts new file mode 100644 index 00000000..08a9ecd3 --- /dev/null +++ b/packages/server/src/utils/apiKey.ts @@ -0,0 +1,147 @@ +import { randomBytes, scryptSync, timingSafeEqual } from 'crypto' +import { ICommonObject } from 'flowise-components' +import moment from 'moment' +import fs from 'fs' +import path from 'path' +import logger from './logger' + +/** + * Returns the api key path + * @returns {string} + */ +export const getAPIKeyPath = (): string => { + return process.env.APIKEY_PATH ? path.join(process.env.APIKEY_PATH, 'api.json') : path.join(__dirname, '..', '..', 'api.json') +} + +/** + * Generate the api key + * @returns {string} + */ +export const generateAPIKey = (): string => { + const buffer = randomBytes(32) + return buffer.toString('base64') +} + +/** + * Generate the secret key + * @param {string} apiKey + * @returns {string} + */ +export const generateSecretHash = (apiKey: string): string => { + const salt = randomBytes(8).toString('hex') + const buffer = scryptSync(apiKey, salt, 64) as Buffer + return `${buffer.toString('hex')}.${salt}` +} + +/** + * Verify valid keys + * @param {string} storedKey + * @param {string} suppliedKey + * @returns {boolean} + */ +export const compareKeys = (storedKey: string, suppliedKey: string): boolean => { + const [hashedPassword, salt] = storedKey.split('.') + const buffer = scryptSync(suppliedKey, salt, 64) as Buffer + return timingSafeEqual(Buffer.from(hashedPassword, 'hex'), buffer) +} + +/** + * Get API keys + * @returns {Promise} + */ +export const getAPIKeys = async (): Promise => { + try { + const content = await fs.promises.readFile(getAPIKeyPath(), 'utf8') + return JSON.parse(content) + } catch (error) { + const keyName = 'DefaultKey' + const apiKey = generateAPIKey() + const apiSecret = generateSecretHash(apiKey) + const content = [ + { + keyName, + apiKey, + apiSecret, + createdAt: moment().format('DD-MMM-YY'), + id: randomBytes(16).toString('hex') + } + ] + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') + return content + } +} + +/** + * Add new API key + * @param {string} keyName + * @returns {Promise} + */ +export const addAPIKey = async (keyName: string): Promise => { + const existingAPIKeys = await getAPIKeys() + const apiKey = generateAPIKey() + const apiSecret = generateSecretHash(apiKey) + const content = [ + ...existingAPIKeys, + { + keyName, + apiKey, + apiSecret, + createdAt: moment().format('DD-MMM-YY'), + id: randomBytes(16).toString('hex') + } + ] + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') + 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 + * @param {string} newKeyName + * @returns {Promise} + */ +export const updateAPIKey = async (keyIdToUpdate: string, newKeyName: string): Promise => { + const existingAPIKeys = await getAPIKeys() + const keyIndex = existingAPIKeys.findIndex((key) => key.id === keyIdToUpdate) + if (keyIndex < 0) return [] + existingAPIKeys[keyIndex].keyName = newKeyName + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(existingAPIKeys), 'utf8') + return existingAPIKeys +} + +/** + * Delete API key + * @param {string} keyIdToDelete + * @returns {Promise} + */ +export const deleteAPIKey = async (keyIdToDelete: string): Promise => { + const existingAPIKeys = await getAPIKeys() + const result = existingAPIKeys.filter((key) => key.id !== keyIdToDelete) + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(result), 'utf8') + return result +} + +/** + * Replace all api keys + * @param {ICommonObject[]} content + * @returns {Promise} + */ +export const replaceAllAPIKeys = async (content: ICommonObject[]): Promise => { + try { + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') + } catch (error) { + logger.error(error) + } +} diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 0c6f2362..bc36c78f 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -1,33 +1,32 @@ import path from 'path' import fs from 'fs' -import moment from 'moment' import logger from './logger' import { + IComponentCredentials, IComponentNodes, + ICredentialDataDecrypted, + ICredentialReqBody, IDepthQueue, IExploredNode, + INodeData, INodeDependencies, INodeDirectedGraph, INodeQueue, + IOverrideConfig, IReactFlowEdge, IReactFlowNode, - IVariableDict, - INodeData, - IOverrideConfig, - ICredentialDataDecrypted, - IComponentCredentials, - ICredentialReqBody + IVariableDict } from '../Interface' import { cloneDeep, get, isEqual } from 'lodash' import { - ICommonObject, + convertChatHistoryToText, getInputVariables, - IDatabaseEntity, handleEscapeCharacters, - IMessage, - convertChatHistoryToText + ICommonObject, + IDatabaseEntity, + IMessage } from 'flowise-components' -import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' +import { randomBytes } from 'crypto' import { AES, enc } from 'crypto-js' import { ChatFlow } from '../database/entities/ChatFlow' @@ -574,147 +573,6 @@ export const isSameOverrideConfig = ( return false } -/** - * Returns the api key path - * @returns {string} - */ -export const getAPIKeyPath = (): string => { - return process.env.APIKEY_PATH ? path.join(process.env.APIKEY_PATH, 'api.json') : path.join(__dirname, '..', '..', 'api.json') -} - -/** - * Generate the api key - * @returns {string} - */ -export const generateAPIKey = (): string => { - const buffer = randomBytes(32) - return buffer.toString('base64') -} - -/** - * Generate the secret key - * @param {string} apiKey - * @returns {string} - */ -export const generateSecretHash = (apiKey: string): string => { - const salt = randomBytes(8).toString('hex') - const buffer = scryptSync(apiKey, salt, 64) as Buffer - return `${buffer.toString('hex')}.${salt}` -} - -/** - * Verify valid keys - * @param {string} storedKey - * @param {string} suppliedKey - * @returns {boolean} - */ -export const compareKeys = (storedKey: string, suppliedKey: string): boolean => { - const [hashedPassword, salt] = storedKey.split('.') - const buffer = scryptSync(suppliedKey, salt, 64) as Buffer - return timingSafeEqual(Buffer.from(hashedPassword, 'hex'), buffer) -} - -/** - * Get API keys - * @returns {Promise} - */ -export const getAPIKeys = async (): Promise => { - try { - const content = await fs.promises.readFile(getAPIKeyPath(), 'utf8') - return JSON.parse(content) - } catch (error) { - const keyName = 'DefaultKey' - const apiKey = generateAPIKey() - const apiSecret = generateSecretHash(apiKey) - const content = [ - { - keyName, - apiKey, - apiSecret, - createdAt: moment().format('DD-MMM-YY'), - id: randomBytes(16).toString('hex') - } - ] - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') - return content - } -} - -/** - * Add new API key - * @param {string} keyName - * @returns {Promise} - */ -export const addAPIKey = async (keyName: string): Promise => { - const existingAPIKeys = await getAPIKeys() - const apiKey = generateAPIKey() - const apiSecret = generateSecretHash(apiKey) - const content = [ - ...existingAPIKeys, - { - keyName, - apiKey, - apiSecret, - createdAt: moment().format('DD-MMM-YY'), - id: randomBytes(16).toString('hex') - } - ] - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') - 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 - * @param {string} newKeyName - * @returns {Promise} - */ -export const updateAPIKey = async (keyIdToUpdate: string, newKeyName: string): Promise => { - const existingAPIKeys = await getAPIKeys() - const keyIndex = existingAPIKeys.findIndex((key) => key.id === keyIdToUpdate) - if (keyIndex < 0) return [] - existingAPIKeys[keyIndex].keyName = newKeyName - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(existingAPIKeys), 'utf8') - return existingAPIKeys -} - -/** - * Delete API key - * @param {string} keyIdToDelete - * @returns {Promise} - */ -export const deleteAPIKey = async (keyIdToDelete: string): Promise => { - const existingAPIKeys = await getAPIKeys() - const result = existingAPIKeys.filter((key) => key.id !== keyIdToDelete) - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(result), 'utf8') - return result -} - -/** - * Replace all api keys - * @param {ICommonObject[]} content - * @returns {Promise} - */ -export const replaceAllAPIKeys = async (content: ICommonObject[]): Promise => { - try { - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') - } catch (error) { - logger.error(error) - } -} - /** * Map MimeType to InputField * @param {string} mimeType From 1cd465a08ead29087932def8d640e71363106ae1 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 28 Nov 2023 16:23:11 +0000 Subject: [PATCH 2/9] update openai assistant version --- .../nodes/agents/OpenAIAssistant/OpenAIAssistant.ts | 2 +- .../marketplaces/chatflows/OpenAI Assistant.json | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index 2dc5a95d..35299057 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -23,7 +23,7 @@ class OpenAIAssistant_Agents implements INode { constructor() { this.label = 'OpenAI Assistant' this.name = 'openAIAssistant' - this.version = 1.0 + this.version = 2.0 this.type = 'OpenAIAssistant' this.category = 'Agents' this.icon = 'openai.png' diff --git a/packages/server/marketplaces/chatflows/OpenAI Assistant.json b/packages/server/marketplaces/chatflows/OpenAI Assistant.json index 2f9a860c..ba4c6134 100644 --- a/packages/server/marketplaces/chatflows/OpenAI Assistant.json +++ b/packages/server/marketplaces/chatflows/OpenAI Assistant.json @@ -14,7 +14,7 @@ "data": { "id": "openAIAssistant_0", "label": "OpenAI Assistant", - "version": 1, + "version": 2, "name": "openAIAssistant", "type": "OpenAIAssistant", "baseClasses": ["OpenAIAssistant"], @@ -27,6 +27,15 @@ "type": "asyncOptions", "loadMethod": "listAssistants", "id": "openAIAssistant_0-input-selectedAssistant-asyncOptions" + }, + { + "label": "Disable File Download", + "name": "disableFileDownload", + "type": "boolean", + "description": "Messages can contain text, images, or files. In some cases, you may want to prevent others from downloading the files. Learn more from OpenAI File Annotation docs", + "optional": true, + "additionalParams": true, + "id": "openAIAssistant_0-input-disableFileDownload-boolean" } ], "inputAnchors": [ From 0ee47c2ccf87716cd954b1f09dc429e568636fdb Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 28 Nov 2023 16:58:16 +0000 Subject: [PATCH 3/9] remove import/export database --- packages/server/src/Interface.ts | 6 -- packages/server/src/index.ts | 54 +--------- packages/ui/src/api/database.js | 9 -- .../MainLayout/Header/ProfileSection/index.js | 100 +----------------- 4 files changed, 3 insertions(+), 166 deletions(-) delete mode 100644 packages/ui/src/api/database.js diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index c562b4ee..d5890ab6 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -190,12 +190,6 @@ export interface IOverrideConfig { type: string } -export interface IDatabaseExport { - chatmessages: IChatMessage[] - chatflows: IChatFlow[] - apikeys: ICommonObject[] -} - export type ICredentialDataDecrypted = ICommonObject // Plain credential object sent to server diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 50169c3c..1ebd9312 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -17,7 +17,6 @@ import { IReactFlowNode, IReactFlowObject, INodeData, - IDatabaseExport, ICredentialReturnResponse, chatType, IChatMessage, @@ -57,7 +56,7 @@ import { ChatflowPool } from './ChatflowPool' import { CachePool } from './CachePool' import { ICommonObject, INodeOptionsValue } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' -import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, replaceAllAPIKeys, updateAPIKey } from './utils/apiKey' +import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey' export class App { app: express.Application @@ -1019,57 +1018,6 @@ export class App { } }) - // ---------------------------------------- - // Export Load Chatflow & ChatMessage & Apikeys - // ---------------------------------------- - - this.app.get('/api/v1/database/export', async (req: Request, res: Response) => { - const chatmessages = await this.AppDataSource.getRepository(ChatMessage).find() - const chatflows = await this.AppDataSource.getRepository(ChatFlow).find() - const apikeys = await getAPIKeys() - const result: IDatabaseExport = { - chatmessages, - chatflows, - apikeys - } - return res.json(result) - }) - - this.app.post('/api/v1/database/load', async (req: Request, res: Response) => { - const databaseItems: IDatabaseExport = req.body - - await this.AppDataSource.getRepository(ChatFlow).delete({}) - await this.AppDataSource.getRepository(ChatMessage).delete({}) - - let error = '' - - // Get a new query runner instance - const queryRunner = this.AppDataSource.createQueryRunner() - - // Start a new transaction - await queryRunner.startTransaction() - - try { - const chatflows: ChatFlow[] = databaseItems.chatflows - const chatmessages: ChatMessage[] = databaseItems.chatmessages - - await queryRunner.manager.insert(ChatFlow, chatflows) - await queryRunner.manager.insert(ChatMessage, chatmessages) - - await queryRunner.commitTransaction() - } catch (err: any) { - error = err?.message ?? 'Error loading database' - await queryRunner.rollbackTransaction() - } finally { - await queryRunner.release() - } - - await replaceAllAPIKeys(databaseItems.apikeys) - - if (error) return res.status(500).send(error) - return res.status(201).send('OK') - }) - // ---------------------------------------- // Upsert // ---------------------------------------- diff --git a/packages/ui/src/api/database.js b/packages/ui/src/api/database.js deleted file mode 100644 index f36fb72c..00000000 --- a/packages/ui/src/api/database.js +++ /dev/null @@ -1,9 +0,0 @@ -import client from './client' - -const getExportDatabase = () => client.get('/database/export') -const createLoadDatabase = (body) => client.post('/database/load', body) - -export default { - getExportDatabase, - createLoadDatabase -} diff --git a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js index c10b3289..c2de41b2 100644 --- a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js +++ b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js @@ -1,7 +1,6 @@ import { useState, useRef, useEffect } from 'react' import PropTypes from 'prop-types' -import { useSelector, useDispatch } from 'react-redux' -import { useNavigate } from 'react-router-dom' +import { useSelector } from 'react-redux' // material-ui import { useTheme } from '@mui/material/styles' @@ -26,16 +25,10 @@ import PerfectScrollbar from 'react-perfect-scrollbar' // project imports import MainCard from 'ui-component/cards/MainCard' import Transitions from 'ui-component/extended/Transitions' -import { BackdropLoader } from 'ui-component/loading/BackdropLoader' import AboutDialog from 'ui-component/dialog/AboutDialog' // assets -import { IconLogout, IconSettings, IconFileExport, IconFileDownload, IconInfoCircle } from '@tabler/icons' - -// API -import databaseApi from 'api/database' - -import { SET_MENU } from 'store/actions' +import { IconLogout, IconSettings } from '@tabler/icons' import './index.css' @@ -43,17 +36,13 @@ import './index.css' const ProfileSection = ({ username, handleLogout }) => { const theme = useTheme() - const dispatch = useDispatch() - const navigate = useNavigate() const customization = useSelector((state) => state.customization) const [open, setOpen] = useState(false) - const [loading, setLoading] = useState(false) const [aboutDialogOpen, setAboutDialogOpen] = useState(false) const anchorRef = useRef(null) - const uploadRef = useRef(null) const handleClose = (event) => { if (anchorRef.current && anchorRef.current.contains(event.target)) { @@ -66,56 +55,6 @@ const ProfileSection = ({ username, handleLogout }) => { setOpen((prevOpen) => !prevOpen) } - const handleExportDB = async () => { - setOpen(false) - try { - const response = await databaseApi.getExportDatabase() - const exportItems = response.data - let dataStr = JSON.stringify(exportItems, null, 2) - let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr) - - let exportFileDefaultName = `DB.json` - - let linkElement = document.createElement('a') - linkElement.setAttribute('href', dataUri) - linkElement.setAttribute('download', exportFileDefaultName) - linkElement.click() - } 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 = async (evt) => { - if (!evt?.target?.result) { - return - } - const { result } = evt.target - - if (result.includes(`"chatmessages":[`) && result.includes(`"chatflows":[`) && result.includes(`"apikeys":[`)) { - dispatch({ type: SET_MENU, opened: false }) - setLoading(true) - - try { - await databaseApi.createLoadDatabase(JSON.parse(result)) - setLoading(false) - navigate('/', { replace: true }) - navigate(0) - } catch (e) { - console.error(e) - setLoading(false) - } - } else { - alert('Incorrect Flowise Database Format') - } - } - reader.readAsText(file) - } - const prevOpen = useRef(open) useEffect(() => { if (prevOpen.current === true && open === false) { @@ -196,39 +135,6 @@ const ProfileSection = ({ username, handleLogout }) => { } }} > - { - setOpen(false) - uploadRef.current.click() - }} - > - - - - Load Database} /> - - - - - - Export Database} /> - - { - setOpen(false) - setAboutDialogOpen(true) - }} - > - - - - About Flowise} /> - {localStorage.getItem('username') && localStorage.getItem('password') && ( { )} - handleFileUpload(e)} /> - setAboutDialogOpen(false)} /> ) From 147526ff72802a00132d41eedcc2ef9ee0d1c7c7 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 28 Nov 2023 17:00:10 +0000 Subject: [PATCH 4/9] remove zapier NLA as its not supported anymore --- .../nodes/tools/ZapierNLA/ZapierNLA.ts | 52 ------------------- .../nodes/tools/ZapierNLA/zapier.svg | 8 --- 2 files changed, 60 deletions(-) delete mode 100644 packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts delete mode 100644 packages/components/nodes/tools/ZapierNLA/zapier.svg diff --git a/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts b/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts deleted file mode 100644 index 31ac989b..00000000 --- a/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ZapierNLAWrapper, ZapierNLAWrapperParams } from 'langchain/tools' -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 - name: string - version: number - description: string - type: string - icon: string - category: string - badge: string - baseClasses: string[] - inputs: INodeParams[] - credential: INodeParams - - constructor() { - this.label = 'Zapier NLA' - this.name = 'zapierNLA' - this.version = 1.0 - this.type = 'ZapierNLA' - 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.badge = 'DEPRECATING' - this.inputs = [] - this.credential = { - label: 'Connect Credential', - name: 'credential', - type: 'credential', - credentialNames: ['zapierNLAApi'] - } - this.baseClasses = [this.type, 'Tool'] - } - - 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: zapierNLAApiKey - } - const zapier = new ZapierNLAWrapper(obj) - const toolkit = await ZapierToolKit.fromZapierNLAWrapper(zapier) - - return toolkit.tools - } -} - -module.exports = { nodeClass: ZapierNLA_Tools } diff --git a/packages/components/nodes/tools/ZapierNLA/zapier.svg b/packages/components/nodes/tools/ZapierNLA/zapier.svg deleted file mode 100644 index 6ed35f29..00000000 --- a/packages/components/nodes/tools/ZapierNLA/zapier.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file From 464a23e0f49b63553faca6222e42c733b78fbc2a Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 29 Nov 2023 00:25:08 +0000 Subject: [PATCH 5/9] update agent model --- .../ConversationalRetrievalAgent.ts | 4 ++-- .../agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts | 4 ++-- .../marketplaces/chatflows/API Agent OpenAI.json | 6 +++--- .../chatflows/Conversational Retrieval Agent.json | 10 +++++----- .../server/marketplaces/chatflows/OpenAI Agent.json | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts index 7b71cb5f..c6354e18 100644 --- a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts +++ b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts @@ -21,7 +21,7 @@ class ConversationalRetrievalAgent_Agents implements INode { constructor() { this.label = 'Conversational Retrieval Agent' this.name = 'conversationalRetrievalAgent' - this.version = 2.0 + this.version = 3.0 this.type = 'AgentExecutor' this.category = 'Agents' this.icon = 'agent.svg' @@ -42,7 +42,7 @@ class ConversationalRetrievalAgent_Agents implements INode { { label: 'OpenAI/Azure Chat Model', name: 'model', - type: 'ChatOpenAI | AzureChatOpenAI' + type: 'BaseChatModel' }, { label: 'System Message', diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index ce6f576f..25a540e8 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -20,7 +20,7 @@ class OpenAIFunctionAgent_Agents implements INode { constructor() { this.label = 'OpenAI Function Agent' this.name = 'openAIFunctionAgent' - this.version = 2.0 + this.version = 3.0 this.type = 'AgentExecutor' this.category = 'Agents' this.icon = 'openai.png' @@ -41,7 +41,7 @@ class OpenAIFunctionAgent_Agents implements INode { { label: 'OpenAI/Azure Chat Model', name: 'model', - type: 'ChatOpenAI | AzureChatOpenAI' + type: 'BaseChatModel' }, { label: 'System Message', diff --git a/packages/server/marketplaces/chatflows/API Agent OpenAI.json b/packages/server/marketplaces/chatflows/API Agent OpenAI.json index 4950a6a6..1a07e60d 100644 --- a/packages/server/marketplaces/chatflows/API Agent OpenAI.json +++ b/packages/server/marketplaces/chatflows/API Agent OpenAI.json @@ -334,7 +334,7 @@ "id": "openAIFunctionAgent_0", "label": "OpenAI Function Agent", "name": "openAIFunctionAgent", - "version": 2, + "version": 3, "type": "AgentExecutor", "baseClasses": ["AgentExecutor", "BaseChain"], "category": "Agents", @@ -367,8 +367,8 @@ { "label": "OpenAI/Azure Chat Model", "name": "model", - "type": "ChatOpenAI | AzureChatOpenAI", - "id": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI" + "type": "BaseChatModel", + "id": "openAIFunctionAgent_0-input-model-BaseChatModel" } ], "inputs": { diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json index 800ae300..0e9e41bd 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json @@ -98,7 +98,7 @@ "data": { "id": "conversationalRetrievalAgent_0", "label": "Conversational Retrieval Agent", - "version": 2, + "version": 3, "name": "conversationalRetrievalAgent", "type": "AgentExecutor", "baseClasses": ["AgentExecutor", "BaseChain", "Runnable"], @@ -132,8 +132,8 @@ { "label": "OpenAI/Azure Chat Model", "name": "model", - "type": "ChatOpenAI | AzureChatOpenAI", - "id": "conversationalRetrievalAgent_0-input-model-ChatOpenAI | AzureChatOpenAI" + "type": "BaseChatModel", + "id": "conversationalRetrievalAgent_0-input-model-BaseChatModel" } ], "inputs": { @@ -642,9 +642,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalAgent_0", - "targetHandle": "conversationalRetrievalAgent_0-input-model-ChatOpenAI", + "targetHandle": "conversationalRetrievalAgent_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalAgent_0-conversationalRetrievalAgent_0-input-model-ChatOpenAI", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalAgent_0-conversationalRetrievalAgent_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/OpenAI Agent.json b/packages/server/marketplaces/chatflows/OpenAI Agent.json index bc27a9fe..931bae0c 100644 --- a/packages/server/marketplaces/chatflows/OpenAI Agent.json +++ b/packages/server/marketplaces/chatflows/OpenAI Agent.json @@ -206,7 +206,7 @@ "id": "openAIFunctionAgent_0", "label": "OpenAI Function Agent", "name": "openAIFunctionAgent", - "version": 2, + "version": 3, "type": "AgentExecutor", "baseClasses": ["AgentExecutor", "BaseChain"], "category": "Agents", @@ -239,8 +239,8 @@ { "label": "OpenAI/Azure Chat Model", "name": "model", - "type": "ChatOpenAI | AzureChatOpenAI", - "id": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI" + "type": "BaseChatModel", + "id": "openAIFunctionAgent_0-input-model-BaseChatModel" } ], "inputs": { From 6a7726956c76c27da01413520c534339243ad229 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 29 Nov 2023 00:52:48 +0000 Subject: [PATCH 6/9] update marketplace templates --- packages/server/marketplaces/chatflows/API Agent OpenAI.json | 4 ++-- packages/server/marketplaces/chatflows/OpenAI Agent.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/marketplaces/chatflows/API Agent OpenAI.json b/packages/server/marketplaces/chatflows/API Agent OpenAI.json index 26c1961e..002c08c1 100644 --- a/packages/server/marketplaces/chatflows/API Agent OpenAI.json +++ b/packages/server/marketplaces/chatflows/API Agent OpenAI.json @@ -672,9 +672,9 @@ "source": "chatOpenAI_2", "sourceHandle": "chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "target": "openAIFunctionAgent_0", - "targetHandle": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_2-chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "id": "chatOpenAI_2-chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/OpenAI Agent.json b/packages/server/marketplaces/chatflows/OpenAI Agent.json index 0cb3c3b3..17e59236 100644 --- a/packages/server/marketplaces/chatflows/OpenAI Agent.json +++ b/packages/server/marketplaces/chatflows/OpenAI Agent.json @@ -488,9 +488,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "target": "openAIFunctionAgent_0", - "targetHandle": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-BaseChatModel", "data": { "label": "" } From 5a5097e9970be804642c714d99c99ababc1b82d9 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 29 Nov 2023 13:42:17 +0000 Subject: [PATCH 7/9] update flowise-components to 1.4.5 --- 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 bb074392..bea9a7a0 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.4.4", + "version": "1.4.5", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From 4b5f7028e3320d72d92d60deb55ebee44b56deb8 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 30 Nov 2023 16:01:16 +0000 Subject: [PATCH 8/9] fix chat history --- .../agents/OpenAIAssistant/OpenAIAssistant.ts | 70 ++++++++++--------- .../nodes/memory/DynamoDb/DynamoDb.ts | 35 +++++++--- .../memory/MongoDBMemory/MongoDBMemory.ts | 35 +++++++--- .../memory/MotorheadMemory/MotorheadMemory.ts | 25 +++++-- .../RedisBackedChatMemory.ts | 26 ++++--- .../UpstashRedisBackedChatMemory.ts | 25 ++++--- .../nodes/memory/ZepMemory/ZepMemory.ts | 27 +++++-- .../components/nodes/tools/ChainTool/core.ts | 4 +- packages/components/src/Interface.ts | 5 +- packages/components/src/utils.ts | 12 ++++ packages/server/src/index.ts | 38 +++++++--- packages/server/src/utils/index.ts | 58 ++++++++++++--- 12 files changed, 262 insertions(+), 98 deletions(-) diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index 35299057..7f2377bd 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -85,43 +85,46 @@ class OpenAIAssistant_Agents implements INode { return null } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const selectedAssistantId = nodeData.inputs?.selectedAssistant as string - const appDataSource = options.appDataSource as DataSource - const databaseEntities = options.databaseEntities as IDatabaseEntity - let sessionId = nodeData.inputs?.sessionId as string + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const selectedAssistantId = nodeData.inputs?.selectedAssistant as string + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + let sessionId = nodeData.inputs?.sessionId as string - const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({ - id: selectedAssistantId - }) - - if (!assistant) { - options.logger.error(`Assistant ${selectedAssistantId} not found`) - return - } - - if (!sessionId && options.chatId) { - const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({ - chatId: options.chatId + const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({ + id: selectedAssistantId }) - if (!chatmsg) { - options.logger.error(`Chat Message with Chat Id: ${options.chatId} not found`) + + if (!assistant) { + options.logger.error(`Assistant ${selectedAssistantId} not found`) return } - sessionId = chatmsg.sessionId - } - const credentialData = await getCredentialData(assistant.credential ?? '', options) - const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) - if (!openAIApiKey) { - options.logger.error(`OpenAI ApiKey not found`) - return - } + if (!sessionId && options.chatId) { + const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({ + chatId: options.chatId + }) + if (!chatmsg) { + options.logger.error(`Chat Message with Chat Id: ${options.chatId} not found`) + return + } + sessionId = chatmsg.sessionId + } - const openai = new OpenAI({ apiKey: openAIApiKey }) - options.logger.info(`Clearing OpenAI Thread ${sessionId}`) - if (sessionId) await openai.beta.threads.del(sessionId) - options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`) + const credentialData = await getCredentialData(assistant.credential ?? '', options) + const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) + if (!openAIApiKey) { + options.logger.error(`OpenAI ApiKey not found`) + return + } + + const openai = new OpenAI({ apiKey: openAIApiKey }) + options.logger.info(`Clearing OpenAI Thread ${sessionId}`) + if (sessionId) await openai.beta.threads.del(sessionId) + options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`) + } } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { @@ -359,7 +362,10 @@ class OpenAIAssistant_Agents implements INode { } // Replace the text with a footnote - message_content.value = message_content.value.replace(`${annotation.text}`, `${filePath}`) + message_content.value = message_content.value.replace( + `${annotation.text}`, + `${disableFileDownload ? '' : filePath}` + ) } returnVal += message_content.value diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index ac4f7602..8ca6cf9e 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -1,4 +1,13 @@ -import { ICommonObject, INode, INodeData, INodeParams, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src' +import { + ICommonObject, + INode, + INodeData, + INodeParams, + getBaseClasses, + getCredentialData, + getCredentialParam, + serializeChatHistory +} from '../../../src' import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' @@ -70,13 +79,23 @@ class DynamoDb_Memory implements INode { return initalizeDynamoDB(nodeData, options) } - 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}`) + //@ts-ignore + memoryMethods = { + 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}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const dynamodbMemory = await initalizeDynamoDB(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await dynamodbMemory.loadMemoryVariables({}) + return serializeChatHistory(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts index 6f800cdc..76cb7e31 100644 --- a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts +++ b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts @@ -1,4 +1,13 @@ -import { getBaseClasses, getCredentialData, getCredentialParam, ICommonObject, INode, INodeData, INodeParams } from '../../../src' +import { + getBaseClasses, + getCredentialData, + getCredentialParam, + ICommonObject, + INode, + INodeData, + INodeParams, + serializeChatHistory +} from '../../../src' import { MongoDBChatMessageHistory } from 'langchain/stores/message/mongodb' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { BaseMessage, mapStoredMessageToChatMessage } from 'langchain/schema' @@ -67,13 +76,23 @@ class MongoDB_Memory implements INode { return initializeMongoDB(nodeData, options) } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const mongodbMemory = await initializeMongoDB(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing MongoDB memory session ${sessionId ? sessionId : chatId}`) - await mongodbMemory.clear() - options.logger.info(`Successfully cleared MongoDB memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const mongodbMemory = await initializeMongoDB(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing MongoDB memory session ${sessionId ? sessionId : chatId}`) + await mongodbMemory.clear() + options.logger.info(`Successfully cleared MongoDB memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const mongodbMemory = await initializeMongoDB(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await mongodbMemory.loadMemoryVariables({}) + return serializeChatHistory(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 0ec2f42a..9cdbcd5c 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -3,6 +3,7 @@ import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../ import { ICommonObject } from '../../../src' import { MotorheadMemory, MotorheadMemoryInput } from 'langchain/memory' import fetch from 'node-fetch' +import { getBufferString } from 'langchain/memory' class MotorMemory_Memory implements INode { label: string @@ -64,13 +65,23 @@ class MotorMemory_Memory implements INode { return initalizeMotorhead(nodeData, options) } - 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}`) + //@ts-ignore + memoryMethods = { + 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}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const motorhead = await initalizeMotorhead(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await motorhead.loadMemoryVariables({}) + return getBufferString(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index bdb62911..7fe447ad 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -1,5 +1,5 @@ import { INode, INodeData, INodeParams, ICommonObject } from '../../../src/Interface' -import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam, serializeChatHistory } from '../../../src/utils' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/ioredis' import { mapStoredMessageToChatMessage, BaseMessage } from 'langchain/schema' @@ -65,13 +65,23 @@ class RedisBackedChatMemory_Memory implements INode { return await initalizeRedis(nodeData, options) } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const redis = await initalizeRedis(nodeData, options) - 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}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const redis = await initalizeRedis(nodeData, options) + 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}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const redis = await initalizeRedis(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await redis.loadMemoryVariables({}) + return serializeChatHistory(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts index 2b8b4650..8bca0440 100644 --- a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts @@ -1,5 +1,5 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam, serializeChatHistory } from '../../../src/utils' import { ICommonObject } from '../../../src' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { UpstashRedisChatMessageHistory } from 'langchain/stores/message/upstash_redis' @@ -63,13 +63,22 @@ class UpstashRedisBackedChatMemory_Memory implements INode { return initalizeUpstashRedis(nodeData, options) } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const redis = await initalizeUpstashRedis(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Upstash Redis memory session ${sessionId ? sessionId : chatId}`) - await redis.clear() - options.logger.info(`Successfully cleared Upstash Redis memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const redis = await initalizeUpstashRedis(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Upstash Redis memory session ${sessionId ? sessionId : chatId}`) + await redis.clear() + options.logger.info(`Successfully cleared Upstash Redis memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const redis = await initalizeUpstashRedis(nodeData, options) + const key = 'chat_history' + const memoryResult = await redis.loadMemoryVariables({}) + return serializeChatHistory(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index c4498644..ced871a1 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -3,6 +3,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' import { ICommonObject } from '../../../src' +import { getBufferString } from 'langchain/memory' class ZepMemory_Memory implements INode { label: string @@ -140,13 +141,25 @@ class ZepMemory_Memory implements INode { return zep } - 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}`) + //@ts-ignore + memoryMethods = { + 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}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const aiPrefix = nodeData.inputs?.aiPrefix as string + const humanPrefix = nodeData.inputs?.humanPrefix as string + const zep = await initalizeZep(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await zep.loadMemoryVariables({}) + return getBufferString(memoryResult[key], humanPrefix, aiPrefix) + } } } diff --git a/packages/components/nodes/tools/ChainTool/core.ts b/packages/components/nodes/tools/ChainTool/core.ts index 6c3dba55..5520d6df 100644 --- a/packages/components/nodes/tools/ChainTool/core.ts +++ b/packages/components/nodes/tools/ChainTool/core.ts @@ -1,5 +1,6 @@ import { DynamicTool, DynamicToolInput } from 'langchain/tools' import { BaseChain } from 'langchain/chains' +import { handleEscapeCharacters } from '../../../src/utils' export interface ChainToolInput extends Omit { chain: BaseChain @@ -14,7 +15,8 @@ export class ChainTool extends DynamicTool { 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()) + const promptValues = handleEscapeCharacters((chain as any).prompt.promptValues, true) + const values = await chain.call(promptValues, runManager?.getChild()) return values?.text } return chain.run(input, runManager?.getChild()) diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index af304272..6752f944 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -107,9 +107,12 @@ export interface INode extends INodeProperties { search: (nodeData: INodeData, options?: ICommonObject) => Promise delete: (nodeData: INodeData, options?: ICommonObject) => Promise } + memoryMethods?: { + clearSessionMemory: (nodeData: INodeData, options?: ICommonObject) => Promise + getChatMessages: (nodeData: INodeData, options?: ICommonObject) => Promise + } 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 69f8b268..404f7c75 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -549,6 +549,18 @@ export const convertChatHistoryToText = (chatHistory: IMessage[] = []): string = .join('\n') } +/** + * Serialize array chat history to string + * @param {IMessage[]} chatHistory + * @returns {string} + */ +export const serializeChatHistory = (chatHistory: string | Array) => { + if (Array.isArray(chatHistory)) { + return chatHistory.join('\n') + } + return chatHistory +} + /** * Convert schema to zod schema * @param {string | object} schema diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 1ebd9312..d87d2c0a 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -42,7 +42,8 @@ import { getEncryptionKey, checkMemorySessionId, clearSessionMemoryFromViewMessageDialog, - getUserHome + getUserHome, + replaceChatHistory } from './utils' import { cloneDeep, omit, uniqWith, isEqual } from 'lodash' import { getDataSource } from './DataSource' @@ -54,7 +55,7 @@ import { Tool } from './database/entities/Tool' import { Assistant } from './database/entities/Assistant' import { ChatflowPool } from './ChatflowPool' import { CachePool } from './CachePool' -import { ICommonObject, INodeOptionsValue } from 'flowise-components' +import { ICommonObject, IMessage, INodeOptionsValue } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey' @@ -1267,14 +1268,14 @@ export class App { * @param {IReactFlowEdge[]} edges * @returns {string | undefined} */ - findMemoryLabel(nodes: IReactFlowNode[], edges: IReactFlowEdge[]): string | undefined { + findMemoryLabel(nodes: IReactFlowNode[], edges: IReactFlowEdge[]): IReactFlowNode | undefined { const memoryNodes = nodes.filter((node) => node.data.category === 'Memory') const memoryNodeIds = memoryNodes.map((mem) => mem.data.id) for (const edge of edges) { if (memoryNodeIds.includes(edge.source)) { const memoryNode = nodes.find((node) => node.data.id === edge.source) - return memoryNode ? memoryNode.data.label : undefined + return memoryNode } } return undefined @@ -1395,6 +1396,19 @@ export class App { isStreamValid = isFlowValidForStream(nodes, endingNodeData) + let chatHistory: IMessage[] | string = incomingInput.history + if ( + endingNodeData.inputs?.memory && + !incomingInput.history && + (incomingInput.chatId || incomingInput.overrideConfig?.sessionId) + ) { + const memoryNodeId = endingNodeData.inputs?.memory.split('.')[0].replace('{{', '') + const memoryNode = nodes.find((node) => node.data.id === memoryNodeId) + if (memoryNode) { + chatHistory = await replaceChatHistory(memoryNode, incomingInput, this.AppDataSource, databaseEntities, logger) + } + } + /*** Get Starting Nodes with Non-Directed Graph ***/ const constructedObj = constructGraphs(nodes, edges, true) const nonDirectedGraph = constructedObj.graph @@ -1409,7 +1423,7 @@ export class App { depthQueue, this.nodesPool.componentNodes, incomingInput.question, - incomingInput.history, + chatHistory, chatId, chatflowid, this.AppDataSource, @@ -1429,7 +1443,7 @@ export class App { nodeToExecute.data, reactFlowNodes, incomingInput.question, - incomingInput.history + chatHistory ) nodeToExecuteData = reactFlowNodeData @@ -1446,11 +1460,17 @@ export class App { let sessionId = undefined if (nodeToExecuteData.instance) sessionId = checkMemorySessionId(nodeToExecuteData.instance, chatId) - const memoryType = this.findMemoryLabel(nodes, edges) + const memoryNode = this.findMemoryLabel(nodes, edges) + const memoryType = memoryNode?.data.label + + let chatHistory: IMessage[] | string = incomingInput.history + if (memoryNode && !incomingInput.history && (incomingInput.chatId || incomingInput.overrideConfig?.sessionId)) { + chatHistory = await replaceChatHistory(memoryNode, incomingInput, this.AppDataSource, databaseEntities, logger) + } let result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { - chatHistory: incomingInput.history, + chatHistory, socketIO, socketIOClientId: incomingInput.socketIOClientId, logger, @@ -1460,7 +1480,7 @@ export class App { chatId }) : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { - chatHistory: incomingInput.history, + chatHistory, logger, appDataSource: this.AppDataSource, databaseEntities, diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 423d32af..0b1e62d2 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -15,7 +15,8 @@ import { IOverrideConfig, IReactFlowEdge, IReactFlowNode, - IVariableDict + IVariableDict, + IncomingInput } from '../Interface' import { cloneDeep, get, isEqual } from 'lodash' import { @@ -216,7 +217,7 @@ export const buildLangchain = async ( depthQueue: IDepthQueue, componentNodes: IComponentNodes, question: string, - chatHistory: IMessage[], + chatHistory: IMessage[] | string, chatId: string, chatflowid: string, appDataSource: DataSource, @@ -347,8 +348,8 @@ export const clearAllSessionMemory = async ( node.data.inputs.sessionId = sessionId } - if (newNodeInstance.clearSessionMemory) { - await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) + if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) { + await newNodeInstance.memoryMethods.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) } } } @@ -380,8 +381,8 @@ export const clearSessionMemoryFromViewMessageDialog = async ( if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId - if (newNodeInstance.clearSessionMemory) { - await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) + if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) { + await newNodeInstance.memoryMethods.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) return } } @@ -399,7 +400,7 @@ export const getVariableValue = ( paramValue: string, reactFlowNodes: IReactFlowNode[], question: string, - chatHistory: IMessage[], + chatHistory: IMessage[] | string, isAcceptVariable = false ) => { let returnVal = paramValue @@ -432,7 +433,10 @@ export const getVariableValue = ( } if (isAcceptVariable && variableFullPath === CHAT_HISTORY_VAR_PREFIX) { - variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(convertChatHistoryToText(chatHistory), false) + variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters( + typeof chatHistory === 'string' ? chatHistory : convertChatHistoryToText(chatHistory), + false + ) } // Split by first occurrence of '.' to get just nodeId @@ -475,7 +479,7 @@ export const resolveVariables = ( reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[], question: string, - chatHistory: IMessage[] + chatHistory: IMessage[] | string ): INodeData => { let flowNodeData = cloneDeep(reactFlowNodeData) const types = 'inputs' @@ -873,3 +877,39 @@ export const checkMemorySessionId = (instance: any, chatId: string): string | un return instance.memory.chatHistory.sessionId return undefined } + +/** + * Replace chatHistory if incomingInput.history is empty and sessionId/chatId is provided + * @param {IReactFlowNode} memoryNode + * @param {IncomingInput} incomingInput + * @param {DataSource} appDataSource + * @param {IDatabaseEntity} databaseEntities + * @param {any} logger + * @returns {string} + */ +export const replaceChatHistory = async ( + memoryNode: IReactFlowNode, + incomingInput: IncomingInput, + appDataSource: DataSource, + databaseEntities: IDatabaseEntity, + logger: any +): Promise => { + const nodeInstanceFilePath = memoryNode.data.filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const newNodeInstance = new nodeModule.nodeClass() + + if (incomingInput.overrideConfig?.sessionId && memoryNode.data.inputs) { + memoryNode.data.inputs.sessionId = incomingInput.overrideConfig.sessionId + } + + if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.getChatMessages) { + return await newNodeInstance.memoryMethods.getChatMessages(memoryNode.data, { + chatId: incomingInput.chatId, + appDataSource, + databaseEntities, + logger + }) + } + + return '' +} From 674b8996f013ec8d9ea3c3db9f13ddf43dac0b38 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 30 Nov 2023 16:57:34 +0000 Subject: [PATCH 9/9] add back accidentally removed about dialog --- .../MainLayout/Header/ProfileSection/index.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js index c2de41b2..ac114c6c 100644 --- a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js +++ b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js @@ -28,7 +28,7 @@ import Transitions from 'ui-component/extended/Transitions' import AboutDialog from 'ui-component/dialog/AboutDialog' // assets -import { IconLogout, IconSettings } from '@tabler/icons' +import { IconLogout, IconSettings, IconInfoCircle } from '@tabler/icons' import './index.css' @@ -135,6 +135,18 @@ const ProfileSection = ({ username, handleLogout }) => { } }} > + { + setOpen(false) + setAboutDialogOpen(true) + }} + > + + + + About Flowise} /> + {localStorage.getItem('username') && localStorage.getItem('password') && (