From bcf3946efbab202698c73458c3150b6b5c147a1d Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 24 Jul 2023 19:11:39 +0100 Subject: [PATCH] - add refine chain type custom system message - allow all memory type to conver_re_qa_chain - fix startsWith undefined where override config file is not passed - ability to generate new session id when sharing chatbot --- .../ConversationalRetrievalQAChain.ts | 58 ++- .../ConversationalRetrievalQAChain/prompts.ts | 31 +- .../Conversational Retrieval QA Chain.json | 429 +++++++++--------- packages/server/src/ChildProcess.ts | 12 +- packages/server/src/index.ts | 5 +- packages/server/src/utils/index.ts | 9 +- packages/ui/src/views/canvas/CanvasHeader.js | 20 +- packages/ui/src/views/chatbot/index.js | 19 +- .../ui/src/views/chatflows/APICodeDialog.js | 4 +- .../ui/src/views/chatflows/ShareChatbot.js | 26 +- 10 files changed, 373 insertions(+), 240 deletions(-) diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 8ec663f9..3ac99bc3 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -1,9 +1,9 @@ import { BaseLanguageModel } from 'langchain/base_language' import { ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { ConversationalRetrievalQAChain } from 'langchain/chains' +import { ConversationalRetrievalQAChain, QAChainParams } from 'langchain/chains' import { AIMessage, BaseRetriever, HumanMessage } from 'langchain/schema' -import { BaseChatMemory, BufferMemory, ChatMessageHistory } from 'langchain/memory' +import { BaseChatMemory, BufferMemory, ChatMessageHistory, BufferMemoryInput } from 'langchain/memory' import { PromptTemplate } from 'langchain/prompts' import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' import { @@ -11,7 +11,9 @@ import { default_qa_template, qa_template, map_reduce_template, - CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT + CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT, + refine_question_template, + refine_template } from './prompts' class ConversationalRetrievalQAChain_Chains implements INode { @@ -46,9 +48,9 @@ class ConversationalRetrievalQAChain_Chains implements INode { { label: 'Memory', name: 'memory', - type: 'DynamoDBChatMemory | RedisBackedChatMemory | ZepMemory', + type: 'BaseMemory', optional: true, - description: 'If no memory connected, default BufferMemory will be used' + description: 'If left empty, a default BufferMemory will be used' }, { label: 'Return Source Documents', @@ -115,28 +117,43 @@ class ConversationalRetrievalQAChain_Chains implements INode { combinePrompt: PromptTemplate.fromTemplate( systemMessagePrompt ? `${systemMessagePrompt}\n${map_reduce_template}` : default_map_reduce_template ) - } + } as QAChainParams } else if (chainOption === 'refine') { - // TODO: Add custom system message + const qprompt = new PromptTemplate({ + inputVariables: ['context', 'question'], + template: refine_question_template(systemMessagePrompt) + }) + const rprompt = new PromptTemplate({ + inputVariables: ['context', 'question', 'existing_answer'], + template: refine_template + }) + obj.qaChainOptions = { + type: 'refine', + questionPrompt: qprompt, + refinePrompt: rprompt + } as QAChainParams } else { obj.qaChainOptions = { type: 'stuff', prompt: PromptTemplate.fromTemplate(systemMessagePrompt ? `${systemMessagePrompt}\n${qa_template}` : default_qa_template) - } + } as QAChainParams } if (memory) { memory.inputKey = 'question' - memory.outputKey = 'text' memory.memoryKey = 'chat_history' + if (chainOption === 'refine') memory.outputKey = 'output_text' + else memory.outputKey = 'text' obj.memory = memory } else { - obj.memory = new BufferMemory({ + const fields: BufferMemoryInput = { memoryKey: 'chat_history', inputKey: 'question', - outputKey: 'text', returnMessages: true - }) + } + if (chainOption === 'refine') fields.outputKey = 'output_text' + else fields.outputKey = 'text' + obj.memory = new BufferMemory(fields) } const chain = ConversationalRetrievalQAChain.fromLLM(model, vectorStoreRetriever, obj) @@ -147,6 +164,7 @@ class ConversationalRetrievalQAChain_Chains implements INode { const chain = nodeData.instance as ConversationalRetrievalQAChain const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean const memory = nodeData.inputs?.memory + const chainOption = nodeData.inputs?.chainOption as string let model = nodeData.inputs?.model @@ -176,8 +194,22 @@ class ConversationalRetrievalQAChain_Chains implements INode { const loggerHandler = new ConsoleCallbackHandler(options.logger) if (options.socketIO && options.socketIOClientId) { - const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, undefined, returnSourceDocuments) + const handler = new CustomChainHandler( + options.socketIO, + options.socketIOClientId, + chainOption === 'refine' ? 4 : undefined, + returnSourceDocuments + ) const res = await chain.call(obj, [loggerHandler, handler]) + if (chainOption === 'refine') { + if (res.output_text && res.sourceDocuments) { + return { + text: res.output_text, + sourceDocuments: res.sourceDocuments + } + } + return res?.output_text + } if (res.text && res.sourceDocuments) return res return res?.text } else { diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/prompts.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/prompts.ts index 66b80f47..132e3a97 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/prompts.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/prompts.ts @@ -27,7 +27,36 @@ export const map_reduce_template = `Given the following extracted parts of a lon Question: {question} Helpful Answer:` -export const CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language. include it in the standalone question. +export const refine_question_template = (sysPrompt?: string) => { + let returnPrompt = '' + if (sysPrompt) + returnPrompt = `Context information is below. +--------------------- +{context} +--------------------- +Given the context information and not prior knowledge, ${sysPrompt} +Answer the question: {question}. +Answer:` + if (!sysPrompt) + returnPrompt = `Context information is below. +--------------------- +{context} +--------------------- +Given the context information and not prior knowledge, answer the question: {question}. +Answer:` + return returnPrompt +} + +export const refine_template = `The original question is as follows: {question} +We have provided an existing answer: {existing_answer} +We have the opportunity to refine the existing answer (only if needed) with some more context below. +------------ +{context} +------------ +Given the new context, refine the original answer to better answer the question. +If you can't find answer from the context, return the original answer.` + +export const CUSTOM_QUESTION_GENERATOR_CHAIN_PROMPT = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, answer in the same language as the follow up question. include it in the standalone question. Chat History: {chat_history} diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json index e420fefe..e267214e 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json @@ -58,70 +58,6 @@ }, "dragging": false }, - { - "width": 300, - "height": 392, - "id": "textFile_1", - "position": { - "x": 810.6456923854021, - "y": 61.45989039390216 - }, - "type": "customNode", - "data": { - "id": "textFile_1", - "label": "Text File", - "name": "textFile", - "type": "Document", - "baseClasses": ["Document"], - "category": "Document Loaders", - "description": "Load data from text files", - "inputParams": [ - { - "label": "Txt File", - "name": "txtFile", - "type": "file", - "fileType": ".txt", - "id": "textFile_1-input-txtFile-file" - }, - { - "label": "Metadata", - "name": "metadata", - "type": "json", - "optional": true, - "additionalParams": true, - "id": "textFile_1-input-metadata-json" - } - ], - "inputAnchors": [ - { - "label": "Text Splitter", - "name": "textSplitter", - "type": "TextSplitter", - "optional": true, - "id": "textFile_1-input-textSplitter-TextSplitter" - } - ], - "inputs": { - "textSplitter": "{{recursiveCharacterTextSplitter_1.data.instance}}" - }, - "outputAnchors": [ - { - "id": "textFile_1-output-textFile-Document", - "name": "textFile", - "label": "Document", - "type": "Document" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 810.6456923854021, - "y": 61.45989039390216 - }, - "dragging": false - }, { "width": 300, "height": 330, @@ -203,118 +139,6 @@ }, "dragging": false }, - { - "width": 300, - "height": 702, - "id": "pineconeUpsert_1", - "position": { - "x": 1201.3427203075867, - "y": 545.1800202023215 - }, - "type": "customNode", - "data": { - "id": "pineconeUpsert_1", - "label": "Pinecone Upsert Document", - "name": "pineconeUpsert", - "type": "Pinecone", - "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], - "category": "Vector Stores", - "description": "Upsert documents to Pinecone", - "inputParams": [ - { - "label": "Pinecone Api Key", - "name": "pineconeApiKey", - "type": "password", - "id": "pineconeUpsert_1-input-pineconeApiKey-password" - }, - { - "label": "Pinecone Environment", - "name": "pineconeEnv", - "type": "string", - "id": "pineconeUpsert_1-input-pineconeEnv-string" - }, - { - "label": "Pinecone Index", - "name": "pineconeIndex", - "type": "string", - "id": "pineconeUpsert_1-input-pineconeIndex-string" - }, - { - "label": "Pinecone Namespace", - "name": "pineconeNamespace", - "type": "string", - "placeholder": "my-first-namespace", - "optional": true, - "additionalParams": true, - "id": "pineconeUpsert_1-input-pineconeNamespace-string" - }, - { - "label": "Top K", - "name": "topK", - "description": "Number of top results to fetch. Default to 4", - "placeholder": "4", - "type": "number", - "additionalParams": true, - "optional": true, - "id": "pineconeUpsert_1-input-topK-number" - } - ], - "inputAnchors": [ - { - "label": "Document", - "name": "document", - "type": "Document", - "list": true, - "id": "pineconeUpsert_1-input-document-Document" - }, - { - "label": "Embeddings", - "name": "embeddings", - "type": "Embeddings", - "id": "pineconeUpsert_1-input-embeddings-Embeddings" - } - ], - "inputs": { - "document": ["{{textFile_1.data.instance}}"], - "embeddings": "{{openAIEmbeddings_1.data.instance}}", - "pineconeEnv": "us-west4-gcp", - "pineconeIndex": "myindex", - "pineconeNamespace": "mynamespace" - }, - "outputAnchors": [ - { - "name": "output", - "label": "Output", - "type": "options", - "options": [ - { - "id": "pineconeUpsert_1-output-retriever-Pinecone|VectorStoreRetriever|BaseRetriever", - "name": "retriever", - "label": "Pinecone Retriever", - "type": "Pinecone | VectorStoreRetriever | BaseRetriever" - }, - { - "id": "pineconeUpsert_1-output-vectorStore-Pinecone|VectorStore", - "name": "vectorStore", - "label": "Pinecone Vector Store", - "type": "Pinecone | VectorStore" - } - ], - "default": "retriever" - } - ], - "outputs": { - "output": "retriever" - }, - "selected": false - }, - "selected": false, - "dragging": false, - "positionAbsolute": { - "x": 1201.3427203075867, - "y": 545.1800202023215 - } - }, { "width": 300, "height": 524, @@ -468,7 +292,7 @@ }, { "width": 300, - "height": 280, + "height": 480, "id": "conversationalRetrievalQAChain_0", "position": { "x": 1627.1855024401737, @@ -551,7 +375,7 @@ ], "inputs": { "model": "{{chatOpenAI_0.data.instance}}", - "vectorStoreRetriever": "{{pineconeUpsert_1.data.instance}}" + "vectorStoreRetriever": "{{pineconeUpsert_0.data.instance}}" }, "outputAnchors": [ { @@ -570,42 +394,186 @@ "y": 394.42287890442145 }, "dragging": false + }, + { + "width": 300, + "height": 411, + "id": "textFile_0", + "position": { + "x": 806.1207502345, + "y": 98.75458062792087 + }, + "type": "customNode", + "data": { + "id": "textFile_0", + "label": "Text File", + "name": "textFile", + "type": "Document", + "baseClasses": ["Document"], + "category": "Document Loaders", + "description": "Load data from text files", + "inputParams": [ + { + "label": "Txt File", + "name": "txtFile", + "type": "file", + "fileType": ".txt", + "id": "textFile_0-input-txtFile-file" + }, + { + "label": "Metadata", + "name": "metadata", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "textFile_0-input-metadata-json" + } + ], + "inputAnchors": [ + { + "label": "Text Splitter", + "name": "textSplitter", + "type": "TextSplitter", + "optional": true, + "id": "textFile_0-input-textSplitter-TextSplitter" + } + ], + "inputs": { + "textSplitter": "{{recursiveCharacterTextSplitter_1.data.instance}}", + "metadata": "" + }, + "outputAnchors": [ + { + "id": "textFile_0-output-textFile-Document", + "name": "textFile", + "label": "Document", + "type": "Document" + } + ], + "outputs": {}, + "selected": false + }, + "positionAbsolute": { + "x": 806.1207502345, + "y": 98.75458062792087 + }, + "selected": false + }, + { + "width": 300, + "height": 655, + "id": "pineconeUpsert_0", + "position": { + "x": 1206.7979889462367, + "y": 526.4616330622748 + }, + "type": "customNode", + "data": { + "id": "pineconeUpsert_0", + "label": "Pinecone Upsert Document", + "name": "pineconeUpsert", + "type": "Pinecone", + "baseClasses": ["Pinecone", "VectorStoreRetriever", "BaseRetriever"], + "category": "Vector Stores", + "description": "Upsert documents to Pinecone", + "inputParams": [ + { + "label": "Pinecone Api Key", + "name": "pineconeApiKey", + "type": "password", + "id": "pineconeUpsert_0-input-pineconeApiKey-password" + }, + { + "label": "Pinecone Environment", + "name": "pineconeEnv", + "type": "string", + "id": "pineconeUpsert_0-input-pineconeEnv-string" + }, + { + "label": "Pinecone Index", + "name": "pineconeIndex", + "type": "string", + "id": "pineconeUpsert_0-input-pineconeIndex-string" + }, + { + "label": "Pinecone Namespace", + "name": "pineconeNamespace", + "type": "string", + "placeholder": "my-first-namespace", + "additionalParams": true, + "optional": true, + "id": "pineconeUpsert_0-input-pineconeNamespace-string" + }, + { + "label": "Top K", + "name": "topK", + "description": "Number of top results to fetch. Default to 4", + "placeholder": "4", + "type": "number", + "additionalParams": true, + "optional": true, + "id": "pineconeUpsert_0-input-topK-number" + } + ], + "inputAnchors": [ + { + "label": "Document", + "name": "document", + "type": "Document", + "list": true, + "id": "pineconeUpsert_0-input-document-Document" + }, + { + "label": "Embeddings", + "name": "embeddings", + "type": "Embeddings", + "id": "pineconeUpsert_0-input-embeddings-Embeddings" + } + ], + "inputs": { + "document": ["{{textFile_0.data.instance}}"], + "embeddings": "{{openAIEmbeddings_1.data.instance}}", + "pineconeEnv": "", + "pineconeIndex": "", + "pineconeNamespace": "", + "topK": "" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "pineconeUpsert_0-output-retriever-Pinecone|VectorStoreRetriever|BaseRetriever", + "name": "retriever", + "label": "Pinecone Retriever", + "type": "Pinecone | VectorStoreRetriever | BaseRetriever" + }, + { + "id": "pineconeUpsert_0-output-vectorStore-Pinecone|VectorStore", + "name": "vectorStore", + "label": "Pinecone Vector Store", + "type": "Pinecone | VectorStore" + } + ], + "default": "retriever" + } + ], + "outputs": { + "output": "retriever" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1206.7979889462367, + "y": 526.4616330622748 + }, + "dragging": false } ], "edges": [ - { - "source": "openAIEmbeddings_1", - "sourceHandle": "openAIEmbeddings_1-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", - "target": "pineconeUpsert_1", - "targetHandle": "pineconeUpsert_1-input-embeddings-Embeddings", - "type": "buttonedge", - "id": "openAIEmbeddings_1-openAIEmbeddings_1-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings-pineconeUpsert_1-pineconeUpsert_1-input-embeddings-Embeddings", - "data": { - "label": "" - } - }, - { - "source": "textFile_1", - "sourceHandle": "textFile_1-output-textFile-Document", - "target": "pineconeUpsert_1", - "targetHandle": "pineconeUpsert_1-input-document-Document", - "type": "buttonedge", - "id": "textFile_1-textFile_1-output-textFile-Document-pineconeUpsert_1-pineconeUpsert_1-input-document-Document", - "data": { - "label": "" - } - }, - { - "source": "recursiveCharacterTextSplitter_1", - "sourceHandle": "recursiveCharacterTextSplitter_1-output-recursiveCharacterTextSplitter-RecursiveCharacterTextSplitter|TextSplitter", - "target": "textFile_1", - "targetHandle": "textFile_1-input-textSplitter-TextSplitter", - "type": "buttonedge", - "id": "recursiveCharacterTextSplitter_1-recursiveCharacterTextSplitter_1-output-recursiveCharacterTextSplitter-RecursiveCharacterTextSplitter|TextSplitter-textFile_1-textFile_1-input-textSplitter-TextSplitter", - "data": { - "label": "" - } - }, { "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|BaseLangChain", @@ -618,12 +586,45 @@ } }, { - "source": "pineconeUpsert_1", - "sourceHandle": "pineconeUpsert_1-output-retriever-Pinecone|VectorStoreRetriever|BaseRetriever", + "source": "recursiveCharacterTextSplitter_1", + "sourceHandle": "recursiveCharacterTextSplitter_1-output-recursiveCharacterTextSplitter-RecursiveCharacterTextSplitter|TextSplitter", + "target": "textFile_0", + "targetHandle": "textFile_0-input-textSplitter-TextSplitter", + "type": "buttonedge", + "id": "recursiveCharacterTextSplitter_1-recursiveCharacterTextSplitter_1-output-recursiveCharacterTextSplitter-RecursiveCharacterTextSplitter|TextSplitter-textFile_0-textFile_0-input-textSplitter-TextSplitter", + "data": { + "label": "" + } + }, + { + "source": "textFile_0", + "sourceHandle": "textFile_0-output-textFile-Document", + "target": "pineconeUpsert_0", + "targetHandle": "pineconeUpsert_0-input-document-Document", + "type": "buttonedge", + "id": "textFile_0-textFile_0-output-textFile-Document-pineconeUpsert_0-pineconeUpsert_0-input-document-Document", + "data": { + "label": "" + } + }, + { + "source": "openAIEmbeddings_1", + "sourceHandle": "openAIEmbeddings_1-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings", + "target": "pineconeUpsert_0", + "targetHandle": "pineconeUpsert_0-input-embeddings-Embeddings", + "type": "buttonedge", + "id": "openAIEmbeddings_1-openAIEmbeddings_1-output-openAIEmbeddings-OpenAIEmbeddings|Embeddings-pineconeUpsert_0-pineconeUpsert_0-input-embeddings-Embeddings", + "data": { + "label": "" + } + }, + { + "source": "pineconeUpsert_0", + "sourceHandle": "pineconeUpsert_0-output-retriever-Pinecone|VectorStoreRetriever|BaseRetriever", "target": "conversationalRetrievalQAChain_0", "targetHandle": "conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", "type": "buttonedge", - "id": "pineconeUpsert_1-pineconeUpsert_1-output-retriever-Pinecone|VectorStoreRetriever|BaseRetriever-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", + "id": "pineconeUpsert_0-pineconeUpsert_0-output-retriever-Pinecone|VectorStoreRetriever|BaseRetriever-conversationalRetrievalQAChain_0-conversationalRetrievalQAChain_0-input-vectorStoreRetriever-BaseRetriever", "data": { "label": "" } diff --git a/packages/server/src/ChildProcess.ts b/packages/server/src/ChildProcess.ts index f0117b6d..2228cda6 100644 --- a/packages/server/src/ChildProcess.ts +++ b/packages/server/src/ChildProcess.ts @@ -1,6 +1,14 @@ import path from 'path' import { IChildProcessMessage, IReactFlowNode, IReactFlowObject, IRunChatflowMessageValue, INodeData } from './Interface' -import { buildLangchain, constructGraphs, getEndingNode, getStartingNodes, getUserHome, resolveVariables } from './utils' +import { + buildLangchain, + constructGraphs, + getEndingNode, + getStartingNodes, + getUserHome, + replaceInputsWithConfig, + resolveVariables +} from './utils' import { DataSource } from 'typeorm' import { ChatFlow } from './entity/ChatFlow' import { ChatMessage } from './entity/ChatMessage' @@ -109,6 +117,8 @@ export class ChildProcess { return } + if (incomingInput.overrideConfig) + nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig) const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question) nodeToExecuteData = reactFlowNodeData diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index aece8b6f..6746278e 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -40,7 +40,8 @@ import { isVectorStoreFaiss, databaseEntities, getApiKey, - clearSessionMemory + clearSessionMemory, + replaceInputsWithConfig } from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' @@ -819,6 +820,8 @@ export class App { const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId) if (!nodeToExecute) return res.status(404).send(`Node ${endingNodeId} not found`) + if (incomingInput.overrideConfig) + nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig) const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question) nodeToExecuteData = reactFlowNodeData diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 5fa33cff..8e743031 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -428,9 +428,12 @@ export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig: const types = 'inputs' const getParamValues = (paramsObj: ICommonObject) => { - for (const key in paramsObj) { - const paramValue: string = paramsObj[key] - paramsObj[key] = overrideConfig[key] ?? paramValue + for (const config in overrideConfig) { + let paramValue = overrideConfig[config] ?? paramsObj[config] + // Check if boolean + if (paramValue === 'true') paramValue = true + else if (paramValue === 'false') paramValue = false + paramsObj[config] = paramValue } } diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index 1c1e5212..d2563532 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -90,8 +90,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl } const onAPIDialogClick = () => { + // If file type is file, isFormDataRequired = true let isFormDataRequired = false - try { const flowData = JSON.parse(chatflow.flowData) const nodes = flowData.nodes @@ -105,11 +105,27 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl console.error(e) } + // If sessionId memory, isSessionMemory = true + let isSessionMemory = false + try { + const flowData = JSON.parse(chatflow.flowData) + const nodes = flowData.nodes + for (const node of nodes) { + if (node.data.inputParams.find((param) => param.name === 'sessionId')) { + isSessionMemory = true + break + } + } + } catch (e) { + console.error(e) + } + setAPIDialogProps({ title: 'Embed in website or use as API', chatflowid: chatflow.id, chatflowApiKeyId: chatflow.apikeyid, - isFormDataRequired + isFormDataRequired, + isSessionMemory }) setAPIDialogOpen(true) } diff --git a/packages/ui/src/views/chatbot/index.js b/packages/ui/src/views/chatbot/index.js index f29c35ee..2b5721e7 100644 --- a/packages/ui/src/views/chatbot/index.js +++ b/packages/ui/src/views/chatbot/index.js @@ -26,6 +26,7 @@ const ChatbotFull = () => { const [loginDialogOpen, setLoginDialogOpen] = useState(false) const [loginDialogProps, setLoginDialogProps] = useState({}) const [isLoading, setLoading] = useState(true) + const [chatbotOverrideConfig, setChatbotOverrideConfig] = useState({}) const getSpecificChatflowFromPublicApi = useApi(chatflowsApi.getSpecificChatflowFromPublicEndpoint) const getSpecificChatflowApi = useApi(chatflowsApi.getSpecificChatflow) @@ -77,10 +78,19 @@ const ChatbotFull = () => { setChatflow(chatflowData) if (chatflowData.chatbotConfig) { try { - setChatbotTheme(JSON.parse(chatflowData.chatbotConfig)) + const parsedConfig = JSON.parse(chatflowData.chatbotConfig) + setChatbotTheme(parsedConfig) + if (parsedConfig.overrideConfig) { + // Generate new sessionId + if (parsedConfig.overrideConfig.generateNewSession) { + parsedConfig.overrideConfig.sessionId = Date.now().toString() + } + setChatbotOverrideConfig(parsedConfig.overrideConfig) + } } catch (e) { console.error(e) setChatbotTheme({}) + setChatbotOverrideConfig({}) } } } @@ -97,7 +107,12 @@ const ChatbotFull = () => { {!chatflow || chatflow.apikeyid ? (

Invalid Chatbot

) : ( - + )} diff --git a/packages/ui/src/views/chatflows/APICodeDialog.js b/packages/ui/src/views/chatflows/APICodeDialog.js index 716ac3ff..994f7552 100644 --- a/packages/ui/src/views/chatflows/APICodeDialog.js +++ b/packages/ui/src/views/chatflows/APICodeDialog.js @@ -612,7 +612,9 @@ query({ )} )} - {codeLang === 'Share Chatbot' && !chatflowApiKeyId && } + {codeLang === 'Share Chatbot' && !chatflowApiKeyId && ( + + )} ))} diff --git a/packages/ui/src/views/chatflows/ShareChatbot.js b/packages/ui/src/views/chatflows/ShareChatbot.js index 51e12e54..0f05c28a 100644 --- a/packages/ui/src/views/chatflows/ShareChatbot.js +++ b/packages/ui/src/views/chatflows/ShareChatbot.js @@ -2,6 +2,7 @@ import { useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions' import { SketchPicker } from 'react-color' +import PropTypes from 'prop-types' import { Box, Typography, Button, Switch, OutlinedInput, Popover, Stack, IconButton } from '@mui/material' import { useTheme } from '@mui/material/styles' @@ -41,7 +42,7 @@ const defaultConfig = { } } -const ShareChatbot = () => { +const ShareChatbot = ({ isSessionMemory }) => { const dispatch = useDispatch() const theme = useTheme() const chatflow = useSelector((state) => state.canvas.chatflow) @@ -54,6 +55,7 @@ const ShareChatbot = () => { const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) const [isPublicChatflow, setChatflowIsPublic] = useState(chatflow.isPublic ?? false) + const [generateNewSession, setGenerateNewSession] = useState(chatbotConfig?.generateNewSession ?? false) const [welcomeMessage, setWelcomeMessage] = useState(chatbotConfig?.welcomeMessage ?? '') const [backgroundColor, setBackgroundColor] = useState(chatbotConfig?.backgroundColor ?? defaultConfig.backgroundColor) @@ -103,7 +105,8 @@ const ShareChatbot = () => { userMessage: { showAvatar: false }, - textInput: {} + textInput: {}, + overrideConfig: {} } if (welcomeMessage) obj.welcomeMessage = welcomeMessage if (backgroundColor) obj.backgroundColor = backgroundColor @@ -125,6 +128,8 @@ const ShareChatbot = () => { if (textInputPlaceholder) obj.textInput.placeholder = textInputPlaceholder if (textInputSendButtonColor) obj.textInput.sendButtonColor = textInputSendButtonColor + if (isSessionMemory) obj.overrideConfig.generateNewSession = generateNewSession + return obj } @@ -273,6 +278,9 @@ const ShareChatbot = () => { case 'userMessageShowAvatar': setUserMessageShowAvatar(value) break + case 'generateNewSession': + setGenerateNewSession(value) + break } } @@ -431,6 +439,16 @@ const ShareChatbot = () => { {textField(textInputPlaceholder, 'textInputPlaceholder', 'TextInput Placeholder', 'string', `Type question..`)} {colorField(textInputSendButtonColor, 'textInputSendButtonColor', 'TextIntput Send Button Color')} + {/*Session Memory Input*/} + {isSessionMemory && ( + <> + + Session Memory + + {booleanField(generateNewSession, 'generateNewSession', 'Start new session when chatbot link is opened or refreshed')} + + )} + onSave()}> Save Changes @@ -470,4 +488,8 @@ const ShareChatbot = () => { ) } +ShareChatbot.propTypes = { + isSessionMemory: PropTypes.bool +} + export default ShareChatbot