Feature/Custom Assistant Builder (#3631)

* add custom assistant builder

* add tools to custom assistant

* add save assistant button
This commit is contained in:
Henry Heng
2024-12-06 22:11:17 +00:00
committed by GitHub
parent e02045285f
commit fe2ed26999
46 changed files with 3134 additions and 221 deletions
+12 -2
View File
@@ -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
}
+4 -1
View File
@@ -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
+8 -8
View File
@@ -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',
+15
View File
@@ -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 && (
+2 -1
View File
@@ -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' }}>&nbsp;*</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'
}
]
}
+114 -173
View File
@@ -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>
</>
)
}
@@ -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'
+2 -1
View File
@@ -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
+11 -5
View File
@@ -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,
+3 -1
View File
@@ -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`