|
import PropTypes from 'prop-types' |
|
import { useNavigate } from 'react-router-dom' |
|
import { useSelector } from 'react-redux' |
|
import { useEffect, useRef, useState } from 'react' |
|
|
|
|
|
import { useTheme } from '@mui/material/styles' |
|
import { Avatar, Box, ButtonBase, Typography, Stack, TextField } from '@mui/material' |
|
|
|
|
|
import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck, IconX, IconCode } from '@tabler/icons' |
|
|
|
|
|
import Settings from 'views/settings' |
|
import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog' |
|
import APICodeDialog from 'ui-component/dialog/APICodeDialog' |
|
|
|
|
|
import chatflowsApi from 'api/chatflows' |
|
|
|
|
|
import useApi from 'hooks/useApi' |
|
|
|
|
|
import { generateExportFlowData } from 'utils/genericHelper' |
|
import { uiBaseURL } from 'store/constant' |
|
|
|
|
|
|
|
const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFlow }) => { |
|
const theme = useTheme() |
|
const navigate = useNavigate() |
|
const flowNameRef = useRef() |
|
const settingsRef = useRef() |
|
|
|
const [isEditingFlowName, setEditingFlowName] = useState(null) |
|
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) |
|
|
|
const onSettingsItemClick = (setting) => { |
|
setSettingsOpen(false) |
|
|
|
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) |
|
let dataStr = JSON.stringify(generateExportFlowData(flowData)) |
|
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr) |
|
|
|
let exportFileDefaultName = `${chatflow.name} Chatflow.json` |
|
|
|
let linkElement = document.createElement('a') |
|
linkElement.setAttribute('href', dataUri) |
|
linkElement.setAttribute('download', exportFileDefaultName) |
|
linkElement.click() |
|
} catch (e) { |
|
console.error(e) |
|
} |
|
} |
|
} |
|
|
|
const onUploadFile = (file) => { |
|
setSettingsOpen(false) |
|
handleLoadFlow(file) |
|
} |
|
|
|
const submitFlowName = () => { |
|
if (chatflow.id) { |
|
const updateBody = { |
|
name: flowNameRef.current.value |
|
} |
|
updateChatflowApi.request(chatflow.id, updateBody) |
|
} |
|
} |
|
|
|
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: 'Embed in website or use as API', |
|
chatflowid: chatflow.id, |
|
chatflowApiKeyId: chatflow.apikeyid, |
|
isFormDataRequired |
|
}) |
|
setAPIDialogOpen(true) |
|
} |
|
|
|
const onSaveChatflowClick = () => { |
|
if (chatflow.id) handleSaveFlow(flowName) |
|
else setFlowDialogOpen(true) |
|
} |
|
|
|
const onConfirmSaveName = (flowName) => { |
|
setFlowDialogOpen(false) |
|
handleSaveFlow(flowName) |
|
} |
|
|
|
useEffect(() => { |
|
if (updateChatflowApi.data) { |
|
setFlowName(updateChatflowApi.data.name) |
|
} |
|
setEditingFlowName(false) |
|
|
|
|
|
}, [updateChatflowApi.data]) |
|
|
|
useEffect(() => { |
|
if (chatflow) { |
|
setFlowName(chatflow.name) |
|
} |
|
}, [chatflow]) |
|
|
|
return ( |
|
<> |
|
<Box> |
|
<ButtonBase title='Back' sx={{ borderRadius: '50%' }}> |
|
<Avatar |
|
variant='rounded' |
|
sx={{ |
|
...theme.typography.commonAvatar, |
|
...theme.typography.mediumAvatar, |
|
transition: 'all .2s ease-in-out', |
|
background: theme.palette.secondary.light, |
|
color: theme.palette.secondary.dark, |
|
'&:hover': { |
|
background: theme.palette.secondary.dark, |
|
color: theme.palette.secondary.light |
|
} |
|
}} |
|
color='inherit' |
|
onClick={() => |
|
window.history.state && window.history.state.idx > 0 ? navigate(-1) : navigate('/', { replace: true }) |
|
} |
|
> |
|
<IconChevronLeft stroke={1.5} size='1.3rem' /> |
|
</Avatar> |
|
</ButtonBase> |
|
</Box> |
|
<Box sx={{ flexGrow: 1 }}> |
|
{!isEditingFlowName && ( |
|
<Stack flexDirection='row'> |
|
<Typography |
|
sx={{ |
|
fontSize: '1.5rem', |
|
fontWeight: 600, |
|
ml: 2 |
|
}} |
|
> |
|
{canvas.isDirty && <strong style={{ color: theme.palette.orange.main }}>*</strong>} {flowName} |
|
</Typography> |
|
{chatflow?.id && ( |
|
<ButtonBase title='Edit Name' sx={{ borderRadius: '50%' }}> |
|
<Avatar |
|
variant='rounded' |
|
sx={{ |
|
...theme.typography.commonAvatar, |
|
...theme.typography.mediumAvatar, |
|
transition: 'all .2s ease-in-out', |
|
ml: 1, |
|
background: theme.palette.secondary.light, |
|
color: theme.palette.secondary.dark, |
|
'&:hover': { |
|
background: theme.palette.secondary.dark, |
|
color: theme.palette.secondary.light |
|
} |
|
}} |
|
color='inherit' |
|
onClick={() => setEditingFlowName(true)} |
|
> |
|
<IconPencil stroke={1.5} size='1.3rem' /> |
|
</Avatar> |
|
</ButtonBase> |
|
)} |
|
</Stack> |
|
)} |
|
{isEditingFlowName && ( |
|
<Stack flexDirection='row'> |
|
<TextField |
|
size='small' |
|
inputRef={flowNameRef} |
|
sx={{ |
|
width: '50%', |
|
ml: 2 |
|
}} |
|
defaultValue={flowName} |
|
/> |
|
<ButtonBase title='Save Name' sx={{ borderRadius: '50%' }}> |
|
<Avatar |
|
variant='rounded' |
|
sx={{ |
|
...theme.typography.commonAvatar, |
|
...theme.typography.mediumAvatar, |
|
transition: 'all .2s ease-in-out', |
|
background: theme.palette.success.light, |
|
color: theme.palette.success.dark, |
|
ml: 1, |
|
'&:hover': { |
|
background: theme.palette.success.dark, |
|
color: theme.palette.success.light |
|
} |
|
}} |
|
color='inherit' |
|
onClick={submitFlowName} |
|
> |
|
<IconCheck stroke={1.5} size='1.3rem' /> |
|
</Avatar> |
|
</ButtonBase> |
|
<ButtonBase title='Cancel' sx={{ borderRadius: '50%' }}> |
|
<Avatar |
|
variant='rounded' |
|
sx={{ |
|
...theme.typography.commonAvatar, |
|
...theme.typography.mediumAvatar, |
|
transition: 'all .2s ease-in-out', |
|
background: theme.palette.error.light, |
|
color: theme.palette.error.dark, |
|
ml: 1, |
|
'&:hover': { |
|
background: theme.palette.error.dark, |
|
color: theme.palette.error.light |
|
} |
|
}} |
|
color='inherit' |
|
onClick={() => setEditingFlowName(false)} |
|
> |
|
<IconX stroke={1.5} size='1.3rem' /> |
|
</Avatar> |
|
</ButtonBase> |
|
</Stack> |
|
)} |
|
</Box> |
|
<Box> |
|
{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' |
|
sx={{ |
|
...theme.typography.commonAvatar, |
|
...theme.typography.mediumAvatar, |
|
transition: 'all .2s ease-in-out', |
|
background: theme.palette.canvasHeader.saveLight, |
|
color: theme.palette.canvasHeader.saveDark, |
|
'&:hover': { |
|
background: theme.palette.canvasHeader.saveDark, |
|
color: theme.palette.canvasHeader.saveLight |
|
} |
|
}} |
|
color='inherit' |
|
onClick={onSaveChatflowClick} |
|
> |
|
<IconDeviceFloppy stroke={1.5} size='1.3rem' /> |
|
</Avatar> |
|
</ButtonBase> |
|
<ButtonBase ref={settingsRef} title='Settings' sx={{ borderRadius: '50%' }}> |
|
<Avatar |
|
variant='rounded' |
|
sx={{ |
|
...theme.typography.commonAvatar, |
|
...theme.typography.mediumAvatar, |
|
transition: 'all .2s ease-in-out', |
|
background: theme.palette.canvasHeader.settingsLight, |
|
color: theme.palette.canvasHeader.settingsDark, |
|
'&:hover': { |
|
background: theme.palette.canvasHeader.settingsDark, |
|
color: theme.palette.canvasHeader.settingsLight |
|
} |
|
}} |
|
onClick={() => setSettingsOpen(!isSettingsOpen)} |
|
> |
|
<IconSettings stroke={1.5} size='1.3rem' /> |
|
</Avatar> |
|
</ButtonBase> |
|
</Box> |
|
<Settings |
|
chatflow={chatflow} |
|
isSettingsOpen={isSettingsOpen} |
|
anchorEl={settingsRef.current} |
|
onClose={() => setSettingsOpen(false)} |
|
onSettingsItemClick={onSettingsItemClick} |
|
onUploadFile={onUploadFile} |
|
/> |
|
<SaveChatflowDialog |
|
show={flowDialogOpen} |
|
dialogProps={{ |
|
title: `Save New Chatflow`, |
|
confirmButtonName: 'Save', |
|
cancelButtonName: 'Cancel' |
|
}} |
|
onCancel={() => setFlowDialogOpen(false)} |
|
onConfirm={onConfirmSaveName} |
|
/> |
|
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} /> |
|
</> |
|
) |
|
} |
|
|
|
CanvasHeader.propTypes = { |
|
chatflow: PropTypes.object, |
|
handleSaveFlow: PropTypes.func, |
|
handleDeleteFlow: PropTypes.func, |
|
handleLoadFlow: PropTypes.func |
|
} |
|
|
|
export default CanvasHeader |
|
|