add api config

This commit is contained in:
Henry 2023-05-04 18:44:51 +01:00
parent 57b8620b93
commit 8d3a374257
18 changed files with 589 additions and 59 deletions

3
.gitignore vendored
View File

@ -38,5 +38,8 @@
**/*.key
**/api.json
## uploads
**/uploads
## compressed
**/*.tgz

View File

@ -1,6 +1,7 @@
import { INode, INodeData, INodeParams } from '../../../src/Interface'
import { TextSplitter } from 'langchain/text_splitter'
import { CSVLoader } from 'langchain/document_loaders/fs/csv'
import { getBlob } from '../../../src/utils'
class Csv_DocumentLoaders implements INode {
label: string
@ -48,11 +49,8 @@ class Csv_DocumentLoaders implements INode {
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
const csvFileBase64 = nodeData.inputs?.csvFile 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())
if (textSplitter) {

View File

@ -1,6 +1,7 @@
import { INode, INodeData, INodeParams } from '../../../src/Interface'
import { TextSplitter } from 'langchain/text_splitter'
import { DocxLoader } from 'langchain/document_loaders/fs/docx'
import { getBlob } from '../../../src/utils'
class Docx_DocumentLoaders implements INode {
label: string
@ -39,11 +40,8 @@ class Docx_DocumentLoaders implements INode {
async init(nodeData: INodeData): Promise<any> {
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
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)
if (textSplitter) {

View File

@ -1,6 +1,7 @@
import { INode, INodeData, INodeParams } from '../../../src/Interface'
import { TextSplitter } from 'langchain/text_splitter'
import { JSONLoader } from 'langchain/document_loaders/fs/json'
import { getBlob } from '../../../src/utils'
class Json_DocumentLoaders implements INode {
label: string
@ -48,9 +49,6 @@ class Json_DocumentLoaders implements INode {
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
const jsonFileBase64 = nodeData.inputs?.jsonFile 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[] = []
if (pointersName) {
@ -58,7 +56,7 @@ class Json_DocumentLoaders implements INode {
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)
if (textSplitter) {

View File

@ -1,6 +1,7 @@
import { INode, INodeData, INodeParams } from '../../../src/Interface'
import { TextSplitter } from 'langchain/text_splitter'
import { PDFLoader } from 'langchain/document_loaders/fs/pdf'
import { getBlob } from '../../../src/utils'
class Pdf_DocumentLoaders implements INode {
label: string
@ -57,10 +58,7 @@ class Pdf_DocumentLoaders implements INode {
const pdfFileBase64 = nodeData.inputs?.pdfFile as string
const usage = nodeData.inputs?.usage as string
const splitDataURI = pdfFileBase64.split(',')
splitDataURI.pop()
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
const blob = new Blob([bf])
const blob = new Blob(getBlob(pdfFileBase64))
if (usage === 'perFile') {
// @ts-ignore

View File

@ -1,6 +1,7 @@
import { INode, INodeData, INodeParams } from '../../../src/Interface'
import { TextSplitter } from 'langchain/text_splitter'
import { TextLoader } from 'langchain/document_loaders/fs/text'
import { getBlob } from '../../../src/utils'
class Text_DocumentLoaders implements INode {
label: string
@ -39,11 +40,8 @@ class Text_DocumentLoaders implements INode {
async init(nodeData: INodeData): Promise<any> {
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
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)
if (textSplitter) {

View File

@ -149,3 +149,27 @@ export const getInputVariables = (paramValue: string): string[] => {
}
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
}

View File

@ -54,12 +54,14 @@
"flowise-components": "*",
"flowise-ui": "*",
"moment-timezone": "^0.5.34",
"multer": "^1.4.5-lts.1",
"reflect-metadata": "^0.1.13",
"sqlite3": "^5.1.6",
"typeorm": "^0.3.6"
},
"devDependencies": {
"@types/cors": "^2.8.12",
"@types/multer": "^1.4.7",
"concurrently": "^7.1.0",
"nodemon": "^2.0.15",
"oclif": "^3",

View File

@ -1,3 +1,4 @@
import { ICommonObject } from 'flowise-components'
import { IActiveChatflows, INodeData, IReactFlowNode } from './Interface'
/**
@ -12,13 +13,15 @@ export class ChatflowPool {
* @param {string} chatflowid
* @param {INodeData} endingNodeData
* @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] = {
startingNodes,
endingNodeData,
inSync: true
}
if (overrideConfig) this.activeChatflows[chatflowid].overrideConfig = overrideConfig
}
/**

View File

@ -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'
@ -114,6 +114,7 @@ export interface IMessage {
export interface IncomingInput {
question: string
history: IMessage[]
overrideConfig?: ICommonObject
}
export interface IActiveChatflows {
@ -121,5 +122,13 @@ export interface IActiveChatflows {
startingNodes: IReactFlowNode[]
endingNodeData: INodeData
inSync: boolean
overrideConfig?: ICommonObject
}
}
export interface IOverrideConfig {
node: string
label: string
name: string
type: string
}

View File

@ -1,4 +1,5 @@
import express, { Request, Response } from 'express'
import multer from 'multer'
import path from 'path'
import cors from 'cors'
import http from 'http'
@ -17,7 +18,10 @@ import {
addAPIKey,
updateAPIKey,
deleteAPIKey,
compareKeys
compareKeys,
mapMimeTypeToInputField,
findAvailableConfigs,
isSameOverrideConfig
} from './utils'
import { cloneDeep } from 'lodash'
import { getDataSource } from './DataSource'
@ -25,6 +29,7 @@ import { NodesPool } from './NodesPool'
import { ChatFlow } from './entity/ChatFlow'
import { ChatMessage } from './entity/ChatMessage'
import { ChatflowPool } from './ChatflowPool'
import { ICommonObject } from 'flowise-components'
export class App {
app: express.Application
@ -66,6 +71,8 @@ export class App {
this.app.use(cors({ credentials: true, origin: 'http://localhost:8080' }))
}
const upload = multer({ dest: `${path.join(__dirname, '..', 'uploads')}/` })
// ----------------------------------------
// Nodes
// ----------------------------------------
@ -199,6 +206,47 @@ export class App {
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
// ----------------------------------------
@ -281,19 +329,7 @@ export class App {
})
}
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) {
async validateKey(req: Request, res: Response, chatflow: ChatFlow) {
const chatFlowApiKeyId = chatflow.apikeyid
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
* - 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
***/
if (
Object.prototype.hasOwnProperty.call(this.chatflowPool.activeChatflows, chatflowid) &&
this.chatflowPool.activeChatflows[chatflowid].inSync &&
isSameOverrideConfig(this.chatflowPool.activeChatflows[chatflowid].overrideConfig, incomingInput.overrideConfig) &&
!isStartNodeDependOnInput(this.chatflowPool.activeChatflows[chatflowid].startingNodes)
) {
nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData
@ -359,7 +413,8 @@ export class App {
graph,
depthQueue,
this.nodesPool.componentNodes,
incomingInput.question
incomingInput.question,
incomingInput?.overrideConfig
)
const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId)
@ -369,7 +424,7 @@ export class App {
nodeToExecuteData = reactFlowNodeData
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

View File

@ -11,7 +11,8 @@ import {
IReactFlowEdge,
IReactFlowNode,
IVariableDict,
INodeData
INodeData,
IOverrideConfig
} from '../Interface'
import { cloneDeep, get } from 'lodash'
import { ICommonObject, getInputVariables } from 'flowise-components'
@ -180,7 +181,8 @@ export const buildLangchain = async (
graph: INodeDirectedGraph,
depthQueue: IDepthQueue,
componentNodes: IComponentNodes,
question: string
question: string,
overrideConfig?: ICommonObject
) => {
const flowNodes = cloneDeep(reactFlowNodes)
@ -208,7 +210,9 @@ export const buildLangchain = async (
const nodeModule = await import(nodeInstanceFilePath)
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)
} 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)
@ -365,6 +386,24 @@ export const isStartNodeDependOnInput = (startingNodes: IReactFlowNode[]): boole
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 {string}
@ -480,3 +519,67 @@ export const deleteAPIKey = async (keyIdToDelete: string): Promise<ICommonObject
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(result), 'utf8')
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
}

View File

@ -0,0 +1,7 @@
import client from './client'
const getConfig = (id) => client.get(`/flow-config/${id}`)
export default {
getConfig
}

View File

@ -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
}

View File

@ -22,9 +22,12 @@ import cURLSVG from 'assets/images/cURL.svg'
// API
import apiKeyApi from 'api/apikey'
import chatflowsApi from 'api/chatflows'
import configApi from 'api/config'
// Hooks
import useApi from 'hooks/useApi'
import { CheckboxInput } from 'ui-component/checkbox/Checkbox'
import { TableViewOnly } from 'ui-component/table/Table'
function TabPanel(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 portalElement = document.getElementById('portal')
const navigate = useNavigate()
@ -64,9 +127,18 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
const [apiKeys, setAPIKeys] = useState([])
const [chatflowApiKeyId, setChatflowApiKeyId] = useState('')
const [selectedApiKey, setSelectedApiKey] = useState({})
const [checkboxVal, setCheckbox] = useState(false)
const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys)
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
const getConfigApi = useApi(configApi.getConfig)
const onCheckBoxChanged = (newVal) => {
setCheckbox(newVal)
if (newVal) {
getConfigApi.request(dialogProps.chatflowid)
}
}
const onApiKeySelected = (keyValue) => {
if (keyValue === 'addnewkey') {
@ -106,7 +178,7 @@ output = query({
})
`
} else if (codeLang === 'JavaScript') {
return `async function query(data) {
return `async function query() {
const response = await fetch(
"${baseURL}/api/v1/prediction/${dialogProps.chatflowid}",
{
@ -190,6 +262,147 @@ output = query({
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(() => {
if (getAllAPIKeysApi.data) {
const options = [
@ -219,7 +432,9 @@ output = query({
}, [dialogProps, getAllAPIKeysApi.data])
useEffect(() => {
if (show) getAllAPIKeysApi.request()
if (show) {
getAllAPIKeysApi.request()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [show])
@ -277,6 +492,23 @@ output = query({
showLineNumbers={false}
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>
))}
</DialogContent>

View File

@ -10,9 +10,10 @@ export const File = ({ value, fileType, onChange, disabled = false }) => {
const [myValue, setMyValue] = useState(value ?? '')
const handleFileUpload = (e) => {
const handleFileUpload = async (e) => {
if (!e.target.files) return
if (e.target.files.length === 1) {
const file = e.target.files[0]
const { name } = file
@ -29,6 +30,28 @@ export const File = ({ value, fileType, onChange, disabled = false }) => {
onChange(value)
}
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 (
@ -51,7 +74,7 @@ export const File = ({ value, fileType, onChange, disabled = false }) => {
sx={{ marginRight: '1rem' }}
>
{'Upload File'}
<input type='file' accept={fileType} hidden onChange={(e) => handleFileUpload(e)} />
<input type='file' multiple accept={fileType} hidden onChange={(e) => handleFileUpload(e)} />
</Button>
</FormControl>
)

View File

@ -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
}

View File

@ -206,9 +206,20 @@ export const convertDateStringToDateObject = (dateString) => {
}
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 filename = splitDataURI[splitDataURI.length - 1].split(':')[1]
return filename
}
}
export const getFolderName = (base64ArrayStr) => {