diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts
index a168570d..99704e81 100644
--- a/packages/server/src/index.ts
+++ b/packages/server/src/index.ts
@@ -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
diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts
index 1b4b41c6..8f668e12 100644
--- a/packages/server/src/utils/index.ts
+++ b/packages/server/src/utils/index.ts
@@ -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,
diff --git a/packages/ui/src/menu-items/settings.js b/packages/ui/src/menu-items/settings.js
index 25fd7a55..77b9ebf5 100644
--- a/packages/ui/src/menu-items/settings.js
+++ b/packages/ui/src/menu-items/settings.js
@@ -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',
diff --git a/packages/ui/src/store/constant.js b/packages/ui/src/store/constant.js
index 61acbd92..c3138257 100644
--- a/packages/ui/src/store/constant.js
+++ b/packages/ui/src/store/constant.js
@@ -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
diff --git a/packages/ui/src/ui-component/dialog/APICodeDialog.js b/packages/ui/src/ui-component/dialog/APICodeDialog.js
index f160c44d..b744b919 100644
--- a/packages/ui/src/ui-component/dialog/APICodeDialog.js
+++ b/packages/ui/src/ui-component/dialog/APICodeDialog.js
@@ -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)}
diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js
index 70c6aa02..03bf61f2 100644
--- a/packages/ui/src/views/canvas/CanvasHeader.js
+++ b/packages/ui/src/views/canvas/CanvasHeader.js
@@ -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 })
+ }
>
@@ -231,26 +257,28 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
)}
-
-
-
-
-
+ {chatflow?.id && (
+
+
+
+
+
+ )}
{
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: {
diff --git a/packages/ui/src/views/marketplaces/MarketplaceCanvas.js b/packages/ui/src/views/marketplaces/MarketplaceCanvas.js
index 66ce0283..7ce29451 100644
--- a/packages/ui/src/views/marketplaces/MarketplaceCanvas.js
+++ b/packages/ui/src/views/marketplaces/MarketplaceCanvas.js
@@ -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 } })
}