Feature/extract import all (#2796)

* use existing route to get all chatflows

* add export all chatflows functionality

* add read exported all chatflows json file functionality

* add save chatflows functionality in server

* chore rename saveChatflows to importChatflows and others

* chore rewrite snackbar message

* fix import chatflows when no data in chatflows db

* add handle when import file array length is 0

* chore update and add meaning comment in importChatflows

* update method of storing flowdata for importChatflows function

* Refresh/redirect to chatflows when import is successful

* fix lint

---------

Co-authored-by: Ilango <rajagopalilango@gmail.com>
This commit is contained in:
Ong Chung Yau
2024-07-16 09:47:41 +08:00
committed by GitHub
parent 074bb738a3
commit 95b2cf7b7f
7 changed files with 254 additions and 30 deletions
@@ -1,13 +1,15 @@
import { useState, useRef, useEffect } from 'react'
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction, MENU_OPEN, REMOVE_DIRTY } from '@/store/actions'
import { sanitizeChatflows } from '@/utils/genericHelper'
import useNotifier from '@/utils/useNotifier'
import PropTypes from 'prop-types'
import { useSelector } from 'react-redux'
import { useEffect, useRef, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
// material-ui
import { useTheme } from '@mui/material/styles'
import {
Box,
ButtonBase,
Avatar,
Box,
Button,
ButtonBase,
ClickAwayListener,
Divider,
List,
@@ -18,20 +20,27 @@ import {
Popper,
Typography
} from '@mui/material'
import { useTheme } from '@mui/material/styles'
// third-party
import PerfectScrollbar from 'react-perfect-scrollbar'
// project imports
import MainCard from '@/ui-component/cards/MainCard'
import Transitions from '@/ui-component/extended/Transitions'
import AboutDialog from '@/ui-component/dialog/AboutDialog'
import Transitions from '@/ui-component/extended/Transitions'
// assets
import { IconLogout, IconSettings, IconInfoCircle } from '@tabler/icons-react'
import { IconFileExport, IconFileUpload, IconInfoCircle, IconLogout, IconSettings, IconX } from '@tabler/icons-react'
import './index.css'
//API
import chatFlowsApi from '@/api/chatflows'
// Hooks
import useApi from '@/hooks/useApi'
import { useLocation, useNavigate } from 'react-router-dom'
// ==============================|| PROFILE MENU ||============================== //
const ProfileSection = ({ username, handleLogout }) => {
@@ -43,6 +52,17 @@ const ProfileSection = ({ username, handleLogout }) => {
const [aboutDialogOpen, setAboutDialogOpen] = useState(false)
const anchorRef = useRef(null)
const inputRef = useRef()
const navigate = useNavigate()
const location = useLocation()
// ==============================|| Snackbar ||============================== //
useNotifier()
const dispatch = useDispatch()
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
@@ -55,6 +75,106 @@ const ProfileSection = ({ username, handleLogout }) => {
setOpen((prevOpen) => !prevOpen)
}
const errorFailed = (message) => {
enqueueSnackbar({
message: message,
options: {
key: new Date().getTime() + Math.random(),
variant: 'error',
persist: true,
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
const importChatflowsApi = useApi(chatFlowsApi.importChatflows)
const fileChange = (e) => {
if (!e.target.files) return
const file = e.target.files[0]
const reader = new FileReader()
reader.onload = (evt) => {
if (!evt?.target?.result) {
return
}
const chatflows = JSON.parse(evt.target.result)
importChatflowsApi.request(chatflows)
}
reader.readAsText(file)
}
const importChatflowsSuccess = () => {
dispatch({ type: REMOVE_DIRTY })
enqueueSnackbar({
message: `Import chatflows successful`,
options: {
key: new Date().getTime() + Math.random(),
variant: 'success',
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
useEffect(() => {
if (importChatflowsApi.error) errorFailed(`Failed to import chatflows: ${importChatflowsApi.error.response.data.message}`)
if (importChatflowsApi.data) {
importChatflowsSuccess()
// if current location is /chatflows, refresh the page
if (location.pathname === '/chatflows') navigate(0)
else {
// if not redirect to /chatflows
dispatch({ type: MENU_OPEN, id: 'chatflows' })
navigate('/chatflows')
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [importChatflowsApi.error, importChatflowsApi.data])
const importAllChatflows = () => {
inputRef.current.click()
}
const getAllChatflowsApi = useApi(chatFlowsApi.getAllChatflows)
const exportChatflowsSuccess = () => {
dispatch({ type: REMOVE_DIRTY })
enqueueSnackbar({
message: `Export chatflows successful`,
options: {
key: new Date().getTime() + Math.random(),
variant: 'success',
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
useEffect(() => {
if (getAllChatflowsApi.error) errorFailed(`Failed to export Chatflows: ${getAllChatflowsApi.error.response.data.message}`)
if (getAllChatflowsApi.data) {
const sanitizedChatflows = sanitizeChatflows(getAllChatflowsApi.data)
const dataStr = JSON.stringify({ Chatflows: sanitizedChatflows }, null, 2)
const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
const exportFileDefaultName = 'AllChatflows.json'
const linkElement = document.createElement('a')
linkElement.setAttribute('href', dataUri)
linkElement.setAttribute('download', exportFileDefaultName)
linkElement.click()
exportChatflowsSuccess()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getAllChatflowsApi.error, getAllChatflowsApi.data])
const prevOpen = useRef(open)
useEffect(() => {
if (prevOpen.current === true && open === false) {
@@ -135,6 +255,29 @@ const ProfileSection = ({ username, handleLogout }) => {
}
}}
>
<ListItemButton
sx={{ borderRadius: `${customization.borderRadius}px` }}
onClick={() => {
getAllChatflowsApi.request()
}}
>
<ListItemIcon>
<IconFileExport stroke={1.5} size='1.3rem' />
</ListItemIcon>
<ListItemText primary={<Typography variant='body2'>Export Chatflows</Typography>} />
</ListItemButton>
<ListItemButton
sx={{ borderRadius: `${customization.borderRadius}px` }}
onClick={() => {
importAllChatflows()
}}
>
<ListItemIcon>
<IconFileUpload stroke={1.5} size='1.3rem' />
</ListItemIcon>
<ListItemText primary={<Typography variant='body2'>Import Chatflows</Typography>} />
</ListItemButton>
<input ref={inputRef} type='file' hidden onChange={fileChange} />
<ListItemButton
sx={{ borderRadius: `${customization.borderRadius}px` }}
onClick={() => {