removed flow-config endpoint and resort to just prediction endpoint

This commit is contained in:
Henry
2023-05-10 01:06:53 +01:00
parent a1539edadf
commit 601b0b0f5b
8 changed files with 255 additions and 162 deletions
+24 -28
View File
@@ -222,37 +222,12 @@ export class App {
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
// ----------------------------------------
// Send input message and get prediction result (External)
this.app.post('/api/v1/prediction/:id', async (req: Request, res: Response) => {
this.app.post('/api/v1/prediction/:id', upload.array('files'), async (req: Request, res: Response) => {
await this.processPrediction(req, res)
})
@@ -346,7 +321,7 @@ export class App {
async processPrediction(req: Request, res: Response, isInternal = false) {
try {
const chatflowid = req.params.id
const incomingInput: IncomingInput = req.body
let incomingInput: IncomingInput = req.body
let nodeToExecuteData: INodeData
@@ -359,6 +334,28 @@ export class App {
await this.validateKey(req, res, chatflow)
}
const files = (req.files as any[]) || []
if (files.length) {
const overrideConfig: ICommonObject = { ...req.body }
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])
}
}
incomingInput = {
question: req.body.question ?? 'hello',
overrideConfig,
history: []
}
}
/* 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)
@@ -378,7 +375,6 @@ export class App {
nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData
} else {
/*** Get chatflows and prepare data ***/
const flowData = chatflow.flowData
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
const nodes = parsedFlowData.nodes
+7 -6
View File
@@ -353,6 +353,12 @@ export const resolveVariables = (reactFlowNodeData: INodeData, reactFlowNodes: I
return flowNodeData
}
/**
* Loop through each inputs and replace their value with override config values
* @param {INodeData} flowNodeData
* @param {ICommonObject} overrideConfig
* @returns {INodeData}
*/
export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig: ICommonObject) => {
const types = 'inputs'
@@ -560,12 +566,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => {
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'
}
continue
} else if (inputParam.type === 'file') {
obj = {
node: flowNode.data.label,
+9 -2
View File
@@ -1,8 +1,8 @@
// assets
import { IconTrash, IconFileUpload, IconFileExport } from '@tabler/icons'
import { IconTrash, IconFileUpload, IconFileExport, IconCopy } from '@tabler/icons'
// constant
const icons = { IconTrash, IconFileUpload, IconFileExport }
const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy }
// ==============================|| SETTINGS MENU ITEMS ||============================== //
@@ -11,6 +11,13 @@ const settings = {
title: '',
type: 'group',
children: [
{
id: 'duplicateChatflow',
title: 'Duplicate Chatflow',
type: 'item',
url: '',
icon: icons.IconCopy
},
{
id: 'loadChatflow',
title: 'Load Chatflow',
+1
View File
@@ -4,3 +4,4 @@ export const drawerWidth = 260
export const appDrawerWidth = 320
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
@@ -66,7 +66,7 @@ const unshiftFiles = (configData) => {
return configData
}
const getFormDataExamplesForJS = (configData) => {
const getConfigExamplesForJS = (configData, bodyType) => {
let finalStr = ''
configData = unshiftFiles(configData)
const loop = Math.min(configData.length, 4)
@@ -77,12 +77,13 @@ const getFormDataExamplesForJS = (configData) => {
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`
finalStr += bodyType === 'json' ? `\n "${config.name}": ${exampleVal},` : `formData.append("${config.name}", ${exampleVal})\n`
if (i === loop - 1 && bodyType !== 'json') `formData.append("question", "Hey, how are you?")\n`
}
return finalStr
}
const getFormDataExamplesForPython = (configData) => {
const getConfigExamplesForPython = (configData, bodyType) => {
let finalStr = ''
configData = unshiftFiles(configData)
const loop = Math.min(configData.length, 4)
@@ -93,26 +94,26 @@ const getFormDataExamplesForPython = (configData) => {
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`
finalStr += bodyType === 'json' ? `\n "${config.name}": ${exampleVal},` : `\n "${config.name}": ${exampleVal},`
if (i === loop - 1 && bodyType !== 'json') finalStr += `\n "question": "Hey, how are you?"\n`
}
return finalStr
}
const getFormDataExamplesForCurl = (configData) => {
const getConfigExamplesForCurl = (configData, bodyType) => {
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`
if (config.type === 'string') exampleVal = bodyType === 'json' ? `"example"` : `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 += ` \\`
finalStr += bodyType === 'json' ? `"${config.name}": ${exampleVal}` : `\n -F "${config.name}=${exampleVal}"`
if (i === loop - 1) finalStr += bodyType === 'json' ? ` }` : ` \\\n -F "question=Hey, how are you?"`
else finalStr += bodyType === 'json' ? `, ` : ` \\`
}
return finalStr
}
@@ -178,19 +179,21 @@ output = query({
})
`
} else if (codeLang === 'JavaScript') {
return `async function query() {
return `async function query(data) {
const response = await fetch(
"${baseURL}/api/v1/prediction/${dialogProps.chatflowid}",
{
method: "POST",
body: {
"question": "Hey, how are you?"
},
body: data
}
);
const result = await response.json();
return result;
}
query({"question": "Hey, how are you?"}).then((response) => {
console.log(response);
});
`
} else if (codeLang === 'cURL') {
return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\
@@ -222,14 +225,16 @@ output = query({
{
headers: { Authorization: "Bearer ${selectedApiKey?.apiKey}" },
method: "POST",
body: {
"question": "Hey, how are you?"
},
body: data
}
);
const result = await response.json();
return result;
}
query({"question": "Hey, how are you?"}).then((response) => {
console.log(response);
});
`
} else if (codeLang === 'cURL') {
return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\
@@ -262,143 +267,190 @@ output = query({
return pythonSVG
}
const getConfigCode = (codeLang, configData) => {
// ----------------------------CONFIG FORM DATA --------------------------//
const getConfigCodeWithFormData = (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)
API_URL = "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}"
# use form data to upload files
form_data = {${getConfigExamplesForPython(configData, 'formData')}}
def query(form_data):
response = requests.post(API_URL, 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
})
output = query(form_data)
`
} 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) {
return `// use FormData to upload files
let formData = new FormData();
${getConfigExamplesForJS(configData, 'formData')}
async function query(formData) {
const response = await fetch(
"${baseURL}/api/v1/prediction/${dialogProps.chatflowid}",
{
method: "POST",
body: {
"question": "Hey, how are you?",
"overrideConfig": config
},
body: formData
}
);
const result = await response.json();
return result;
}
// Set initial config
const config = await setConfig()
// Run prediction with config
const res = await query(config)
query(formData).then((response) => {
console.log(response);
});
`
} 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 `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\
-X POST \\${getConfigExamplesForCurl(configData, 'formData')}`
}
return ''
}
const getConfigCodeWithAuthorization = (codeLang, configData) => {
// ----------------------------CONFIG FORM DATA with AUTH--------------------------//
const getConfigCodeWithFormDataWithAuth = (codeLang, configData) => {
if (codeLang === 'Python') {
return `import requests
form_data = {${getFormDataExamplesForPython(configData)}}
API_URL = "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}"
headers = {"Authorization": "Bearer ${selectedApiKey?.apiKey}"}
def setConfig():
response = requests.post("${baseURL}/api/v1/flow-config/${dialogProps.chatflowid}", headers=headers, files=form_data)
# use form data to upload files
form_data = {${getConfigExamplesForPython(configData, 'formData')}}
def query(form_data):
response = requests.post(API_URL, 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
})
output = query(form_data)
`
} else if (codeLang === 'JavaScript') {
return `let formData = new FormData();
${getFormDataExamplesForJS(configData)}
async function setConfig() {
return `// use FormData to upload files
let formData = new FormData();
${getConfigExamplesForJS(configData, 'formData')}
async function query(formData) {
const response = await fetch(
"${baseURL}/api/v1/flow-config/${dialogProps.chatflowid}",
"${baseURL}/api/v1/prediction/${dialogProps.chatflowid}",
{
headers: { Authorization: "Bearer ${selectedApiKey?.apiKey}" },
method: "POST",
body: formData
}
);
const config = await response.json();
return config; //Returns a config object
const result = await response.json();
return result;
}
async function query(config) {
query(formData).then((response) => {
console.log(response);
});
`
} else if (codeLang === 'cURL') {
return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\
-X POST \\${getConfigExamplesForCurl(configData, 'formData')} \\
-H "Authorization: Bearer ${selectedApiKey?.apiKey}"`
}
return ''
}
// ----------------------------CONFIG JSON--------------------------//
const getConfigCode = (codeLang, configData) => {
if (codeLang === 'Python') {
return `import requests
API_URL = "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}"
def query(payload):
response = requests.post(API_URL, json=payload)
return response.json()
output = query({
"question": "Hey, how are you?",
"overrideConfig": {${getConfigExamplesForPython(configData, 'json')}
}
})
`
} else if (codeLang === 'JavaScript') {
return `async function query(data) {
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
},
body: data
}
);
const result = await response.json();
return result;
}
// Set initial config
const config = await setConfig()
// Run prediction with config
const res = await query(config)
query({
"question": "Hey, how are you?",
"overrideConfig": {${getConfigExamplesForJS(configData, 'json')}
}
}).then((response) => {
console.log(response);
});
`
} else if (codeLang === 'cURL') {
return `CONFIG=$(curl ${baseURL}/api/v1/flow-config/${dialogProps.chatflowid} \\
return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\
-X POST \\
-H "Authorization: Bearer ${selectedApiKey?.apiKey}"\\${getFormDataExamplesForCurl(configData)}
curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\
-d '{"question": "Hey, how are you?", "overrideConfig": {${getConfigExamplesForCurl(configData, 'json')}}'`
}
return ''
}
// ----------------------------CONFIG JSON with AUTH--------------------------//
const getConfigCodeWithAuthorization = (codeLang, configData) => {
if (codeLang === 'Python') {
return `import requests
API_URL = "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}"
headers = {"Authorization": "Bearer ${selectedApiKey?.apiKey}"}
def query(payload):
response = requests.post(API_URL, headers=headers, json=payload)
return response.json()
output = query({
"question": "Hey, how are you?",
"overrideConfig": {${getConfigExamplesForPython(configData, 'json')}
}
})
`
} else if (codeLang === 'JavaScript') {
return `async function query(data) {
const response = await fetch(
"${baseURL}/api/v1/prediction/${dialogProps.chatflowid}",
{
headers: { Authorization: "Bearer ${selectedApiKey?.apiKey}" },
method: "POST",
body: data
}
);
const result = await response.json();
return result;
}
query({
"question": "Hey, how are you?",
"overrideConfig": {${getConfigExamplesForJS(configData, 'json')}
}
}).then((response) => {
console.log(response);
});
`
} else if (codeLang === 'cURL') {
return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\
-X POST \\
-H "Authorization: Bearer ${selectedApiKey?.apiKey}"
-d '{"question": "Hey, how are you?", "overrideConfig": $CONFIG}'`
-d '{"question": "Hey, how are you?", "overrideConfig": {${getConfigExamplesForCurl(configData, 'json')}}' \\
-H "Authorization: Bearer ${selectedApiKey?.apiKey}"`
}
return ''
}
@@ -500,7 +552,11 @@ curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\
theme={atomOneDark}
text={
chatflowApiKeyId
? getConfigCodeWithAuthorization(codeLang, getConfigApi.data)
? dialogProps.isFormDataRequired
? getConfigCodeWithFormDataWithAuth(codeLang, getConfigApi.data)
: getConfigCodeWithAuthorization(codeLang, getConfigApi.data)
: dialogProps.isFormDataRequired
? getConfigCodeWithFormData(codeLang, getConfigApi.data)
: getConfigCode(codeLang, getConfigApi.data)
}
language={getLang(codeLang)}
+50 -22
View File
@@ -23,6 +23,7 @@ import useApi from 'hooks/useApi'
// utils
import { generateExportFlowData } from 'utils/genericHelper'
import { uiBaseURL } from 'store/constant'
// ==============================|| CANVAS HEADER ||============================== //
@@ -47,6 +48,13 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
if (setting === 'deleteChatflow') {
handleDeleteFlow()
} else if (setting === 'duplicateChatflow') {
try {
localStorage.setItem('duplicatedFlowData', chatflow.flowData)
window.open(`${uiBaseURL}/canvas`, '_blank')
} catch (e) {
console.error(e)
}
} else if (setting === 'exportChatflow') {
try {
const flowData = JSON.parse(chatflow.flowData)
@@ -80,10 +88,26 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
}
const onAPIDialogClick = () => {
let isFormDataRequired = false
try {
const flowData = JSON.parse(chatflow.flowData)
const nodes = flowData.nodes
for (const node of nodes) {
if (node.data.inputParams.find((param) => param.type === 'file')) {
isFormDataRequired = true
break
}
}
} catch (e) {
console.error(e)
}
setAPIDialogProps({
title: 'Use this chatflow with API',
chatflowid: chatflow.id,
chatflowApiKeyId: chatflow.apikeyid
chatflowApiKeyId: chatflow.apikeyid,
isFormDataRequired
})
setAPIDialogOpen(true)
}
@@ -131,7 +155,9 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
}
}}
color='inherit'
onClick={() => navigate(-1)}
onClick={() =>
window.history.state && window.history.state.idx > 0 ? navigate(-1) : navigate('/', { replace: true })
}
>
<IconChevronLeft stroke={1.5} size='1.3rem' />
</Avatar>
@@ -231,26 +257,28 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
)}
</Box>
<Box>
<ButtonBase title='API Endpoint' sx={{ borderRadius: '50%', mr: 2 }}>
<Avatar
variant='rounded'
sx={{
...theme.typography.commonAvatar,
...theme.typography.mediumAvatar,
transition: 'all .2s ease-in-out',
background: theme.palette.canvasHeader.deployLight,
color: theme.palette.canvasHeader.deployDark,
'&:hover': {
background: theme.palette.canvasHeader.deployDark,
color: theme.palette.canvasHeader.deployLight
}
}}
color='inherit'
onClick={onAPIDialogClick}
>
<IconCode stroke={1.5} size='1.3rem' />
</Avatar>
</ButtonBase>
{chatflow?.id && (
<ButtonBase title='API Endpoint' sx={{ borderRadius: '50%', mr: 2 }}>
<Avatar
variant='rounded'
sx={{
...theme.typography.commonAvatar,
...theme.typography.mediumAvatar,
transition: 'all .2s ease-in-out',
background: theme.palette.canvasHeader.deployLight,
color: theme.palette.canvasHeader.deployDark,
'&:hover': {
background: theme.palette.canvasHeader.deployDark,
color: theme.palette.canvasHeader.deployLight
}
}}
color='inherit'
onClick={onAPIDialogClick}
>
<IconCode stroke={1.5} size='1.3rem' />
</Avatar>
</ButtonBase>
)}
<ButtonBase title='Save Chatflow' sx={{ borderRadius: '50%', mr: 2 }}>
<Avatar
variant='rounded'
+7 -2
View File
@@ -407,8 +407,13 @@ const Canvas = () => {
if (chatflowId) {
getSpecificChatflowApi.request(chatflowId)
} else {
setNodes([])
setEdges([])
if (localStorage.getItem('duplicatedFlowData')) {
handleLoadFlow(localStorage.getItem('duplicatedFlowData'))
setTimeout(() => localStorage.removeItem('duplicatedFlowData'), 0)
} else {
setNodes([])
setEdges([])
}
dispatch({
type: SET_CHATFLOW,
chatflow: {
@@ -46,7 +46,6 @@ const MarketplaceCanvas = () => {
}, [flowData])
const onChatflowCopy = (flowData) => {
//navigator.clipboard.writeText(JSON.stringify(flowData))
const templateFlowData = JSON.stringify(flowData)
navigate(`/canvas`, { state: { templateFlowData } })
}