add api config
This commit is contained in:
parent
57b8620b93
commit
8d3a374257
|
|
@ -38,5 +38,8 @@
|
||||||
**/*.key
|
**/*.key
|
||||||
**/api.json
|
**/api.json
|
||||||
|
|
||||||
|
## uploads
|
||||||
|
**/uploads
|
||||||
|
|
||||||
## compressed
|
## compressed
|
||||||
**/*.tgz
|
**/*.tgz
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
import { TextSplitter } from 'langchain/text_splitter'
|
import { TextSplitter } from 'langchain/text_splitter'
|
||||||
import { CSVLoader } from 'langchain/document_loaders/fs/csv'
|
import { CSVLoader } from 'langchain/document_loaders/fs/csv'
|
||||||
|
import { getBlob } from '../../../src/utils'
|
||||||
|
|
||||||
class Csv_DocumentLoaders implements INode {
|
class Csv_DocumentLoaders implements INode {
|
||||||
label: string
|
label: string
|
||||||
|
|
@ -48,11 +49,8 @@ class Csv_DocumentLoaders implements INode {
|
||||||
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
|
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
|
||||||
const csvFileBase64 = nodeData.inputs?.csvFile as string
|
const csvFileBase64 = nodeData.inputs?.csvFile as string
|
||||||
const columnName = nodeData.inputs?.columnName as string
|
const columnName = nodeData.inputs?.columnName as string
|
||||||
const splitDataURI = csvFileBase64.split(',')
|
|
||||||
splitDataURI.pop()
|
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
|
||||||
|
|
||||||
const blob = new Blob([bf])
|
const blob = new Blob(getBlob(csvFileBase64))
|
||||||
const loader = new CSVLoader(blob, columnName.trim().length === 0 ? undefined : columnName.trim())
|
const loader = new CSVLoader(blob, columnName.trim().length === 0 ? undefined : columnName.trim())
|
||||||
|
|
||||||
if (textSplitter) {
|
if (textSplitter) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
import { TextSplitter } from 'langchain/text_splitter'
|
import { TextSplitter } from 'langchain/text_splitter'
|
||||||
import { DocxLoader } from 'langchain/document_loaders/fs/docx'
|
import { DocxLoader } from 'langchain/document_loaders/fs/docx'
|
||||||
|
import { getBlob } from '../../../src/utils'
|
||||||
|
|
||||||
class Docx_DocumentLoaders implements INode {
|
class Docx_DocumentLoaders implements INode {
|
||||||
label: string
|
label: string
|
||||||
|
|
@ -39,11 +40,8 @@ class Docx_DocumentLoaders implements INode {
|
||||||
async init(nodeData: INodeData): Promise<any> {
|
async init(nodeData: INodeData): Promise<any> {
|
||||||
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
|
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
|
||||||
const docxFileBase64 = nodeData.inputs?.docxFile as string
|
const docxFileBase64 = nodeData.inputs?.docxFile as string
|
||||||
const splitDataURI = docxFileBase64.split(',')
|
|
||||||
splitDataURI.pop()
|
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
|
||||||
|
|
||||||
const blob = new Blob([bf])
|
const blob = new Blob(getBlob(docxFileBase64))
|
||||||
const loader = new DocxLoader(blob)
|
const loader = new DocxLoader(blob)
|
||||||
|
|
||||||
if (textSplitter) {
|
if (textSplitter) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
import { TextSplitter } from 'langchain/text_splitter'
|
import { TextSplitter } from 'langchain/text_splitter'
|
||||||
import { JSONLoader } from 'langchain/document_loaders/fs/json'
|
import { JSONLoader } from 'langchain/document_loaders/fs/json'
|
||||||
|
import { getBlob } from '../../../src/utils'
|
||||||
|
|
||||||
class Json_DocumentLoaders implements INode {
|
class Json_DocumentLoaders implements INode {
|
||||||
label: string
|
label: string
|
||||||
|
|
@ -48,9 +49,6 @@ class Json_DocumentLoaders implements INode {
|
||||||
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
|
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
|
||||||
const jsonFileBase64 = nodeData.inputs?.jsonFile as string
|
const jsonFileBase64 = nodeData.inputs?.jsonFile as string
|
||||||
const pointersName = nodeData.inputs?.pointersName as string
|
const pointersName = nodeData.inputs?.pointersName as string
|
||||||
const splitDataURI = jsonFileBase64.split(',')
|
|
||||||
splitDataURI.pop()
|
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
|
||||||
|
|
||||||
let pointers: string[] = []
|
let pointers: string[] = []
|
||||||
if (pointersName) {
|
if (pointersName) {
|
||||||
|
|
@ -58,7 +56,7 @@ class Json_DocumentLoaders implements INode {
|
||||||
pointers = outputString.split(',').map((pointer) => '/' + pointer.trim())
|
pointers = outputString.split(',').map((pointer) => '/' + pointer.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = new Blob([bf])
|
const blob = new Blob(getBlob(jsonFileBase64))
|
||||||
const loader = new JSONLoader(blob, pointers.length != 0 ? pointers : undefined)
|
const loader = new JSONLoader(blob, pointers.length != 0 ? pointers : undefined)
|
||||||
|
|
||||||
if (textSplitter) {
|
if (textSplitter) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
import { TextSplitter } from 'langchain/text_splitter'
|
import { TextSplitter } from 'langchain/text_splitter'
|
||||||
import { PDFLoader } from 'langchain/document_loaders/fs/pdf'
|
import { PDFLoader } from 'langchain/document_loaders/fs/pdf'
|
||||||
|
import { getBlob } from '../../../src/utils'
|
||||||
|
|
||||||
class Pdf_DocumentLoaders implements INode {
|
class Pdf_DocumentLoaders implements INode {
|
||||||
label: string
|
label: string
|
||||||
|
|
@ -57,10 +58,7 @@ class Pdf_DocumentLoaders implements INode {
|
||||||
const pdfFileBase64 = nodeData.inputs?.pdfFile as string
|
const pdfFileBase64 = nodeData.inputs?.pdfFile as string
|
||||||
const usage = nodeData.inputs?.usage as string
|
const usage = nodeData.inputs?.usage as string
|
||||||
|
|
||||||
const splitDataURI = pdfFileBase64.split(',')
|
const blob = new Blob(getBlob(pdfFileBase64))
|
||||||
splitDataURI.pop()
|
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
|
||||||
const blob = new Blob([bf])
|
|
||||||
|
|
||||||
if (usage === 'perFile') {
|
if (usage === 'perFile') {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
import { TextSplitter } from 'langchain/text_splitter'
|
import { TextSplitter } from 'langchain/text_splitter'
|
||||||
import { TextLoader } from 'langchain/document_loaders/fs/text'
|
import { TextLoader } from 'langchain/document_loaders/fs/text'
|
||||||
|
import { getBlob } from '../../../src/utils'
|
||||||
|
|
||||||
class Text_DocumentLoaders implements INode {
|
class Text_DocumentLoaders implements INode {
|
||||||
label: string
|
label: string
|
||||||
|
|
@ -39,11 +40,8 @@ class Text_DocumentLoaders implements INode {
|
||||||
async init(nodeData: INodeData): Promise<any> {
|
async init(nodeData: INodeData): Promise<any> {
|
||||||
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
|
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
|
||||||
const txtFileBase64 = nodeData.inputs?.txtFile as string
|
const txtFileBase64 = nodeData.inputs?.txtFile as string
|
||||||
const splitDataURI = txtFileBase64.split(',')
|
|
||||||
splitDataURI.pop()
|
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
|
||||||
|
|
||||||
const blob = new Blob([bf])
|
const blob = new Blob(getBlob(txtFileBase64))
|
||||||
const loader = new TextLoader(blob)
|
const loader = new TextLoader(blob)
|
||||||
|
|
||||||
if (textSplitter) {
|
if (textSplitter) {
|
||||||
|
|
|
||||||
|
|
@ -149,3 +149,27 @@ export const getInputVariables = (paramValue: string): string[] => {
|
||||||
}
|
}
|
||||||
return inputVariables
|
return inputVariables
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get blob
|
||||||
|
* @param {string} fileBase64Str
|
||||||
|
* @returns {Buffer[]}
|
||||||
|
*/
|
||||||
|
export const getBlob = (fileBase64Str: string) => {
|
||||||
|
let bufferArray: Buffer[] = []
|
||||||
|
let files: string[] = []
|
||||||
|
|
||||||
|
if (fileBase64Str.startsWith('[') && fileBase64Str.endsWith(']')) {
|
||||||
|
files = JSON.parse(fileBase64Str)
|
||||||
|
} else {
|
||||||
|
files = [fileBase64Str]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const splitDataURI = file.split(',')
|
||||||
|
splitDataURI.pop()
|
||||||
|
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
|
bufferArray.push(bf)
|
||||||
|
}
|
||||||
|
return bufferArray
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,12 +54,14 @@
|
||||||
"flowise-components": "*",
|
"flowise-components": "*",
|
||||||
"flowise-ui": "*",
|
"flowise-ui": "*",
|
||||||
"moment-timezone": "^0.5.34",
|
"moment-timezone": "^0.5.34",
|
||||||
|
"multer": "^1.4.5-lts.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"sqlite3": "^5.1.6",
|
"sqlite3": "^5.1.6",
|
||||||
"typeorm": "^0.3.6"
|
"typeorm": "^0.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
|
"@types/multer": "^1.4.7",
|
||||||
"concurrently": "^7.1.0",
|
"concurrently": "^7.1.0",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
"oclif": "^3",
|
"oclif": "^3",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { ICommonObject } from 'flowise-components'
|
||||||
import { IActiveChatflows, INodeData, IReactFlowNode } from './Interface'
|
import { IActiveChatflows, INodeData, IReactFlowNode } from './Interface'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -12,13 +13,15 @@ export class ChatflowPool {
|
||||||
* @param {string} chatflowid
|
* @param {string} chatflowid
|
||||||
* @param {INodeData} endingNodeData
|
* @param {INodeData} endingNodeData
|
||||||
* @param {IReactFlowNode[]} startingNodes
|
* @param {IReactFlowNode[]} startingNodes
|
||||||
|
* @param {ICommonObject} overrideConfig
|
||||||
*/
|
*/
|
||||||
add(chatflowid: string, endingNodeData: INodeData, startingNodes: IReactFlowNode[]) {
|
add(chatflowid: string, endingNodeData: INodeData, startingNodes: IReactFlowNode[], overrideConfig?: ICommonObject) {
|
||||||
this.activeChatflows[chatflowid] = {
|
this.activeChatflows[chatflowid] = {
|
||||||
startingNodes,
|
startingNodes,
|
||||||
endingNodeData,
|
endingNodeData,
|
||||||
inSync: true
|
inSync: true
|
||||||
}
|
}
|
||||||
|
if (overrideConfig) this.activeChatflows[chatflowid].overrideConfig = overrideConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { INode, INodeData as INodeDataFromComponent, INodeParams } from 'flowise-components'
|
import { ICommonObject, INode, INodeData as INodeDataFromComponent, INodeParams } from 'flowise-components'
|
||||||
|
|
||||||
export type MessageType = 'apiMessage' | 'userMessage'
|
export type MessageType = 'apiMessage' | 'userMessage'
|
||||||
|
|
||||||
|
|
@ -114,6 +114,7 @@ export interface IMessage {
|
||||||
export interface IncomingInput {
|
export interface IncomingInput {
|
||||||
question: string
|
question: string
|
||||||
history: IMessage[]
|
history: IMessage[]
|
||||||
|
overrideConfig?: ICommonObject
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IActiveChatflows {
|
export interface IActiveChatflows {
|
||||||
|
|
@ -121,5 +122,13 @@ export interface IActiveChatflows {
|
||||||
startingNodes: IReactFlowNode[]
|
startingNodes: IReactFlowNode[]
|
||||||
endingNodeData: INodeData
|
endingNodeData: INodeData
|
||||||
inSync: boolean
|
inSync: boolean
|
||||||
|
overrideConfig?: ICommonObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IOverrideConfig {
|
||||||
|
node: string
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import express, { Request, Response } from 'express'
|
import express, { Request, Response } from 'express'
|
||||||
|
import multer from 'multer'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
import http from 'http'
|
import http from 'http'
|
||||||
|
|
@ -17,7 +18,10 @@ import {
|
||||||
addAPIKey,
|
addAPIKey,
|
||||||
updateAPIKey,
|
updateAPIKey,
|
||||||
deleteAPIKey,
|
deleteAPIKey,
|
||||||
compareKeys
|
compareKeys,
|
||||||
|
mapMimeTypeToInputField,
|
||||||
|
findAvailableConfigs,
|
||||||
|
isSameOverrideConfig
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import { getDataSource } from './DataSource'
|
import { getDataSource } from './DataSource'
|
||||||
|
|
@ -25,6 +29,7 @@ import { NodesPool } from './NodesPool'
|
||||||
import { ChatFlow } from './entity/ChatFlow'
|
import { ChatFlow } from './entity/ChatFlow'
|
||||||
import { ChatMessage } from './entity/ChatMessage'
|
import { ChatMessage } from './entity/ChatMessage'
|
||||||
import { ChatflowPool } from './ChatflowPool'
|
import { ChatflowPool } from './ChatflowPool'
|
||||||
|
import { ICommonObject } from 'flowise-components'
|
||||||
|
|
||||||
export class App {
|
export class App {
|
||||||
app: express.Application
|
app: express.Application
|
||||||
|
|
@ -66,6 +71,8 @@ export class App {
|
||||||
this.app.use(cors({ credentials: true, origin: 'http://localhost:8080' }))
|
this.app.use(cors({ credentials: true, origin: 'http://localhost:8080' }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const upload = multer({ dest: `${path.join(__dirname, '..', 'uploads')}/` })
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Nodes
|
// Nodes
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
@ -199,6 +206,47 @@ export class App {
|
||||||
return res.json(results)
|
return res.json(results)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Configuration
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
this.app.get('/api/v1/flow-config/:id', async (req: Request, res: Response) => {
|
||||||
|
const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({
|
||||||
|
id: req.params.id
|
||||||
|
})
|
||||||
|
if (!chatflow) return res.status(404).send(`Chatflow ${req.params.id} not found`)
|
||||||
|
const flowData = chatflow.flowData
|
||||||
|
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
|
||||||
|
const nodes = parsedFlowData.nodes
|
||||||
|
const availableConfigs = findAvailableConfigs(nodes)
|
||||||
|
return res.json(availableConfigs)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.app.post('/api/v1/flow-config/:id', upload.array('files'), async (req: Request, res: Response) => {
|
||||||
|
const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({
|
||||||
|
id: req.params.id
|
||||||
|
})
|
||||||
|
if (!chatflow) return res.status(404).send(`Chatflow ${req.params.id} not found`)
|
||||||
|
await this.validateKey(req, res, chatflow)
|
||||||
|
|
||||||
|
const overrideConfig: ICommonObject = { ...req.body }
|
||||||
|
const files = req.files as any[]
|
||||||
|
if (!files || !files.length) return
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const fileData = fs.readFileSync(file.path, { encoding: 'base64' })
|
||||||
|
const dataBase64String = `data:${file.mimetype};base64,${fileData},filename:${file.filename}`
|
||||||
|
|
||||||
|
const fileInputField = mapMimeTypeToInputField(file.mimetype)
|
||||||
|
if (overrideConfig[fileInputField]) {
|
||||||
|
overrideConfig[fileInputField] = JSON.stringify([...JSON.parse(overrideConfig[fileInputField]), dataBase64String])
|
||||||
|
} else {
|
||||||
|
overrideConfig[fileInputField] = JSON.stringify([dataBase64String])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res.json(overrideConfig)
|
||||||
|
})
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Prediction
|
// Prediction
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
@ -281,19 +329,7 @@ export class App {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async processPrediction(req: Request, res: Response, isInternal = false) {
|
async validateKey(req: Request, res: Response, chatflow: ChatFlow) {
|
||||||
try {
|
|
||||||
const chatflowid = req.params.id
|
|
||||||
const incomingInput: IncomingInput = req.body
|
|
||||||
|
|
||||||
let nodeToExecuteData: INodeData
|
|
||||||
|
|
||||||
const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({
|
|
||||||
id: chatflowid
|
|
||||||
})
|
|
||||||
if (!chatflow) return res.status(404).send(`Chatflow ${chatflowid} not found`)
|
|
||||||
|
|
||||||
if (!isInternal) {
|
|
||||||
const chatFlowApiKeyId = chatflow.apikeyid
|
const chatFlowApiKeyId = chatflow.apikeyid
|
||||||
const authorizationHeader = (req.headers['Authorization'] as string) ?? (req.headers['authorization'] as string) ?? ''
|
const authorizationHeader = (req.headers['Authorization'] as string) ?? (req.headers['authorization'] as string) ?? ''
|
||||||
|
|
||||||
|
|
@ -307,14 +343,32 @@ export class App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if:
|
async processPrediction(req: Request, res: Response, isInternal = false) {
|
||||||
|
try {
|
||||||
|
const chatflowid = req.params.id
|
||||||
|
const incomingInput: IncomingInput = req.body
|
||||||
|
|
||||||
|
let nodeToExecuteData: INodeData
|
||||||
|
|
||||||
|
const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({
|
||||||
|
id: chatflowid
|
||||||
|
})
|
||||||
|
if (!chatflow) return res.status(404).send(`Chatflow ${chatflowid} not found`)
|
||||||
|
|
||||||
|
if (!isInternal) {
|
||||||
|
await this.validateKey(req, res, chatflow)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Don't rebuild the flow (to avoid duplicated upsert, recomputation) when all these conditions met:
|
||||||
* - Node Data already exists in pool
|
* - Node Data already exists in pool
|
||||||
* - Still in sync (i.e the flow has not been modified since)
|
* - Still in sync (i.e the flow has not been modified since)
|
||||||
|
* - Existing overrideConfig and new overrideConfig are the same
|
||||||
* - Flow doesn't start with nodes that depend on incomingInput.question
|
* - Flow doesn't start with nodes that depend on incomingInput.question
|
||||||
***/
|
***/
|
||||||
if (
|
if (
|
||||||
Object.prototype.hasOwnProperty.call(this.chatflowPool.activeChatflows, chatflowid) &&
|
Object.prototype.hasOwnProperty.call(this.chatflowPool.activeChatflows, chatflowid) &&
|
||||||
this.chatflowPool.activeChatflows[chatflowid].inSync &&
|
this.chatflowPool.activeChatflows[chatflowid].inSync &&
|
||||||
|
isSameOverrideConfig(this.chatflowPool.activeChatflows[chatflowid].overrideConfig, incomingInput.overrideConfig) &&
|
||||||
!isStartNodeDependOnInput(this.chatflowPool.activeChatflows[chatflowid].startingNodes)
|
!isStartNodeDependOnInput(this.chatflowPool.activeChatflows[chatflowid].startingNodes)
|
||||||
) {
|
) {
|
||||||
nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData
|
nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData
|
||||||
|
|
@ -359,7 +413,8 @@ export class App {
|
||||||
graph,
|
graph,
|
||||||
depthQueue,
|
depthQueue,
|
||||||
this.nodesPool.componentNodes,
|
this.nodesPool.componentNodes,
|
||||||
incomingInput.question
|
incomingInput.question,
|
||||||
|
incomingInput?.overrideConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId)
|
const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId)
|
||||||
|
|
@ -369,7 +424,7 @@ export class App {
|
||||||
nodeToExecuteData = reactFlowNodeData
|
nodeToExecuteData = reactFlowNodeData
|
||||||
|
|
||||||
const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id))
|
const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id))
|
||||||
this.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes)
|
this.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, incomingInput?.overrideConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeInstanceFilePath = this.nodesPool.componentNodes[nodeToExecuteData.name].filePath as string
|
const nodeInstanceFilePath = this.nodesPool.componentNodes[nodeToExecuteData.name].filePath as string
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ import {
|
||||||
IReactFlowEdge,
|
IReactFlowEdge,
|
||||||
IReactFlowNode,
|
IReactFlowNode,
|
||||||
IVariableDict,
|
IVariableDict,
|
||||||
INodeData
|
INodeData,
|
||||||
|
IOverrideConfig
|
||||||
} from '../Interface'
|
} from '../Interface'
|
||||||
import { cloneDeep, get } from 'lodash'
|
import { cloneDeep, get } from 'lodash'
|
||||||
import { ICommonObject, getInputVariables } from 'flowise-components'
|
import { ICommonObject, getInputVariables } from 'flowise-components'
|
||||||
|
|
@ -180,7 +181,8 @@ export const buildLangchain = async (
|
||||||
graph: INodeDirectedGraph,
|
graph: INodeDirectedGraph,
|
||||||
depthQueue: IDepthQueue,
|
depthQueue: IDepthQueue,
|
||||||
componentNodes: IComponentNodes,
|
componentNodes: IComponentNodes,
|
||||||
question: string
|
question: string,
|
||||||
|
overrideConfig?: ICommonObject
|
||||||
) => {
|
) => {
|
||||||
const flowNodes = cloneDeep(reactFlowNodes)
|
const flowNodes = cloneDeep(reactFlowNodes)
|
||||||
|
|
||||||
|
|
@ -208,7 +210,9 @@ export const buildLangchain = async (
|
||||||
const nodeModule = await import(nodeInstanceFilePath)
|
const nodeModule = await import(nodeInstanceFilePath)
|
||||||
const newNodeInstance = new nodeModule.nodeClass()
|
const newNodeInstance = new nodeModule.nodeClass()
|
||||||
|
|
||||||
const reactFlowNodeData: INodeData = resolveVariables(reactFlowNode.data, flowNodes, question)
|
let flowNodeData = cloneDeep(reactFlowNode.data)
|
||||||
|
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
|
||||||
|
const reactFlowNodeData: INodeData = resolveVariables(flowNodeData, flowNodes, question)
|
||||||
|
|
||||||
flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question)
|
flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|
@ -342,7 +346,24 @@ export const resolveVariables = (reactFlowNodeData: INodeData, reactFlowNodes: I
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const paramsObj = (flowNodeData as any)[types]
|
const paramsObj = flowNodeData[types] ?? {}
|
||||||
|
|
||||||
|
getParamValues(paramsObj)
|
||||||
|
|
||||||
|
return flowNodeData
|
||||||
|
}
|
||||||
|
|
||||||
|
export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig: ICommonObject) => {
|
||||||
|
const types = 'inputs'
|
||||||
|
|
||||||
|
const getParamValues = (paramsObj: ICommonObject) => {
|
||||||
|
for (const key in paramsObj) {
|
||||||
|
const paramValue: string = paramsObj[key]
|
||||||
|
paramsObj[key] = overrideConfig[key] ?? paramValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramsObj = flowNodeData[types] ?? {}
|
||||||
|
|
||||||
getParamValues(paramsObj)
|
getParamValues(paramsObj)
|
||||||
|
|
||||||
|
|
@ -365,6 +386,24 @@ export const isStartNodeDependOnInput = (startingNodes: IReactFlowNode[]): boole
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild flow if new override config is provided
|
||||||
|
* @param {ICommonObject} existingOverrideConfig
|
||||||
|
* @param {ICommonObject} newOverrideConfig
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const isSameOverrideConfig = (existingOverrideConfig?: ICommonObject, newOverrideConfig?: ICommonObject): boolean => {
|
||||||
|
if (
|
||||||
|
existingOverrideConfig &&
|
||||||
|
Object.keys(existingOverrideConfig).length &&
|
||||||
|
newOverrideConfig &&
|
||||||
|
Object.keys(newOverrideConfig).length &&
|
||||||
|
JSON.stringify(existingOverrideConfig) === JSON.stringify(newOverrideConfig)
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the api key path
|
* Returns the api key path
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
|
|
@ -480,3 +519,67 @@ export const deleteAPIKey = async (keyIdToDelete: string): Promise<ICommonObject
|
||||||
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(result), 'utf8')
|
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(result), 'utf8')
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map MimeType to InputField
|
||||||
|
* @param {string} mimeType
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
export const mapMimeTypeToInputField = (mimeType: string) => {
|
||||||
|
switch (mimeType) {
|
||||||
|
case 'text/plain':
|
||||||
|
return 'txtFile'
|
||||||
|
case 'application/pdf':
|
||||||
|
return 'pdfFile'
|
||||||
|
case 'application/json':
|
||||||
|
return 'jsonFile'
|
||||||
|
case 'text/csv':
|
||||||
|
return 'csvFile'
|
||||||
|
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
||||||
|
return 'docxFile'
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all available inpur params config
|
||||||
|
* @param {IReactFlowNode[]} reactFlowNodes
|
||||||
|
* @returns {Promise<IOverrideConfig[]>}
|
||||||
|
*/
|
||||||
|
export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => {
|
||||||
|
const configs: IOverrideConfig[] = []
|
||||||
|
|
||||||
|
for (const flowNode of reactFlowNodes) {
|
||||||
|
for (const inputParam of flowNode.data.inputParams) {
|
||||||
|
let obj: IOverrideConfig
|
||||||
|
if (inputParam.type === 'password' || inputParam.type === 'options') {
|
||||||
|
obj = {
|
||||||
|
node: flowNode.data.label,
|
||||||
|
label: inputParam.label,
|
||||||
|
name: inputParam.name,
|
||||||
|
type: 'string'
|
||||||
|
}
|
||||||
|
} else if (inputParam.type === 'file') {
|
||||||
|
obj = {
|
||||||
|
node: flowNode.data.label,
|
||||||
|
label: inputParam.label,
|
||||||
|
name: 'files',
|
||||||
|
type: inputParam.fileType ?? inputParam.type
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj = {
|
||||||
|
node: flowNode.data.label,
|
||||||
|
label: inputParam.label,
|
||||||
|
name: inputParam.name,
|
||||||
|
type: inputParam.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!configs.some((config) => JSON.stringify(config) === JSON.stringify(obj))) {
|
||||||
|
configs.push(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configs
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import client from './client'
|
||||||
|
|
||||||
|
const getConfig = (id) => client.get(`/flow-config/${id}`)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getConfig
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { FormControlLabel, Checkbox } from '@mui/material'
|
||||||
|
|
||||||
|
export const CheckboxInput = ({ value, label, onChange, disabled = false }) => {
|
||||||
|
const [myValue, setMyValue] = useState(value)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormControlLabel
|
||||||
|
sx={{ mt: 1, width: '100%' }}
|
||||||
|
size='small'
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
disabled={disabled}
|
||||||
|
checked={myValue}
|
||||||
|
onChange={(event) => {
|
||||||
|
setMyValue(event.target.checked)
|
||||||
|
onChange(event.target.checked)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={label}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckboxInput.propTypes = {
|
||||||
|
value: PropTypes.bool,
|
||||||
|
label: PropTypes.string,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
disabled: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
@ -22,9 +22,12 @@ import cURLSVG from 'assets/images/cURL.svg'
|
||||||
// API
|
// API
|
||||||
import apiKeyApi from 'api/apikey'
|
import apiKeyApi from 'api/apikey'
|
||||||
import chatflowsApi from 'api/chatflows'
|
import chatflowsApi from 'api/chatflows'
|
||||||
|
import configApi from 'api/config'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import useApi from 'hooks/useApi'
|
import useApi from 'hooks/useApi'
|
||||||
|
import { CheckboxInput } from 'ui-component/checkbox/Checkbox'
|
||||||
|
import { TableViewOnly } from 'ui-component/table/Table'
|
||||||
|
|
||||||
function TabPanel(props) {
|
function TabPanel(props) {
|
||||||
const { children, value, index, ...other } = props
|
const { children, value, index, ...other } = props
|
||||||
|
|
@ -54,6 +57,66 @@ function a11yProps(index) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unshiftFiles = (configData) => {
|
||||||
|
const filesConfig = configData.find((config) => config.name === 'files')
|
||||||
|
if (filesConfig) {
|
||||||
|
configData = configData.filter((config) => config.name !== 'files')
|
||||||
|
configData.unshift(filesConfig)
|
||||||
|
}
|
||||||
|
return configData
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFormDataExamplesForJS = (configData) => {
|
||||||
|
let finalStr = ''
|
||||||
|
configData = unshiftFiles(configData)
|
||||||
|
const loop = Math.min(configData.length, 4)
|
||||||
|
for (let i = 0; i < loop; i += 1) {
|
||||||
|
const config = configData[i]
|
||||||
|
let exampleVal = `"example"`
|
||||||
|
if (config.type === 'string') exampleVal = `"example"`
|
||||||
|
else if (config.type === 'boolean') exampleVal = `true`
|
||||||
|
else if (config.type === 'number') exampleVal = `1`
|
||||||
|
else if (config.name === 'files') exampleVal = `input.files[0]`
|
||||||
|
finalStr += `formData.append("${config.name}", ${exampleVal})\n`
|
||||||
|
}
|
||||||
|
return finalStr
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFormDataExamplesForPython = (configData) => {
|
||||||
|
let finalStr = ''
|
||||||
|
configData = unshiftFiles(configData)
|
||||||
|
const loop = Math.min(configData.length, 4)
|
||||||
|
for (let i = 0; i < loop; i += 1) {
|
||||||
|
const config = configData[i]
|
||||||
|
let exampleVal = `"example"`
|
||||||
|
if (config.type === 'string') exampleVal = `"example"`
|
||||||
|
else if (config.type === 'boolean') exampleVal = `true`
|
||||||
|
else if (config.type === 'number') exampleVal = `1`
|
||||||
|
else if (config.name === 'files') exampleVal = `('example${config.type}', open('example${config.type}', 'rb'))`
|
||||||
|
finalStr += `\n "${config.name}": ${exampleVal}`
|
||||||
|
if (i === loop - 1) finalStr += `\n`
|
||||||
|
}
|
||||||
|
return finalStr
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFormDataExamplesForCurl = (configData) => {
|
||||||
|
let finalStr = ''
|
||||||
|
configData = unshiftFiles(configData)
|
||||||
|
const loop = Math.min(configData.length, 4)
|
||||||
|
for (let i = 0; i < loop; i += 1) {
|
||||||
|
const config = configData[i]
|
||||||
|
let exampleVal = `example`
|
||||||
|
if (config.type === 'string') exampleVal = `example`
|
||||||
|
else if (config.type === 'boolean') exampleVal = `true`
|
||||||
|
else if (config.type === 'number') exampleVal = `1`
|
||||||
|
else if (config.name === 'files') exampleVal = `@/home/user1/Desktop/example${config.type}`
|
||||||
|
finalStr += `\n -F "${config.name}=${exampleVal}"`
|
||||||
|
if (i === loop - 1) finalStr += `)\n`
|
||||||
|
else finalStr += ` \\`
|
||||||
|
}
|
||||||
|
return finalStr
|
||||||
|
}
|
||||||
|
|
||||||
const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
const portalElement = document.getElementById('portal')
|
const portalElement = document.getElementById('portal')
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
@ -64,9 +127,18 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
const [apiKeys, setAPIKeys] = useState([])
|
const [apiKeys, setAPIKeys] = useState([])
|
||||||
const [chatflowApiKeyId, setChatflowApiKeyId] = useState('')
|
const [chatflowApiKeyId, setChatflowApiKeyId] = useState('')
|
||||||
const [selectedApiKey, setSelectedApiKey] = useState({})
|
const [selectedApiKey, setSelectedApiKey] = useState({})
|
||||||
|
const [checkboxVal, setCheckbox] = useState(false)
|
||||||
|
|
||||||
const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys)
|
const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys)
|
||||||
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
||||||
|
const getConfigApi = useApi(configApi.getConfig)
|
||||||
|
|
||||||
|
const onCheckBoxChanged = (newVal) => {
|
||||||
|
setCheckbox(newVal)
|
||||||
|
if (newVal) {
|
||||||
|
getConfigApi.request(dialogProps.chatflowid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onApiKeySelected = (keyValue) => {
|
const onApiKeySelected = (keyValue) => {
|
||||||
if (keyValue === 'addnewkey') {
|
if (keyValue === 'addnewkey') {
|
||||||
|
|
@ -106,7 +178,7 @@ output = query({
|
||||||
})
|
})
|
||||||
`
|
`
|
||||||
} else if (codeLang === 'JavaScript') {
|
} else if (codeLang === 'JavaScript') {
|
||||||
return `async function query(data) {
|
return `async function query() {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
"${baseURL}/api/v1/prediction/${dialogProps.chatflowid}",
|
"${baseURL}/api/v1/prediction/${dialogProps.chatflowid}",
|
||||||
{
|
{
|
||||||
|
|
@ -190,6 +262,147 @@ output = query({
|
||||||
return pythonSVG
|
return pythonSVG
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getConfigCode = (codeLang, configData) => {
|
||||||
|
if (codeLang === 'Python') {
|
||||||
|
return `import requests
|
||||||
|
form_data = {${getFormDataExamplesForPython(configData)}}
|
||||||
|
|
||||||
|
def setConfig():
|
||||||
|
response = requests.post("${baseURL}/api/v1/flow-config/${dialogProps.chatflowid}", files=form_data)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def query(payload):
|
||||||
|
response = requests.post("${baseURL}/api/v1/prediction/${dialogProps.chatflowid}", json=payload)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
# Set initial config
|
||||||
|
config = setConfig()
|
||||||
|
|
||||||
|
# Run prediction with config
|
||||||
|
output = query({
|
||||||
|
"question": "Hey, how are you?",
|
||||||
|
"overrideConfig": config
|
||||||
|
})
|
||||||
|
`
|
||||||
|
} else if (codeLang === 'JavaScript') {
|
||||||
|
return `let formData = new FormData();
|
||||||
|
${getFormDataExamplesForJS(configData)}
|
||||||
|
async function setConfig() {
|
||||||
|
const response = await fetch(
|
||||||
|
"${baseURL}/api/v1/flow-config/${dialogProps.chatflowid}",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: formData
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const config = await response.json();
|
||||||
|
return config; //Returns a config object
|
||||||
|
}
|
||||||
|
|
||||||
|
async function query(config) {
|
||||||
|
const response = await fetch(
|
||||||
|
"${baseURL}/api/v1/prediction/${dialogProps.chatflowid}",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
"question": "Hey, how are you?",
|
||||||
|
"overrideConfig": config
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const result = await response.json();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set initial config
|
||||||
|
const config = await setConfig()
|
||||||
|
|
||||||
|
// Run prediction with config
|
||||||
|
const res = await query(config)
|
||||||
|
`
|
||||||
|
} else if (codeLang === 'cURL') {
|
||||||
|
return `CONFIG=$(curl ${baseURL}/api/v1/flow-config/${dialogProps.chatflowid} \\
|
||||||
|
-X POST \\${getFormDataExamplesForCurl(configData)}
|
||||||
|
curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\
|
||||||
|
-X POST \\
|
||||||
|
-d '{"question": "Hey, how are you?", "overrideConfig": $CONFIG}'`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const getConfigCodeWithAuthorization = (codeLang, configData) => {
|
||||||
|
if (codeLang === 'Python') {
|
||||||
|
return `import requests
|
||||||
|
form_data = {${getFormDataExamplesForPython(configData)}}
|
||||||
|
headers = {"Authorization": "Bearer ${selectedApiKey?.apiKey}"}
|
||||||
|
|
||||||
|
def setConfig():
|
||||||
|
response = requests.post("${baseURL}/api/v1/flow-config/${dialogProps.chatflowid}", headers=headers, files=form_data)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def query(payload):
|
||||||
|
response = requests.post("${baseURL}/api/v1/prediction/${dialogProps.chatflowid}", headers=headers, json=payload)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
# Set initial config
|
||||||
|
config = setConfig()
|
||||||
|
|
||||||
|
# Run prediction with config
|
||||||
|
output = query({
|
||||||
|
"question": "Hey, how are you?",
|
||||||
|
"overrideConfig": config
|
||||||
|
})
|
||||||
|
`
|
||||||
|
} else if (codeLang === 'JavaScript') {
|
||||||
|
return `let formData = new FormData();
|
||||||
|
${getFormDataExamplesForJS(configData)}
|
||||||
|
async function setConfig() {
|
||||||
|
const response = await fetch(
|
||||||
|
"${baseURL}/api/v1/flow-config/${dialogProps.chatflowid}",
|
||||||
|
{
|
||||||
|
headers: { Authorization: "Bearer ${selectedApiKey?.apiKey}" },
|
||||||
|
method: "POST",
|
||||||
|
body: formData
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const config = await response.json();
|
||||||
|
return config; //Returns a config object
|
||||||
|
}
|
||||||
|
|
||||||
|
async function query(config) {
|
||||||
|
const response = await fetch(
|
||||||
|
"${baseURL}/api/v1/prediction/${dialogProps.chatflowid}",
|
||||||
|
{
|
||||||
|
headers: { Authorization: "Bearer ${selectedApiKey?.apiKey}" },
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
"question": "Hey, how are you?",
|
||||||
|
"overrideConfig": config
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const result = await response.json();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set initial config
|
||||||
|
const config = await setConfig()
|
||||||
|
|
||||||
|
// Run prediction with config
|
||||||
|
const res = await query(config)
|
||||||
|
`
|
||||||
|
} else if (codeLang === 'cURL') {
|
||||||
|
return `CONFIG=$(curl ${baseURL}/api/v1/flow-config/${dialogProps.chatflowid} \\
|
||||||
|
-X POST \\
|
||||||
|
-H "Authorization: Bearer ${selectedApiKey?.apiKey}"\\${getFormDataExamplesForCurl(configData)}
|
||||||
|
curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\
|
||||||
|
-X POST \\
|
||||||
|
-H "Authorization: Bearer ${selectedApiKey?.apiKey}"
|
||||||
|
-d '{"question": "Hey, how are you?", "overrideConfig": $CONFIG}'`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getAllAPIKeysApi.data) {
|
if (getAllAPIKeysApi.data) {
|
||||||
const options = [
|
const options = [
|
||||||
|
|
@ -219,7 +432,9 @@ output = query({
|
||||||
}, [dialogProps, getAllAPIKeysApi.data])
|
}, [dialogProps, getAllAPIKeysApi.data])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) getAllAPIKeysApi.request()
|
if (show) {
|
||||||
|
getAllAPIKeysApi.request()
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [show])
|
}, [show])
|
||||||
|
|
@ -277,6 +492,23 @@ output = query({
|
||||||
showLineNumbers={false}
|
showLineNumbers={false}
|
||||||
wrapLines
|
wrapLines
|
||||||
/>
|
/>
|
||||||
|
<CheckboxInput label='Show Input Config' value={checkboxVal} onChange={onCheckBoxChanged} />
|
||||||
|
{checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && (
|
||||||
|
<>
|
||||||
|
<TableViewOnly rows={getConfigApi.data} columns={Object.keys(getConfigApi.data[0])} />
|
||||||
|
<CopyBlock
|
||||||
|
theme={atomOneDark}
|
||||||
|
text={
|
||||||
|
chatflowApiKeyId
|
||||||
|
? getConfigCodeWithAuthorization(codeLang, getConfigApi.data)
|
||||||
|
: getConfigCode(codeLang, getConfigApi.data)
|
||||||
|
}
|
||||||
|
language={getLang(codeLang)}
|
||||||
|
showLineNumbers={false}
|
||||||
|
wrapLines
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
))}
|
))}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ export const File = ({ value, fileType, onChange, disabled = false }) => {
|
||||||
|
|
||||||
const [myValue, setMyValue] = useState(value ?? '')
|
const [myValue, setMyValue] = useState(value ?? '')
|
||||||
|
|
||||||
const handleFileUpload = (e) => {
|
const handleFileUpload = async (e) => {
|
||||||
if (!e.target.files) return
|
if (!e.target.files) return
|
||||||
|
|
||||||
|
if (e.target.files.length === 1) {
|
||||||
const file = e.target.files[0]
|
const file = e.target.files[0]
|
||||||
const { name } = file
|
const { name } = file
|
||||||
|
|
||||||
|
|
@ -29,6 +30,28 @@ export const File = ({ value, fileType, onChange, disabled = false }) => {
|
||||||
onChange(value)
|
onChange(value)
|
||||||
}
|
}
|
||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
|
} else if (e.target.files.length > 0) {
|
||||||
|
let files = Array.from(e.target.files).map((file) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
const { name } = file
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
reader.onload = (evt) => {
|
||||||
|
if (!evt?.target?.result) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { result } = evt.target
|
||||||
|
const value = result + `,filename:${name}`
|
||||||
|
resolve(value)
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await Promise.all(files)
|
||||||
|
setMyValue(JSON.stringify(res))
|
||||||
|
onChange(JSON.stringify(res))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -51,7 +74,7 @@ export const File = ({ value, fileType, onChange, disabled = false }) => {
|
||||||
sx={{ marginRight: '1rem' }}
|
sx={{ marginRight: '1rem' }}
|
||||||
>
|
>
|
||||||
{'Upload File'}
|
{'Upload File'}
|
||||||
<input type='file' accept={fileType} hidden onChange={(e) => handleFileUpload(e)} />
|
<input type='file' multiple accept={fileType} hidden onChange={(e) => handleFileUpload(e)} />
|
||||||
</Button>
|
</Button>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { TableContainer, Table, TableHead, TableCell, TableRow, TableBody, Paper } from '@mui/material'
|
||||||
|
|
||||||
|
export const TableViewOnly = ({ columns, rows }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
{columns.map((col, index) => (
|
||||||
|
<TableCell key={index}>{col.charAt(0).toUpperCase() + col.slice(1)}</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{rows.map((row, index) => (
|
||||||
|
<TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||||
|
{Object.keys(row).map((key, index) => (
|
||||||
|
<TableCell key={index}>{row[key]}</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TableViewOnly.propTypes = {
|
||||||
|
rows: PropTypes.array,
|
||||||
|
columns: PropTypes.array
|
||||||
|
}
|
||||||
|
|
@ -206,9 +206,20 @@ export const convertDateStringToDateObject = (dateString) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFileName = (fileBase64) => {
|
export const getFileName = (fileBase64) => {
|
||||||
|
let fileNames = []
|
||||||
|
if (fileBase64.startsWith('[') && fileBase64.endsWith(']')) {
|
||||||
|
const files = JSON.parse(fileBase64)
|
||||||
|
for (const file of files) {
|
||||||
|
const splitDataURI = file.split(',')
|
||||||
|
const filename = splitDataURI[splitDataURI.length - 1].split(':')[1]
|
||||||
|
fileNames.push(filename)
|
||||||
|
}
|
||||||
|
return fileNames.join(', ')
|
||||||
|
} else {
|
||||||
const splitDataURI = fileBase64.split(',')
|
const splitDataURI = fileBase64.split(',')
|
||||||
const filename = splitDataURI[splitDataURI.length - 1].split(':')[1]
|
const filename = splitDataURI[splitDataURI.length - 1].split(':')[1]
|
||||||
return filename
|
return filename
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFolderName = (base64ArrayStr) => {
|
export const getFolderName = (base64ArrayStr) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue