mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 13:00:56 +03:00
Feature/Custom Assistant Builder (#3631)
* add custom assistant builder * add tools to custom assistant * add save assistant button
This commit is contained in:
@@ -8,7 +8,7 @@ const getAllAvailableAssistants = (credentialId) => client.get(`/openai-assistan
|
||||
// Assistant
|
||||
const createNewAssistant = (body) => client.post(`/assistants`, body)
|
||||
|
||||
const getAllAssistants = () => client.get('/assistants')
|
||||
const getAllAssistants = (type) => client.get('/assistants?type=' + type)
|
||||
|
||||
const getSpecificAssistant = (id) => client.get(`/assistants/${id}`)
|
||||
|
||||
@@ -44,6 +44,12 @@ const uploadFilesToAssistant = (credentialId, formData) =>
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
|
||||
const getChatModels = () => client.get('/assistants/components/chatmodels')
|
||||
const getDocStores = () => client.get('/assistants/components/docstores')
|
||||
const getTools = () => client.get('/assistants/components/tools')
|
||||
|
||||
const generateAssistantInstruction = (body) => client.post(`/assistants/generate/instruction`, body)
|
||||
|
||||
export default {
|
||||
getAllAssistants,
|
||||
getSpecificAssistant,
|
||||
@@ -59,5 +65,9 @@ export default {
|
||||
uploadFilesToAssistant,
|
||||
uploadFilesToAssistantVectorStore,
|
||||
deleteFilesFromAssistantVectorStore,
|
||||
deleteAssistantVectorStore
|
||||
deleteAssistantVectorStore,
|
||||
getChatModels,
|
||||
getDocStores,
|
||||
getTools,
|
||||
generateAssistantInstruction
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ const getVectorStoreProviders = () => client.get('/document-store/components/vec
|
||||
const getEmbeddingProviders = () => client.get('/document-store/components/embeddings')
|
||||
const getRecordManagerProviders = () => client.get('/document-store/components/recordmanager')
|
||||
|
||||
const generateDocStoreToolDesc = (storeId, body) => client.post('/document-store/generate-tool-desc/' + storeId, body)
|
||||
|
||||
export default {
|
||||
getAllDocumentStores,
|
||||
getSpecificDocumentStore,
|
||||
@@ -49,5 +51,6 @@ export default {
|
||||
deleteVectorStoreDataFromStore,
|
||||
updateVectorStoreConfig,
|
||||
saveProcessingLoader,
|
||||
refreshLoader
|
||||
refreshLoader,
|
||||
generateDocStoreToolDesc
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@ const Sidebar = ({ drawerOpen, drawerToggle, window }) => {
|
||||
top: `${headerHeight}px`
|
||||
},
|
||||
borderRight: drawerOpen ? '1px solid' : 'none',
|
||||
borderColor: drawerOpen ? theme.palette.primary[200] + 75 : 'transparent'
|
||||
borderColor: drawerOpen ? theme.palette.primary[200] + 75 : 'transparent',
|
||||
zIndex: 1000
|
||||
}
|
||||
}}
|
||||
ModalProps={{ keepMounted: true }}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// assets
|
||||
import { IconTrash, IconMessage, IconAdjustmentsHorizontal, IconUsers } from '@tabler/icons-react'
|
||||
|
||||
// constant
|
||||
const icons = {
|
||||
IconTrash,
|
||||
IconMessage,
|
||||
IconAdjustmentsHorizontal,
|
||||
IconUsers
|
||||
}
|
||||
|
||||
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
||||
|
||||
const customAssistantSettings = {
|
||||
id: 'settings',
|
||||
title: '',
|
||||
type: 'group',
|
||||
children: [
|
||||
{
|
||||
id: 'viewMessages',
|
||||
title: 'View Messages',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconMessage
|
||||
},
|
||||
{
|
||||
id: 'viewLeads',
|
||||
title: 'View Leads',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconUsers
|
||||
},
|
||||
{
|
||||
id: 'chatflowConfiguration',
|
||||
title: 'Configuration',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconAdjustmentsHorizontal
|
||||
},
|
||||
{
|
||||
id: 'deleteAssistant',
|
||||
title: 'Delete Assistant',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconTrash
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default customAssistantSettings
|
||||
@@ -38,6 +38,14 @@ const dashboard = {
|
||||
breadcrumbs: true,
|
||||
isBeta: true
|
||||
},
|
||||
{
|
||||
id: 'assistants',
|
||||
title: 'Assistants',
|
||||
type: 'item',
|
||||
url: '/assistants',
|
||||
icon: icons.IconRobot,
|
||||
breadcrumbs: true
|
||||
},
|
||||
{
|
||||
id: 'marketplaces',
|
||||
title: 'Marketplaces',
|
||||
@@ -54,14 +62,6 @@ const dashboard = {
|
||||
icon: icons.IconTool,
|
||||
breadcrumbs: true
|
||||
},
|
||||
{
|
||||
id: 'assistants',
|
||||
title: 'Assistants',
|
||||
type: 'item',
|
||||
url: '/assistants',
|
||||
icon: icons.IconRobot,
|
||||
breadcrumbs: true
|
||||
},
|
||||
{
|
||||
id: 'credentials',
|
||||
title: 'Credentials',
|
||||
|
||||
@@ -21,6 +21,9 @@ const Tools = Loadable(lazy(() => import('@/views/tools')))
|
||||
|
||||
// assistants routing
|
||||
const Assistants = Loadable(lazy(() => import('@/views/assistants')))
|
||||
const OpenAIAssistantLayout = Loadable(lazy(() => import('@/views/assistants/openai/OpenAIAssistantLayout')))
|
||||
const CustomAssistantLayout = Loadable(lazy(() => import('@/views/assistants/custom/CustomAssistantLayout')))
|
||||
const CustomAssistantConfigurePreview = Loadable(lazy(() => import('@/views/assistants/custom/CustomAssistantConfigurePreview')))
|
||||
|
||||
// credentials routing
|
||||
const Credentials = Loadable(lazy(() => import('@/views/credentials')))
|
||||
@@ -70,6 +73,18 @@ const MainRoutes = {
|
||||
path: '/assistants',
|
||||
element: <Assistants />
|
||||
},
|
||||
{
|
||||
path: '/assistants/custom',
|
||||
element: <CustomAssistantLayout />
|
||||
},
|
||||
{
|
||||
path: '/assistants/custom/:id',
|
||||
element: <CustomAssistantConfigurePreview />
|
||||
},
|
||||
{
|
||||
path: '/assistants/openai',
|
||||
element: <OpenAIAssistantLayout />
|
||||
},
|
||||
{
|
||||
path: '/credentials',
|
||||
element: <Credentials />
|
||||
|
||||
@@ -272,7 +272,9 @@ export default function FlowListMenu({ chatflow, isAgentCanvas, setError, update
|
||||
try {
|
||||
const flowData = JSON.parse(chatflow.flowData)
|
||||
let dataStr = JSON.stringify(generateExportFlowData(flowData), null, 2)
|
||||
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
//let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
const blob = new Blob([dataStr], { type: 'application/json' })
|
||||
const dataUri = URL.createObjectURL(blob)
|
||||
|
||||
let exportFileDefaultName = `${chatflow.name} ${title}.json`
|
||||
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { OutlinedInput, DialogActions, Button, Dialog, DialogContent, DialogTitle } from '@mui/material'
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import assistantsApi from '@/api/assistants'
|
||||
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions'
|
||||
import { IconX, IconWand, IconArrowLeft, IconNotebook, IconLanguage, IconMail, IconCode, IconReport, IconWorld } from '@tabler/icons-react'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
import { LoadingButton } from '@mui/lab'
|
||||
|
||||
const defaultInstructions = [
|
||||
{
|
||||
text: 'Summarize a document',
|
||||
img: <IconNotebook />
|
||||
},
|
||||
{
|
||||
text: 'Translate the language',
|
||||
img: <IconLanguage />
|
||||
},
|
||||
{
|
||||
text: 'Write me an email',
|
||||
img: <IconMail />
|
||||
},
|
||||
{
|
||||
text: 'Convert the code to another language',
|
||||
img: <IconCode />
|
||||
},
|
||||
{
|
||||
text: 'Research and generate a report',
|
||||
img: <IconReport />
|
||||
},
|
||||
{
|
||||
text: 'Plan a trip',
|
||||
img: <IconWorld />
|
||||
}
|
||||
]
|
||||
|
||||
const AssistantPromptGenerator = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const [customAssistantInstruction, setCustomAssistantInstruction] = useState('')
|
||||
const [generatedInstruction, setGeneratedInstruction] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// ==============================|| Snackbar ||============================== //
|
||||
const dispatch = useDispatch()
|
||||
useNotifier()
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const onGenerate = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const selectedChatModelObj = {
|
||||
name: dialogProps.data.selectedChatModel.name,
|
||||
inputs: dialogProps.data.selectedChatModel.inputs
|
||||
}
|
||||
const resp = await assistantsApi.generateAssistantInstruction({
|
||||
selectedChatModel: selectedChatModelObj,
|
||||
task: customAssistantInstruction
|
||||
})
|
||||
|
||||
if (resp.data) {
|
||||
setLoading(false)
|
||||
if (resp.data.content) {
|
||||
setGeneratedInstruction(resp.data.content)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
enqueueSnackbar({
|
||||
message: typeof error.response.data === 'object' ? error.response.data.message : error.response.data,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// clear the state when dialog is closed
|
||||
useEffect(() => {
|
||||
if (!show) {
|
||||
setCustomAssistantInstruction('')
|
||||
setGeneratedInstruction('')
|
||||
}
|
||||
}, [show])
|
||||
|
||||
const component = show ? (
|
||||
<>
|
||||
<Dialog
|
||||
fullWidth
|
||||
maxWidth='md'
|
||||
open={show}
|
||||
onClose={onCancel}
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
{dialogProps.title}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<span>{dialogProps.description}</span>
|
||||
<div
|
||||
style={{
|
||||
display: 'block',
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
marginTop: '15px'
|
||||
}}
|
||||
>
|
||||
{defaultInstructions.map((instruction, index) => {
|
||||
return (
|
||||
<Button
|
||||
size='small'
|
||||
key={index}
|
||||
sx={{ textTransform: 'none', mr: 1, mb: 1, borderRadius: '16px' }}
|
||||
variant='outlined'
|
||||
color='inherit'
|
||||
onClick={() => {
|
||||
setCustomAssistantInstruction(instruction.text)
|
||||
setGeneratedInstruction('')
|
||||
}}
|
||||
startIcon={instruction.img}
|
||||
>
|
||||
{instruction.text}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{!generatedInstruction && (
|
||||
<OutlinedInput
|
||||
sx={{ mt: 2, width: '100%' }}
|
||||
type={'text'}
|
||||
multiline={true}
|
||||
rows={12}
|
||||
disabled={loading}
|
||||
value={customAssistantInstruction}
|
||||
placeholder={'Describe your task here'}
|
||||
onChange={(event) => setCustomAssistantInstruction(event.target.value)}
|
||||
/>
|
||||
)}
|
||||
{generatedInstruction && (
|
||||
<OutlinedInput
|
||||
sx={{ mt: 2, width: '100%' }}
|
||||
type={'text'}
|
||||
multiline={true}
|
||||
rows={12}
|
||||
value={generatedInstruction}
|
||||
onChange={(event) => setGeneratedInstruction(event.target.value)}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ pb: 3, pr: 3 }}>
|
||||
{!generatedInstruction && (
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
variant='contained'
|
||||
onClick={() => {
|
||||
onGenerate()
|
||||
}}
|
||||
startIcon={<IconWand size={20} />}
|
||||
>
|
||||
Generate
|
||||
</LoadingButton>
|
||||
)}
|
||||
{generatedInstruction && (
|
||||
<Button
|
||||
variant='outlined'
|
||||
startIcon={<IconArrowLeft size={20} />}
|
||||
onClick={() => {
|
||||
setGeneratedInstruction('')
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
{generatedInstruction && (
|
||||
<StyledButton variant='contained' onClick={() => onConfirm(generatedInstruction)}>
|
||||
Apply
|
||||
</StyledButton>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
AssistantPromptGenerator.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onConfirm: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
}
|
||||
|
||||
export default AssistantPromptGenerator
|
||||
@@ -75,7 +75,9 @@ const ViewLeadsDialog = ({ show, dialogProps, onCancel }) => {
|
||||
leads
|
||||
}
|
||||
const dataStr = JSON.stringify(exportData, null, 2)
|
||||
const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
//const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
const blob = new Blob([dataStr], { type: 'application/json' })
|
||||
const dataUri = URL.createObjectURL(blob)
|
||||
|
||||
const exportFileDefaultName = `${dialogProps.chatflow.id}-leads.json`
|
||||
|
||||
|
||||
@@ -372,7 +372,9 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
}
|
||||
|
||||
const dataStr = JSON.stringify(exportMessages, null, 2)
|
||||
const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
//const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
const blob = new Blob([dataStr], { type: 'application/json' })
|
||||
const dataUri = URL.createObjectURL(blob)
|
||||
|
||||
const exportFileDefaultName = `${dialogProps.chatflow.id}-Message.json`
|
||||
|
||||
|
||||
@@ -40,11 +40,48 @@ export const Dropdown = ({ name, value, loading, options, onSelect, disabled = f
|
||||
onSelect(value)
|
||||
}}
|
||||
PopperComponent={StyledPopper}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} value={internalValue} sx={{ height: '100%', '& .MuiInputBase-root': { height: '100%' } }} />
|
||||
)}
|
||||
renderInput={(params) => {
|
||||
const matchingOption = findMatchingOptions(options, internalValue)
|
||||
return (
|
||||
<TextField
|
||||
{...params}
|
||||
value={internalValue}
|
||||
sx={{
|
||||
height: '100%',
|
||||
'& .MuiInputBase-root': { height: '100%' }
|
||||
}}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
startAdornment: matchingOption?.imageSrc ? (
|
||||
<Box
|
||||
component='img'
|
||||
src={matchingOption.imageSrc}
|
||||
alt={matchingOption.label || 'Selected Option'}
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
/>
|
||||
) : null
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
renderOption={(props, option) => (
|
||||
<Box component='li' {...props}>
|
||||
<Box component='li' {...props} sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{option.imageSrc && (
|
||||
<img
|
||||
src={option.imageSrc}
|
||||
alt={option.description}
|
||||
style={{
|
||||
width: 30,
|
||||
height: 30,
|
||||
padding: 1,
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Typography variant='h5'>{option.label}</Typography>
|
||||
{option.description && (
|
||||
|
||||
@@ -58,7 +58,8 @@ const Agentflows = () => {
|
||||
function filterFlows(data) {
|
||||
return (
|
||||
data.name.toLowerCase().indexOf(search.toLowerCase()) > -1 ||
|
||||
(data.category && data.category.toLowerCase().indexOf(search.toLowerCase()) > -1)
|
||||
(data.category && data.category.toLowerCase().indexOf(search.toLowerCase()) > -1) ||
|
||||
data.id.toLowerCase().indexOf(search.toLowerCase()) > -1
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import {
|
||||
HIDE_CANVAS_DIALOG,
|
||||
SHOW_CANVAS_DIALOG,
|
||||
enqueueSnackbar as enqueueSnackbarAction,
|
||||
closeSnackbar as closeSnackbarAction
|
||||
} from '@/store/actions'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
// Material
|
||||
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Box, Typography, OutlinedInput } from '@mui/material'
|
||||
|
||||
// Project imports
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog'
|
||||
|
||||
// Icons
|
||||
import { IconX, IconFiles } from '@tabler/icons-react'
|
||||
|
||||
// API
|
||||
import assistantsApi from '@/api/assistants'
|
||||
|
||||
// utils
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
|
||||
const AddCustomAssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
// ==============================|| Snackbar ||============================== //
|
||||
|
||||
useNotifier()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [customAssistantName, setCustomAssistantName] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
}, [show, dispatch])
|
||||
|
||||
const createCustomAssistant = async () => {
|
||||
try {
|
||||
const obj = {
|
||||
details: JSON.stringify({
|
||||
name: customAssistantName
|
||||
}),
|
||||
credential: uuidv4(),
|
||||
type: 'CUSTOM'
|
||||
}
|
||||
const createResp = await assistantsApi.createNewAssistant(obj)
|
||||
if (createResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'New Custom Assistant created.',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm(createResp.data.id)
|
||||
}
|
||||
} catch (err) {
|
||||
const errorData = typeof err === 'string' ? err : err.response?.data || `${err.response.data.message}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to add new Custom Assistant: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
fullWidth
|
||||
maxWidth='sm'
|
||||
open={show}
|
||||
onClose={onCancel}
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle style={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<IconFiles style={{ marginRight: '10px' }} />
|
||||
{dialogProps.title}
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<Typography>
|
||||
Name<span style={{ color: 'red' }}> *</span>
|
||||
</Typography>
|
||||
|
||||
<div style={{ flexGrow: 1 }}></div>
|
||||
</div>
|
||||
<OutlinedInput
|
||||
size='small'
|
||||
sx={{ mt: 1 }}
|
||||
type='string'
|
||||
fullWidth
|
||||
key='customAssistantName'
|
||||
onChange={(e) => setCustomAssistantName(e.target.value)}
|
||||
value={customAssistantName ?? ''}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => onCancel()}>Cancel</Button>
|
||||
<StyledButton disabled={!customAssistantName} variant='contained' onClick={() => createCustomAssistant()}>
|
||||
{dialogProps.confirmButtonName}
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
<ConfirmDialog />
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
AddCustomAssistantDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func,
|
||||
onConfirm: PropTypes.func
|
||||
}
|
||||
|
||||
export default AddCustomAssistantDialog
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,160 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
// material-ui
|
||||
import { Box, Stack, Skeleton } from '@mui/material'
|
||||
|
||||
// project imports
|
||||
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||
import MainCard from '@/ui-component/cards/MainCard'
|
||||
import ItemCard from '@/ui-component/cards/ItemCard'
|
||||
import { baseURL, gridSpacing } from '@/store/constant'
|
||||
import AssistantEmptySVG from '@/assets/images/assistant_empty.svg'
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import AddCustomAssistantDialog from './AddCustomAssistantDialog'
|
||||
import ErrorBoundary from '@/ErrorBoundary'
|
||||
|
||||
// API
|
||||
import assistantsApi from '@/api/assistants'
|
||||
|
||||
// Hooks
|
||||
import useApi from '@/hooks/useApi'
|
||||
|
||||
// icons
|
||||
import { IconPlus } from '@tabler/icons-react'
|
||||
|
||||
// ==============================|| CustomAssistantLayout ||============================== //
|
||||
|
||||
const CustomAssistantLayout = () => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const getAllAssistantsApi = useApi(assistantsApi.getAllAssistants)
|
||||
|
||||
const [isLoading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
const [showDialog, setShowDialog] = useState(false)
|
||||
const [dialogProps, setDialogProps] = useState({})
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
const onSearchChange = (event) => {
|
||||
setSearch(event.target.value)
|
||||
}
|
||||
|
||||
const addNew = () => {
|
||||
const dialogProp = {
|
||||
title: 'Add New Custom Assistant',
|
||||
type: 'ADD',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Add'
|
||||
}
|
||||
setDialogProps(dialogProp)
|
||||
setShowDialog(true)
|
||||
}
|
||||
|
||||
const onConfirm = (assistantId) => {
|
||||
setShowDialog(false)
|
||||
navigate(`/assistants/custom/${assistantId}`)
|
||||
}
|
||||
|
||||
function filterAssistants(data) {
|
||||
const parsedData = JSON.parse(data.details)
|
||||
return parsedData && parsedData.name && parsedData.name.toLowerCase().indexOf(search.toLowerCase()) > -1
|
||||
}
|
||||
|
||||
const getImages = (details) => {
|
||||
const images = []
|
||||
if (details && details.chatModel && details.chatModel.name) {
|
||||
images.push(`${baseURL}/api/v1/node-icon/${details.chatModel.name}`)
|
||||
}
|
||||
return images
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllAssistantsApi.request('CUSTOM')
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(getAllAssistantsApi.loading)
|
||||
}, [getAllAssistantsApi.loading])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllAssistantsApi.error) {
|
||||
setError(getAllAssistantsApi.error)
|
||||
}
|
||||
}, [getAllAssistantsApi.error])
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainCard>
|
||||
{error ? (
|
||||
<ErrorBoundary error={error} />
|
||||
) : (
|
||||
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
||||
<ViewHeader
|
||||
isBackButton={true}
|
||||
onSearchChange={onSearchChange}
|
||||
search={true}
|
||||
searchPlaceholder='Search Assistants'
|
||||
title='Custom Assistant'
|
||||
onBack={() => navigate(-1)}
|
||||
>
|
||||
<StyledButton
|
||||
variant='contained'
|
||||
sx={{ borderRadius: 2, height: 40 }}
|
||||
onClick={addNew}
|
||||
startIcon={<IconPlus />}
|
||||
>
|
||||
Add
|
||||
</StyledButton>
|
||||
</ViewHeader>
|
||||
{isLoading ? (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
</Box>
|
||||
) : (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
{getAllAssistantsApi.data &&
|
||||
getAllAssistantsApi.data?.filter(filterAssistants).map((data, index) => (
|
||||
<ItemCard
|
||||
data={{
|
||||
name: JSON.parse(data.details)?.name,
|
||||
description: JSON.parse(data.details)?.instruction
|
||||
}}
|
||||
images={getImages(JSON.parse(data.details))}
|
||||
key={index}
|
||||
onClick={() => navigate('/assistants/custom/' + data.id)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{!isLoading && (!getAllAssistantsApi.data || getAllAssistantsApi.data.length === 0) && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '20vh', width: 'auto' }}
|
||||
src={AssistantEmptySVG}
|
||||
alt='AssistantEmptySVG'
|
||||
/>
|
||||
</Box>
|
||||
<div>No Custom Assistants Added Yet</div>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</MainCard>
|
||||
<AddCustomAssistantDialog
|
||||
show={showDialog}
|
||||
dialogProps={dialogProps}
|
||||
onCancel={() => setShowDialog(false)}
|
||||
onConfirm={onConfirm}
|
||||
setError={setError}
|
||||
></AddCustomAssistantDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomAssistantLayout
|
||||
@@ -0,0 +1,361 @@
|
||||
export const toolAgentFlow = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'bufferMemory_0',
|
||||
data: {
|
||||
id: 'bufferMemory_0',
|
||||
label: 'Buffer Memory',
|
||||
version: 2,
|
||||
name: 'bufferMemory',
|
||||
type: 'BufferMemory',
|
||||
baseClasses: ['BufferMemory', 'BaseChatMemory', 'BaseMemory'],
|
||||
category: 'Memory',
|
||||
description: 'Retrieve chat messages stored in database',
|
||||
inputParams: [
|
||||
{
|
||||
label: 'Session Id',
|
||||
name: 'sessionId',
|
||||
type: 'string',
|
||||
description:
|
||||
'If not specified, a random id will be used. Learn <a target="_blank" href="https://docs.flowiseai.com/memory#ui-and-embedded-chat">more</a>',
|
||||
default: '',
|
||||
additionalParams: true,
|
||||
optional: true,
|
||||
id: 'bufferMemory_0-input-sessionId-string'
|
||||
},
|
||||
{
|
||||
label: 'Memory Key',
|
||||
name: 'memoryKey',
|
||||
type: 'string',
|
||||
default: 'chat_history',
|
||||
additionalParams: true,
|
||||
id: 'bufferMemory_0-input-memoryKey-string'
|
||||
}
|
||||
],
|
||||
inputAnchors: [],
|
||||
inputs: {
|
||||
sessionId: '',
|
||||
memoryKey: 'chat_history'
|
||||
},
|
||||
outputAnchors: [
|
||||
{
|
||||
id: 'bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory',
|
||||
name: 'bufferMemory',
|
||||
label: 'BufferMemory',
|
||||
description: 'Retrieve chat messages stored in database',
|
||||
type: 'BufferMemory | BaseChatMemory | BaseMemory'
|
||||
}
|
||||
],
|
||||
outputs: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'chatOpenAI_0',
|
||||
data: {
|
||||
id: 'chatOpenAI_0',
|
||||
label: 'ChatOpenAI',
|
||||
version: 8,
|
||||
name: 'chatOpenAI',
|
||||
type: 'ChatOpenAI',
|
||||
baseClasses: ['ChatOpenAI', 'BaseChatModel', 'BaseLanguageModel', 'Runnable'],
|
||||
category: 'Chat Models',
|
||||
description: 'Wrapper around OpenAI large language models that use the Chat endpoint',
|
||||
inputParams: [
|
||||
{
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['openAIApi'],
|
||||
id: 'chatOpenAI_0-input-credential-credential'
|
||||
},
|
||||
{
|
||||
label: 'Model Name',
|
||||
name: 'modelName',
|
||||
type: 'asyncOptions',
|
||||
loadMethod: 'listModels',
|
||||
default: 'gpt-4o-mini',
|
||||
id: 'chatOpenAI_0-input-modelName-asyncOptions'
|
||||
},
|
||||
{
|
||||
label: 'Temperature',
|
||||
name: 'temperature',
|
||||
type: 'number',
|
||||
step: 0.1,
|
||||
default: 0.9,
|
||||
optional: true,
|
||||
id: 'chatOpenAI_0-input-temperature-number'
|
||||
},
|
||||
{
|
||||
label: 'Streaming',
|
||||
name: 'streaming',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
optional: true,
|
||||
additionalParams: true,
|
||||
id: 'chatOpenAI_0-input-streaming-boolean'
|
||||
},
|
||||
{
|
||||
label: 'Max Tokens',
|
||||
name: 'maxTokens',
|
||||
type: 'number',
|
||||
step: 1,
|
||||
optional: true,
|
||||
additionalParams: true,
|
||||
id: 'chatOpenAI_0-input-maxTokens-number'
|
||||
},
|
||||
{
|
||||
label: 'Top Probability',
|
||||
name: 'topP',
|
||||
type: 'number',
|
||||
step: 0.1,
|
||||
optional: true,
|
||||
additionalParams: true,
|
||||
id: 'chatOpenAI_0-input-topP-number'
|
||||
},
|
||||
{
|
||||
label: 'Frequency Penalty',
|
||||
name: 'frequencyPenalty',
|
||||
type: 'number',
|
||||
step: 0.1,
|
||||
optional: true,
|
||||
additionalParams: true,
|
||||
id: 'chatOpenAI_0-input-frequencyPenalty-number'
|
||||
},
|
||||
{
|
||||
label: 'Presence Penalty',
|
||||
name: 'presencePenalty',
|
||||
type: 'number',
|
||||
step: 0.1,
|
||||
optional: true,
|
||||
additionalParams: true,
|
||||
id: 'chatOpenAI_0-input-presencePenalty-number'
|
||||
},
|
||||
{
|
||||
label: 'Timeout',
|
||||
name: 'timeout',
|
||||
type: 'number',
|
||||
step: 1,
|
||||
optional: true,
|
||||
additionalParams: true,
|
||||
id: 'chatOpenAI_0-input-timeout-number'
|
||||
},
|
||||
{
|
||||
label: 'BasePath',
|
||||
name: 'basepath',
|
||||
type: 'string',
|
||||
optional: true,
|
||||
additionalParams: true,
|
||||
id: 'chatOpenAI_0-input-basepath-string'
|
||||
},
|
||||
{
|
||||
label: 'Proxy Url',
|
||||
name: 'proxyUrl',
|
||||
type: 'string',
|
||||
optional: true,
|
||||
additionalParams: true,
|
||||
id: 'chatOpenAI_0-input-proxyUrl-string'
|
||||
},
|
||||
{
|
||||
label: 'Stop Sequence',
|
||||
name: 'stopSequence',
|
||||
type: 'string',
|
||||
rows: 4,
|
||||
optional: true,
|
||||
description: 'List of stop words to use when generating. Use comma to separate multiple stop words.',
|
||||
additionalParams: true,
|
||||
id: 'chatOpenAI_0-input-stopSequence-string'
|
||||
},
|
||||
{
|
||||
label: 'BaseOptions',
|
||||
name: 'baseOptions',
|
||||
type: 'json',
|
||||
optional: true,
|
||||
additionalParams: true,
|
||||
id: 'chatOpenAI_0-input-baseOptions-json'
|
||||
},
|
||||
{
|
||||
label: 'Allow Image Uploads',
|
||||
name: 'allowImageUploads',
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Allow image input. Refer to the <a href="https://docs.flowiseai.com/using-flowise/uploads#image" target="_blank">docs</a> for more details.',
|
||||
default: false,
|
||||
optional: true,
|
||||
id: 'chatOpenAI_0-input-allowImageUploads-boolean'
|
||||
},
|
||||
{
|
||||
label: 'Image Resolution',
|
||||
description: 'This parameter controls the resolution in which the model views the image.',
|
||||
name: 'imageResolution',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
label: 'Low',
|
||||
name: 'low'
|
||||
},
|
||||
{
|
||||
label: 'High',
|
||||
name: 'high'
|
||||
},
|
||||
{
|
||||
label: 'Auto',
|
||||
name: 'auto'
|
||||
}
|
||||
],
|
||||
default: 'low',
|
||||
optional: false,
|
||||
additionalParams: true,
|
||||
id: 'chatOpenAI_0-input-imageResolution-options'
|
||||
}
|
||||
],
|
||||
inputAnchors: [
|
||||
{
|
||||
label: 'Cache',
|
||||
name: 'cache',
|
||||
type: 'BaseCache',
|
||||
optional: true,
|
||||
id: 'chatOpenAI_0-input-cache-BaseCache'
|
||||
}
|
||||
],
|
||||
inputs: {
|
||||
cache: '',
|
||||
modelName: 'gpt-4o-mini',
|
||||
temperature: 0.9,
|
||||
streaming: true,
|
||||
maxTokens: '',
|
||||
topP: '',
|
||||
frequencyPenalty: '',
|
||||
presencePenalty: '',
|
||||
timeout: '',
|
||||
basepath: '',
|
||||
proxyUrl: '',
|
||||
stopSequence: '',
|
||||
baseOptions: '',
|
||||
allowImageUploads: '',
|
||||
imageResolution: 'low'
|
||||
},
|
||||
outputAnchors: [
|
||||
{
|
||||
id: 'chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable',
|
||||
name: 'chatOpenAI',
|
||||
label: 'ChatOpenAI',
|
||||
description: 'Wrapper around OpenAI large language models that use the Chat endpoint',
|
||||
type: 'ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable'
|
||||
}
|
||||
],
|
||||
outputs: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'toolAgent_0',
|
||||
data: {
|
||||
id: 'toolAgent_0',
|
||||
label: 'Tool Agent',
|
||||
version: 2,
|
||||
name: 'toolAgent',
|
||||
type: 'AgentExecutor',
|
||||
baseClasses: ['AgentExecutor', 'BaseChain', 'Runnable'],
|
||||
category: 'Agents',
|
||||
description: 'Agent that uses Function Calling to pick the tools and args to call',
|
||||
inputParams: [
|
||||
{
|
||||
label: 'System Message',
|
||||
name: 'systemMessage',
|
||||
type: 'string',
|
||||
default: 'You are a helpful AI assistant.',
|
||||
description: 'If Chat Prompt Template is provided, this will be ignored',
|
||||
rows: 4,
|
||||
optional: true,
|
||||
additionalParams: true,
|
||||
id: 'toolAgent_0-input-systemMessage-string'
|
||||
},
|
||||
{
|
||||
label: 'Max Iterations',
|
||||
name: 'maxIterations',
|
||||
type: 'number',
|
||||
optional: true,
|
||||
additionalParams: true,
|
||||
id: 'toolAgent_0-input-maxIterations-number'
|
||||
}
|
||||
],
|
||||
inputAnchors: [
|
||||
{
|
||||
label: 'Tools',
|
||||
name: 'tools',
|
||||
type: 'Tool',
|
||||
list: true,
|
||||
id: 'toolAgent_0-input-tools-Tool'
|
||||
},
|
||||
{
|
||||
label: 'Memory',
|
||||
name: 'memory',
|
||||
type: 'BaseChatMemory',
|
||||
id: 'toolAgent_0-input-memory-BaseChatMemory'
|
||||
},
|
||||
{
|
||||
label: 'Tool Calling Chat Model',
|
||||
name: 'model',
|
||||
type: 'BaseChatModel',
|
||||
description:
|
||||
'Only compatible with models that are capable of function calling: ChatOpenAI, ChatMistral, ChatAnthropic, ChatGoogleGenerativeAI, ChatVertexAI, GroqChat',
|
||||
id: 'toolAgent_0-input-model-BaseChatModel'
|
||||
},
|
||||
{
|
||||
label: 'Chat Prompt Template',
|
||||
name: 'chatPromptTemplate',
|
||||
type: 'ChatPromptTemplate',
|
||||
description: 'Override existing prompt with Chat Prompt Template. Human Message must includes {input} variable',
|
||||
optional: true,
|
||||
id: 'toolAgent_0-input-chatPromptTemplate-ChatPromptTemplate'
|
||||
},
|
||||
{
|
||||
label: 'Input Moderation',
|
||||
description: 'Detect text that could generate harmful output and prevent it from being sent to the language model',
|
||||
name: 'inputModeration',
|
||||
type: 'Moderation',
|
||||
optional: true,
|
||||
list: true,
|
||||
id: 'toolAgent_0-input-inputModeration-Moderation'
|
||||
}
|
||||
],
|
||||
inputs: {
|
||||
tools: [],
|
||||
memory: '{{bufferMemory_0.data.instance}}',
|
||||
model: '{{chatOpenAI_0.data.instance}}',
|
||||
chatPromptTemplate: '',
|
||||
systemMessage: 'You are helpful assistant',
|
||||
inputModeration: '',
|
||||
maxIterations: ''
|
||||
},
|
||||
outputAnchors: [
|
||||
{
|
||||
id: 'toolAgent_0-output-toolAgent-AgentExecutor|BaseChain|Runnable',
|
||||
name: 'toolAgent',
|
||||
label: 'AgentExecutor',
|
||||
description: 'Agent that uses Function Calling to pick the tools and args to call',
|
||||
type: 'AgentExecutor | BaseChain | Runnable'
|
||||
}
|
||||
],
|
||||
outputs: {}
|
||||
}
|
||||
}
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
source: 'bufferMemory_0',
|
||||
sourceHandle: 'bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory',
|
||||
target: 'toolAgent_0',
|
||||
targetHandle: 'toolAgent_0-input-memory-BaseChatMemory',
|
||||
type: 'buttonedge',
|
||||
id: 'bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-toolAgent_0-toolAgent_0-input-memory-BaseChatMemory'
|
||||
},
|
||||
{
|
||||
source: 'chatOpenAI_0',
|
||||
sourceHandle: 'chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable',
|
||||
target: 'toolAgent_0',
|
||||
targetHandle: 'toolAgent_0-input-model-BaseChatModel',
|
||||
type: 'buttonedge',
|
||||
id: 'chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-toolAgent_0-toolAgent_0-input-model-BaseChatModel'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,190 +1,131 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
// material-ui
|
||||
import { Box, Stack, Button, Skeleton } from '@mui/material'
|
||||
import { Card, CardContent, Stack } from '@mui/material'
|
||||
import { useTheme, styled } from '@mui/material/styles'
|
||||
|
||||
// project imports
|
||||
import MainCard from '@/ui-component/cards/MainCard'
|
||||
import ItemCard from '@/ui-component/cards/ItemCard'
|
||||
import { gridSpacing } from '@/store/constant'
|
||||
import AssistantEmptySVG from '@/assets/images/assistant_empty.svg'
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import AssistantDialog from './AssistantDialog'
|
||||
import LoadAssistantDialog from './LoadAssistantDialog'
|
||||
|
||||
// API
|
||||
import assistantsApi from '@/api/assistants'
|
||||
|
||||
// Hooks
|
||||
import useApi from '@/hooks/useApi'
|
||||
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||
|
||||
// icons
|
||||
import { IconPlus, IconFileUpload } from '@tabler/icons-react'
|
||||
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||
import ErrorBoundary from '@/ErrorBoundary'
|
||||
import { IconRobotFace, IconBrandOpenai, IconBrandAzure } from '@tabler/icons-react'
|
||||
|
||||
// ==============================|| CHATFLOWS ||============================== //
|
||||
const cards = [
|
||||
{
|
||||
title: 'Custom Assistant',
|
||||
description: 'Create custom assistant using your choice of LLMs',
|
||||
icon: <IconRobotFace />,
|
||||
iconText: 'Custom',
|
||||
gradient: 'linear-gradient(135deg, #fff8e14e 0%, #ffcc802f 100%)'
|
||||
},
|
||||
{
|
||||
title: 'OpenAI Assistant',
|
||||
description: 'Create assistant using OpenAI Assistant API',
|
||||
icon: <IconBrandOpenai />,
|
||||
iconText: 'OpenAI',
|
||||
gradient: 'linear-gradient(135deg, #c9ffd85f 0%, #a0f0b567 100%)'
|
||||
},
|
||||
{
|
||||
title: 'Azure Assistant (Coming Soon)',
|
||||
description: 'Create assistant using Azure Assistant API',
|
||||
icon: <IconBrandAzure />,
|
||||
iconText: 'Azure',
|
||||
gradient: 'linear-gradient(135deg, #c4e1ff57 0%, #80b7ff5a 100%)'
|
||||
}
|
||||
]
|
||||
|
||||
const StyledCard = styled(Card)(({ gradient }) => ({
|
||||
height: '300px',
|
||||
background: gradient,
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
transition: 'transform 0.3s ease-in-out, box-shadow 0.3s ease-in-out',
|
||||
cursor: 'pointer'
|
||||
}))
|
||||
|
||||
const FeatureIcon = styled('div')(() => ({
|
||||
display: 'inline-flex',
|
||||
padding: '4px 8px',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.05)',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '16px',
|
||||
'& svg': {
|
||||
width: '1.2rem',
|
||||
height: '1.2rem',
|
||||
marginRight: '8px'
|
||||
}
|
||||
}))
|
||||
|
||||
const FeatureCards = () => {
|
||||
const navigate = useNavigate()
|
||||
const theme = useTheme()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
|
||||
const onCardClick = (index) => {
|
||||
if (index === 0) navigate('/assistants/custom')
|
||||
if (index === 1) navigate('/assistants/openai')
|
||||
if (index === 2) alert('Under Development')
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
spacing={3}
|
||||
direction='row'
|
||||
sx={{
|
||||
width: '100%',
|
||||
justifyContent: 'space-between'
|
||||
}}
|
||||
>
|
||||
{cards.map((card, index) => (
|
||||
<StyledCard
|
||||
key={index}
|
||||
gradient={card.gradient}
|
||||
sx={{
|
||||
flex: 1,
|
||||
maxWidth: 'calc((100% - 2 * 16px) / 3)',
|
||||
height: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
border: 1,
|
||||
borderColor: theme.palette.grey[900] + 25,
|
||||
borderRadius: 2,
|
||||
color: customization.isDarkMode ? theme.palette.common.white : '#333333',
|
||||
cursor: index === 2 ? 'not-allowed' : 'pointer',
|
||||
opacity: index === 2 ? 0.6 : 1,
|
||||
'&:hover': {
|
||||
boxShadow: index === 2 ? 'none' : '0 4px 20px rgba(0, 0, 0, 0.1)'
|
||||
}
|
||||
}}
|
||||
onClick={() => index !== 2 && onCardClick(index)}
|
||||
>
|
||||
<CardContent className='h-full relative z-10'>
|
||||
<FeatureIcon>
|
||||
{card.icon}
|
||||
<span className='text-xs uppercase'>{card.iconText}</span>
|
||||
</FeatureIcon>
|
||||
<h2 className='text-2xl font-bold mb-2'>{card.title}</h2>
|
||||
<p className='text-gray-600'>{card.description}</p>
|
||||
</CardContent>
|
||||
</StyledCard>
|
||||
))}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
// ==============================|| ASSISTANTS ||============================== //
|
||||
|
||||
const Assistants = () => {
|
||||
const getAllAssistantsApi = useApi(assistantsApi.getAllAssistants)
|
||||
|
||||
const [isLoading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
const [showDialog, setShowDialog] = useState(false)
|
||||
const [dialogProps, setDialogProps] = useState({})
|
||||
const [showLoadDialog, setShowLoadDialog] = useState(false)
|
||||
const [loadDialogProps, setLoadDialogProps] = useState({})
|
||||
|
||||
const loadExisting = () => {
|
||||
const dialogProp = {
|
||||
title: 'Load Existing Assistant'
|
||||
}
|
||||
setLoadDialogProps(dialogProp)
|
||||
setShowLoadDialog(true)
|
||||
}
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
const onSearchChange = (event) => {
|
||||
setSearch(event.target.value)
|
||||
}
|
||||
|
||||
const onAssistantSelected = (selectedOpenAIAssistantId, credential) => {
|
||||
setShowLoadDialog(false)
|
||||
addNew(selectedOpenAIAssistantId, credential)
|
||||
}
|
||||
|
||||
const addNew = (selectedOpenAIAssistantId, credential) => {
|
||||
const dialogProp = {
|
||||
title: 'Add New Assistant',
|
||||
type: 'ADD',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Add',
|
||||
selectedOpenAIAssistantId,
|
||||
credential
|
||||
}
|
||||
setDialogProps(dialogProp)
|
||||
setShowDialog(true)
|
||||
}
|
||||
|
||||
const edit = (selectedAssistant) => {
|
||||
const dialogProp = {
|
||||
title: 'Edit Assistant',
|
||||
type: 'EDIT',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Save',
|
||||
data: selectedAssistant
|
||||
}
|
||||
setDialogProps(dialogProp)
|
||||
setShowDialog(true)
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
setShowDialog(false)
|
||||
getAllAssistantsApi.request()
|
||||
}
|
||||
|
||||
function filterAssistants(data) {
|
||||
const parsedData = JSON.parse(data.details)
|
||||
return parsedData && parsedData.name && parsedData.name.toLowerCase().indexOf(search.toLowerCase()) > -1
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllAssistantsApi.request()
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(getAllAssistantsApi.loading)
|
||||
}, [getAllAssistantsApi.loading])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllAssistantsApi.error) {
|
||||
setError(getAllAssistantsApi.error)
|
||||
}
|
||||
}, [getAllAssistantsApi.error])
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainCard>
|
||||
{error ? (
|
||||
<ErrorBoundary error={error} />
|
||||
) : (
|
||||
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
||||
<ViewHeader
|
||||
onSearchChange={onSearchChange}
|
||||
search={true}
|
||||
searchPlaceholder='Search Assistants'
|
||||
title='OpenAI Assistants'
|
||||
>
|
||||
<Button
|
||||
variant='outlined'
|
||||
onClick={loadExisting}
|
||||
startIcon={<IconFileUpload />}
|
||||
sx={{ borderRadius: 2, height: 40 }}
|
||||
>
|
||||
Load
|
||||
</Button>
|
||||
<StyledButton
|
||||
variant='contained'
|
||||
sx={{ borderRadius: 2, height: 40 }}
|
||||
onClick={addNew}
|
||||
startIcon={<IconPlus />}
|
||||
>
|
||||
Add
|
||||
</StyledButton>
|
||||
</ViewHeader>
|
||||
{isLoading ? (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
</Box>
|
||||
) : (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
{getAllAssistantsApi.data &&
|
||||
getAllAssistantsApi.data?.filter(filterAssistants).map((data, index) => (
|
||||
<ItemCard
|
||||
data={{
|
||||
name: JSON.parse(data.details)?.name,
|
||||
description: JSON.parse(data.details)?.instructions,
|
||||
iconSrc: data.iconSrc
|
||||
}}
|
||||
key={index}
|
||||
onClick={() => edit(data)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{!isLoading && (!getAllAssistantsApi.data || getAllAssistantsApi.data.length === 0) && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '20vh', width: 'auto' }}
|
||||
src={AssistantEmptySVG}
|
||||
alt='AssistantEmptySVG'
|
||||
/>
|
||||
</Box>
|
||||
<div>No Assistants Added Yet</div>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
||||
<ViewHeader title='Assistants' />
|
||||
<FeatureCards />
|
||||
</Stack>
|
||||
</MainCard>
|
||||
<LoadAssistantDialog
|
||||
show={showLoadDialog}
|
||||
dialogProps={loadDialogProps}
|
||||
onCancel={() => setShowLoadDialog(false)}
|
||||
onAssistantSelected={onAssistantSelected}
|
||||
setError={setError}
|
||||
></LoadAssistantDialog>
|
||||
<AssistantDialog
|
||||
show={showDialog}
|
||||
dialogProps={dialogProps}
|
||||
onCancel={() => setShowDialog(false)}
|
||||
onConfirm={onConfirm}
|
||||
setError={setError}
|
||||
></AssistantDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+2
-1
@@ -338,7 +338,8 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
const obj = {
|
||||
details: JSON.stringify(assistantDetails),
|
||||
iconSrc: assistantIcon,
|
||||
credential: assistantCredential
|
||||
credential: assistantCredential,
|
||||
type: 'OPENAI'
|
||||
}
|
||||
|
||||
const createResp = await assistantsApi.createNewAssistant(obj)
|
||||
@@ -0,0 +1,197 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
// material-ui
|
||||
import { Box, Stack, Button, Skeleton } from '@mui/material'
|
||||
|
||||
// project imports
|
||||
import MainCard from '@/ui-component/cards/MainCard'
|
||||
import ItemCard from '@/ui-component/cards/ItemCard'
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import AssistantDialog from './AssistantDialog'
|
||||
import LoadAssistantDialog from './LoadAssistantDialog'
|
||||
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||
import ErrorBoundary from '@/ErrorBoundary'
|
||||
|
||||
// API
|
||||
import assistantsApi from '@/api/assistants'
|
||||
|
||||
// Hooks
|
||||
import useApi from '@/hooks/useApi'
|
||||
|
||||
// icons
|
||||
import { IconPlus, IconFileUpload } from '@tabler/icons-react'
|
||||
import AssistantEmptySVG from '@/assets/images/assistant_empty.svg'
|
||||
import { gridSpacing } from '@/store/constant'
|
||||
|
||||
// ==============================|| OpenAIAssistantLayout ||============================== //
|
||||
|
||||
const OpenAIAssistantLayout = () => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const getAllAssistantsApi = useApi(assistantsApi.getAllAssistants)
|
||||
|
||||
const [isLoading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
const [showDialog, setShowDialog] = useState(false)
|
||||
const [dialogProps, setDialogProps] = useState({})
|
||||
const [showLoadDialog, setShowLoadDialog] = useState(false)
|
||||
const [loadDialogProps, setLoadDialogProps] = useState({})
|
||||
|
||||
const loadExisting = () => {
|
||||
const dialogProp = {
|
||||
title: 'Load Existing Assistant'
|
||||
}
|
||||
setLoadDialogProps(dialogProp)
|
||||
setShowLoadDialog(true)
|
||||
}
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
const onSearchChange = (event) => {
|
||||
setSearch(event.target.value)
|
||||
}
|
||||
|
||||
const onAssistantSelected = (selectedOpenAIAssistantId, credential) => {
|
||||
setShowLoadDialog(false)
|
||||
addNew(selectedOpenAIAssistantId, credential)
|
||||
}
|
||||
|
||||
const addNew = (selectedOpenAIAssistantId, credential) => {
|
||||
const dialogProp = {
|
||||
title: 'Add New Assistant',
|
||||
type: 'ADD',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Add',
|
||||
selectedOpenAIAssistantId,
|
||||
credential
|
||||
}
|
||||
setDialogProps(dialogProp)
|
||||
setShowDialog(true)
|
||||
}
|
||||
|
||||
const edit = (selectedAssistant) => {
|
||||
const dialogProp = {
|
||||
title: 'Edit Assistant',
|
||||
type: 'EDIT',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Save',
|
||||
data: selectedAssistant
|
||||
}
|
||||
setDialogProps(dialogProp)
|
||||
setShowDialog(true)
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
setShowDialog(false)
|
||||
getAllAssistantsApi.request('OPENAI')
|
||||
}
|
||||
|
||||
function filterAssistants(data) {
|
||||
const parsedData = JSON.parse(data.details)
|
||||
return parsedData && parsedData.name && parsedData.name.toLowerCase().indexOf(search.toLowerCase()) > -1
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllAssistantsApi.request('OPENAI')
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(getAllAssistantsApi.loading)
|
||||
}, [getAllAssistantsApi.loading])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllAssistantsApi.error) {
|
||||
setError(getAllAssistantsApi.error)
|
||||
}
|
||||
}, [getAllAssistantsApi.error])
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainCard>
|
||||
{error ? (
|
||||
<ErrorBoundary error={error} />
|
||||
) : (
|
||||
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
||||
<ViewHeader
|
||||
isBackButton={true}
|
||||
onSearchChange={onSearchChange}
|
||||
search={true}
|
||||
searchPlaceholder='Search Assistants'
|
||||
title='OpenAI Assistant'
|
||||
onBack={() => navigate(-1)}
|
||||
>
|
||||
<Button
|
||||
variant='outlined'
|
||||
onClick={loadExisting}
|
||||
startIcon={<IconFileUpload />}
|
||||
sx={{ borderRadius: 2, height: 40 }}
|
||||
>
|
||||
Load
|
||||
</Button>
|
||||
<StyledButton
|
||||
variant='contained'
|
||||
sx={{ borderRadius: 2, height: 40 }}
|
||||
onClick={addNew}
|
||||
startIcon={<IconPlus />}
|
||||
>
|
||||
Add
|
||||
</StyledButton>
|
||||
</ViewHeader>
|
||||
{isLoading ? (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
</Box>
|
||||
) : (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
{getAllAssistantsApi.data &&
|
||||
getAllAssistantsApi.data?.filter(filterAssistants).map((data, index) => (
|
||||
<ItemCard
|
||||
data={{
|
||||
name: JSON.parse(data.details)?.name,
|
||||
description: JSON.parse(data.details)?.instructions,
|
||||
iconSrc: data.iconSrc
|
||||
}}
|
||||
key={index}
|
||||
onClick={() => edit(data)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{!isLoading && (!getAllAssistantsApi.data || getAllAssistantsApi.data.length === 0) && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '20vh', width: 'auto' }}
|
||||
src={AssistantEmptySVG}
|
||||
alt='AssistantEmptySVG'
|
||||
/>
|
||||
</Box>
|
||||
<div>No OpenAI Assistants Added Yet</div>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</MainCard>
|
||||
<LoadAssistantDialog
|
||||
show={showLoadDialog}
|
||||
dialogProps={loadDialogProps}
|
||||
onCancel={() => setShowLoadDialog(false)}
|
||||
onAssistantSelected={onAssistantSelected}
|
||||
setError={setError}
|
||||
></LoadAssistantDialog>
|
||||
<AssistantDialog
|
||||
show={showDialog}
|
||||
dialogProps={dialogProps}
|
||||
onCancel={() => setShowDialog(false)}
|
||||
onConfirm={onConfirm}
|
||||
setError={setError}
|
||||
></AssistantDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default OpenAIAssistantLayout
|
||||
@@ -17,6 +17,8 @@ import APICodeDialog from '@/views/chatflows/APICodeDialog'
|
||||
import ViewMessagesDialog from '@/ui-component/dialog/ViewMessagesDialog'
|
||||
import ChatflowConfigurationDialog from '@/ui-component/dialog/ChatflowConfigurationDialog'
|
||||
import UpsertHistoryDialog from '@/views/vectorstore/UpsertHistoryDialog'
|
||||
import ViewLeadsDialog from '@/ui-component/dialog/ViewLeadsDialog'
|
||||
import ExportAsTemplateDialog from '@/ui-component/dialog/ExportAsTemplateDialog'
|
||||
|
||||
// API
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
@@ -28,8 +30,6 @@ import useApi from '@/hooks/useApi'
|
||||
import { generateExportFlowData } from '@/utils/genericHelper'
|
||||
import { uiBaseURL } from '@/store/constant'
|
||||
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||
import ViewLeadsDialog from '@/ui-component/dialog/ViewLeadsDialog'
|
||||
import ExportAsTemplateDialog from '@/ui-component/dialog/ExportAsTemplateDialog'
|
||||
|
||||
// ==============================|| CANVAS HEADER ||============================== //
|
||||
|
||||
@@ -130,7 +130,9 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, handleSaveFlow, handleDeleteFlo
|
||||
try {
|
||||
const flowData = JSON.parse(chatflow.flowData)
|
||||
let dataStr = JSON.stringify(generateExportFlowData(flowData), null, 2)
|
||||
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
//let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
const blob = new Blob([dataStr], { type: 'application/json' })
|
||||
const dataUri = URL.createObjectURL(blob)
|
||||
|
||||
let exportFileDefaultName = `${chatflow.name} ${title}.json`
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import { TabPanel } from '@/ui-component/tabs/TabPanel'
|
||||
import { TabsList } from '@/ui-component/tabs/TabsList'
|
||||
import { Tab } from '@/ui-component/tabs/Tab'
|
||||
import ToolDialog from '@/views/tools/ToolDialog'
|
||||
import AssistantDialog from '@/views/assistants/AssistantDialog'
|
||||
import AssistantDialog from '@/views/assistants/openai/AssistantDialog'
|
||||
import FormatPromptValuesDialog from '@/ui-component/dialog/FormatPromptValuesDialog'
|
||||
import ExpandTextDialog from '@/ui-component/dialog/ExpandTextDialog'
|
||||
import ConditionDialog from '@/ui-component/dialog/ConditionDialog'
|
||||
|
||||
@@ -58,7 +58,8 @@ const Chatflows = () => {
|
||||
function filterFlows(data) {
|
||||
return (
|
||||
data.name.toLowerCase().indexOf(search.toLowerCase()) > -1 ||
|
||||
(data.category && data.category.toLowerCase().indexOf(search.toLowerCase()) > -1)
|
||||
(data.category && data.category.toLowerCase().indexOf(search.toLowerCase()) > -1) ||
|
||||
data.id.toLowerCase().indexOf(search.toLowerCase()) > -1
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,14 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false }) => {
|
||||
data.inputs[inputParamName] = newValue
|
||||
}
|
||||
|
||||
const getCredential = () => {
|
||||
const credential = data.inputs.credential || data.inputs[FLOWISE_CREDENTIAL_ID]
|
||||
if (credential) {
|
||||
return { credential }
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{inputParam && (
|
||||
@@ -120,7 +128,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false }) => {
|
||||
<CredentialInputHandler
|
||||
key={JSON.stringify(inputParam)}
|
||||
disabled={disabled}
|
||||
data={data.inputs.credential ? { credential: data.inputs.credential } : {}}
|
||||
data={getCredential()}
|
||||
inputParam={inputParam}
|
||||
onSelect={(newValue) => {
|
||||
data.credential = newValue
|
||||
|
||||
@@ -15,10 +15,11 @@ import MainCard from '@/ui-component/cards/MainCard'
|
||||
import Transitions from '@/ui-component/extended/Transitions'
|
||||
import settings from '@/menu-items/settings'
|
||||
import agentsettings from '@/menu-items/agentsettings'
|
||||
import customAssistantSettings from '@/menu-items/customassistant'
|
||||
|
||||
// ==============================|| SETTINGS ||============================== //
|
||||
|
||||
const Settings = ({ chatflow, isSettingsOpen, anchorEl, isAgentCanvas, onSettingsItemClick, onUploadFile, onClose }) => {
|
||||
const Settings = ({ chatflow, isSettingsOpen, isCustomAssistant, anchorEl, isAgentCanvas, onSettingsItemClick, onUploadFile, onClose }) => {
|
||||
const theme = useTheme()
|
||||
const [settingsMenu, setSettingsMenu] = useState([])
|
||||
const customization = useSelector((state) => state.customization)
|
||||
@@ -47,11 +48,15 @@ const Settings = ({ chatflow, isSettingsOpen, anchorEl, isAgentCanvas, onSetting
|
||||
const settingsMenu = menus.children.filter((menu) => menu.id === 'loadChatflow')
|
||||
setSettingsMenu(settingsMenu)
|
||||
} else if (chatflow && chatflow.id) {
|
||||
const menus = isAgentCanvas ? agentsettings : settings
|
||||
const settingsMenu = menus.children
|
||||
setSettingsMenu(settingsMenu)
|
||||
if (isCustomAssistant) {
|
||||
const menus = customAssistantSettings
|
||||
setSettingsMenu(menus.children)
|
||||
} else {
|
||||
const menus = isAgentCanvas ? agentsettings : settings
|
||||
setSettingsMenu(menus.children)
|
||||
}
|
||||
}
|
||||
}, [chatflow, isAgentCanvas])
|
||||
}, [chatflow, isAgentCanvas, isCustomAssistant])
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(isSettingsOpen)
|
||||
@@ -147,6 +152,7 @@ const Settings = ({ chatflow, isSettingsOpen, anchorEl, isAgentCanvas, onSetting
|
||||
Settings.propTypes = {
|
||||
chatflow: PropTypes.object,
|
||||
isSettingsOpen: PropTypes.bool,
|
||||
isCustomAssistant: PropTypes.bool,
|
||||
anchorEl: PropTypes.any,
|
||||
onSettingsItemClick: PropTypes.func,
|
||||
onUploadFile: PropTypes.func,
|
||||
|
||||
@@ -240,7 +240,9 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
|
||||
delete toolData.createdDate
|
||||
delete toolData.updatedDate
|
||||
let dataStr = JSON.stringify(toolData, null, 2)
|
||||
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
//let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
const blob = new Blob([dataStr], { type: 'application/json' })
|
||||
const dataUri = URL.createObjectURL(blob)
|
||||
|
||||
let exportFileDefaultName = `${toolName}-CustomTool.json`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user