Merge pull request #908 from FlowiseAI/feature/Analytic
Feature/Add analytic
This commit is contained in:
commit
3d35536de5
|
|
@ -0,0 +1,39 @@
|
|||
import { INodeParams, INodeCredential } from '../src/Interface'
|
||||
|
||||
class LangfuseApi implements INodeCredential {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Langfuse API'
|
||||
this.name = 'langfuseApi'
|
||||
this.version = 1.0
|
||||
this.description =
|
||||
'Refer to <a target="_blank" href="https://langfuse.com/docs/get-started/">official guide</a> on how to get API key on Langfuse'
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Secret Key',
|
||||
name: 'langFuseSecretKey',
|
||||
type: 'password',
|
||||
placeholder: 'sk-lf-abcdefg'
|
||||
},
|
||||
{
|
||||
label: 'Public Key',
|
||||
name: 'langFusePublicKey',
|
||||
type: 'string',
|
||||
placeholder: 'pk-lf-abcdefg'
|
||||
},
|
||||
{
|
||||
label: 'Endpoint',
|
||||
name: 'langFuseEndpoint',
|
||||
type: 'string',
|
||||
default: 'https://cloud.langfuse.com'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { credClass: LangfuseApi }
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { INodeParams, INodeCredential } from '../src/Interface'
|
||||
|
||||
class LangsmithApi implements INodeCredential {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Langsmith API'
|
||||
this.name = 'langsmithApi'
|
||||
this.version = 1.0
|
||||
this.description =
|
||||
'Refer to <a target="_blank" href="https://docs.smith.langchain.com/">official guide</a> on how to get API key on Langsmith'
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'API Key',
|
||||
name: 'langSmithApiKey',
|
||||
type: 'password',
|
||||
placeholder: '<LANGSMITH_API_KEY>'
|
||||
},
|
||||
{
|
||||
label: 'Endpoint',
|
||||
name: 'langSmithEndpoint',
|
||||
type: 'string',
|
||||
default: 'https://api.smith.langchain.com'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { credClass: LangsmithApi }
|
||||
|
|
@ -4,7 +4,7 @@ import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../
|
|||
import { LoadPyodide, finalSystemPrompt, systemPrompt } from './core'
|
||||
import { LLMChain } from 'langchain/chains'
|
||||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
import axios from 'axios'
|
||||
|
||||
class Airtable_Agents implements INode {
|
||||
|
|
@ -102,6 +102,7 @@ class Airtable_Agents implements INode {
|
|||
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
const pyodide = await LoadPyodide()
|
||||
|
||||
|
|
@ -141,7 +142,7 @@ json.dumps(my_dict)`
|
|||
dict: dataframeColDict,
|
||||
question: input
|
||||
}
|
||||
const res = await chain.call(inputs, [loggerHandler])
|
||||
const res = await chain.call(inputs, [loggerHandler, ...callbacks])
|
||||
pythonCode = res?.text
|
||||
}
|
||||
|
||||
|
|
@ -169,10 +170,10 @@ json.dumps(my_dict)`
|
|||
}
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const result = await chain.call(inputs, [loggerHandler, handler])
|
||||
const result = await chain.call(inputs, [loggerHandler, handler, ...callbacks])
|
||||
return result?.text
|
||||
} else {
|
||||
const result = await chain.call(inputs, [loggerHandler])
|
||||
const result = await chain.call(inputs, [loggerHandler, ...callbacks])
|
||||
return result?.text
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { getBaseClasses } from '../../../src/utils'
|
|||
import { LoadPyodide, finalSystemPrompt, systemPrompt } from './core'
|
||||
import { LLMChain } from 'langchain/chains'
|
||||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
|
||||
class CSV_Agents implements INode {
|
||||
label: string
|
||||
|
|
@ -63,6 +63,7 @@ class CSV_Agents implements INode {
|
|||
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
let files: string[] = []
|
||||
|
||||
|
|
@ -119,7 +120,7 @@ json.dumps(my_dict)`
|
|||
dict: dataframeColDict,
|
||||
question: input
|
||||
}
|
||||
const res = await chain.call(inputs, [loggerHandler])
|
||||
const res = await chain.call(inputs, [loggerHandler, ...callbacks])
|
||||
pythonCode = res?.text
|
||||
}
|
||||
|
||||
|
|
@ -149,10 +150,10 @@ json.dumps(my_dict)`
|
|||
}
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const result = await chain.call(inputs, [loggerHandler, handler])
|
||||
const result = await chain.call(inputs, [loggerHandler, handler, ...callbacks])
|
||||
return result?.text
|
||||
} else {
|
||||
const result = await chain.call(inputs, [loggerHandler])
|
||||
const result = await chain.call(inputs, [loggerHandler, ...callbacks])
|
||||
return result?.text
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/age
|
|||
import { getBaseClasses, mapChatHistory } from '../../../src/utils'
|
||||
import { flatten } from 'lodash'
|
||||
import { BaseChatMemory } from 'langchain/memory'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
|
||||
const defaultMessage = `Do your best to answer the questions. Feel free to use any tools available to look up relevant information, only if necessary.`
|
||||
|
||||
|
|
@ -86,13 +86,14 @@ class ConversationalRetrievalAgent_Agents implements INode {
|
|||
}
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
|
||||
const result = await executor.call({ input }, [loggerHandler, handler])
|
||||
const result = await executor.call({ input }, [loggerHandler, handler, ...callbacks])
|
||||
return result?.output
|
||||
} else {
|
||||
const result = await executor.call({ input }, [loggerHandler])
|
||||
const result = await executor.call({ input }, [loggerHandler, ...callbacks])
|
||||
return result?.output
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { getBaseClasses, mapChatHistory } from '../../../src/utils'
|
|||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { flatten } from 'lodash'
|
||||
import { BaseChatMemory } from 'langchain/memory'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
|
||||
class OpenAIFunctionAgent_Agents implements INode {
|
||||
label: string
|
||||
|
|
@ -86,13 +86,14 @@ class OpenAIFunctionAgent_Agents implements INode {
|
|||
}
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
|
||||
const result = await executor.run(input, [loggerHandler, handler])
|
||||
const result = await executor.run(input, [loggerHandler, handler, ...callbacks])
|
||||
return result
|
||||
} else {
|
||||
const result = await executor.run(input, [loggerHandler])
|
||||
const result = await executor.run(input, [loggerHandler, ...callbacks])
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import { INode, INodeParams } from '../../../src/Interface'
|
||||
|
||||
class LangFuse_Analytic implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs?: INodeParams[]
|
||||
credential: INodeParams
|
||||
|
||||
constructor() {
|
||||
this.label = 'LangFuse'
|
||||
this.name = 'langFuse'
|
||||
this.version = 1.0
|
||||
this.type = 'LangFuse'
|
||||
this.icon = 'langfuse.png'
|
||||
this.category = 'Analytic'
|
||||
this.baseClasses = [this.type]
|
||||
this.inputs = []
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['langfuseApi']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: LangFuse_Analytic }
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -0,0 +1,33 @@
|
|||
import { INode, INodeParams } from '../../../src/Interface'
|
||||
|
||||
class LangSmith_Analytic implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs?: INodeParams[]
|
||||
credential: INodeParams
|
||||
|
||||
constructor() {
|
||||
this.label = 'LangSmith'
|
||||
this.name = 'langSmith'
|
||||
this.version = 1.0
|
||||
this.type = 'LangSmith'
|
||||
this.icon = 'langchain.png'
|
||||
this.category = 'Analytic'
|
||||
this.baseClasses = [this.type]
|
||||
this.inputs = []
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['langsmithApi']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: LangSmith_Analytic }
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
|
|
@ -3,7 +3,7 @@ import { APIChain } from 'langchain/chains'
|
|||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { PromptTemplate } from 'langchain/prompts'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
|
||||
export const API_URL_RAW_PROMPT_TEMPLATE = `You are given the below API Documentation:
|
||||
{api_docs}
|
||||
|
|
@ -99,13 +99,14 @@ class GETApiChain_Chains implements INode {
|
|||
|
||||
const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt)
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2)
|
||||
const res = await chain.run(input, [loggerHandler, handler])
|
||||
const res = await chain.run(input, [loggerHandler, handler, ...callbacks])
|
||||
return res
|
||||
} else {
|
||||
const res = await chain.run(input, [loggerHandler])
|
||||
const res = await chain.run(input, [loggerHandler, ...callbacks])
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Inter
|
|||
import { APIChain, createOpenAPIChain } from 'langchain/chains'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { ChatOpenAI } from 'langchain/chat_models/openai'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
|
||||
class OpenApiChain_Chains implements INode {
|
||||
label: string
|
||||
|
|
@ -61,13 +61,14 @@ class OpenApiChain_Chains implements INode {
|
|||
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> {
|
||||
const chain = await initChain(nodeData)
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
|
||||
const res = await chain.run(input, [loggerHandler, handler])
|
||||
const res = await chain.run(input, [loggerHandler, handler, ...callbacks])
|
||||
return res
|
||||
} else {
|
||||
const res = await chain.run(input, [loggerHandler])
|
||||
const res = await chain.run(input, [loggerHandler, ...callbacks])
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { getBaseClasses } from '../../../src/utils'
|
|||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { PromptTemplate } from 'langchain/prompts'
|
||||
import { API_RESPONSE_RAW_PROMPT_TEMPLATE, API_URL_RAW_PROMPT_TEMPLATE, APIChain } from './postCore'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
|
||||
class POSTApiChain_Chains implements INode {
|
||||
label: string
|
||||
|
|
@ -88,13 +88,14 @@ class POSTApiChain_Chains implements INode {
|
|||
|
||||
const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt)
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2)
|
||||
const res = await chain.run(input, [loggerHandler, handler])
|
||||
const res = await chain.run(input, [loggerHandler, handler, ...callbacks])
|
||||
return res
|
||||
} else {
|
||||
const res = await chain.run(input, [loggerHandler])
|
||||
const res = await chain.run(input, [loggerHandler, ...callbacks])
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { getBaseClasses, mapChatHistory } from '../../../src/utils'
|
|||
import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate } from 'langchain/prompts'
|
||||
import { BufferMemory } from 'langchain/memory'
|
||||
import { BaseChatModel } from 'langchain/chat_models/base'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
import { flatten } from 'lodash'
|
||||
import { Document } from 'langchain/document'
|
||||
|
||||
|
|
@ -111,13 +111,14 @@ class ConversationChain_Chains implements INode {
|
|||
}
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
|
||||
const res = await chain.call({ input }, [loggerHandler, handler])
|
||||
const res = await chain.call({ input }, [loggerHandler, handler, ...callbacks])
|
||||
return res?.response
|
||||
} else {
|
||||
const res = await chain.call({ input }, [loggerHandler])
|
||||
const res = await chain.call({ input }, [loggerHandler, ...callbacks])
|
||||
return res?.response
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ConversationalRetrievalQAChain, QAChainParams } from 'langchain/chains'
|
|||
import { BaseRetriever } from 'langchain/schema/retriever'
|
||||
import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
|
||||
import { PromptTemplate } from 'langchain/prompts'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
import {
|
||||
default_map_reduce_template,
|
||||
default_qa_template,
|
||||
|
|
@ -183,6 +183,7 @@ class ConversationalRetrievalQAChain_Chains implements INode {
|
|||
}
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(
|
||||
|
|
@ -191,7 +192,7 @@ class ConversationalRetrievalQAChain_Chains implements INode {
|
|||
chainOption === 'refine' ? 4 : undefined,
|
||||
returnSourceDocuments
|
||||
)
|
||||
const res = await chain.call(obj, [loggerHandler, handler])
|
||||
const res = await chain.call(obj, [loggerHandler, handler, ...callbacks])
|
||||
if (chainOption === 'refine') {
|
||||
if (res.output_text && res.sourceDocuments) {
|
||||
return {
|
||||
|
|
@ -204,7 +205,7 @@ class ConversationalRetrievalQAChain_Chains implements INode {
|
|||
if (res.text && res.sourceDocuments) return res
|
||||
return res?.text
|
||||
} else {
|
||||
const res = await chain.call(obj, [loggerHandler])
|
||||
const res = await chain.call(obj, [loggerHandler, ...callbacks])
|
||||
if (res.text && res.sourceDocuments) return res
|
||||
return res?.text
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from
|
|||
import { getBaseClasses, handleEscapeCharacters } from '../../../src/utils'
|
||||
import { LLMChain } from 'langchain/chains'
|
||||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
|
||||
class LLMChain_Chains implements INode {
|
||||
label: string
|
||||
|
|
@ -70,7 +70,7 @@ class LLMChain_Chains implements INode {
|
|||
} else if (output === 'outputPrediction') {
|
||||
const chain = new LLMChain({ llm: model, prompt, verbose: process.env.DEBUG === 'true' ? true : false })
|
||||
const inputVariables = chain.prompt.inputVariables as string[] // ["product"]
|
||||
const res = await runPrediction(inputVariables, chain, input, promptValues, options)
|
||||
const res = await runPrediction(inputVariables, chain, input, promptValues, options, nodeData)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('\x1b[92m\x1b[1m\n*****OUTPUT PREDICTION*****\n\x1b[0m\x1b[0m')
|
||||
// eslint-disable-next-line no-console
|
||||
|
|
@ -88,7 +88,7 @@ class LLMChain_Chains implements INode {
|
|||
const inputVariables = nodeData.instance.prompt.inputVariables as string[] // ["product"]
|
||||
const chain = nodeData.instance as LLMChain
|
||||
const promptValues = nodeData.inputs?.prompt.promptValues as ICommonObject
|
||||
const res = await runPrediction(inputVariables, chain, input, promptValues, options)
|
||||
const res = await runPrediction(inputVariables, chain, input, promptValues, options, nodeData)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('\x1b[93m\x1b[1m\n*****FINAL RESULT*****\n\x1b[0m\x1b[0m')
|
||||
// eslint-disable-next-line no-console
|
||||
|
|
@ -102,9 +102,12 @@ const runPrediction = async (
|
|||
chain: LLMChain,
|
||||
input: string,
|
||||
promptValuesRaw: ICommonObject,
|
||||
options: ICommonObject
|
||||
options: ICommonObject,
|
||||
nodeData: INodeData
|
||||
) => {
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
const isStreaming = options.socketIO && options.socketIOClientId
|
||||
const socketIO = isStreaming ? options.socketIO : undefined
|
||||
const socketIOClientId = isStreaming ? options.socketIOClientId : ''
|
||||
|
|
@ -131,10 +134,10 @@ const runPrediction = async (
|
|||
const options = { ...promptValues }
|
||||
if (isStreaming) {
|
||||
const handler = new CustomChainHandler(socketIO, socketIOClientId)
|
||||
const res = await chain.call(options, [loggerHandler, handler])
|
||||
const res = await chain.call(options, [loggerHandler, handler, ...callbacks])
|
||||
return res?.text
|
||||
} else {
|
||||
const res = await chain.call(options, [loggerHandler])
|
||||
const res = await chain.call(options, [loggerHandler, ...callbacks])
|
||||
return res?.text
|
||||
}
|
||||
} else if (seen.length === 1) {
|
||||
|
|
@ -147,10 +150,10 @@ const runPrediction = async (
|
|||
}
|
||||
if (isStreaming) {
|
||||
const handler = new CustomChainHandler(socketIO, socketIOClientId)
|
||||
const res = await chain.call(options, [loggerHandler, handler])
|
||||
const res = await chain.call(options, [loggerHandler, handler, ...callbacks])
|
||||
return res?.text
|
||||
} else {
|
||||
const res = await chain.call(options, [loggerHandler])
|
||||
const res = await chain.call(options, [loggerHandler, ...callbacks])
|
||||
return res?.text
|
||||
}
|
||||
} else {
|
||||
|
|
@ -159,10 +162,10 @@ const runPrediction = async (
|
|||
} else {
|
||||
if (isStreaming) {
|
||||
const handler = new CustomChainHandler(socketIO, socketIOClientId)
|
||||
const res = await chain.run(input, [loggerHandler, handler])
|
||||
const res = await chain.run(input, [loggerHandler, handler, ...callbacks])
|
||||
return res
|
||||
} else {
|
||||
const res = await chain.run(input, [loggerHandler])
|
||||
const res = await chain.run(input, [loggerHandler, ...callbacks])
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { BaseLanguageModel } from 'langchain/base_language'
|
|||
import { ICommonObject, INode, INodeData, INodeParams, PromptRetriever } from '../../../src/Interface'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { MultiPromptChain } from 'langchain/chains'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
|
||||
class MultiPromptChain_Chains implements INode {
|
||||
label: string
|
||||
|
|
@ -67,13 +67,14 @@ class MultiPromptChain_Chains implements INode {
|
|||
const obj = { input }
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2)
|
||||
const res = await chain.call(obj, [loggerHandler, handler])
|
||||
const res = await chain.call(obj, [loggerHandler, handler, ...callbacks])
|
||||
return res?.text
|
||||
} else {
|
||||
const res = await chain.call(obj, [loggerHandler])
|
||||
const res = await chain.call(obj, [loggerHandler, ...callbacks])
|
||||
return res?.text
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { BaseLanguageModel } from 'langchain/base_language'
|
|||
import { ICommonObject, INode, INodeData, INodeParams, VectorStoreRetriever } from '../../../src/Interface'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { MultiRetrievalQAChain } from 'langchain/chains'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
|
||||
class MultiRetrievalQAChain_Chains implements INode {
|
||||
label: string
|
||||
|
|
@ -75,14 +75,15 @@ class MultiRetrievalQAChain_Chains implements INode {
|
|||
|
||||
const obj = { input }
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2, returnSourceDocuments)
|
||||
const res = await chain.call(obj, [loggerHandler, handler])
|
||||
const res = await chain.call(obj, [loggerHandler, handler, ...callbacks])
|
||||
if (res.text && res.sourceDocuments) return res
|
||||
return res?.text
|
||||
} else {
|
||||
const res = await chain.call(obj, [loggerHandler])
|
||||
const res = await chain.call(obj, [loggerHandler, ...callbacks])
|
||||
if (res.text && res.sourceDocuments) return res
|
||||
return res?.text
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { RetrievalQAChain } from 'langchain/chains'
|
|||
import { BaseRetriever } from 'langchain/schema/retriever'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
|
||||
class RetrievalQAChain_Chains implements INode {
|
||||
label: string
|
||||
|
|
@ -53,13 +53,14 @@ class RetrievalQAChain_Chains implements INode {
|
|||
query: input
|
||||
}
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
|
||||
const res = await chain.call(obj, [loggerHandler, handler])
|
||||
const res = await chain.call(obj, [loggerHandler, handler, ...callbacks])
|
||||
return res?.text
|
||||
} else {
|
||||
const res = await chain.call(obj, [loggerHandler])
|
||||
const res = await chain.call(obj, [loggerHandler, ...callbacks])
|
||||
return res?.text
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { DataSource } from 'typeorm'
|
|||
import { SqlDatabase } from 'langchain/sql_db'
|
||||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { PromptTemplate, PromptTemplateInput } from 'langchain/prompts'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
import { DataSourceOptions } from 'typeorm/data-source'
|
||||
|
||||
type DatabaseType = 'sqlite' | 'postgres' | 'mssql' | 'mysql'
|
||||
|
|
@ -184,13 +184,14 @@ class SqlDatabaseChain_Chains implements INode {
|
|||
customPrompt
|
||||
)
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId, 2)
|
||||
const res = await chain.run(input, [loggerHandler, handler])
|
||||
const res = await chain.run(input, [loggerHandler, handler, ...callbacks])
|
||||
return res
|
||||
} else {
|
||||
const res = await chain.run(input, [loggerHandler])
|
||||
const res = await chain.run(input, [loggerHandler, ...callbacks])
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { getBaseClasses } from '../../../src/utils'
|
|||
import { VectorDBQAChain } from 'langchain/chains'
|
||||
import { BaseLanguageModel } from 'langchain/base_language'
|
||||
import { VectorStore } from 'langchain/vectorstores'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler'
|
||||
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
|
||||
|
||||
class VectorDBQAChain_Chains implements INode {
|
||||
label: string
|
||||
|
|
@ -57,13 +57,14 @@ class VectorDBQAChain_Chains implements INode {
|
|||
}
|
||||
|
||||
const loggerHandler = new ConsoleCallbackHandler(options.logger)
|
||||
const callbacks = await additionalCallbacks(nodeData, options)
|
||||
|
||||
if (options.socketIO && options.socketIOClientId) {
|
||||
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
|
||||
const res = await chain.call(obj, [loggerHandler, handler])
|
||||
const res = await chain.call(obj, [loggerHandler, handler, ...callbacks])
|
||||
return res?.text
|
||||
} else {
|
||||
const res = await chain.call(obj, [loggerHandler])
|
||||
const res = await chain.call(obj, [loggerHandler, ...callbacks])
|
||||
return res?.text
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@
|
|||
"google-auth-library": "^9.0.0",
|
||||
"graphql": "^16.6.0",
|
||||
"html-to-text": "^9.0.5",
|
||||
"langchain": "^0.0.133",
|
||||
"langchain": "^0.0.145",
|
||||
"langfuse-langchain": "^1.0.14-alpha.0",
|
||||
"langsmith": "^0.0.32",
|
||||
"linkifyjs": "^4.1.1",
|
||||
"mammoth": "^1.5.1",
|
||||
"moment": "^2.29.3",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@ import { BaseTracer, Run, BaseCallbackHandler } from 'langchain/callbacks'
|
|||
import { AgentAction, ChainValues } from 'langchain/schema'
|
||||
import { Logger } from 'winston'
|
||||
import { Server } from 'socket.io'
|
||||
import { Client } from 'langsmith'
|
||||
import { LangChainTracer } from 'langchain/callbacks'
|
||||
import { getCredentialData, getCredentialParam } from './utils'
|
||||
import { ICommonObject, INodeData } from './Interface'
|
||||
import CallbackHandler from 'langfuse-langchain'
|
||||
|
||||
interface AgentRun extends Run {
|
||||
actions: AgentAction[]
|
||||
|
|
@ -178,3 +183,65 @@ export class CustomChainHandler extends BaseCallbackHandler {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const additionalCallbacks = async (nodeData: INodeData, options: ICommonObject) => {
|
||||
try {
|
||||
if (!options.analytic) return []
|
||||
|
||||
const analytic = JSON.parse(options.analytic)
|
||||
const callbacks: any = []
|
||||
|
||||
for (const provider in analytic) {
|
||||
const providerStatus = analytic[provider].status as boolean
|
||||
if (providerStatus) {
|
||||
if (provider === 'langSmith') {
|
||||
const credentialId = analytic[provider].credentialId as string
|
||||
const langSmithProject = analytic[provider].projectName as string
|
||||
|
||||
const credentialData = await getCredentialData(credentialId ?? '', options)
|
||||
const langSmithApiKey = getCredentialParam('langSmithApiKey', credentialData, nodeData)
|
||||
const langSmithEndpoint = getCredentialParam('langSmithEndpoint', credentialData, nodeData)
|
||||
|
||||
const client = new Client({
|
||||
apiUrl: langSmithEndpoint ?? 'https://api.smith.langchain.com',
|
||||
apiKey: langSmithApiKey
|
||||
})
|
||||
|
||||
const tracer = new LangChainTracer({
|
||||
projectName: langSmithProject ?? 'default',
|
||||
//@ts-ignore
|
||||
client
|
||||
})
|
||||
callbacks.push(tracer)
|
||||
} else if (provider === 'langFuse') {
|
||||
const credentialId = analytic[provider].credentialId as string
|
||||
const flushAt = analytic[provider].flushAt as string
|
||||
const flushInterval = analytic[provider].flushInterval as string
|
||||
const requestTimeout = analytic[provider].requestTimeout as string
|
||||
const release = analytic[provider].release as string
|
||||
|
||||
const credentialData = await getCredentialData(credentialId ?? '', options)
|
||||
const langFuseSecretKey = getCredentialParam('langFuseSecretKey', credentialData, nodeData)
|
||||
const langFusePublicKey = getCredentialParam('langFusePublicKey', credentialData, nodeData)
|
||||
const langFuseEndpoint = getCredentialParam('langFuseEndpoint', credentialData, nodeData)
|
||||
|
||||
const langFuseOptions: ICommonObject = {
|
||||
secretKey: langFuseSecretKey,
|
||||
publicKey: langFusePublicKey,
|
||||
baseUrl: langFuseEndpoint ?? 'https://cloud.langfuse.com'
|
||||
}
|
||||
if (flushAt) langFuseOptions.flushAt = parseInt(flushAt, 10)
|
||||
if (flushInterval) langFuseOptions.flushInterval = parseInt(flushInterval, 10)
|
||||
if (requestTimeout) langFuseOptions.requestTimeout = parseInt(requestTimeout, 10)
|
||||
if (release) langFuseOptions.release = release
|
||||
|
||||
const handler = new CallbackHandler(langFuseOptions)
|
||||
callbacks.push(handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
return callbacks
|
||||
} catch (e) {
|
||||
throw new Error(e)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export interface IChatFlow {
|
|||
deployed?: boolean
|
||||
isPublic?: boolean
|
||||
apikeyid?: string
|
||||
analytic?: string
|
||||
chatbotConfig?: string
|
||||
apiConfig?: any
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export class NodesPool {
|
|||
*/
|
||||
async initialize() {
|
||||
await this.initializeNodes()
|
||||
await this.initializeCrdentials()
|
||||
await this.initializeCredentials()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -34,8 +34,6 @@ export class NodesPool {
|
|||
const newNodeInstance = new nodeModule.nodeClass()
|
||||
newNodeInstance.filePath = file
|
||||
|
||||
this.componentNodes[newNodeInstance.name] = newNodeInstance
|
||||
|
||||
// Replace file icon with absolute path
|
||||
if (
|
||||
newNodeInstance.icon &&
|
||||
|
|
@ -46,7 +44,7 @@ export class NodesPool {
|
|||
const filePath = file.replace(/\\/g, '/').split('/')
|
||||
filePath.pop()
|
||||
const nodeIconAbsolutePath = `${filePath.join('/')}/${newNodeInstance.icon}`
|
||||
this.componentNodes[newNodeInstance.name].icon = nodeIconAbsolutePath
|
||||
newNodeInstance.icon = nodeIconAbsolutePath
|
||||
|
||||
// Store icon path for componentCredentials
|
||||
if (newNodeInstance.credential) {
|
||||
|
|
@ -55,6 +53,11 @@ export class NodesPool {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
const skipCategories = ['Analytic']
|
||||
if (!skipCategories.includes(newNodeInstance.category)) {
|
||||
this.componentNodes[newNodeInstance.name] = newNodeInstance
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -64,7 +67,7 @@ export class NodesPool {
|
|||
/**
|
||||
* Initialize credentials
|
||||
*/
|
||||
private async initializeCrdentials() {
|
||||
private async initializeCredentials() {
|
||||
const packagePath = getNodeModulesPackagePath('flowise-components')
|
||||
const nodesPath = path.join(packagePath, 'dist', 'credentials')
|
||||
const nodeFiles = await this.getFiles(nodesPath)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ export class ChatFlow implements IChatFlow {
|
|||
@Column({ nullable: true, type: 'text' })
|
||||
apiConfig?: string
|
||||
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
analytic?: string
|
||||
|
||||
@CreateDateColumn()
|
||||
createdDate: Date
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddAnalytic1694432361423 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`analytic\` TEXT;`)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`chat_flow\` DROP COLUMN \`analytic\`;`)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import { ModifyChatMessage1693999022236 } from './1693999022236-ModifyChatMessag
|
|||
import { ModifyCredential1693999261583 } from './1693999261583-ModifyCredential'
|
||||
import { ModifyTool1694001465232 } from './1694001465232-ModifyTool'
|
||||
import { AddApiConfig1694099200729 } from './1694099200729-AddApiConfig'
|
||||
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
|
||||
|
||||
export const mysqlMigrations = [
|
||||
Init1693840429259,
|
||||
|
|
@ -11,5 +12,6 @@ export const mysqlMigrations = [
|
|||
ModifyChatMessage1693999022236,
|
||||
ModifyCredential1693999261583,
|
||||
ModifyTool1694001465232,
|
||||
AddApiConfig1694099200729
|
||||
AddApiConfig1694099200729,
|
||||
AddAnalytic1694432361423
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddAnalytic1694432361423 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN "analytic" TEXT;`)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "chat_flow" DROP COLUMN "analytic";`)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import { ModifyChatMessage1693996694528 } from './1693996694528-ModifyChatMessag
|
|||
import { ModifyCredential1693997070000 } from './1693997070000-ModifyCredential'
|
||||
import { ModifyTool1693997339912 } from './1693997339912-ModifyTool'
|
||||
import { AddApiConfig1694099183389 } from './1694099183389-AddApiConfig'
|
||||
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
|
||||
|
||||
export const postgresMigrations = [
|
||||
Init1693891895163,
|
||||
|
|
@ -11,5 +12,6 @@ export const postgresMigrations = [
|
|||
ModifyChatMessage1693996694528,
|
||||
ModifyCredential1693997070000,
|
||||
ModifyTool1693997339912,
|
||||
AddApiConfig1694099183389
|
||||
AddApiConfig1694099183389,
|
||||
AddAnalytic1694432361423
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddAnalytic1694432361423 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN "analytic" TEXT;`)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "chat_flow" DROP COLUMN "analytic";`)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import { ModifyChatMessage1693921865247 } from './1693921865247-ModifyChatMessag
|
|||
import { ModifyCredential1693923551694 } from './1693923551694-ModifyCredential'
|
||||
import { ModifyTool1693924207475 } from './1693924207475-ModifyTool'
|
||||
import { AddApiConfig1694090982460 } from './1694090982460-AddApiConfig'
|
||||
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
|
||||
|
||||
export const sqliteMigrations = [
|
||||
Init1693835579790,
|
||||
|
|
@ -11,5 +12,6 @@ export const sqliteMigrations = [
|
|||
ModifyChatMessage1693921865247,
|
||||
ModifyCredential1693923551694,
|
||||
ModifyTool1693924207475,
|
||||
AddApiConfig1694090982460
|
||||
AddApiConfig1694090982460,
|
||||
AddAnalytic1694432361423
|
||||
]
|
||||
|
|
|
|||
|
|
@ -980,13 +980,15 @@ export class App {
|
|||
socketIOClientId: incomingInput.socketIOClientId,
|
||||
logger,
|
||||
appDataSource: this.AppDataSource,
|
||||
databaseEntities
|
||||
databaseEntities,
|
||||
analytic: chatflow.analytic
|
||||
})
|
||||
: await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
|
||||
chatHistory: incomingInput.history,
|
||||
logger,
|
||||
appDataSource: this.AppDataSource,
|
||||
databaseEntities
|
||||
databaseEntities,
|
||||
analytic: chatflow.analytic
|
||||
})
|
||||
|
||||
logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`)
|
||||
|
|
|
|||
|
|
@ -448,10 +448,11 @@ export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig:
|
|||
// If overrideConfig[key] is object
|
||||
if (overrideConfig[config] && typeof overrideConfig[config] === 'object') {
|
||||
const nodeIds = Object.keys(overrideConfig[config])
|
||||
if (!nodeIds.includes(flowNodeData.id)) continue
|
||||
else paramsObj[config] = overrideConfig[config][flowNodeData.id]
|
||||
if (nodeIds.includes(flowNodeData.id)) {
|
||||
paramsObj[config] = overrideConfig[config][flowNodeData.id]
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
let paramValue = overrideConfig[config] ?? paramsObj[config]
|
||||
// Check if boolean
|
||||
|
|
@ -877,12 +878,14 @@ export const decryptCredentialData = async (
|
|||
* @returns {Credential}
|
||||
*/
|
||||
export const transformToCredentialEntity = async (body: ICredentialReqBody): Promise<Credential> => {
|
||||
const encryptedData = await encryptCredentialData(body.plainDataObj)
|
||||
|
||||
const credentialBody = {
|
||||
const credentialBody: ICommonObject = {
|
||||
name: body.name,
|
||||
credentialName: body.credentialName,
|
||||
encryptedData
|
||||
credentialName: body.credentialName
|
||||
}
|
||||
|
||||
if (body.plainDataObj) {
|
||||
const encryptedData = await encryptCredentialData(body.plainDataObj)
|
||||
credentialBody.encryptedData = encryptedData
|
||||
}
|
||||
|
||||
const newCredential = new Credential()
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -1,8 +1,8 @@
|
|||
// assets
|
||||
import { IconTrash, IconFileUpload, IconFileExport, IconCopy } from '@tabler/icons'
|
||||
import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch } from '@tabler/icons'
|
||||
|
||||
// constant
|
||||
const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy }
|
||||
const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch }
|
||||
|
||||
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
||||
|
||||
|
|
@ -32,6 +32,13 @@ const settings = {
|
|||
url: '',
|
||||
icon: icons.IconFileExport
|
||||
},
|
||||
{
|
||||
id: 'analyseChatflow',
|
||||
title: 'Analyse Chatflow',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconSearch
|
||||
},
|
||||
{
|
||||
id: 'deleteChatflow',
|
||||
title: 'Delete Chatflow',
|
||||
|
|
|
|||
|
|
@ -6,3 +6,4 @@ export const maxScroll = 100000
|
|||
export const baseURL = process.env.NODE_ENV === 'production' ? window.location.origin : window.location.origin.replace(':8080', ':3000')
|
||||
export const uiBaseURL = window.location.origin
|
||||
export const FLOWISE_CREDENTIAL_ID = 'FLOWISE_CREDENTIAL_ID'
|
||||
export const REDACTED_CREDENTIAL_VALUE = '_FLOWISE_BLANK_07167752-1a71-43b1-bf8f-4f32252165db'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,358 @@
|
|||
import { createPortal } from 'react-dom'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions'
|
||||
|
||||
// material-ui
|
||||
import {
|
||||
Typography,
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogActions,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText
|
||||
} from '@mui/material'
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||
import { IconX } from '@tabler/icons'
|
||||
|
||||
// Project import
|
||||
import CredentialInputHandler from 'views/canvas/CredentialInputHandler'
|
||||
import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser'
|
||||
import { SwitchInput } from 'ui-component/switch/Switch'
|
||||
import { Input } from 'ui-component/input/Input'
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
import langsmithPNG from 'assets/images/langchain.png'
|
||||
import langfusePNG from 'assets/images/langfuse.png'
|
||||
|
||||
// store
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
// API
|
||||
import chatflowsApi from 'api/chatflows'
|
||||
|
||||
const analyticProviders = [
|
||||
{
|
||||
label: 'LangSmith',
|
||||
name: 'langSmith',
|
||||
icon: langsmithPNG,
|
||||
url: 'https://smith.langchain.com',
|
||||
inputs: [
|
||||
{
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['langsmithApi']
|
||||
},
|
||||
{
|
||||
label: 'Project Name',
|
||||
name: 'projectName',
|
||||
type: 'string',
|
||||
optional: true,
|
||||
description: 'If not provided, default will be used',
|
||||
placeholder: 'default'
|
||||
},
|
||||
{
|
||||
label: 'On/Off',
|
||||
name: 'status',
|
||||
type: 'boolean',
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'LangFuse',
|
||||
name: 'langFuse',
|
||||
icon: langfusePNG,
|
||||
url: 'https://langfuse.com',
|
||||
inputs: [
|
||||
{
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['langfuseApi']
|
||||
},
|
||||
{
|
||||
label: 'Flush At',
|
||||
name: 'flushAt',
|
||||
type: 'number',
|
||||
optional: true,
|
||||
description: 'Number of queued requests'
|
||||
},
|
||||
{
|
||||
label: 'Flush Interval',
|
||||
name: 'flushInterval',
|
||||
type: 'number',
|
||||
optional: true,
|
||||
description: 'Interval in ms to flush requests'
|
||||
},
|
||||
{
|
||||
label: 'Request Timeout',
|
||||
name: 'requestTimeout',
|
||||
type: 'number',
|
||||
optional: true,
|
||||
description: 'Timeout in ms for requests'
|
||||
},
|
||||
{
|
||||
label: 'Release',
|
||||
name: 'release',
|
||||
type: 'string',
|
||||
optional: true,
|
||||
description: 'The release number/hash of the application to provide analytics grouped by release'
|
||||
},
|
||||
{
|
||||
label: 'On/Off',
|
||||
name: 'status',
|
||||
type: 'boolean',
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const AnalyseFlowDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useNotifier()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [analytic, setAnalytic] = useState({})
|
||||
const [providerExpanded, setProviderExpanded] = useState({})
|
||||
|
||||
const onSave = async () => {
|
||||
try {
|
||||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||
analytic: JSON.stringify(analytic)
|
||||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Analytic Configuration Saved',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data })
|
||||
}
|
||||
onCancel()
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save Analytic Configuration: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const setValue = (value, providerName, inputParamName) => {
|
||||
let newVal = {}
|
||||
if (!Object.prototype.hasOwnProperty.call(analytic, providerName)) {
|
||||
newVal = { ...analytic, [providerName]: {} }
|
||||
} else {
|
||||
newVal = { ...analytic }
|
||||
}
|
||||
|
||||
newVal[providerName][inputParamName] = value
|
||||
setAnalytic(newVal)
|
||||
}
|
||||
|
||||
const handleAccordionChange = (providerName) => (event, isExpanded) => {
|
||||
const accordianProviders = { ...providerExpanded }
|
||||
accordianProviders[providerName] = isExpanded
|
||||
setProviderExpanded(accordianProviders)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.chatflow && dialogProps.chatflow.analytic) {
|
||||
try {
|
||||
setAnalytic(JSON.parse(dialogProps.chatflow.analytic))
|
||||
} catch (e) {
|
||||
setAnalytic({})
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
setAnalytic({})
|
||||
setProviderExpanded({})
|
||||
}
|
||||
}, [dialogProps])
|
||||
|
||||
useEffect(() => {
|
||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
}, [show, dispatch])
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
onClose={onCancel}
|
||||
open={show}
|
||||
fullWidth
|
||||
maxWidth='sm'
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
Analyse Chatflow
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{analyticProviders.map((provider, index) => (
|
||||
<Accordion
|
||||
expanded={providerExpanded[provider.name] || false}
|
||||
onChange={handleAccordionChange(provider.name)}
|
||||
disableGutters
|
||||
key={index}
|
||||
>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls={provider.name} id={provider.name}>
|
||||
<ListItem style={{ padding: 0, margin: 0 }} alignItems='center'>
|
||||
<ListItemAvatar>
|
||||
<div
|
||||
style={{
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 10,
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
alt='AI'
|
||||
src={provider.icon}
|
||||
/>
|
||||
</div>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
sx={{ ml: 1 }}
|
||||
primary={provider.label}
|
||||
secondary={
|
||||
<a target='_blank' rel='noreferrer' href={provider.url}>
|
||||
{provider.url}
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
{analytic[provider.name] && analytic[provider.name].status && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignContent: 'center',
|
||||
alignItems: 'center',
|
||||
background: '#d8f3dc',
|
||||
borderRadius: 15,
|
||||
padding: 5,
|
||||
paddingLeft: 7,
|
||||
paddingRight: 7,
|
||||
marginRight: 10
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 15,
|
||||
height: 15,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: '#70e000'
|
||||
}}
|
||||
/>
|
||||
<span style={{ color: '#006400', marginLeft: 10 }}>ON</span>
|
||||
</div>
|
||||
)}
|
||||
</ListItem>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{provider.inputs.map((inputParam, index) => (
|
||||
<Box key={index} sx={{ p: 2 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<Typography>
|
||||
{inputParam.label}
|
||||
{!inputParam.optional && <span style={{ color: 'red' }}> *</span>}
|
||||
{inputParam.description && (
|
||||
<TooltipWithParser style={{ marginLeft: 10 }} title={inputParam.description} />
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
{providerExpanded[provider.name] && inputParam.type === 'credential' && (
|
||||
<CredentialInputHandler
|
||||
data={analytic[provider.name] ? { credential: analytic[provider.name].credentialId } : {}}
|
||||
inputParam={inputParam}
|
||||
onSelect={(newValue) => setValue(newValue, provider.name, 'credentialId')}
|
||||
/>
|
||||
)}
|
||||
{providerExpanded[provider.name] && inputParam.type === 'boolean' && (
|
||||
<SwitchInput
|
||||
onChange={(newValue) => setValue(newValue, provider.name, inputParam.name)}
|
||||
value={
|
||||
analytic[provider.name]
|
||||
? analytic[provider.name][inputParam.name]
|
||||
: inputParam.default ?? false
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{providerExpanded[provider.name] &&
|
||||
(inputParam.type === 'string' ||
|
||||
inputParam.type === 'password' ||
|
||||
inputParam.type === 'number') && (
|
||||
<Input
|
||||
inputParam={inputParam}
|
||||
onChange={(newValue) => setValue(newValue, provider.name, inputParam.name)}
|
||||
value={
|
||||
analytic[provider.name]
|
||||
? analytic[provider.name][inputParam.name]
|
||||
: inputParam.default ?? ''
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<StyledButton variant='contained' onClick={onSave}>
|
||||
Save
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
AnalyseFlowDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func
|
||||
}
|
||||
|
||||
export default AnalyseFlowDialog
|
||||
|
|
@ -22,7 +22,7 @@ export const SwitchInput = ({ value, onChange, disabled = false }) => {
|
|||
}
|
||||
|
||||
SwitchInput.propTypes = {
|
||||
value: PropTypes.string,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
||||
onChange: PropTypes.func,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck,
|
|||
import Settings from 'views/settings'
|
||||
import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog'
|
||||
import APICodeDialog from 'views/chatflows/APICodeDialog'
|
||||
import AnalyseFlowDialog from 'ui-component/dialog/AnalyseFlowDialog'
|
||||
|
||||
// API
|
||||
import chatflowsApi from 'api/chatflows'
|
||||
|
|
@ -41,6 +42,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||
const [flowDialogOpen, setFlowDialogOpen] = useState(false)
|
||||
const [apiDialogOpen, setAPIDialogOpen] = useState(false)
|
||||
const [apiDialogProps, setAPIDialogProps] = useState({})
|
||||
const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false)
|
||||
const [analyseDialogProps, setAnalyseDialogProps] = useState({})
|
||||
|
||||
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
||||
const canvas = useSelector((state) => state.canvas)
|
||||
|
|
@ -50,6 +53,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||
|
||||
if (setting === 'deleteChatflow') {
|
||||
handleDeleteFlow()
|
||||
} else if (setting === 'analyseChatflow') {
|
||||
setAnalyseDialogProps({
|
||||
title: 'Analyse Chatflow',
|
||||
chatflow: chatflow
|
||||
})
|
||||
setAnalyseDialogOpen(true)
|
||||
} else if (setting === 'duplicateChatflow') {
|
||||
try {
|
||||
localStorage.setItem('duplicatedFlowData', chatflow.flowData)
|
||||
|
|
@ -357,6 +366,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||
onConfirm={onConfirmSaveName}
|
||||
/>
|
||||
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
|
||||
<AnalyseFlowDialog show={analyseDialogOpen} dialogProps={analyseDialogProps} onCancel={() => setAnalyseDialogOpen(false)} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import useApi from 'hooks/useApi'
|
|||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
// const
|
||||
import { baseURL } from 'store/constant'
|
||||
import { baseURL, REDACTED_CREDENTIAL_VALUE } from 'store/constant'
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||
|
||||
const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
|
|
@ -118,7 +118,7 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) =>
|
|||
onConfirm(createResp.data.id)
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
const errorData = typeof err === 'string' ? err : err.response.data || `${err.response.status}: ${err.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to add new Credential: ${errorData}`,
|
||||
options: {
|
||||
|
|
@ -138,11 +138,20 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) =>
|
|||
|
||||
const saveCredential = async () => {
|
||||
try {
|
||||
const saveResp = await credentialsApi.updateCredential(credential.id, {
|
||||
const saveObj = {
|
||||
name,
|
||||
credentialName: componentCredential.name,
|
||||
plainDataObj: credentialData
|
||||
})
|
||||
credentialName: componentCredential.name
|
||||
}
|
||||
|
||||
let plainDataObj = {}
|
||||
for (const key in credentialData) {
|
||||
if (credentialData[key] !== REDACTED_CREDENTIAL_VALUE) {
|
||||
plainDataObj[key] = credentialData[key]
|
||||
}
|
||||
}
|
||||
if (Object.keys(plainDataObj).length) saveObj.plainDataObj = plainDataObj
|
||||
|
||||
const saveResp = await credentialsApi.updateCredential(credential.id, saveObj)
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Credential saved',
|
||||
|
|
|
|||
Loading…
Reference in New Issue