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
@@ -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 && (