mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 23:01:09 +03:00
add openai assistant
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import client from './client'
|
||||
|
||||
const getAllAssistants = () => client.get('/assistants')
|
||||
|
||||
const getSpecificAssistant = (id) => client.get(`/assistants/${id}`)
|
||||
|
||||
const getAssistantObj = (id, credential) => client.get(`/openai-assistants/${id}?credential=${credential}`)
|
||||
|
||||
const getAllAvailableAssistants = (credential) => client.get(`/openai-assistants?credential=${credential}`)
|
||||
|
||||
const createNewAssistant = (body) => client.post(`/assistants`, body)
|
||||
|
||||
const updateAssistant = (id, body) => client.put(`/assistants/${id}`, body)
|
||||
|
||||
const deleteAssistant = (id) => client.delete(`/assistants/${id}`)
|
||||
|
||||
export default {
|
||||
getAllAssistants,
|
||||
getSpecificAssistant,
|
||||
getAssistantObj,
|
||||
getAllAvailableAssistants,
|
||||
createNewAssistant,
|
||||
updateAssistant,
|
||||
deleteAssistant
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// assets
|
||||
import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock } from '@tabler/icons'
|
||||
import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot } from '@tabler/icons'
|
||||
|
||||
// constant
|
||||
const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock }
|
||||
const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot }
|
||||
|
||||
// ==============================|| DASHBOARD MENU ITEMS ||============================== //
|
||||
|
||||
@@ -35,6 +35,14 @@ const dashboard = {
|
||||
icon: icons.IconTool,
|
||||
breadcrumbs: true
|
||||
},
|
||||
{
|
||||
id: 'assistants',
|
||||
title: 'Assistants',
|
||||
type: 'item',
|
||||
url: '/assistants',
|
||||
icon: icons.IconRobot,
|
||||
breadcrumbs: true
|
||||
},
|
||||
{
|
||||
id: 'credentials',
|
||||
title: 'Credentials',
|
||||
|
||||
@@ -16,6 +16,9 @@ const APIKey = Loadable(lazy(() => import('views/apikey')))
|
||||
// tools routing
|
||||
const Tools = Loadable(lazy(() => import('views/tools')))
|
||||
|
||||
// assistants routing
|
||||
const Assistants = Loadable(lazy(() => import('views/assistants')))
|
||||
|
||||
// credentials routing
|
||||
const Credentials = Loadable(lazy(() => import('views/credentials')))
|
||||
|
||||
@@ -45,6 +48,10 @@ const MainRoutes = {
|
||||
path: '/tools',
|
||||
element: <Tools />
|
||||
},
|
||||
{
|
||||
path: '/assistants',
|
||||
element: <Assistants />
|
||||
},
|
||||
{
|
||||
path: '/credentials',
|
||||
element: <Credentials />
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState, useEffect, forwardRef } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import moment from 'moment'
|
||||
import rehypeMathjax from 'rehype-mathjax'
|
||||
import rehypeRaw from 'rehype-raw'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
|
||||
@@ -263,9 +264,10 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
}
|
||||
|
||||
const transformChatPKToParams = (chatPK) => {
|
||||
const chatId = chatPK.split('_')[0]
|
||||
const memoryType = chatPK.split('_')[1]
|
||||
const sessionId = chatPK.split('_')[2]
|
||||
let [c1, c2, ...rest] = chatPK.split('_')
|
||||
const chatId = c1
|
||||
const memoryType = c2
|
||||
const sessionId = rest.join('_')
|
||||
|
||||
const params = { chatId }
|
||||
if (memoryType !== 'null') params.memoryType = memoryType
|
||||
@@ -601,7 +603,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
{/* Messages are being rendered in Markdown format */}
|
||||
<MemoizedReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeMathjax]}
|
||||
rehypePlugins={[rehypeMathjax, rehypeRaw]}
|
||||
components={{
|
||||
code({ inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
|
||||
@@ -0,0 +1,545 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { Box, Typography, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, OutlinedInput } from '@mui/material'
|
||||
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser'
|
||||
import ConfirmDialog from 'ui-component/dialog/ConfirmDialog'
|
||||
import { Dropdown } from 'ui-component/dropdown/Dropdown'
|
||||
import { MultiDropdown } from 'ui-component/dropdown/MultiDropdown'
|
||||
import CredentialInputHandler from 'views/canvas/CredentialInputHandler'
|
||||
|
||||
// Icons
|
||||
import { IconX } from '@tabler/icons'
|
||||
|
||||
// API
|
||||
import assistantsApi from 'api/assistants'
|
||||
|
||||
// Hooks
|
||||
import useConfirm from 'hooks/useConfirm'
|
||||
import useApi from 'hooks/useApi'
|
||||
|
||||
// utils
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||
|
||||
const assistantAvailableModels = [
|
||||
{
|
||||
label: 'gpt-4',
|
||||
name: 'gpt-4'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-1106-preview',
|
||||
name: 'gpt-4-1106-preview'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-vision-preview',
|
||||
name: 'gpt-4-vision-preview'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-0613',
|
||||
name: 'gpt-4-0613'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-32k',
|
||||
name: 'gpt-4-32k'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-32k-0613',
|
||||
name: 'gpt-4-32k-0613'
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo',
|
||||
name: 'gpt-3.5-turbo'
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-1106',
|
||||
name: 'gpt-3.5-turbo-1106'
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-0613',
|
||||
name: 'gpt-3.5-turbo-0613'
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-16k',
|
||||
name: 'gpt-3.5-turbo-16k'
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-16k-0613',
|
||||
name: 'gpt-3.5-turbo-16k-0613'
|
||||
}
|
||||
]
|
||||
|
||||
const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
// ==============================|| Snackbar ||============================== //
|
||||
|
||||
useNotifier()
|
||||
const { confirm } = useConfirm()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const getSpecificAssistantApi = useApi(assistantsApi.getSpecificAssistant)
|
||||
const getAssistantObjApi = useApi(assistantsApi.getAssistantObj)
|
||||
|
||||
const [assistantId, setAssistantId] = useState('')
|
||||
const [openAIAssistantId, setOpenAIAssistantId] = useState('')
|
||||
const [assistantName, setAssistantName] = useState('')
|
||||
const [assistantDesc, setAssistantDesc] = useState('')
|
||||
const [assistantIcon, setAssistantIcon] = useState(`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`)
|
||||
const [assistantModel, setAssistantModel] = useState('')
|
||||
const [assistantCredential, setAssistantCredential] = useState('')
|
||||
const [assistantInstructions, setAssistantInstructions] = useState('')
|
||||
const [assistantTools, setAssistantTools] = useState(['code_interpreter', 'retrieval'])
|
||||
|
||||
useEffect(() => {
|
||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
}, [show, dispatch])
|
||||
|
||||
useEffect(() => {
|
||||
if (getSpecificAssistantApi.data) {
|
||||
setAssistantId(getSpecificAssistantApi.data.id)
|
||||
setAssistantIcon(getSpecificAssistantApi.data.iconSrc)
|
||||
setAssistantCredential(getSpecificAssistantApi.data.credential)
|
||||
|
||||
const assistantDetails = JSON.parse(getSpecificAssistantApi.data.details)
|
||||
setOpenAIAssistantId(assistantDetails.id)
|
||||
setAssistantName(assistantDetails.name)
|
||||
setAssistantDesc(assistantDetails.description)
|
||||
setAssistantModel(assistantDetails.model)
|
||||
setAssistantInstructions(assistantDetails.instructions)
|
||||
setAssistantTools(assistantDetails.tools ?? [])
|
||||
}
|
||||
}, [getSpecificAssistantApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAssistantObjApi.data) {
|
||||
setOpenAIAssistantId(getAssistantObjApi.data.id)
|
||||
setAssistantName(getAssistantObjApi.data.name)
|
||||
setAssistantDesc(getAssistantObjApi.data.description)
|
||||
setAssistantModel(getAssistantObjApi.data.model)
|
||||
setAssistantInstructions(getAssistantObjApi.data.instructions)
|
||||
|
||||
let tools = []
|
||||
if (getAssistantObjApi.data.tools && getAssistantObjApi.data.tools.length) {
|
||||
for (const tool of getAssistantObjApi.data.tools) {
|
||||
tools.push(tool.type)
|
||||
}
|
||||
}
|
||||
setAssistantTools(tools)
|
||||
}
|
||||
}, [getAssistantObjApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.type === 'EDIT' && dialogProps.data) {
|
||||
// When assistant dialog is opened from Assistants dashboard
|
||||
setAssistantId(dialogProps.data.id)
|
||||
setAssistantIcon(dialogProps.data.iconSrc)
|
||||
setAssistantCredential(dialogProps.data.credential)
|
||||
|
||||
const assistantDetails = JSON.parse(dialogProps.data.details)
|
||||
setOpenAIAssistantId(assistantDetails.id)
|
||||
setAssistantName(assistantDetails.name)
|
||||
setAssistantDesc(assistantDetails.description)
|
||||
setAssistantModel(assistantDetails.model)
|
||||
setAssistantInstructions(assistantDetails.instructions)
|
||||
setAssistantTools(assistantDetails.tools ?? [])
|
||||
} else if (dialogProps.type === 'EDIT' && dialogProps.assistantId) {
|
||||
// When assistant dialog is opened from OpenAIAssistant node in canvas
|
||||
getSpecificAssistantApi.request(dialogProps.assistantId)
|
||||
} else if (dialogProps.type === 'ADD' && dialogProps.selectedOpenAIAssistantId && dialogProps.credential) {
|
||||
// When assistant dialog is to add new assistant from existing
|
||||
setAssistantId('')
|
||||
setAssistantIcon(`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`)
|
||||
setAssistantCredential(dialogProps.credential)
|
||||
|
||||
getAssistantObjApi.request(dialogProps.selectedOpenAIAssistantId, dialogProps.credential)
|
||||
} else if (dialogProps.type === 'ADD' && !dialogProps.selectedOpenAIAssistantId) {
|
||||
// When assistant dialog is to add a blank new assistant
|
||||
setAssistantId('')
|
||||
setAssistantIcon(`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`)
|
||||
setAssistantCredential('')
|
||||
|
||||
setOpenAIAssistantId('')
|
||||
setAssistantName('')
|
||||
setAssistantDesc('')
|
||||
setAssistantModel('')
|
||||
setAssistantInstructions('')
|
||||
setAssistantTools(['code_interpreter', 'retrieval'])
|
||||
}
|
||||
|
||||
return () => {
|
||||
setAssistantId('')
|
||||
setAssistantIcon(`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`)
|
||||
setAssistantCredential('')
|
||||
|
||||
setOpenAIAssistantId('')
|
||||
setAssistantName('')
|
||||
setAssistantDesc('')
|
||||
setAssistantModel('')
|
||||
setAssistantInstructions('')
|
||||
setAssistantTools(['code_interpreter', 'retrieval'])
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dialogProps])
|
||||
|
||||
const addNewAssistant = async () => {
|
||||
try {
|
||||
const assistantDetails = {
|
||||
id: openAIAssistantId,
|
||||
name: assistantName,
|
||||
description: assistantDesc,
|
||||
model: assistantModel,
|
||||
instructions: assistantInstructions,
|
||||
tools: assistantTools
|
||||
}
|
||||
const obj = {
|
||||
details: JSON.stringify(assistantDetails),
|
||||
iconSrc: assistantIcon,
|
||||
credential: assistantCredential
|
||||
}
|
||||
|
||||
const createResp = await assistantsApi.createNewAssistant(obj)
|
||||
if (createResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'New Assistant added',
|
||||
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 (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to add new 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 saveAssistant = async () => {
|
||||
try {
|
||||
const assistantDetails = {
|
||||
name: assistantName,
|
||||
description: assistantDesc,
|
||||
model: assistantModel,
|
||||
instructions: assistantInstructions,
|
||||
tools: assistantTools
|
||||
}
|
||||
const obj = {
|
||||
details: JSON.stringify(assistantDetails),
|
||||
iconSrc: assistantIcon,
|
||||
credential: assistantCredential
|
||||
}
|
||||
const saveResp = await assistantsApi.updateAssistant(assistantId, obj)
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Assistant saved',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm(saveResp.data.id)
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save 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 deleteAssistant = async () => {
|
||||
const confirmPayload = {
|
||||
title: `Delete Assistant`,
|
||||
description: `Delete Assistant ${assistantName}?`,
|
||||
confirmButtonName: 'Delete',
|
||||
cancelButtonName: 'Cancel'
|
||||
}
|
||||
const isConfirmed = await confirm(confirmPayload)
|
||||
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
const delResp = await assistantsApi.deleteAssistant(assistantId)
|
||||
if (delResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Assistant deleted',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm()
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to delete 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='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>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
Assistant Name
|
||||
<TooltipWithParser
|
||||
style={{ marginLeft: 10 }}
|
||||
title={'The name of the assistant. The maximum length is 256 characters.'}
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='assistantName'
|
||||
type='string'
|
||||
fullWidth
|
||||
placeholder='My New Assistant'
|
||||
value={assistantName}
|
||||
name='assistantName'
|
||||
onChange={(e) => setAssistantName(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
Assistant Description
|
||||
<TooltipWithParser
|
||||
style={{ marginLeft: 10 }}
|
||||
title={'The description of the assistant. The maximum length is 512 characters.'}
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='assistantDesc'
|
||||
type='string'
|
||||
fullWidth
|
||||
placeholder='Description of what the Assistant does'
|
||||
multiline={true}
|
||||
rows={3}
|
||||
value={assistantDesc}
|
||||
name='assistantDesc'
|
||||
onChange={(e) => setAssistantDesc(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>Assistant Icon Src</Typography>
|
||||
</Stack>
|
||||
<div
|
||||
style={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 5,
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
alt={assistantName}
|
||||
src={assistantIcon}
|
||||
/>
|
||||
</div>
|
||||
<OutlinedInput
|
||||
id='assistantIcon'
|
||||
type='string'
|
||||
fullWidth
|
||||
placeholder={`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`}
|
||||
value={assistantIcon}
|
||||
name='assistantIcon'
|
||||
onChange={(e) => setAssistantIcon(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
Assistant Model
|
||||
<span style={{ color: 'red' }}> *</span>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Dropdown
|
||||
key={assistantModel}
|
||||
name={assistantModel}
|
||||
options={assistantAvailableModels}
|
||||
onSelect={(newValue) => setAssistantModel(newValue)}
|
||||
value={assistantModel ?? 'choose an option'}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
OpenAI Credential
|
||||
<span style={{ color: 'red' }}> *</span>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<CredentialInputHandler
|
||||
key={assistantCredential}
|
||||
data={assistantCredential ? { credential: assistantCredential } : {}}
|
||||
inputParam={{
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['openAIApi']
|
||||
}}
|
||||
onSelect={(newValue) => setAssistantCredential(newValue)}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
Assistant Instruction
|
||||
<TooltipWithParser
|
||||
style={{ marginLeft: 10 }}
|
||||
title={'The system instructions that the assistant uses. The maximum length is 32768 characters.'}
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='assistantInstructions'
|
||||
type='string'
|
||||
fullWidth
|
||||
placeholder='You are a personal math tutor. When asked a question, write and run Python code to answer the question.'
|
||||
multiline={true}
|
||||
rows={3}
|
||||
value={assistantInstructions}
|
||||
name='assistantInstructions'
|
||||
onChange={(e) => setAssistantInstructions(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
Assistant Tools
|
||||
<TooltipWithParser
|
||||
style={{ marginLeft: 10 }}
|
||||
title='A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant.'
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<MultiDropdown
|
||||
key={JSON.stringify(assistantTools)}
|
||||
name={JSON.stringify(assistantTools)}
|
||||
options={[
|
||||
{
|
||||
label: 'Code Interpreter',
|
||||
name: 'code_interpreter'
|
||||
},
|
||||
{
|
||||
label: 'Retrieval',
|
||||
name: 'retrieval'
|
||||
}
|
||||
]}
|
||||
onSelect={(newValue) => (newValue ? setAssistantTools(JSON.parse(newValue)) : setAssistantTools([]))}
|
||||
value={assistantTools ?? 'choose an option'}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{dialogProps.type === 'EDIT' && (
|
||||
<StyledButton color='error' variant='contained' onClick={() => deleteAssistant()}>
|
||||
Delete
|
||||
</StyledButton>
|
||||
)}
|
||||
<StyledButton
|
||||
disabled={!(assistantModel && assistantCredential)}
|
||||
variant='contained'
|
||||
onClick={() => (dialogProps.type === 'ADD' ? addNewAssistant() : saveAssistant())}
|
||||
>
|
||||
{dialogProps.confirmButtonName}
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
<ConfirmDialog />
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
AssistantDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func,
|
||||
onConfirm: PropTypes.func
|
||||
}
|
||||
|
||||
export default AssistantDialog
|
||||
@@ -0,0 +1,114 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Stack, Typography, Dialog, DialogContent, DialogTitle, DialogActions, Box } from '@mui/material'
|
||||
import CredentialInputHandler from 'views/canvas/CredentialInputHandler'
|
||||
import { Dropdown } from 'ui-component/dropdown/Dropdown'
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
import assistantsApi from 'api/assistants'
|
||||
import useApi from 'hooks/useApi'
|
||||
|
||||
const LoadAssistantDialog = ({ show, dialogProps, onCancel, onAssistantSelected }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
|
||||
const getAllAvailableAssistantsApi = useApi(assistantsApi.getAllAvailableAssistants)
|
||||
|
||||
const [credentialId, setCredentialId] = useState('')
|
||||
const [availableAssistantsOptions, setAvailableAssistantsOptions] = useState([])
|
||||
const [selectedOpenAIAssistantId, setSelectedOpenAIAssistantId] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setCredentialId('')
|
||||
setAvailableAssistantsOptions([])
|
||||
setSelectedOpenAIAssistantId('')
|
||||
}
|
||||
}, [dialogProps])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllAvailableAssistantsApi.data && getAllAvailableAssistantsApi.data.length) {
|
||||
const assistants = []
|
||||
for (let i = 0; i < getAllAvailableAssistantsApi.data.length; i += 1) {
|
||||
assistants.push({
|
||||
label: getAllAvailableAssistantsApi.data[i].name,
|
||||
name: getAllAvailableAssistantsApi.data[i].id,
|
||||
description: getAllAvailableAssistantsApi.data[i].instructions
|
||||
})
|
||||
}
|
||||
setAvailableAssistantsOptions(assistants)
|
||||
}
|
||||
}, [getAllAvailableAssistantsApi.data])
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
fullWidth
|
||||
maxWidth='xs'
|
||||
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>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
OpenAI Credential
|
||||
<span style={{ color: 'red' }}> *</span>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<CredentialInputHandler
|
||||
key={credentialId}
|
||||
data={credentialId ? { credential: credentialId } : {}}
|
||||
inputParam={{
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['openAIApi']
|
||||
}}
|
||||
onSelect={(newValue) => {
|
||||
setCredentialId(newValue)
|
||||
if (newValue) getAllAvailableAssistantsApi.request(newValue)
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{credentialId && (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
Assistants
|
||||
<span style={{ color: 'red' }}> *</span>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Dropdown
|
||||
name={selectedOpenAIAssistantId}
|
||||
options={availableAssistantsOptions}
|
||||
onSelect={(newValue) => setSelectedOpenAIAssistantId(newValue)}
|
||||
value={selectedOpenAIAssistantId ?? 'choose an option'}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</DialogContent>
|
||||
{selectedOpenAIAssistantId && (
|
||||
<DialogActions>
|
||||
<StyledButton variant='contained' onClick={() => onAssistantSelected(selectedOpenAIAssistantId, credentialId)}>
|
||||
Load
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
)}
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
LoadAssistantDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func,
|
||||
onAssistantSelected: PropTypes.func
|
||||
}
|
||||
|
||||
export default LoadAssistantDialog
|
||||
@@ -0,0 +1,146 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
// material-ui
|
||||
import { Grid, Box, Stack, Button } from '@mui/material'
|
||||
import { useTheme } 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 ToolEmptySVG from 'assets/images/tools_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'
|
||||
|
||||
// icons
|
||||
import { IconPlus, IconFileImport } from '@tabler/icons'
|
||||
|
||||
// ==============================|| CHATFLOWS ||============================== //
|
||||
|
||||
const Assistants = () => {
|
||||
const theme = useTheme()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
|
||||
const getAllAssistantsApi = useApi(assistantsApi.getAllAssistants)
|
||||
|
||||
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 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()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllAssistantsApi.request()
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
||||
<Stack flexDirection='row'>
|
||||
<Grid sx={{ mb: 1.25 }} container direction='row'>
|
||||
<h1>OpenAI Assistants</h1>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Grid item>
|
||||
<Button variant='outlined' sx={{ mr: 2 }} onClick={loadExisting} startIcon={<IconFileImport />}>
|
||||
Load
|
||||
</Button>
|
||||
<StyledButton variant='contained' sx={{ color: 'white' }} onClick={addNew} startIcon={<IconPlus />}>
|
||||
Add
|
||||
</StyledButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
<Grid container spacing={gridSpacing}>
|
||||
{!getAllAssistantsApi.loading &&
|
||||
getAllAssistantsApi.data &&
|
||||
getAllAssistantsApi.data.map((data, index) => (
|
||||
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
|
||||
<ItemCard
|
||||
data={{
|
||||
name: JSON.parse(data.details)?.name,
|
||||
description: JSON.parse(data.details)?.instructions,
|
||||
iconSrc: data.iconSrc
|
||||
}}
|
||||
onClick={() => edit(data)}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
{!getAllAssistantsApi.loading && (!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: '30vh', width: 'auto' }} src={ToolEmptySVG} alt='ToolEmptySVG' />
|
||||
</Box>
|
||||
<div>No Assistants Added Yet</div>
|
||||
</Stack>
|
||||
)}
|
||||
</MainCard>
|
||||
<LoadAssistantDialog
|
||||
show={showLoadDialog}
|
||||
dialogProps={loadDialogProps}
|
||||
onCancel={() => setShowLoadDialog(false)}
|
||||
onAssistantSelected={onAssistantSelected}
|
||||
></LoadAssistantDialog>
|
||||
<AssistantDialog
|
||||
show={showDialog}
|
||||
dialogProps={dialogProps}
|
||||
onCancel={() => setShowDialog(false)}
|
||||
onConfirm={onConfirm}
|
||||
></AssistantDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Assistants
|
||||
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types'
|
||||
import socketIOClient from 'socket.io-client'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import rehypeMathjax from 'rehype-mathjax'
|
||||
import rehypeRaw from 'rehype-raw'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
|
||||
@@ -287,7 +288,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
{/* Messages are being rendered in Markdown format */}
|
||||
<MemoizedReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeMathjax]}
|
||||
rehypePlugins={[rehypeMathjax, rehypeRaw]}
|
||||
components={{
|
||||
code({ inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
|
||||
Reference in New Issue
Block a user