Merge pull request #25 from FlowiseAI/feature/API
Feature/Add API dialog
This commit is contained in:
commit
f99abf0109
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue