mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 15:00:57 +03:00
Feature/OpenAI Assistant V2 (#2258)
* add gpt4 turbo to assistant * OpenAI Assistant V2 * update langfuse handler
This commit is contained in:
@@ -1,20 +1,49 @@
|
||||
import client from './client'
|
||||
|
||||
// OpenAI Assistant
|
||||
const getAssistantObj = (id, credentialId) => client.get(`/openai-assistants/${id}?credential=${credentialId}`)
|
||||
|
||||
const getAllAvailableAssistants = (credentialId) => client.get(`/openai-assistants?credential=${credentialId}`)
|
||||
|
||||
// Assistant
|
||||
const createNewAssistant = (body) => client.post(`/assistants`, body)
|
||||
|
||||
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, isDeleteBoth) =>
|
||||
isDeleteBoth ? client.delete(`/assistants/${id}?isDeleteBoth=true`) : client.delete(`/assistants/${id}`)
|
||||
|
||||
// Vector Store
|
||||
const getAssistantVectorStore = (id, credentialId) => client.get(`/openai-assistants-vector-store/${id}?credential=${credentialId}`)
|
||||
|
||||
const listAssistantVectorStore = (credentialId) => client.get(`/openai-assistants-vector-store?credential=${credentialId}`)
|
||||
|
||||
const createAssistantVectorStore = (credentialId, body) => client.post(`/openai-assistants-vector-store?credential=${credentialId}`, body)
|
||||
|
||||
const updateAssistantVectorStore = (id, credentialId, body) =>
|
||||
client.put(`/openai-assistants-vector-store/${id}?credential=${credentialId}`, body)
|
||||
|
||||
const deleteAssistantVectorStore = (id, credentialId) => client.delete(`/openai-assistants-vector-store/${id}?credential=${credentialId}`)
|
||||
|
||||
// Vector Store Files
|
||||
const uploadFilesToAssistantVectorStore = (id, credentialId, formData) =>
|
||||
client.post(`/openai-assistants-vector-store/${id}?credential=${credentialId}`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
|
||||
const deleteFilesFromAssistantVectorStore = (id, credentialId, body) =>
|
||||
client.patch(`/openai-assistants-vector-store/${id}?credential=${credentialId}`, body)
|
||||
|
||||
// Files
|
||||
const uploadFilesToAssistant = (credentialId, formData) =>
|
||||
client.post(`/openai-assistants-file/upload?credential=${credentialId}`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
|
||||
export default {
|
||||
getAllAssistants,
|
||||
getSpecificAssistant,
|
||||
@@ -22,5 +51,13 @@ export default {
|
||||
getAllAvailableAssistants,
|
||||
createNewAssistant,
|
||||
updateAssistant,
|
||||
deleteAssistant
|
||||
deleteAssistant,
|
||||
getAssistantVectorStore,
|
||||
listAssistantVectorStore,
|
||||
updateAssistantVectorStore,
|
||||
createAssistantVectorStore,
|
||||
uploadFilesToAssistant,
|
||||
uploadFilesToAssistantVectorStore,
|
||||
deleteFilesFromAssistantVectorStore,
|
||||
deleteAssistantVectorStore
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const [chatMessages, setChatMessages] = useState([])
|
||||
const [stats, setStats] = useState([])
|
||||
const [selectedMessageIndex, setSelectedMessageIndex] = useState(0)
|
||||
const [selectedChatId, setSelectedChatId] = useState('')
|
||||
const [sourceDialogOpen, setSourceDialogOpen] = useState(false)
|
||||
const [sourceDialogProps, setSourceDialogProps] = useState({})
|
||||
const [chatTypeFilter, setChatTypeFilter] = useState([])
|
||||
@@ -283,6 +284,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const loadedMessages = []
|
||||
for (let i = 0; i < chatmessages.length; i += 1) {
|
||||
const chatmsg = chatmessages[i]
|
||||
setSelectedChatId(chatmsg.chatId)
|
||||
if (!prevDate) {
|
||||
prevDate = chatmsg.createdDate.split('T')[0]
|
||||
loadedMessages.push({
|
||||
@@ -383,8 +385,8 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const downloadFile = async (fileAnnotation) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${baseURL}/api/v1/openai-assistants-file`,
|
||||
{ fileName: fileAnnotation.fileName },
|
||||
`${baseURL}/api/v1/openai-assistants-file/download`,
|
||||
{ fileName: fileAnnotation.fileName, chatflowId: dialogProps.chatflow.id, chatId: selectedChatId },
|
||||
{ responseType: 'blob' }
|
||||
)
|
||||
const blob = new Blob([response.data], { type: response.headers['content-type'] })
|
||||
@@ -444,6 +446,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
setChatMessages([])
|
||||
setChatTypeFilter([])
|
||||
setSelectedMessageIndex(0)
|
||||
setSelectedChatId('')
|
||||
setStartDate(new Date().setMonth(new Date().getMonth() - 1))
|
||||
setEndDate(new Date())
|
||||
setStats([])
|
||||
|
||||
@@ -18,7 +18,7 @@ const StyledPopper = styled(Popper)({
|
||||
}
|
||||
})
|
||||
|
||||
export const Dropdown = ({ name, value, options, onSelect, disabled = false, disableClearable = false }) => {
|
||||
export const Dropdown = ({ name, value, loading, options, onSelect, disabled = false, disableClearable = false }) => {
|
||||
const customization = useSelector((state) => state.customization)
|
||||
const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value)
|
||||
const getDefaultOptionValue = () => ''
|
||||
@@ -31,6 +31,7 @@ export const Dropdown = ({ name, value, options, onSelect, disabled = false, dis
|
||||
disabled={disabled}
|
||||
disableClearable={disableClearable}
|
||||
size='small'
|
||||
loading={loading}
|
||||
options={options || []}
|
||||
value={findMatchingOptions(options, internalValue) || getDefaultOptionValue()}
|
||||
onChange={(e, selection) => {
|
||||
@@ -61,6 +62,7 @@ export const Dropdown = ({ name, value, options, onSelect, disabled = false, dis
|
||||
Dropdown.propTypes = {
|
||||
name: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
loading: PropTypes.bool,
|
||||
options: PropTypes.array,
|
||||
onSelect: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { FormControl, Button } from '@mui/material'
|
||||
import { IconUpload } from '@tabler/icons'
|
||||
import { getFileName } from '@/utils/genericHelper'
|
||||
|
||||
export const File = ({ value, fileType, onChange, disabled = false }) => {
|
||||
export const File = ({ value, formDataUpload, fileType, onChange, onFormDataChange, disabled = false }) => {
|
||||
const theme = useTheme()
|
||||
|
||||
const [myValue, setMyValue] = useState(value ?? '')
|
||||
@@ -54,17 +54,43 @@ export const File = ({ value, fileType, onChange, disabled = false }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleFormDataUpload = async (e) => {
|
||||
if (!e.target.files) return
|
||||
|
||||
if (e.target.files.length === 1) {
|
||||
const file = e.target.files[0]
|
||||
const { name } = file
|
||||
const formData = new FormData()
|
||||
formData.append('files', file)
|
||||
setMyValue(`,filename:${name}`)
|
||||
onChange(`,filename:${name}`)
|
||||
onFormDataChange(formData)
|
||||
} else if (e.target.files.length > 0) {
|
||||
const formData = new FormData()
|
||||
const values = []
|
||||
for (let i = 0; i < e.target.files.length; i++) {
|
||||
formData.append('files', e.target.files[i])
|
||||
values.push(`,filename:${e.target.files[i].name}`)
|
||||
}
|
||||
setMyValue(JSON.stringify(values))
|
||||
onChange(JSON.stringify(values))
|
||||
onFormDataChange(formData)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl sx={{ mt: 1, width: '100%' }} size='small'>
|
||||
<span
|
||||
style={{
|
||||
fontStyle: 'italic',
|
||||
color: theme.palette.grey['800'],
|
||||
marginBottom: '1rem'
|
||||
}}
|
||||
>
|
||||
{myValue ? getFileName(myValue) : 'Choose a file to upload'}
|
||||
</span>
|
||||
{!formDataUpload && (
|
||||
<span
|
||||
style={{
|
||||
fontStyle: 'italic',
|
||||
color: theme.palette.grey['800'],
|
||||
marginBottom: '1rem'
|
||||
}}
|
||||
>
|
||||
{myValue ? getFileName(myValue) : 'Choose a file to upload'}
|
||||
</span>
|
||||
)}
|
||||
<Button
|
||||
disabled={disabled}
|
||||
variant='outlined'
|
||||
@@ -74,7 +100,13 @@ export const File = ({ value, fileType, onChange, disabled = false }) => {
|
||||
sx={{ marginRight: '1rem' }}
|
||||
>
|
||||
{'Upload File'}
|
||||
<input type='file' multiple accept={fileType} hidden onChange={(e) => handleFileUpload(e)} />
|
||||
<input
|
||||
type='file'
|
||||
multiple
|
||||
accept={fileType}
|
||||
hidden
|
||||
onChange={(e) => (formDataUpload ? handleFormDataUpload(e) : handleFileUpload(e))}
|
||||
/>
|
||||
</Button>
|
||||
</FormControl>
|
||||
)
|
||||
@@ -83,6 +115,8 @@ export const File = ({ value, fileType, onChange, disabled = false }) => {
|
||||
File.propTypes = {
|
||||
value: PropTypes.string,
|
||||
fileType: PropTypes.string,
|
||||
formDataUpload: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
onFormDataChange: PropTypes.func,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
@@ -642,3 +642,15 @@ export const getOS = () => {
|
||||
|
||||
return os
|
||||
}
|
||||
|
||||
export const formatBytes = (bytes, decimals = 2) => {
|
||||
if (!+bytes) return '0 Bytes'
|
||||
|
||||
const k = 1024
|
||||
const dm = decimals < 0 ? 0 : decimals
|
||||
const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`
|
||||
}
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { Box, Typography, Button, IconButton, Dialog, DialogActions, DialogContent, DialogTitle, Stack, OutlinedInput } from '@mui/material'
|
||||
import {
|
||||
Chip,
|
||||
Card,
|
||||
CardContent,
|
||||
Box,
|
||||
Typography,
|
||||
Button,
|
||||
IconButton,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Stack,
|
||||
OutlinedInput
|
||||
} from '@mui/material'
|
||||
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
||||
@@ -15,9 +29,10 @@ import CredentialInputHandler from '@/views/canvas/CredentialInputHandler'
|
||||
import { File } from '@/ui-component/file/File'
|
||||
import { BackdropLoader } from '@/ui-component/loading/BackdropLoader'
|
||||
import DeleteConfirmDialog from './DeleteConfirmDialog'
|
||||
import AssistantVectorStoreDialog from './AssistantVectorStoreDialog'
|
||||
|
||||
// Icons
|
||||
import { IconX } from '@tabler/icons'
|
||||
import { IconX, IconPlus } from '@tabler/icons'
|
||||
|
||||
// API
|
||||
import assistantsApi from '@/api/assistants'
|
||||
@@ -28,8 +43,13 @@ import useApi from '@/hooks/useApi'
|
||||
// utils
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||
import { maxScroll } from '@/store/constant'
|
||||
|
||||
const assistantAvailableModels = [
|
||||
{
|
||||
label: 'gpt-4-turbo',
|
||||
name: 'gpt-4-turbo'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-turbo-preview',
|
||||
name: 'gpt-4-turbo-preview'
|
||||
@@ -74,6 +94,8 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
const dispatch = useDispatch()
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
const customization = useSelector((state) => state.customization)
|
||||
const dialogRef = useRef()
|
||||
|
||||
const getSpecificAssistantApi = useApi(assistantsApi.getSpecificAssistant)
|
||||
const getAssistantObjApi = useApi(assistantsApi.getAssistantObj)
|
||||
@@ -86,12 +108,17 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
const [assistantModel, setAssistantModel] = useState('')
|
||||
const [assistantCredential, setAssistantCredential] = useState('')
|
||||
const [assistantInstructions, setAssistantInstructions] = useState('')
|
||||
const [assistantTools, setAssistantTools] = useState(['code_interpreter', 'retrieval'])
|
||||
const [assistantFiles, setAssistantFiles] = useState([])
|
||||
const [uploadAssistantFiles, setUploadAssistantFiles] = useState('')
|
||||
const [assistantTools, setAssistantTools] = useState(['code_interpreter', 'file_search'])
|
||||
const [toolResources, setToolResources] = useState({})
|
||||
const [temperature, setTemperature] = useState(1)
|
||||
const [topP, setTopP] = useState(1)
|
||||
const [uploadCodeInterpreterFiles, setUploadCodeInterpreterFiles] = useState('')
|
||||
const [uploadVectorStoreFiles, setUploadVectorStoreFiles] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
||||
const [deleteDialogProps, setDeleteDialogProps] = useState({})
|
||||
const [assistantVectorStoreDialogOpen, setAssistantVectorStoreDialogOpen] = useState(false)
|
||||
const [assistantVectorStoreDialogProps, setAssistantVectorStoreDialogProps] = useState({})
|
||||
|
||||
useEffect(() => {
|
||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||
@@ -111,8 +138,10 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
setAssistantDesc(assistantDetails.description)
|
||||
setAssistantModel(assistantDetails.model)
|
||||
setAssistantInstructions(assistantDetails.instructions)
|
||||
setTemperature(assistantDetails.temperature)
|
||||
setTopP(assistantDetails.top_p)
|
||||
setAssistantTools(assistantDetails.tools ?? [])
|
||||
setAssistantFiles(assistantDetails.files ?? [])
|
||||
setToolResources(assistantDetails.tool_resources ?? {})
|
||||
}
|
||||
}, [getSpecificAssistantApi.data])
|
||||
|
||||
@@ -124,14 +153,48 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
|
||||
useEffect(() => {
|
||||
if (getAssistantObjApi.error) {
|
||||
syncData(getAssistantObjApi.error)
|
||||
let errMsg = ''
|
||||
if (error?.response?.data) {
|
||||
errMsg = typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||
}
|
||||
enqueueSnackbar({
|
||||
message: `Failed to get assistant: ${errMsg}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getAssistantObjApi.error])
|
||||
|
||||
useEffect(() => {
|
||||
if (getSpecificAssistantApi.error) {
|
||||
syncData(getSpecificAssistantApi.error)
|
||||
let errMsg = ''
|
||||
if (error?.response?.data) {
|
||||
errMsg = typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||
}
|
||||
enqueueSnackbar({
|
||||
message: `Failed to get assistant: ${errMsg}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getSpecificAssistantApi.error])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -147,8 +210,10 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
setAssistantDesc(assistantDetails.description)
|
||||
setAssistantModel(assistantDetails.model)
|
||||
setAssistantInstructions(assistantDetails.instructions)
|
||||
setTemperature(assistantDetails.temperature)
|
||||
setTopP(assistantDetails.top_p)
|
||||
setAssistantTools(assistantDetails.tools ?? [])
|
||||
setAssistantFiles(assistantDetails.files ?? [])
|
||||
setToolResources(assistantDetails.tool_resources ?? {})
|
||||
} else if (dialogProps.type === 'EDIT' && dialogProps.assistantId) {
|
||||
// When assistant dialog is opened from OpenAIAssistant node in canvas
|
||||
getSpecificAssistantApi.request(dialogProps.assistantId)
|
||||
@@ -170,9 +235,12 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
setAssistantDesc('')
|
||||
setAssistantModel('')
|
||||
setAssistantInstructions('')
|
||||
setAssistantTools(['code_interpreter', 'retrieval'])
|
||||
setUploadAssistantFiles('')
|
||||
setAssistantFiles([])
|
||||
setTemperature(1)
|
||||
setTopP(1)
|
||||
setAssistantTools(['code_interpreter', 'file_search'])
|
||||
setUploadCodeInterpreterFiles('')
|
||||
setUploadVectorStoreFiles('')
|
||||
setToolResources({})
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -185,9 +253,12 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
setAssistantDesc('')
|
||||
setAssistantModel('')
|
||||
setAssistantInstructions('')
|
||||
setAssistantTools(['code_interpreter', 'retrieval'])
|
||||
setUploadAssistantFiles('')
|
||||
setAssistantFiles([])
|
||||
setTemperature(1)
|
||||
setTopP(1)
|
||||
setAssistantTools(['code_interpreter', 'file_search'])
|
||||
setUploadCodeInterpreterFiles('')
|
||||
setUploadVectorStoreFiles('')
|
||||
setToolResources({})
|
||||
setLoading(false)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -199,7 +270,9 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
setAssistantDesc(data.description)
|
||||
setAssistantModel(data.model)
|
||||
setAssistantInstructions(data.instructions)
|
||||
setAssistantFiles(data.files ?? [])
|
||||
setTemperature(data.temperature)
|
||||
setTopP(data.top_p)
|
||||
setToolResources(data.tool_resources ?? {})
|
||||
|
||||
let tools = []
|
||||
if (data.tools && data.tools.length) {
|
||||
@@ -210,6 +283,31 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
setAssistantTools(tools)
|
||||
}
|
||||
|
||||
const onEditAssistantVectorStoreClick = (vectorStoreObject) => {
|
||||
const dialogProp = {
|
||||
title: `Edit ${vectorStoreObject.name ? vectorStoreObject.name : vectorStoreObject.id}`,
|
||||
type: 'EDIT',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Save',
|
||||
data: vectorStoreObject,
|
||||
credential: assistantCredential
|
||||
}
|
||||
setAssistantVectorStoreDialogProps(dialogProp)
|
||||
setAssistantVectorStoreDialogOpen(true)
|
||||
}
|
||||
|
||||
const onAddAssistantVectorStoreClick = () => {
|
||||
const dialogProp = {
|
||||
title: `Add Vector Store`,
|
||||
type: 'ADD',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Add',
|
||||
credential: assistantCredential
|
||||
}
|
||||
setAssistantVectorStoreDialogProps(dialogProp)
|
||||
setAssistantVectorStoreDialogOpen(true)
|
||||
}
|
||||
|
||||
const addNewAssistant = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
@@ -219,9 +317,10 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
description: assistantDesc,
|
||||
model: assistantModel,
|
||||
instructions: assistantInstructions,
|
||||
temperature: temperature ? parseFloat(temperature) : null,
|
||||
top_p: topP ? parseFloat(topP) : null,
|
||||
tools: assistantTools,
|
||||
files: assistantFiles,
|
||||
uploadFiles: uploadAssistantFiles
|
||||
tool_resources: toolResources
|
||||
}
|
||||
const obj = {
|
||||
details: JSON.stringify(assistantDetails),
|
||||
@@ -247,7 +346,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
}
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
enqueueSnackbar({
|
||||
message: `Failed to add new Assistant: ${
|
||||
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||
@@ -264,7 +362,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
}
|
||||
})
|
||||
setLoading(false)
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,9 +373,10 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
description: assistantDesc,
|
||||
model: assistantModel,
|
||||
instructions: assistantInstructions,
|
||||
temperature: temperature ? parseFloat(temperature) : null,
|
||||
top_p: topP ? parseFloat(topP) : null,
|
||||
tools: assistantTools,
|
||||
files: assistantFiles,
|
||||
uploadFiles: uploadAssistantFiles
|
||||
tool_resources: toolResources
|
||||
}
|
||||
const obj = {
|
||||
details: JSON.stringify(assistantDetails),
|
||||
@@ -303,7 +401,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
}
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save Assistant: ${
|
||||
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||
@@ -320,7 +417,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
}
|
||||
})
|
||||
setLoading(false)
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +441,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
}
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
enqueueSnackbar({
|
||||
message: `Failed to sync Assistant: ${
|
||||
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||
@@ -365,10 +460,124 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
}
|
||||
}
|
||||
|
||||
const uploadFormDataToVectorStore = async (formData) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const vectorStoreId = toolResources.file_search?.vector_store_ids?.length ? toolResources.file_search.vector_store_ids[0] : ''
|
||||
const uploadResp = await assistantsApi.uploadFilesToAssistantVectorStore(vectorStoreId, assistantCredential, formData)
|
||||
if (uploadResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'File uploaded successfully!',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const uploadedFiles = uploadResp.data
|
||||
const existingFiles = toolResources?.file_search.files ?? []
|
||||
|
||||
setToolResources({
|
||||
...toolResources,
|
||||
file_search: {
|
||||
...toolResources?.file_search,
|
||||
files: [...existingFiles, ...uploadedFiles]
|
||||
}
|
||||
})
|
||||
}
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
enqueueSnackbar({
|
||||
message: `Failed to upload file: ${
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadFormDataToCodeInterpreter = async (formData) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const uploadResp = await assistantsApi.uploadFilesToAssistant(assistantCredential, formData)
|
||||
if (uploadResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'File uploaded successfully!',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const uploadedFiles = uploadResp.data
|
||||
const existingFiles = toolResources?.code_interpreter?.files ?? []
|
||||
const existingFileIds = toolResources?.code_interpreter?.file_ids ?? []
|
||||
|
||||
setToolResources({
|
||||
...toolResources,
|
||||
code_interpreter: {
|
||||
...toolResources?.code_interpreter,
|
||||
files: [...existingFiles, ...uploadedFiles],
|
||||
file_ids: [...existingFileIds, ...uploadedFiles.map((file) => file.id)]
|
||||
}
|
||||
})
|
||||
}
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
enqueueSnackbar({
|
||||
message: `Failed to upload file: ${
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const detachVectorStore = () => {
|
||||
setToolResources({
|
||||
...toolResources,
|
||||
file_search: {
|
||||
files: [],
|
||||
vector_store_object: null,
|
||||
vector_store_ids: []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onDeleteClick = () => {
|
||||
setDeleteDialogProps({
|
||||
title: `Delete Assistant`,
|
||||
description: `Delete Assistant ${assistantName}?`,
|
||||
description: `Select delete method for ${assistantName}`,
|
||||
cancelButtonName: 'Cancel'
|
||||
})
|
||||
setDeleteDialogOpen(true)
|
||||
@@ -394,7 +603,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
onConfirm()
|
||||
}
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
enqueueSnackbar({
|
||||
message: `Failed to delete Assistant: ${
|
||||
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||
@@ -414,8 +622,35 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
}
|
||||
}
|
||||
|
||||
const onFileDeleteClick = async (fileId) => {
|
||||
setAssistantFiles(assistantFiles.filter((file) => file.id !== fileId))
|
||||
const onFileDeleteClick = async (fileId, toolType) => {
|
||||
if (toolType === 'code_interpreter') {
|
||||
setToolResources({
|
||||
...toolResources,
|
||||
code_interpreter: {
|
||||
...toolResources.code_interpreter,
|
||||
files: toolResources.code_interpreter.files.filter((file) => file.id !== fileId),
|
||||
file_ids: toolResources.code_interpreter.file_ids.filter((file_id) => file_id !== fileId)
|
||||
}
|
||||
})
|
||||
} else if (toolType === 'file_search') {
|
||||
// Remove from toolResources
|
||||
setToolResources({
|
||||
...toolResources,
|
||||
file_search: {
|
||||
...toolResources.file_search,
|
||||
files: toolResources.file_search.files.filter((file) => file.id !== fileId)
|
||||
}
|
||||
})
|
||||
// Remove files from vector store
|
||||
try {
|
||||
const vectorStoreId = toolResources.file_search?.vector_store_ids?.length
|
||||
? toolResources.file_search.vector_store_ids[0]
|
||||
: ''
|
||||
await assistantsApi.deleteFilesFromAssistantVectorStore(vectorStoreId, assistantCredential, { file_ids: [fileId] })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const component = show ? (
|
||||
@@ -430,8 +665,45 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
<DialogTitle sx={{ fontSize: '1rem', p: 3, pb: 0 }} id='alert-dialog-title'>
|
||||
{dialogProps.title}
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, maxHeight: '75vh', position: 'relative', px: 3, pb: 3 }}>
|
||||
<DialogContent
|
||||
ref={dialogRef}
|
||||
sx={{ display: 'flex', flexDirection: 'column', gap: 2, maxHeight: '75vh', position: 'relative', px: 3, pb: 3 }}
|
||||
>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 2 }}>
|
||||
<Box>
|
||||
<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>
|
||||
<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>
|
||||
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||
<Typography variant='overline'>Assistant Name</Typography>
|
||||
@@ -440,6 +712,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
<OutlinedInput
|
||||
id='assistantName'
|
||||
type='string'
|
||||
size='small'
|
||||
fullWidth
|
||||
placeholder='My New Assistant'
|
||||
value={assistantName}
|
||||
@@ -455,6 +728,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
<OutlinedInput
|
||||
id='assistantDesc'
|
||||
type='string'
|
||||
size='small'
|
||||
fullWidth
|
||||
placeholder='Description of what the Assistant does'
|
||||
multiline={true}
|
||||
@@ -491,6 +765,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
<OutlinedInput
|
||||
id='assistantIcon'
|
||||
type='string'
|
||||
size='small'
|
||||
fullWidth
|
||||
placeholder={`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`}
|
||||
value={assistantIcon}
|
||||
@@ -498,40 +773,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
onChange={(e) => setAssistantIcon(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<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>
|
||||
<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>
|
||||
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||
<Typography variant='overline'>Assistant Instruction</Typography>
|
||||
@@ -542,6 +783,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
<OutlinedInput
|
||||
id='assistantInstructions'
|
||||
type='string'
|
||||
size='small'
|
||||
fullWidth
|
||||
placeholder='You are a personal math tutor. When asked a question, write and run Python code to answer the question.'
|
||||
multiline={true}
|
||||
@@ -553,64 +795,212 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
</Box>
|
||||
<Box>
|
||||
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||
<Typography variant='overline'>Assistant Tools</Typography>
|
||||
<TooltipWithParser title='A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant.' />
|
||||
</Stack>
|
||||
<MultiDropdown
|
||||
key={JSON.stringify(assistantTools)}
|
||||
name={JSON.stringify(assistantTools)}
|
||||
options={[
|
||||
{
|
||||
label: 'Code Interpreter',
|
||||
name: 'code_interpreter'
|
||||
},
|
||||
{
|
||||
label: 'Retrieval',
|
||||
name: 'retrieval'
|
||||
<Typography variant='overline'>Assistant Temperature</Typography>
|
||||
<TooltipWithParser
|
||||
title={
|
||||
'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.'
|
||||
}
|
||||
]}
|
||||
onSelect={(newValue) => (newValue ? setAssistantTools(JSON.parse(newValue)) : setAssistantTools([]))}
|
||||
value={assistantTools ?? 'choose an option'}
|
||||
/>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='assistantTemp'
|
||||
type='number'
|
||||
size='small'
|
||||
fullWidth
|
||||
value={temperature}
|
||||
name='assistantTemp'
|
||||
onChange={(e) => setTemperature(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||
<Typography variant='overline'>Knowledge Files</Typography>
|
||||
<TooltipWithParser title='Allow assistant to use the content from uploaded files for retrieval and code interpreter. MAX: 20 files' />
|
||||
<Typography variant='overline'>Assistant Top P</Typography>
|
||||
<TooltipWithParser
|
||||
title={
|
||||
'Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered.'
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
{assistantFiles.map((file, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
width: 'max-content',
|
||||
height: 'max-content',
|
||||
borderRadius: 15,
|
||||
background: 'rgb(254,252,191)',
|
||||
paddingLeft: 15,
|
||||
paddingRight: 15,
|
||||
paddingTop: 5,
|
||||
paddingBottom: 5,
|
||||
marginRight: 10
|
||||
}}
|
||||
>
|
||||
<span style={{ color: 'rgb(116,66,16)', marginRight: 10 }}>{file.filename}</span>
|
||||
<IconButton sx={{ height: 15, width: 15, p: 0 }} onClick={() => onFileDeleteClick(file.id)}>
|
||||
<IconX />
|
||||
</IconButton>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<File
|
||||
key={uploadAssistantFiles}
|
||||
fileType='*'
|
||||
onChange={(newValue) => setUploadAssistantFiles(newValue)}
|
||||
value={uploadAssistantFiles ?? 'Choose a file to upload'}
|
||||
<OutlinedInput
|
||||
id='assistantTopP'
|
||||
type='number'
|
||||
fullWidth
|
||||
size='small'
|
||||
value={topP}
|
||||
name='assistantTopP'
|
||||
min='0'
|
||||
max='1'
|
||||
onChange={(e) => setTopP(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
{assistantCredential && (
|
||||
<>
|
||||
<Box>
|
||||
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||
<Typography variant='overline'>Assistant Tools</Typography>
|
||||
<TooltipWithParser title='A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant.' />
|
||||
</Stack>
|
||||
<MultiDropdown
|
||||
key={JSON.stringify(assistantTools)}
|
||||
name={JSON.stringify(assistantTools)}
|
||||
options={[
|
||||
{
|
||||
label: 'Code Interpreter',
|
||||
name: 'code_interpreter'
|
||||
},
|
||||
{
|
||||
label: 'File Search',
|
||||
name: 'file_search'
|
||||
}
|
||||
]}
|
||||
onSelect={(newValue) => {
|
||||
newValue ? setAssistantTools(JSON.parse(newValue)) : setAssistantTools([])
|
||||
setTimeout(() => {
|
||||
dialogRef?.current?.scrollTo({ top: maxScroll })
|
||||
}, 100)
|
||||
}}
|
||||
value={assistantTools ?? 'choose an option'}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
{assistantTools?.length > 0 && assistantTools.includes('code_interpreter') && (
|
||||
<Card sx={{ mb: 2, border: '1px solid #e0e0e0', borderRadius: `${customization.borderRadius}px` }}>
|
||||
<CardContent>
|
||||
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||
<Typography variant='overline'>Code Interpreter Files</Typography>
|
||||
<TooltipWithParser title='Code Interpreter enables the assistant to write and run code. This tool can process files with diverse data and formatting, and generate files such as graphs' />
|
||||
</Stack>
|
||||
{toolResources?.code_interpreter?.files?.length > 0 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap' }}>
|
||||
{toolResources?.code_interpreter?.files?.map((file, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
width: 'max-content',
|
||||
height: 'max-content',
|
||||
borderRadius: 15,
|
||||
background: 'rgb(254,252,191)',
|
||||
paddingLeft: 15,
|
||||
paddingRight: 15,
|
||||
paddingTop: 5,
|
||||
paddingBottom: 5,
|
||||
marginRight: 10,
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<span style={{ color: 'rgb(116,66,16)', marginRight: 10 }}>
|
||||
{file.filename}
|
||||
</span>
|
||||
<IconButton
|
||||
sx={{ height: 15, width: 15, p: 0 }}
|
||||
onClick={() => onFileDeleteClick(file.id, 'code_interpreter')}
|
||||
>
|
||||
<IconX />
|
||||
</IconButton>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<File
|
||||
key={uploadCodeInterpreterFiles}
|
||||
fileType='*'
|
||||
formDataUpload={true}
|
||||
value={uploadCodeInterpreterFiles ?? 'Choose a file to upload'}
|
||||
onChange={(newValue) => setUploadCodeInterpreterFiles(newValue)}
|
||||
onFormDataChange={(formData) => uploadFormDataToCodeInterpreter(formData)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
{assistantTools?.length > 0 && assistantTools.includes('file_search') && (
|
||||
<Card sx={{ mb: 2, border: '1px solid #e0e0e0', borderRadius: `${customization.borderRadius}px` }}>
|
||||
<CardContent>
|
||||
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||
<Typography variant='overline'>File Search Files</Typography>
|
||||
<TooltipWithParser title='File search enables the assistant with knowledge from files that you or your users upload. Once a file is uploaded, the assistant automatically decides when to retrieve content based on user requests' />
|
||||
</Stack>
|
||||
{toolResources?.file_search?.vector_store_object && (
|
||||
<Chip
|
||||
label={
|
||||
toolResources?.file_search?.vector_store_object?.name
|
||||
? toolResources?.file_search?.vector_store_object?.name
|
||||
: toolResources?.file_search?.vector_store_object?.id
|
||||
}
|
||||
component='a'
|
||||
sx={{ mb: 2, mt: 1 }}
|
||||
variant='outlined'
|
||||
clickable
|
||||
color='primary'
|
||||
onDelete={detachVectorStore}
|
||||
onClick={() =>
|
||||
onEditAssistantVectorStoreClick(toolResources?.file_search?.vector_store_object)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{toolResources?.file_search?.files?.length > 0 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap' }}>
|
||||
{toolResources?.file_search?.files?.map((file, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
width: 'max-content',
|
||||
height: 'max-content',
|
||||
borderRadius: 15,
|
||||
background: 'rgb(254,252,191)',
|
||||
paddingLeft: 15,
|
||||
paddingRight: 15,
|
||||
paddingTop: 5,
|
||||
paddingBottom: 5,
|
||||
marginRight: 10,
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<span style={{ color: 'rgb(116,66,16)', marginRight: 10 }}>
|
||||
{file.filename}
|
||||
</span>
|
||||
<IconButton
|
||||
sx={{ height: 15, width: 15, p: 0 }}
|
||||
onClick={() => onFileDeleteClick(file.id, 'file_search')}
|
||||
>
|
||||
<IconX />
|
||||
</IconButton>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!toolResources.file_search || !toolResources.file_search?.vector_store_ids?.length ? (
|
||||
<Button
|
||||
variant='outlined'
|
||||
component='label'
|
||||
fullWidth
|
||||
startIcon={<IconPlus />}
|
||||
sx={{ marginRight: '1rem' }}
|
||||
onClick={() => onAddAssistantVectorStoreClick()}
|
||||
>
|
||||
Add Vector Store
|
||||
</Button>
|
||||
) : (
|
||||
<File
|
||||
key={uploadVectorStoreFiles}
|
||||
fileType='*'
|
||||
formDataUpload={true}
|
||||
value={uploadVectorStoreFiles ?? 'Choose a file to upload'}
|
||||
onChange={(newValue) => setUploadVectorStoreFiles(newValue)}
|
||||
onFormDataChange={(formData) => uploadFormDataToVectorStore(formData)}
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 3, pt: 0 }}>
|
||||
@@ -639,6 +1029,35 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
||||
onDelete={() => deleteAssistant()}
|
||||
onDeleteBoth={() => deleteAssistant(true)}
|
||||
/>
|
||||
<AssistantVectorStoreDialog
|
||||
show={assistantVectorStoreDialogOpen}
|
||||
dialogProps={assistantVectorStoreDialogProps}
|
||||
onCancel={() => setAssistantVectorStoreDialogOpen(false)}
|
||||
onDelete={(vectorStoreId) => {
|
||||
setToolResources({
|
||||
...toolResources,
|
||||
file_search: {
|
||||
vector_store_object: null,
|
||||
files: [],
|
||||
vector_store_ids: toolResources.file_search.vector_store_ids.filter((id) => vectorStoreId !== id)
|
||||
}
|
||||
})
|
||||
setAssistantVectorStoreDialogOpen(false)
|
||||
}}
|
||||
onConfirm={(vectorStoreObj, files) => {
|
||||
setToolResources({
|
||||
...toolResources,
|
||||
file_search: {
|
||||
...toolResources.file_search,
|
||||
vector_store_object: vectorStoreObj,
|
||||
files: files ? files : toolResources.file_search?.files,
|
||||
vector_store_ids: [vectorStoreObj.id]
|
||||
}
|
||||
})
|
||||
setAssistantVectorStoreDialogOpen(false)
|
||||
}}
|
||||
setError={setError}
|
||||
/>
|
||||
{loading && <BackdropLoader open={loading} />}
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { omit } from 'lodash'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions'
|
||||
|
||||
// Material
|
||||
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Box, Stack, OutlinedInput, Typography } from '@mui/material'
|
||||
|
||||
// Project imports
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog'
|
||||
import { SwitchInput } from '@/ui-component/switch/Switch'
|
||||
import { Dropdown } from '@/ui-component/dropdown/Dropdown'
|
||||
import { BackdropLoader } from '@/ui-component/loading/BackdropLoader'
|
||||
|
||||
// Icons
|
||||
import { IconX } from '@tabler/icons'
|
||||
|
||||
// API
|
||||
import assistantsApi from '@/api/assistants'
|
||||
|
||||
// Hooks
|
||||
import useApi from '@/hooks/useApi'
|
||||
|
||||
// utils
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
import { formatBytes } from '@/utils/genericHelper'
|
||||
|
||||
// const
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||
|
||||
const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, onDelete, setError }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
// ==============================|| Snackbar ||============================== //
|
||||
|
||||
useNotifier()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const getAssistantVectorStoreApi = useApi(assistantsApi.getAssistantVectorStore)
|
||||
const listAssistantVectorStoreApi = useApi(assistantsApi.listAssistantVectorStore)
|
||||
|
||||
const [name, setName] = useState('')
|
||||
const [isExpirationOn, setExpirationOnOff] = useState(false)
|
||||
const [expirationDays, setExpirationDays] = useState(7)
|
||||
const [availableVectorStoreOptions, setAvailableVectorStoreOptions] = useState([{ label: '- Create New -', name: '-create-' }])
|
||||
const [selectedVectorStore, setSelectedVectorStore] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (getAssistantVectorStoreApi.data) {
|
||||
if (getAssistantVectorStoreApi.data.name) {
|
||||
setName(getAssistantVectorStoreApi.data.name)
|
||||
} else {
|
||||
setName('')
|
||||
}
|
||||
|
||||
if (getAssistantVectorStoreApi.data.id) {
|
||||
setSelectedVectorStore(getAssistantVectorStoreApi.data.id)
|
||||
} else {
|
||||
setSelectedVectorStore('')
|
||||
}
|
||||
|
||||
if (getAssistantVectorStoreApi.data.expires_after && getAssistantVectorStoreApi.data.expires_after.days) {
|
||||
setExpirationDays(getAssistantVectorStoreApi.data.expires_after.days)
|
||||
setExpirationOnOff(true)
|
||||
} else {
|
||||
setExpirationDays(7)
|
||||
setExpirationOnOff(false)
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getAssistantVectorStoreApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (listAssistantVectorStoreApi.data) {
|
||||
let vectorStores = []
|
||||
for (let i = 0; i < listAssistantVectorStoreApi.data.length; i += 1) {
|
||||
vectorStores.push({
|
||||
label: listAssistantVectorStoreApi.data[i]?.name ?? listAssistantVectorStoreApi.data[i].id,
|
||||
name: listAssistantVectorStoreApi.data[i].id,
|
||||
description: `${listAssistantVectorStoreApi.data[i]?.file_counts?.total} files (${formatBytes(
|
||||
listAssistantVectorStoreApi.data[i]?.usage_bytes
|
||||
)})`
|
||||
})
|
||||
}
|
||||
vectorStores = vectorStores.filter((vs) => vs.name !== '-create-')
|
||||
vectorStores.unshift({ label: '- Create New -', name: '-create-' })
|
||||
setAvailableVectorStoreOptions(vectorStores)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [listAssistantVectorStoreApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAssistantVectorStoreApi.error) {
|
||||
setError(getAssistantVectorStoreApi.error)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getAssistantVectorStoreApi.error])
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.type === 'EDIT' && dialogProps.data) {
|
||||
getAssistantVectorStoreApi.request(dialogProps.data.id, dialogProps.credential)
|
||||
listAssistantVectorStoreApi.request(dialogProps.credential)
|
||||
} else if (dialogProps.type === 'ADD') {
|
||||
listAssistantVectorStoreApi.request(dialogProps.credential)
|
||||
}
|
||||
|
||||
return () => {
|
||||
setName('')
|
||||
setExpirationOnOff(false)
|
||||
setExpirationDays(7)
|
||||
setSelectedVectorStore('')
|
||||
setAvailableVectorStoreOptions([{ label: '- Create New -', name: '-create-' }])
|
||||
setLoading(false)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dialogProps])
|
||||
|
||||
useEffect(() => {
|
||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
}, [show, dispatch])
|
||||
|
||||
const deleteVectorStore = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const deleteResp = await assistantsApi.deleteAssistantVectorStore(selectedVectorStore, dialogProps.credential)
|
||||
if (deleteResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Vector Store deleted',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onDelete(selectedVectorStore)
|
||||
}
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
enqueueSnackbar({
|
||||
message: `Failed to delete Vector Store: ${
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
setLoading(false)
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
const addNewVectorStore = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const obj = {
|
||||
name: name !== '' ? name : null,
|
||||
expires_after: isExpirationOn ? { anchor: 'last_active_at', days: parseFloat(expirationDays) } : null
|
||||
}
|
||||
const createResp = await assistantsApi.createAssistantVectorStore(dialogProps.credential, obj)
|
||||
if (createResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'New Vector Store added',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm(createResp.data)
|
||||
}
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
enqueueSnackbar({
|
||||
message: `Failed to add new Vector Store: ${
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
setLoading(false)
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
const saveVectorStore = async (selectedVectorStoreId) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const saveObj = {
|
||||
name: name !== '' ? name : null,
|
||||
expires_after: isExpirationOn ? { anchor: 'last_active_at', days: parseFloat(expirationDays) } : null
|
||||
}
|
||||
const saveResp = await assistantsApi.updateAssistantVectorStore(selectedVectorStoreId, dialogProps.credential, saveObj)
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Vector Store saved',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
if ('files' in saveResp.data) {
|
||||
const files = saveResp.data.files
|
||||
onConfirm(omit(saveResp.data, ['files']), files)
|
||||
} else {
|
||||
onConfirm(saveResp.data)
|
||||
}
|
||||
}
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
console.error('error=', error)
|
||||
setError(error)
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save Vector Store: ${
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
setLoading(false)
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
fullWidth
|
||||
maxWidth='sm'
|
||||
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'>
|
||||
Select Vector Store
|
||||
<span style={{ color: 'red' }}> *</span>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Dropdown
|
||||
name={selectedVectorStore}
|
||||
options={availableVectorStoreOptions}
|
||||
loading={listAssistantVectorStoreApi.loading}
|
||||
onSelect={(newValue) => {
|
||||
setSelectedVectorStore(newValue)
|
||||
if (newValue === '-create-') {
|
||||
setName('')
|
||||
setExpirationOnOff(false)
|
||||
setExpirationDays(7)
|
||||
} else {
|
||||
getAssistantVectorStoreApi.request(newValue, dialogProps.credential)
|
||||
}
|
||||
}}
|
||||
value={selectedVectorStore ?? 'choose an option'}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{selectedVectorStore !== '' && (
|
||||
<>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>Vector Store Name</Typography>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='vsName'
|
||||
type='string'
|
||||
fullWidth
|
||||
placeholder={'My Vector Store'}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>Vector Store Expiration</Typography>
|
||||
</Stack>
|
||||
<SwitchInput onChange={(newValue) => setExpirationOnOff(newValue)} value={isExpirationOn} />
|
||||
</Box>
|
||||
|
||||
{isExpirationOn && (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
Expiration Days
|
||||
<span style={{ color: 'red' }}> *</span>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='expDays'
|
||||
type='number'
|
||||
fullWidth
|
||||
value={expirationDays}
|
||||
onChange={(e) => setExpirationDays(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{dialogProps.type === 'EDIT' && (
|
||||
<StyledButton color='error' variant='contained' onClick={() => deleteVectorStore()}>
|
||||
Delete
|
||||
</StyledButton>
|
||||
)}
|
||||
<StyledButton
|
||||
disabled={!selectedVectorStore}
|
||||
variant='contained'
|
||||
onClick={() => (selectedVectorStore === '-create-' ? addNewVectorStore() : saveVectorStore(selectedVectorStore))}
|
||||
>
|
||||
{dialogProps.confirmButtonName}
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
<ConfirmDialog />
|
||||
{loading && <BackdropLoader open={loading} />}
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
AssistantVectorStoreDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func,
|
||||
onConfirm: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
setError: PropTypes.func
|
||||
}
|
||||
|
||||
export default AssistantVectorStoreDialog
|
||||
@@ -20,14 +20,13 @@ const DeleteConfirmDialog = ({ show, dialogProps, onCancel, onDelete, onDeleteBo
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<span>{dialogProps.description}</span>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', marginTop: 20 }}>
|
||||
<StyledButton sx={{ mb: 1 }} color='orange' variant='contained' onClick={onDelete}>
|
||||
Delete only from Flowise
|
||||
<div style={{ display: 'flex', flexDirection: 'row', marginTop: 20 }}>
|
||||
<Button sx={{ flex: 1, mb: 1, mr: 1 }} color='error' variant='outlined' onClick={onDelete}>
|
||||
Only Flowise
|
||||
</Button>
|
||||
<StyledButton sx={{ flex: 1, mb: 1, ml: 1 }} color='error' variant='contained' onClick={onDeleteBoth}>
|
||||
OpenAI and Flowise
|
||||
</StyledButton>
|
||||
<StyledButton sx={{ mb: 1 }} color='error' variant='contained' onClick={onDeleteBoth}>
|
||||
Delete from both OpenAI and Flowise
|
||||
</StyledButton>
|
||||
<Button onClick={onCancel}>{dialogProps.cancelButtonName}</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -355,6 +355,15 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
})
|
||||
}
|
||||
|
||||
const updateLastMessageFileAnnotations = (fileAnnotations) => {
|
||||
setMessages((prevMessages) => {
|
||||
let allMessages = [...cloneDeep(prevMessages)]
|
||||
if (allMessages[allMessages.length - 1].type === 'userMessage') return allMessages
|
||||
allMessages[allMessages.length - 1].fileAnnotations = fileAnnotations
|
||||
return allMessages
|
||||
})
|
||||
}
|
||||
|
||||
// Handle errors
|
||||
const handleError = (message = 'Oops! There seems to be an error. Please try again.') => {
|
||||
message = message.replace(`Unable to parse JSON response from chat agent.\n\n`, '')
|
||||
@@ -482,8 +491,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
const downloadFile = async (fileAnnotation) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${baseURL}/api/v1/openai-assistants-file`,
|
||||
{ fileName: fileAnnotation.fileName },
|
||||
`${baseURL}/api/v1/openai-assistants-file/download`,
|
||||
{ fileName: fileAnnotation.fileName, chatflowId: chatflowid, chatId: chatId },
|
||||
{ responseType: 'blob' }
|
||||
)
|
||||
const blob = new Blob([response.data], { type: response.headers['content-type'] })
|
||||
@@ -611,6 +620,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
|
||||
socket.on('usedTools', updateLastMessageUsedTools)
|
||||
|
||||
socket.on('fileAnnotations', updateLastMessageFileAnnotations)
|
||||
|
||||
socket.on('token', updateLastMessage)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user