Merge pull request #25 from FlowiseAI/feature/API

Feature/Add API dialog
This commit is contained in:
Ong Chung Yau 2023-04-21 00:59:05 +07:00 committed by GitHub
commit f99abf0109
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 200 additions and 3 deletions

View File

@ -25,6 +25,7 @@
"prismjs": "^1.28.0",
"prop-types": "^15.7.2",
"react": "^18.2.0",
"react-code-blocks": "^0.0.9-0",
"react-datepicker": "^4.8.0",
"react-device-detect": "^1.17.0",
"react-dom": "^18.2.0",

View File

@ -0,0 +1 @@
<svg class="mr-1.5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><rect width="32" height="32" rx="4" fill="#1683a5"></rect><path d="M6.71,14A5,5,0,0,1,8.82,9.29l2.64-2.2c1.67-1.37,2.52-1.41,4.6-1.41H21.7c1.19,0,2.45.27,2.45,1.79s-1.4,1.78-2.45,1.78H15.44a3.31,3.31,0,0,0-2,.89L11.24,12c-.55.44-1,.81-1,1.52v4.41c0,.7.41,1.07,1,1.52l2.16,1.82a3.34,3.34,0,0,0,2,.89H21.7c1.05,0,2.45.23,2.45,1.78s-1.26,1.78-2.45,1.78H16.06c-2.08,0-2.94,0-4.6-1.4L8.82,22.09A5.05,5.05,0,0,1,6.71,17.4Z" fill="#fff"></path></svg>

After

Width:  |  Height:  |  Size: 670 B

View File

@ -0,0 +1 @@
<svg class="mr-1.5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><rect width="32" height="32" fill="#f7df1e"></rect><path d="M21.5,25a3.27,3.27,0,0,0,3,1.83c1.25,0,2-.63,2-1.49,0-1-.81-1.39-2.19-2L23.56,23C21.39,22.1,20,20.94,20,18.49c0-2.25,1.72-4,4.41-4a4.44,4.44,0,0,1,4.27,2.41l-2.34,1.5a2,2,0,0,0-1.93-1.29,1.31,1.31,0,0,0-1.44,1.29c0,.9.56,1.27,1.85,1.83l.75.32c2.55,1.1,4,2.21,4,4.72,0,2.71-2.12,4.19-5,4.19a5.78,5.78,0,0,1-5.48-3.07Zm-10.63.26c.48.84.91,1.55,1.94,1.55s1.61-.39,1.61-1.89V14.69h3V25c0,3.11-1.83,4.53-4.49,4.53a4.66,4.66,0,0,1-4.51-2.75Z"></path></svg>

After

Width:  |  Height:  |  Size: 737 B

View File

@ -0,0 +1 @@
<svg class="mr-1.5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M15.84.5a16.4,16.4,0,0,0-3.57.32C9.1,1.39,8.53,2.53,8.53,4.64V7.48H16v1H5.77a4.73,4.73,0,0,0-4.7,3.74,14.82,14.82,0,0,0,0,7.54c.57,2.28,1.86,3.82,4,3.82h2.6V20.14a4.73,4.73,0,0,1,4.63-4.63h7.38a3.72,3.72,0,0,0,3.73-3.73V4.64A4.16,4.16,0,0,0,19.65.82,20.49,20.49,0,0,0,15.84.5ZM11.78,2.77a1.39,1.39,0,0,1,1.38,1.46,1.37,1.37,0,0,1-1.38,1.38A1.42,1.42,0,0,1,10.4,4.23,1.44,1.44,0,0,1,11.78,2.77Z" fill="#5a9fd4"></path><path d="M16.16,31.5a16.4,16.4,0,0,0,3.57-.32c3.17-.57,3.74-1.71,3.74-3.82V24.52H16v-1H26.23a4.73,4.73,0,0,0,4.7-3.74,14.82,14.82,0,0,0,0-7.54c-.57-2.28-1.86-3.82-4-3.82h-2.6v3.41a4.73,4.73,0,0,1-4.63,4.63H12.35a3.72,3.72,0,0,0-3.73,3.73v7.14a4.16,4.16,0,0,0,3.73,3.82A20.49,20.49,0,0,0,16.16,31.5Zm4.06-2.27a1.39,1.39,0,0,1-1.38-1.46,1.37,1.37,0,0,1,1.38-1.38,1.42,1.42,0,0,1,1.38,1.38A1.44,1.44,0,0,1,20.22,29.23Z" fill="#ffd43b"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -80,8 +80,6 @@ export default function themePalette(theme) {
main: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.grey50
},
canvasHeader: {
executionLight: theme.colors?.successLight,
executionDark: theme.colors?.successDark,
deployLight: theme.colors?.primaryLight,
deployDark: theme.colors?.primaryDark,
saveLight: theme.colors?.secondaryLight,

View File

@ -0,0 +1,163 @@
import { createPortal } from 'react-dom'
import { useState } from 'react'
import PropTypes from 'prop-types'
import { Tabs, Tab, Dialog, DialogContent, DialogTitle, Box } from '@mui/material'
import { CopyBlock, atomOneDark } from 'react-code-blocks'
import { baseURL } from 'store/constant'
import pythonSVG from 'assets/images/python.svg'
import javascriptSVG from 'assets/images/javascript.svg'
import cURLSVG from 'assets/images/cURL.svg'
function TabPanel(props) {
const { children, value, index, ...other } = props
return (
<div
role='tabpanel'
hidden={value !== index}
id={`attachment-tabpanel-${index}`}
aria-labelledby={`attachment-tab-${index}`}
{...other}
>
{value === index && <Box sx={{ p: 1 }}>{children}</Box>}
</div>
)
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.number.isRequired,
value: PropTypes.number.isRequired
}
function a11yProps(index) {
return {
id: `attachment-tab-${index}`,
'aria-controls': `attachment-tabpanel-${index}`
}
}
const APICodeDialog = ({ show, dialogProps, onCancel }) => {
const portalElement = document.getElementById('portal')
const codes = ['Python', 'JavaScript', 'cURL']
const [value, setValue] = useState(0)
const handleChange = (event, newValue) => {
setValue(newValue)
}
const getCode = (codeLang) => {
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?",
})
`
} else if (codeLang === 'JavaScript') {
return `async function query(data) {
const response = await fetch(
"${baseURL}/api/v1/prediction/${dialogProps.chatflowid}",
{
method: "POST",
body: {
"question": "Hey, how are you?"
},
}
);
const result = await response.json();
return result;
}
`
} else if (codeLang === 'cURL') {
return `curl ${baseURL}/api/v1/prediction/${dialogProps.chatflowid} \\
-X POST \\
-d '{"question": "Hey, how are you?"}'`
}
return ''
}
const getLang = (codeLang) => {
if (codeLang === 'Python') {
return 'python'
} else if (codeLang === 'JavaScript') {
return 'javascript'
} else if (codeLang === 'cURL') {
return 'bash'
}
return 'python'
}
const getSVG = (codeLang) => {
if (codeLang === 'Python') {
return pythonSVG
} else if (codeLang === 'JavaScript') {
return javascriptSVG
} else if (codeLang === 'cURL') {
return cURLSVG
}
return pythonSVG
}
const component = show ? (
<Dialog
open={show}
fullWidth
maxWidth='md'
onClose={onCancel}
aria-labelledby='alert-dialog-title'
aria-describedby='alert-dialog-description'
>
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
{dialogProps.title}
</DialogTitle>
<DialogContent>
<Tabs value={value} onChange={handleChange} aria-label='tabs'>
{codes.map((codeLang, index) => (
<Tab
icon={
<img
style={{ objectFit: 'cover', height: 'auto', width: 'auto', marginLeft: 10 }}
src={getSVG(codeLang)}
alt='code'
/>
}
iconPosition='left'
key={index}
label={codeLang}
{...a11yProps(index)}
></Tab>
))}
</Tabs>
<div style={{ marginTop: 10 }}></div>
{codes.map((codeLang, index) => (
<TabPanel key={index} value={value} index={index}>
<CopyBlock
theme={atomOneDark}
text={getCode(codeLang)}
language={getLang(codeLang)}
showLineNumbers={false}
wrapLines
/>
</TabPanel>
))}
</DialogContent>
</Dialog>
) : null
return createPortal(component, portalElement)
}
APICodeDialog.propTypes = {
show: PropTypes.bool,
dialogProps: PropTypes.object,
onCancel: PropTypes.func
}
export default APICodeDialog

View File

@ -8,11 +8,12 @@ import { useTheme } from '@mui/material/styles'
import { Avatar, Box, ButtonBase, Typography, Stack, TextField } from '@mui/material'
// icons
import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck, IconX } from '@tabler/icons'
import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck, IconX, IconWorldWww } from '@tabler/icons'
// project imports
import Settings from 'views/settings'
import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog'
import APICodeDialog from 'ui-component/dialog/APICodeDialog'
// API
import chatflowsApi from 'api/chatflows'
@ -35,6 +36,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
const [flowName, setFlowName] = useState('')
const [isSettingsOpen, setSettingsOpen] = useState(false)
const [flowDialogOpen, setFlowDialogOpen] = useState(false)
const [apiDialogOpen, setAPIDialogOpen] = useState(false)
const [apiDialogProps, setAPIDialogProps] = useState({})
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
const canvas = useSelector((state) => state.canvas)
@ -76,6 +79,14 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
}
}
const onAPIDialogClick = () => {
setAPIDialogProps({
title: 'Use this chatflow with API',
chatflowid: chatflow.id
})
setAPIDialogOpen(true)
}
const onSaveChatflowClick = () => {
if (chatflow.id) handleSaveFlow(chatflow.name)
else setFlowDialogOpen(true)
@ -219,6 +230,26 @@ 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}
>
<IconWorldWww stroke={1.5} size='1.3rem' />
</Avatar>
</ButtonBase>
<ButtonBase title='Save Chatflow' sx={{ borderRadius: '50%', mr: 2 }}>
<Avatar
variant='rounded'
@ -277,6 +308,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
onCancel={() => setFlowDialogOpen(false)}
onConfirm={onConfirmSaveName}
/>
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
</>
)
}