mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 15:00:57 +03:00
New Feature Pagination (#4704)
* common pagination component * Pagination for Doc Store Dashboard * Pagination for Executions Dashboard * Pagination Support for Tables * lint fixes * update view message dialog UI * initial loading was ignoring the pagination counts * 1) default page size change 2) ensure page limits are passed on load 3) co-pilot review comments (n+1 query) 4) * 1) default page size change 2) ensure page limits are passed on load 3) co-pilot review comments (n+1 query) 4) refresh lists after insert/delete. * Enhancement: Improve handling of empty responses in DocumentStore and API key services - Added check for empty entities in DocumentStoreDTO.fromEntities to return an empty array. - Updated condition in getAllDocumentStores to handle total count correctly, allowing for zero total. - Refined logic in getAllApiKeys to check for empty keys and ensure correct API key retrieval. - Adjusted UI components to safely handle potential undefined apiKeys array. * Refresh API key list on pagination change * Enhancement: Update pagination and filter handling across components - Increased default items per page in AgentExecutions from 10 to 12. - Improved JSON parsing for chat type and feedback type filters in ViewMessagesDialog. - Enhanced execution filtering logic in AgentExecutions to ensure proper pagination and state management. - Refactored filter section in AgentExecutions for better readability and functionality. - Updated refresh logic in Agentflows to use the correct agentflow version. * add workspaceId to removeAllChatMessages * Refactor chat message retrieval logic for improved efficiency and maintainability - Introduced a new `handleFeedbackQuery` function to streamline feedback-related queries. - Enhanced pagination handling for session-based queries in `getMessagesWithFeedback`. - Updated `ViewMessagesDialog` to sort messages in descending order by default. - Simplified image rendering logic in `DocumentStoreTable` for better readability. * - Update `validateChatflowAPIKey` and `validateAPIKey` functions to get the correct keys array - Enhanced error handling in the `sanitizeExecution` function to ensure safe access to nested properties * Refactor API key validation logic for improved accuracy and error handling - Consolidated API key validation in `validateAPIKey` to return detailed validation results. - Updated `validateFlowAPIKey` to streamline flow API key validation. - Introduced `getApiKeyById` function in the API key service for better key retrieval. - Removed unused function `getAllChatSessionsFromChatflow` from the chat message API. --------- Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import client from './client'
|
||||
|
||||
const getAllAPIKeys = () => client.get('/apikey')
|
||||
const getAllAPIKeys = (params) => client.get('/apikey', { params })
|
||||
|
||||
const createNewAPI = (body) => client.post(`/apikey`, body)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import client from './client'
|
||||
|
||||
const getAllChatflows = () => client.get('/chatflows?type=CHATFLOW')
|
||||
const getAllChatflows = (params) => client.get('/chatflows?type=CHATFLOW', { params })
|
||||
|
||||
const getAllAgentflows = (type) => client.get(`/chatflows?type=${type}`)
|
||||
const getAllAgentflows = (type, params) => client.get(`/chatflows?type=${type}`, { params })
|
||||
|
||||
const getSpecificChatflow = (id) => client.get(`/chatflows/${id}`)
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import client from './client'
|
||||
|
||||
const getAllDatasets = () => client.get('/datasets')
|
||||
const getAllDatasets = (params) => client.get('/datasets', { params })
|
||||
|
||||
//dataset
|
||||
const getDataset = (id) => client.get(`/datasets/set/${id}`)
|
||||
const getDataset = (id, params) => client.get(`/datasets/set/${id}`, { params })
|
||||
const createDataset = (body) => client.post(`/datasets/set`, body)
|
||||
const updateDataset = (id, body) => client.put(`/datasets/set/${id}`, body)
|
||||
const deleteDataset = (id) => client.delete(`/datasets/set/${id}`)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import client from './client'
|
||||
|
||||
const getAllDocumentStores = () => client.get('/document-store/store')
|
||||
const getAllDocumentStores = (params) => client.get('/document-store/store', { params })
|
||||
const getDocumentLoaders = () => client.get('/document-store/components/loaders')
|
||||
const getSpecificDocumentStore = (id) => client.get(`/document-store/store/${id}`)
|
||||
const createDocumentStore = (body) => client.post(`/document-store/store`, body)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import client from './client'
|
||||
|
||||
//evaluation
|
||||
const getAllEvaluations = () => client.get('/evaluations')
|
||||
const getAllEvaluations = (params) => client.get('/evaluations', { params })
|
||||
const getIsOutdated = (id) => client.get(`/evaluations/is-outdated/${id}`)
|
||||
const getEvaluation = (id) => client.get(`/evaluations/${id}`)
|
||||
const createEvaluation = (body) => client.post(`/evaluations`, body)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import client from './client'
|
||||
|
||||
const getAllEvaluators = () => client.get('/evaluators')
|
||||
const getAllEvaluators = (params) => client.get('/evaluators', { params })
|
||||
|
||||
//evaluators
|
||||
const createEvaluator = (body) => client.post(`/evaluators`, body)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import client from './client'
|
||||
|
||||
const getAllTools = () => client.get('/tools')
|
||||
const getAllTools = (params) => client.get('/tools', { params })
|
||||
|
||||
const getSpecificTool = (id) => client.get(`/tools/${id}`)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import client from './client'
|
||||
|
||||
const getAllVariables = () => client.get('/variables')
|
||||
const getAllVariables = (params) => client.get('/variables', { params })
|
||||
|
||||
const createVariable = (body) => client.post(`/variables`, body)
|
||||
|
||||
|
||||
@@ -8,8 +8,14 @@ import Typography from '@mui/material/Typography'
|
||||
const StatsCard = ({ title, stat }) => {
|
||||
const customization = useSelector((state) => state.customization)
|
||||
return (
|
||||
<Card sx={{ border: '1px solid #e0e0e0', borderRadius: `${customization.borderRadius}px` }}>
|
||||
<CardContent>
|
||||
<Card
|
||||
sx={{
|
||||
border: customization.isDarkMode ? 'none' : '1px solid #e0e0e0',
|
||||
boxShadow: customization.isDarkMode ? '0px 3px 8px rgba(255, 255, 255, 0.5)' : 'none',
|
||||
borderRadius: `${customization.borderRadius}px`
|
||||
}}
|
||||
>
|
||||
<CardContent sx={{ padding: '12px', '&:last-child': { paddingBottom: '12px', paddingLeft: '18px', paddingRight: '8px' } }}>
|
||||
<Typography sx={{ fontSize: '0.875rem' }} color='text.primary' gutterBottom>
|
||||
{title}
|
||||
</Typography>
|
||||
|
||||
@@ -24,9 +24,14 @@ import {
|
||||
CardContent,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
DialogActions
|
||||
DialogActions,
|
||||
Pagination,
|
||||
Typography,
|
||||
Menu,
|
||||
MenuItem,
|
||||
IconButton
|
||||
} from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import { useTheme, styled, alpha } from '@mui/material/styles'
|
||||
import DatePicker from 'react-datepicker'
|
||||
|
||||
import robotPNG from '@/assets/images/robot.png'
|
||||
@@ -34,7 +39,8 @@ import userPNG from '@/assets/images/account.png'
|
||||
import msgEmptySVG from '@/assets/images/message_empty.svg'
|
||||
import multiagent_supervisorPNG from '@/assets/images/multiagent_supervisor.png'
|
||||
import multiagent_workerPNG from '@/assets/images/multiagent_worker.png'
|
||||
import { IconTool, IconDeviceSdCard, IconFileExport, IconEraser, IconX, IconDownload, IconPaperclip } from '@tabler/icons-react'
|
||||
import { IconTool, IconDeviceSdCard, IconFileExport, IconEraser, IconX, IconDownload, IconPaperclip, IconBulb } from '@tabler/icons-react'
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
|
||||
|
||||
// Project import
|
||||
import { MemoizedReactMarkdown } from '@/ui-component/markdown/MemoizedReactMarkdown'
|
||||
@@ -63,6 +69,42 @@ import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackba
|
||||
import '@/views/chatmessage/ChatMessage.css'
|
||||
import 'react-datepicker/dist/react-datepicker.css'
|
||||
|
||||
const StyledMenu = styled((props) => (
|
||||
<Menu
|
||||
elevation={0}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
))(({ theme }) => ({
|
||||
'& .MuiPaper-root': {
|
||||
borderRadius: 6,
|
||||
marginTop: theme.spacing(1),
|
||||
minWidth: 180,
|
||||
boxShadow:
|
||||
'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px',
|
||||
'& .MuiMenu-list': {
|
||||
padding: '4px 0'
|
||||
},
|
||||
'& .MuiMenuItem-root': {
|
||||
'& .MuiSvgIcon-root': {
|
||||
fontSize: 18,
|
||||
color: theme.palette.text.secondary,
|
||||
marginRight: theme.spacing(1.5)
|
||||
},
|
||||
'&:active': {
|
||||
backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
const DatePickerCustomInput = forwardRef(function DatePickerCustomInput({ value, onClick }, ref) {
|
||||
return (
|
||||
<ListItemButton style={{ borderRadius: 15, border: '1px solid #e0e0e0' }} onClick={onClick} ref={ref}>
|
||||
@@ -104,10 +146,12 @@ const ConfirmDeleteMessageDialog = ({ show, dialogProps, onCancel, onConfirm })
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<span style={{ marginTop: '20px', marginBottom: '20px' }}>{dialogProps.description}</span>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={hardDelete} onChange={(event) => setHardDelete(event.target.checked)} />}
|
||||
label='Remove messages from 3rd party Memory Node'
|
||||
/>
|
||||
{dialogProps.isChatflow && (
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={hardDelete} onChange={(event) => setHardDelete(event.target.checked)} />}
|
||||
label='Remove messages from 3rd party Memory Node'
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel}>{dialogProps.cancelButtonName}</Button>
|
||||
@@ -142,7 +186,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const [chatlogs, setChatLogs] = useState([])
|
||||
const [allChatlogs, setAllChatLogs] = useState([])
|
||||
const [chatMessages, setChatMessages] = useState([])
|
||||
const [stats, setStats] = useState([])
|
||||
const [stats, setStats] = useState({})
|
||||
const [selectedMessageIndex, setSelectedMessageIndex] = useState(0)
|
||||
const [selectedChatId, setSelectedChatId] = useState('')
|
||||
const [sourceDialogOpen, setSourceDialogOpen] = useState(false)
|
||||
@@ -154,6 +198,8 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const [startDate, setStartDate] = useState(new Date(new Date().setMonth(new Date().getMonth() - 1)))
|
||||
const [endDate, setEndDate] = useState(new Date())
|
||||
const [leadEmail, setLeadEmail] = useState('')
|
||||
const [anchorEl, setAnchorEl] = useState(null)
|
||||
const open = Boolean(anchorEl)
|
||||
|
||||
const getChatmessageApi = useApi(chatmessageApi.getAllChatmessageFromChatflow)
|
||||
const getChatmessageFromPKApi = useApi(chatmessageApi.getChatmessageFromPK)
|
||||
@@ -161,74 +207,70 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const getStoragePathFromServer = useApi(chatmessageApi.getStoragePath)
|
||||
let storagePath = ''
|
||||
|
||||
/* Table Pagination */
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageLimit, setPageLimit] = useState(10)
|
||||
const [total, setTotal] = useState(0)
|
||||
const onChange = (event, page) => {
|
||||
setCurrentPage(page)
|
||||
refresh(page, pageLimit, startDate, endDate, chatTypeFilter, feedbackTypeFilter)
|
||||
}
|
||||
|
||||
const refresh = (page, limit, startDate, endDate, chatTypes, feedbackTypes) => {
|
||||
getChatmessageApi.request(dialogProps.chatflow.id, {
|
||||
chatType: chatTypes.length ? chatTypes : undefined,
|
||||
feedbackType: feedbackTypes.length ? feedbackTypes : undefined,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
order: 'DESC',
|
||||
page: page,
|
||||
limit: limit
|
||||
})
|
||||
getStatsApi.request(dialogProps.chatflow.id, {
|
||||
chatType: chatTypes.length ? chatTypes : undefined,
|
||||
feedbackType: feedbackTypes.length ? feedbackTypes : undefined,
|
||||
startDate: startDate,
|
||||
endDate: endDate
|
||||
})
|
||||
setCurrentPage(page)
|
||||
}
|
||||
|
||||
const onStartDateSelected = (date) => {
|
||||
const updatedDate = new Date(date)
|
||||
updatedDate.setHours(0, 0, 0, 0)
|
||||
setStartDate(updatedDate)
|
||||
getChatmessageApi.request(dialogProps.chatflow.id, {
|
||||
startDate: updatedDate,
|
||||
endDate: endDate,
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined,
|
||||
feedbackType: feedbackTypeFilter.length ? feedbackTypeFilter : undefined
|
||||
})
|
||||
getStatsApi.request(dialogProps.chatflow.id, {
|
||||
startDate: updatedDate,
|
||||
endDate: endDate,
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined,
|
||||
feedbackType: feedbackTypeFilter.length ? feedbackTypeFilter : undefined
|
||||
})
|
||||
refresh(1, pageLimit, updatedDate, endDate, chatTypeFilter, feedbackTypeFilter)
|
||||
}
|
||||
|
||||
const onEndDateSelected = (date) => {
|
||||
const updatedDate = new Date(date)
|
||||
updatedDate.setHours(23, 59, 59, 999)
|
||||
setEndDate(updatedDate)
|
||||
getChatmessageApi.request(dialogProps.chatflow.id, {
|
||||
endDate: updatedDate,
|
||||
startDate: startDate,
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined,
|
||||
feedbackType: feedbackTypeFilter.length ? feedbackTypeFilter : undefined
|
||||
})
|
||||
getStatsApi.request(dialogProps.chatflow.id, {
|
||||
endDate: updatedDate,
|
||||
startDate: startDate,
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined,
|
||||
feedbackType: feedbackTypeFilter.length ? feedbackTypeFilter : undefined
|
||||
})
|
||||
refresh(1, pageLimit, startDate, updatedDate, chatTypeFilter, feedbackTypeFilter)
|
||||
}
|
||||
|
||||
const onChatTypeSelected = (chatTypes) => {
|
||||
setChatTypeFilter(chatTypes)
|
||||
getChatmessageApi.request(dialogProps.chatflow.id, {
|
||||
chatType: chatTypes.length ? chatTypes : undefined,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
feedbackType: feedbackTypeFilter.length ? feedbackTypeFilter : undefined
|
||||
})
|
||||
getStatsApi.request(dialogProps.chatflow.id, {
|
||||
chatType: chatTypes.length ? chatTypes : undefined,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
feedbackType: feedbackTypeFilter.length ? feedbackTypeFilter : undefined
|
||||
})
|
||||
// Parse the JSON string from MultiDropdown back to an array
|
||||
let parsedChatTypes = []
|
||||
if (chatTypes && typeof chatTypes === 'string' && chatTypes.startsWith('[') && chatTypes.endsWith(']')) {
|
||||
parsedChatTypes = JSON.parse(chatTypes)
|
||||
} else if (Array.isArray(chatTypes)) {
|
||||
parsedChatTypes = chatTypes
|
||||
}
|
||||
setChatTypeFilter(parsedChatTypes)
|
||||
refresh(1, pageLimit, startDate, endDate, parsedChatTypes, feedbackTypeFilter)
|
||||
}
|
||||
|
||||
const onFeedbackTypeSelected = (feedbackTypes) => {
|
||||
setFeedbackTypeFilter(feedbackTypes)
|
||||
|
||||
getChatmessageApi.request(dialogProps.chatflow.id, {
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined,
|
||||
feedbackType: feedbackTypes.length ? feedbackTypes : undefined,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
order: 'ASC'
|
||||
})
|
||||
getStatsApi.request(dialogProps.chatflow.id, {
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined,
|
||||
feedbackType: feedbackTypes.length ? feedbackTypes : undefined,
|
||||
startDate: startDate,
|
||||
endDate: endDate
|
||||
})
|
||||
// Parse the JSON string from MultiDropdown back to an array
|
||||
let parsedFeedbackTypes = []
|
||||
if (feedbackTypes && typeof feedbackTypes === 'string' && feedbackTypes.startsWith('[') && feedbackTypes.endsWith(']')) {
|
||||
parsedFeedbackTypes = JSON.parse(feedbackTypes)
|
||||
} else if (Array.isArray(feedbackTypes)) {
|
||||
parsedFeedbackTypes = feedbackTypes
|
||||
}
|
||||
setFeedbackTypeFilter(parsedFeedbackTypes)
|
||||
refresh(1, pageLimit, startDate, endDate, chatTypeFilter, parsedFeedbackTypes)
|
||||
}
|
||||
|
||||
const onDeleteMessages = () => {
|
||||
@@ -236,7 +278,8 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
title: 'Delete Messages',
|
||||
description: 'Are you sure you want to delete messages? This action cannot be undone.',
|
||||
confirmButtonName: 'Delete',
|
||||
cancelButtonName: 'Cancel'
|
||||
cancelButtonName: 'Cancel',
|
||||
isChatflow: dialogProps.isChatflow
|
||||
})
|
||||
setHardDeleteDialogOpen(true)
|
||||
}
|
||||
@@ -280,18 +323,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
)
|
||||
}
|
||||
})
|
||||
getChatmessageApi.request(chatflowid, {
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
feedbackType: feedbackTypeFilter.length ? feedbackTypeFilter : undefined
|
||||
})
|
||||
getStatsApi.request(chatflowid, {
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
feedbackType: feedbackTypeFilter.length ? feedbackTypeFilter : undefined
|
||||
})
|
||||
refresh(1, pageLimit, startDate, endDate, chatTypeFilter, feedbackTypeFilter)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
enqueueSnackbar({
|
||||
@@ -555,20 +587,42 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
item: allChatMessages[i]
|
||||
}
|
||||
} else if (Object.prototype.hasOwnProperty.call(seen, PK) && seen[PK].counter === 1) {
|
||||
// Properly identify user and API messages regardless of order
|
||||
const firstMessage = seen[PK].item
|
||||
const secondMessage = item
|
||||
|
||||
let userContent = ''
|
||||
let apiContent = ''
|
||||
|
||||
// Check both messages and assign based on role, not order
|
||||
if (firstMessage.role === 'userMessage') {
|
||||
userContent = `User: ${firstMessage.content}`
|
||||
} else if (firstMessage.role === 'apiMessage') {
|
||||
apiContent = `Bot: ${firstMessage.content}`
|
||||
}
|
||||
|
||||
if (secondMessage.role === 'userMessage') {
|
||||
userContent = `User: ${secondMessage.content}`
|
||||
} else if (secondMessage.role === 'apiMessage') {
|
||||
apiContent = `Bot: ${secondMessage.content}`
|
||||
}
|
||||
|
||||
seen[PK] = {
|
||||
counter: 2,
|
||||
item: {
|
||||
...seen[PK].item,
|
||||
apiContent:
|
||||
seen[PK].item.role === 'apiMessage' ? `Bot: ${seen[PK].item.content}` : `User: ${seen[PK].item.content}`,
|
||||
userContent: item.role === 'apiMessage' ? `Bot: ${item.content}` : `User: ${item.content}`
|
||||
apiContent,
|
||||
userContent
|
||||
}
|
||||
}
|
||||
filteredChatLogs.push(seen[PK].item)
|
||||
}
|
||||
}
|
||||
setChatLogs(filteredChatLogs)
|
||||
if (filteredChatLogs.length) return getChatPK(filteredChatLogs[0])
|
||||
|
||||
// Sort by date to maintain chronological order
|
||||
const sortedChatLogs = filteredChatLogs.sort((a, b) => new Date(b.createdDate) - new Date(a.createdDate))
|
||||
setChatLogs(sortedChatLogs)
|
||||
if (sortedChatLogs.length) return getChatPK(sortedChatLogs[0])
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -613,6 +667,14 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
setSourceDialogOpen(true)
|
||||
}
|
||||
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
const renderFileUploads = (item, index) => {
|
||||
if (item?.mime?.startsWith('image/')) {
|
||||
return (
|
||||
@@ -706,15 +768,13 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
useEffect(() => {
|
||||
if (getStatsApi.data) {
|
||||
setStats(getStatsApi.data)
|
||||
setTotal(getStatsApi.data?.totalSessions ?? 0)
|
||||
}
|
||||
}, [getStatsApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.chatflow) {
|
||||
getChatmessageApi.request(dialogProps.chatflow.id, {
|
||||
startDate: startDate,
|
||||
endDate: endDate
|
||||
})
|
||||
refresh(currentPage, pageLimit, startDate, endDate, chatTypeFilter, feedbackTypeFilter)
|
||||
getStatsApi.request(dialogProps.chatflow.id, {
|
||||
startDate: startDate,
|
||||
endDate: endDate
|
||||
@@ -733,6 +793,9 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
setEndDate(new Date())
|
||||
setStats([])
|
||||
setLeadEmail('')
|
||||
setTotal(0)
|
||||
setCurrentPage(1)
|
||||
setPageLimit(10)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -748,16 +811,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
if (dialogProps.chatflow) {
|
||||
// when the filter is cleared fetch all messages
|
||||
if (feedbackTypeFilter.length === 0) {
|
||||
getChatmessageApi.request(dialogProps.chatflow.id, {
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined
|
||||
})
|
||||
getStatsApi.request(dialogProps.chatflow.id, {
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined
|
||||
})
|
||||
refresh(currentPage, pageLimit, startDate, endDate, chatTypeFilter, feedbackTypeFilter)
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -819,19 +873,10 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
onClose={onCancel}
|
||||
open={show}
|
||||
fullWidth
|
||||
maxWidth={'lg'}
|
||||
maxWidth={'xl'}
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
{dialogProps.title}
|
||||
<div style={{ flex: 1 }} />
|
||||
<Button variant='outlined' onClick={() => exportMessages()} startIcon={<IconFileExport />}>
|
||||
Export
|
||||
</Button>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<>
|
||||
<div
|
||||
@@ -912,7 +957,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
<b style={{ marginRight: 10 }}>Feedback</b>
|
||||
<MultiDropdown
|
||||
key={JSON.stringify(feedbackTypeFilter)}
|
||||
name='chatType'
|
||||
name='feedbackType'
|
||||
options={[
|
||||
{
|
||||
label: 'Positive',
|
||||
@@ -929,31 +974,81 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
/>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}></div>
|
||||
{stats.totalMessages > 0 && (
|
||||
<Button color='error' variant='outlined' onClick={() => onDeleteMessages()} startIcon={<IconEraser />}>
|
||||
Delete Messages
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
id='messages-dialog-action-button'
|
||||
aria-controls={open ? 'messages-dialog-action-menu' : undefined}
|
||||
aria-haspopup='true'
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
variant={customization.isDarkMode ? 'contained' : 'outlined'}
|
||||
disableElevation
|
||||
color='secondary'
|
||||
onClick={handleClick}
|
||||
sx={{
|
||||
minWidth: 150,
|
||||
'&:hover': {
|
||||
backgroundColor: customization.isDarkMode ? alpha(theme.palette.secondary.main, 0.8) : undefined
|
||||
}
|
||||
}}
|
||||
endIcon={
|
||||
<KeyboardArrowDownIcon style={{ backgroundColor: customization.isDarkMode ? 'transparent' : 'inherit' }} />
|
||||
}
|
||||
>
|
||||
More Actions
|
||||
</Button>
|
||||
<StyledMenu
|
||||
id='messages-dialog-action-menu'
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'messages-dialog-action-button'
|
||||
}}
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleClose()
|
||||
exportMessages()
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<IconFileExport style={{ marginRight: 8 }} />
|
||||
Export to JSON
|
||||
</MenuItem>
|
||||
{(stats.totalMessages ?? 0) > 0 && (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleClose()
|
||||
onDeleteMessages()
|
||||
}}
|
||||
disableRipple
|
||||
>
|
||||
<IconEraser style={{ marginRight: 8 }} />
|
||||
Delete All
|
||||
</MenuItem>
|
||||
)}
|
||||
</StyledMenu>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
|
||||
gridTemplateColumns: 'repeat(4, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
marginBottom: 16,
|
||||
marginBottom: 25,
|
||||
marginLeft: 8,
|
||||
marginRight: 8
|
||||
marginRight: 8,
|
||||
marginTop: 20
|
||||
}}
|
||||
>
|
||||
<StatsCard title='Total Messages' stat={`${stats.totalMessages}`} />
|
||||
<StatsCard title='Total Feedback Received' stat={`${stats.totalFeedback}`} />
|
||||
<StatsCard title='Total Sessions' stat={`${stats.totalSessions ?? 0}`} />
|
||||
<StatsCard title='Total Messages' stat={`${stats.totalMessages ?? 0}`} />
|
||||
<StatsCard title='Total Feedback Received' stat={`${stats.totalFeedback ?? 0}`} />
|
||||
<StatsCard
|
||||
title='Positive Feedback'
|
||||
stat={`${((stats.positiveFeedback / stats.totalFeedback) * 100 || 0).toFixed(2)}%`}
|
||||
stat={`${(((stats.positiveFeedback ?? 0) / (stats.totalFeedback ?? 1)) * 100 || 0).toFixed(2)}%`}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
{chatlogs && chatlogs.length == 0 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'row', overflow: 'hidden', minWidth: 0 }}>
|
||||
{chatlogs && chatlogs.length === 0 && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center', width: '100%' }} flexDirection='column'>
|
||||
<Box sx={{ p: 5, height: 'auto' }}>
|
||||
<img
|
||||
@@ -966,7 +1061,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
</Stack>
|
||||
)}
|
||||
{chatlogs && chatlogs.length > 0 && (
|
||||
<div style={{ flexBasis: '40%' }}>
|
||||
<div style={{ flexBasis: '40%', minWidth: 0, overflow: 'hidden' }}>
|
||||
<Box
|
||||
sx={{
|
||||
overflowY: 'auto',
|
||||
@@ -976,6 +1071,28 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
maxHeight: 'calc(100vh - 260px)'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
marginLeft: '15px',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<Typography variant='h5'>
|
||||
Sessions {pageLimit * (currentPage - 1) + 1} - {Math.min(pageLimit * currentPage, total)} of{' '}
|
||||
{total}
|
||||
</Typography>
|
||||
<Pagination
|
||||
style={{ justifyItems: 'right', justifyContent: 'center' }}
|
||||
count={Math.ceil(total / pageLimit)}
|
||||
onChange={onChange}
|
||||
page={currentPage}
|
||||
color='primary'
|
||||
/>
|
||||
</div>
|
||||
{chatlogs.map((chatmsg, index) => (
|
||||
<ListItemButton
|
||||
key={index}
|
||||
@@ -1018,9 +1135,9 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
</div>
|
||||
)}
|
||||
{chatlogs && chatlogs.length > 0 && (
|
||||
<div style={{ flexBasis: '60%', paddingRight: '30px' }}>
|
||||
<div style={{ flexBasis: '60%', paddingRight: '30px', minWidth: 0, overflow: 'hidden' }}>
|
||||
{chatMessages && chatMessages.length > 1 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<div style={{ marginBottom: 10, display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<div style={{ flex: 1, marginLeft: '20px', marginBottom: '15px', marginTop: '10px' }}>
|
||||
{chatMessages[1].sessionId && (
|
||||
<div>
|
||||
@@ -1046,31 +1163,26 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexDirection: 'row',
|
||||
alignContent: 'center',
|
||||
alignItems: 'end'
|
||||
}}
|
||||
>
|
||||
<StyledButton
|
||||
sx={{ height: 'max-content', width: 'max-content' }}
|
||||
variant='outlined'
|
||||
color='error'
|
||||
title='Clear Message'
|
||||
onClick={() => clearChat(chatMessages[1])}
|
||||
startIcon={<IconEraser />}
|
||||
>
|
||||
Clear
|
||||
</StyledButton>
|
||||
<Tooltip title='Clear Message'>
|
||||
<IconButton color='error' onClick={() => clearChat(chatMessages[1])}>
|
||||
<IconEraser />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{chatMessages[1].sessionId && (
|
||||
<Tooltip
|
||||
title={
|
||||
'At your left 👈 you will see the Memory node that was used in this conversation. You need to have the matching Memory node with same parameters in the canvas, in order to delete the session conversations stored on the Memory node'
|
||||
'On the left 👈, you’ll see the Memory node used in this conversation. To delete the session conversations stored on that Memory node, you must have a matching Memory node with identical parameters in the canvas.'
|
||||
}
|
||||
placement='bottom'
|
||||
>
|
||||
<h5 style={{ cursor: 'pointer', color: theme.palette.primary.main }}>
|
||||
Why my session is not deleted?
|
||||
</h5>
|
||||
<IconButton color='primary'>
|
||||
<IconBulb />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
@@ -1081,12 +1193,15 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
marginLeft: '20px',
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: `${customization.borderRadius}px`
|
||||
marginBottom: '5px',
|
||||
border: customization.isDarkMode ? 'none' : '1px solid #e0e0e0',
|
||||
boxShadow: customization.isDarkMode ? '0 0 5px 0 rgba(255, 255, 255, 0.5)' : 'none',
|
||||
borderRadius: `10px`,
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
className='cloud-message'
|
||||
>
|
||||
<div style={{ width: '100%', height: '100%' }}>
|
||||
<div style={{ width: '100%', height: '100%', overflowY: 'auto' }}>
|
||||
{chatMessages &&
|
||||
chatMessages.map((message, index) => {
|
||||
if (message.type === 'apiMessage' || message.type === 'userMessage') {
|
||||
@@ -1125,7 +1240,9 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%'
|
||||
width: '100%',
|
||||
minWidth: 0,
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
{message.fileUploads && message.fileUploads.length > 0 && (
|
||||
@@ -1412,7 +1529,10 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div className='markdownanswer'>
|
||||
<div
|
||||
className='markdownanswer'
|
||||
style={{ wordBreak: 'break-word', overflowWrap: 'break-word' }}
|
||||
>
|
||||
<MemoizedReactMarkdown chatflowid={dialogProps.chatflow.id}>
|
||||
{message.message}
|
||||
</MemoizedReactMarkdown>
|
||||
@@ -1486,7 +1606,9 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
background: theme.palette.timeMessage.main,
|
||||
background: customization.isDarkMode
|
||||
? theme.palette.divider
|
||||
: theme.palette.timeMessage.main,
|
||||
p: 2
|
||||
}}
|
||||
key={index}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Box, FormControl, MenuItem, Pagination, Select, Typography } from '@mui/material'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import { useSelector } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export const DEFAULT_ITEMS_PER_PAGE = 12
|
||||
|
||||
const TablePagination = ({ currentPage, limit, total, onChange }) => {
|
||||
const theme = useTheme()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
const borderColor = theme.palette.grey[900] + 25
|
||||
|
||||
const [itemsPerPage, setItemsPerPage] = useState(DEFAULT_ITEMS_PER_PAGE)
|
||||
const [activePage, setActivePage] = useState(1)
|
||||
const [totalItems, setTotalItems] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
setTotalItems(total)
|
||||
}, [total])
|
||||
|
||||
useEffect(() => {
|
||||
setItemsPerPage(limit)
|
||||
}, [limit])
|
||||
|
||||
useEffect(() => {
|
||||
setActivePage(currentPage)
|
||||
}, [currentPage])
|
||||
|
||||
const handlePageChange = (event, value) => {
|
||||
setActivePage(value)
|
||||
onChange(value, itemsPerPage)
|
||||
}
|
||||
|
||||
const handleLimitChange = (event) => {
|
||||
const itemsPerPage = parseInt(event.target.value, 10)
|
||||
setItemsPerPage(itemsPerPage)
|
||||
setActivePage(1)
|
||||
onChange(1, itemsPerPage)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mt: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Typography variant='body2'>Items per page:</Typography>
|
||||
<FormControl
|
||||
variant='outlined'
|
||||
size='small'
|
||||
sx={{
|
||||
minWidth: 80,
|
||||
'& .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: borderColor
|
||||
},
|
||||
'& .MuiSvgIcon-root': {
|
||||
color: customization.isDarkMode ? '#fff' : 'inherit'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Select value={itemsPerPage} onChange={handleLimitChange} displayEmpty>
|
||||
<MenuItem value={12}>12</MenuItem>
|
||||
<MenuItem value={24}>24</MenuItem>
|
||||
<MenuItem value={48}>48</MenuItem>
|
||||
<MenuItem value={100}>100</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
{totalItems > 0 && (
|
||||
<Typography variant='body2'>
|
||||
Items {activePage * itemsPerPage - itemsPerPage + 1} to{' '}
|
||||
{activePage * itemsPerPage > totalItems ? totalItems : activePage * itemsPerPage} of {totalItems}
|
||||
</Typography>
|
||||
)}
|
||||
<Pagination count={Math.ceil(totalItems / itemsPerPage)} onChange={handlePageChange} page={activePage} color='primary' />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
TablePagination.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
currentPage: PropTypes.number,
|
||||
limit: PropTypes.number,
|
||||
total: PropTypes.number
|
||||
}
|
||||
|
||||
export default TablePagination
|
||||
@@ -0,0 +1,255 @@
|
||||
import { useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { styled } from '@mui/material/styles'
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableSortLabel,
|
||||
useTheme,
|
||||
Typography
|
||||
} from '@mui/material'
|
||||
import { tableCellClasses } from '@mui/material/TableCell'
|
||||
import DocumentStoreStatus from '@/views/docstore/DocumentStoreStatus'
|
||||
|
||||
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
||||
borderColor: theme.palette.grey[900] + 25,
|
||||
|
||||
[`&.${tableCellClasses.head}`]: {
|
||||
color: theme.palette.grey[900]
|
||||
},
|
||||
[`&.${tableCellClasses.body}`]: {
|
||||
fontSize: 14,
|
||||
height: 64
|
||||
}
|
||||
}))
|
||||
|
||||
const StyledTableRow = styled(TableRow)(() => ({
|
||||
// hide last border
|
||||
'&:last-child td, &:last-child th': {
|
||||
border: 0
|
||||
}
|
||||
}))
|
||||
|
||||
export const DocumentStoreTable = ({ data, isLoading, onRowClick, images }) => {
|
||||
const theme = useTheme()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
|
||||
const localStorageKeyOrder = 'doc_store_order'
|
||||
const localStorageKeyOrderBy = 'doc_store_orderBy'
|
||||
|
||||
const [order, setOrder] = useState(localStorage.getItem(localStorageKeyOrder) || 'desc')
|
||||
const [orderBy, setOrderBy] = useState(localStorage.getItem(localStorageKeyOrderBy) || 'name')
|
||||
|
||||
const handleRequestSort = (property) => {
|
||||
const isAsc = orderBy === property && order === 'asc'
|
||||
const newOrder = isAsc ? 'desc' : 'asc'
|
||||
setOrder(newOrder)
|
||||
setOrderBy(property)
|
||||
localStorage.setItem(localStorageKeyOrder, newOrder)
|
||||
localStorage.setItem(localStorageKeyOrderBy, property)
|
||||
}
|
||||
|
||||
const sortedData = data
|
||||
? [...data].sort((a, b) => {
|
||||
if (orderBy === 'name') {
|
||||
return order === 'asc' ? (a.name || '').localeCompare(b.name || '') : (b.name || '').localeCompare(a.name || '')
|
||||
}
|
||||
return 0
|
||||
})
|
||||
: []
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableContainer sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }} component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} size='small' aria-label='document_store_table'>
|
||||
<TableHead
|
||||
sx={{
|
||||
backgroundColor: customization.isDarkMode ? theme.palette.common.black : theme.palette.grey[100],
|
||||
height: 56
|
||||
}}
|
||||
>
|
||||
<TableRow>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<TableSortLabel active={orderBy === 'name'} direction={order} onClick={() => handleRequestSort('name')}>
|
||||
Name
|
||||
</TableSortLabel>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>Description</StyledTableCell>
|
||||
<StyledTableCell>Connected flows</StyledTableCell>
|
||||
<StyledTableCell>Total characters</StyledTableCell>
|
||||
<StyledTableCell>Total chunks</StyledTableCell>
|
||||
<StyledTableCell>Loader Types</StyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</StyledTableRow>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{sortedData.map((row, index) => {
|
||||
return (
|
||||
<StyledTableRow
|
||||
onClick={() => onRowClick(row)}
|
||||
hover
|
||||
key={index}
|
||||
sx={{ cursor: 'pointer', '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
>
|
||||
<StyledTableCell>
|
||||
<DocumentStoreStatus isTableView={true} status={row.status} />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Typography
|
||||
sx={{
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 5,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
{row.name}
|
||||
</Typography>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Typography
|
||||
sx={{
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 5,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
{row?.description}
|
||||
</Typography>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>{row.whereUsed?.length ?? 0}</StyledTableCell>
|
||||
<StyledTableCell>{row.totalChars}</StyledTableCell>
|
||||
<StyledTableCell>{row.totalChunks}</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
{images && images[row.id] && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'start',
|
||||
gap: 1
|
||||
}}
|
||||
>
|
||||
{images[row.id]
|
||||
.slice(0, images[row.id].length > 3 ? 3 : images[row.id].length)
|
||||
.map((img) => (
|
||||
<Box
|
||||
key={img}
|
||||
sx={{
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: customization.isDarkMode
|
||||
? theme.palette.common.white
|
||||
: theme.palette.grey[300] + 75
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 5,
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
alt=''
|
||||
src={img}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
{images?.length > 3 && (
|
||||
<Typography
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
fontSize: '.9rem',
|
||||
fontWeight: 200
|
||||
}}
|
||||
>
|
||||
+ {images.length - 3} More
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</StyledTableCell>
|
||||
</StyledTableRow>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
DocumentStoreTable.propTypes = {
|
||||
data: PropTypes.array,
|
||||
isLoading: PropTypes.bool,
|
||||
images: PropTypes.object,
|
||||
onRowClick: PropTypes.func
|
||||
}
|
||||
|
||||
DocumentStoreTable.displayName = 'DocumentStoreTable'
|
||||
@@ -89,7 +89,7 @@ const sanitizeDocumentStore = (DocumentStore) => {
|
||||
const sanitizeExecution = (Execution) => {
|
||||
try {
|
||||
return Execution.map((execution) => {
|
||||
execution.agentflow.workspaceId = undefined
|
||||
if (execution.agentflow) execution.agentflow.workspaceId = undefined
|
||||
return { ...execution, workspaceId: undefined }
|
||||
})
|
||||
} catch (error) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'react-datepicker/dist/react-datepicker.css'
|
||||
|
||||
// material-ui
|
||||
import {
|
||||
Pagination,
|
||||
Box,
|
||||
Stack,
|
||||
TextField,
|
||||
@@ -21,7 +20,6 @@ import {
|
||||
DialogTitle,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme
|
||||
} from '@mui/material'
|
||||
|
||||
@@ -44,6 +42,7 @@ import { IconTrash } from '@tabler/icons-react'
|
||||
import { ExecutionsListTable } from '@/ui-component/table/ExecutionsListTable'
|
||||
import { ExecutionDetails } from './ExecutionDetails'
|
||||
import { omit } from 'lodash'
|
||||
import TablePagination, { DEFAULT_ITEMS_PER_PAGE } from '@/ui-component/pagination/TablePagination'
|
||||
|
||||
// ==============================|| AGENT EXECUTIONS ||============================== //
|
||||
|
||||
@@ -71,11 +70,6 @@ const AgentExecutions = () => {
|
||||
agentflowId: '',
|
||||
sessionId: ''
|
||||
})
|
||||
const [pagination, setPagination] = useState({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const handleFilterChange = (field, value) => {
|
||||
setFilters({
|
||||
@@ -94,26 +88,25 @@ const AgentExecutions = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const handlePageChange = (event, newPage) => {
|
||||
setPagination({
|
||||
...pagination,
|
||||
page: newPage
|
||||
})
|
||||
/* Table Pagination */
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
|
||||
const [total, setTotal] = useState(0)
|
||||
const onChange = (page, pageLimit) => {
|
||||
setCurrentPage(page)
|
||||
setPageLimit(pageLimit)
|
||||
applyFilters(page, pageLimit)
|
||||
}
|
||||
|
||||
const handleLimitChange = (event) => {
|
||||
setPagination({
|
||||
...pagination,
|
||||
page: 1, // Reset to first page when changing items per page
|
||||
limit: parseInt(event.target.value, 10)
|
||||
})
|
||||
}
|
||||
|
||||
const applyFilters = () => {
|
||||
const applyFilters = (page, limit) => {
|
||||
setLoading(true)
|
||||
// Ensure page and limit are numbers, not objects
|
||||
const pageNum = typeof page === 'number' ? page : currentPage
|
||||
const limitNum = typeof limit === 'number' ? limit : pageLimit
|
||||
|
||||
const params = {
|
||||
page: pagination.page,
|
||||
limit: pagination.limit
|
||||
page: pageNum,
|
||||
limit: limitNum
|
||||
}
|
||||
|
||||
if (filters.state) params.state = filters.state
|
||||
@@ -152,7 +145,8 @@ const AgentExecutions = () => {
|
||||
agentflowId: '',
|
||||
sessionId: ''
|
||||
})
|
||||
getAllExecutions.request()
|
||||
setCurrentPage(1)
|
||||
getAllExecutions.request({ page: 1, limit: pageLimit })
|
||||
}
|
||||
|
||||
const handleExecutionSelectionChange = (selectedIds) => {
|
||||
@@ -175,7 +169,7 @@ const AgentExecutions = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllExecutions.request()
|
||||
getAllExecutions.request({ page: 1, limit: DEFAULT_ITEMS_PER_PAGE })
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
@@ -186,7 +180,7 @@ const AgentExecutions = () => {
|
||||
const { data, total } = getAllExecutions.data
|
||||
if (!Array.isArray(data)) return
|
||||
setExecutions(data)
|
||||
setPagination((prev) => ({ ...prev, total }))
|
||||
setTotal(total)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@@ -201,17 +195,12 @@ const AgentExecutions = () => {
|
||||
setError(getAllExecutions.error)
|
||||
}, [getAllExecutions.error])
|
||||
|
||||
useEffect(() => {
|
||||
applyFilters()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pagination.page, pagination.limit])
|
||||
|
||||
useEffect(() => {
|
||||
if (deleteExecutionsApi.data) {
|
||||
// Refresh the executions list
|
||||
getAllExecutions.request({
|
||||
page: pagination.page,
|
||||
limit: pagination.limit
|
||||
page: currentPage,
|
||||
limit: pageLimit
|
||||
})
|
||||
setSelectedExecutionIds([])
|
||||
}
|
||||
@@ -339,7 +328,12 @@ const AgentExecutions = () => {
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
<Stack direction='row' spacing={1}>
|
||||
<Button variant='contained' color='primary' onClick={applyFilters} size='small'>
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
onClick={() => applyFilters(currentPage, pageLimit)}
|
||||
size='small'
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
<Button variant='outlined' onClick={resetFilters} size='small'>
|
||||
@@ -366,69 +360,47 @@ const AgentExecutions = () => {
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
<ExecutionsListTable
|
||||
data={executions}
|
||||
isLoading={isLoading}
|
||||
onSelectionChange={handleExecutionSelectionChange}
|
||||
onExecutionRowClick={(execution) => {
|
||||
setOpenDrawer(true)
|
||||
const executionDetails =
|
||||
typeof execution.executionData === 'string' ? JSON.parse(execution.executionData) : execution.executionData
|
||||
setSelectedExecutionData(executionDetails)
|
||||
setSelectedMetadata(omit(execution, ['executionData']))
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Pagination and Page Size Controls */}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mt: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Typography variant='body2'>Items per page:</Typography>
|
||||
<FormControl
|
||||
variant='outlined'
|
||||
size='small'
|
||||
sx={{
|
||||
minWidth: 80,
|
||||
'& .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: borderColor
|
||||
},
|
||||
'& .MuiSvgIcon-root': {
|
||||
color: customization.isDarkMode ? '#fff' : 'inherit'
|
||||
}
|
||||
{executions?.length > 0 && (
|
||||
<>
|
||||
<ExecutionsListTable
|
||||
data={executions}
|
||||
isLoading={isLoading}
|
||||
onSelectionChange={handleExecutionSelectionChange}
|
||||
onExecutionRowClick={(execution) => {
|
||||
setOpenDrawer(true)
|
||||
const executionDetails =
|
||||
typeof execution.executionData === 'string'
|
||||
? JSON.parse(execution.executionData)
|
||||
: execution.executionData
|
||||
setSelectedExecutionData(executionDetails)
|
||||
setSelectedMetadata(omit(execution, ['executionData']))
|
||||
}}
|
||||
>
|
||||
<Select value={pagination.limit} onChange={handleLimitChange} displayEmpty>
|
||||
<MenuItem value={10}>10</MenuItem>
|
||||
<MenuItem value={50}>50</MenuItem>
|
||||
<MenuItem value={100}>100</MenuItem>
|
||||
<MenuItem value={1000}>1000</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Pagination
|
||||
count={Math.ceil(pagination.total / pagination.limit)}
|
||||
page={pagination.page}
|
||||
onChange={handlePageChange}
|
||||
color='primary'
|
||||
/>
|
||||
</Box>
|
||||
/>
|
||||
|
||||
<ExecutionDetails
|
||||
open={openDrawer}
|
||||
execution={selectedExecutionData}
|
||||
metadata={selectedMetadata}
|
||||
onClose={() => setOpenDrawer(false)}
|
||||
onProceedSuccess={() => {
|
||||
setOpenDrawer(false)
|
||||
getAllExecutions.request()
|
||||
}}
|
||||
onUpdateSharing={() => {
|
||||
getAllExecutions.request()
|
||||
}}
|
||||
onRefresh={(executionId) => {
|
||||
getAllExecutions.request()
|
||||
getExecutionByIdApi.request(executionId)
|
||||
}}
|
||||
/>
|
||||
{/* Pagination and Page Size Controls */}
|
||||
{!isLoading && total > 0 && (
|
||||
<TablePagination currentPage={currentPage} limit={pageLimit} total={total} onChange={onChange} />
|
||||
)}
|
||||
|
||||
<ExecutionDetails
|
||||
open={openDrawer}
|
||||
execution={selectedExecutionData}
|
||||
metadata={selectedMetadata}
|
||||
onClose={() => setOpenDrawer(false)}
|
||||
onProceedSuccess={() => {
|
||||
setOpenDrawer(false)
|
||||
getAllExecutions.request()
|
||||
}}
|
||||
onUpdateSharing={() => {
|
||||
getAllExecutions.request()
|
||||
}}
|
||||
onRefresh={(executionId) => {
|
||||
getAllExecutions.request()
|
||||
getExecutionByIdApi.request(executionId)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Dialog
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
// material-ui
|
||||
import { Chip, Box, Skeleton, Stack, ToggleButton, ToggleButtonGroup } from '@mui/material'
|
||||
import { Chip, Box, Stack, ToggleButton, ToggleButtonGroup } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
|
||||
// project imports
|
||||
@@ -15,6 +15,7 @@ import { FlowListTable } from '@/ui-component/table/FlowListTable'
|
||||
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||
import ErrorBoundary from '@/ErrorBoundary'
|
||||
import { StyledPermissionButton } from '@/ui-component/button/RBACButtons'
|
||||
import TablePagination, { DEFAULT_ITEMS_PER_PAGE } from '@/ui-component/pagination/TablePagination'
|
||||
|
||||
// API
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
@@ -45,6 +46,25 @@ const Agentflows = () => {
|
||||
const [view, setView] = useState(localStorage.getItem('flowDisplayStyle') || 'card')
|
||||
const [agentflowVersion, setAgentflowVersion] = useState(localStorage.getItem('agentFlowVersion') || 'v2')
|
||||
|
||||
/* Table Pagination */
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
|
||||
const [total, setTotal] = useState(0)
|
||||
|
||||
const onChange = (page, pageLimit) => {
|
||||
setCurrentPage(page)
|
||||
setPageLimit(pageLimit)
|
||||
refresh(page, pageLimit, agentflowVersion)
|
||||
}
|
||||
|
||||
const refresh = (page, limit, nextView) => {
|
||||
const params = {
|
||||
page: page || currentPage,
|
||||
limit: limit || pageLimit
|
||||
}
|
||||
getAllAgentflows.request(nextView === 'v2' ? 'AGENTFLOW' : 'MULTIAGENT', params)
|
||||
}
|
||||
|
||||
const handleChange = (event, nextView) => {
|
||||
if (nextView === null) return
|
||||
localStorage.setItem('flowDisplayStyle', nextView)
|
||||
@@ -55,7 +75,7 @@ const Agentflows = () => {
|
||||
if (nextView === null) return
|
||||
localStorage.setItem('agentFlowVersion', nextView)
|
||||
setAgentflowVersion(nextView)
|
||||
getAllAgentflows.request(nextView === 'v2' ? 'AGENTFLOW' : 'MULTIAGENT')
|
||||
refresh(1, pageLimit, nextView)
|
||||
}
|
||||
|
||||
const onSearchChange = (event) => {
|
||||
@@ -87,7 +107,7 @@ const Agentflows = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllAgentflows.request(agentflowVersion === 'v2' ? 'AGENTFLOW' : 'MULTIAGENT')
|
||||
refresh(currentPage, pageLimit, agentflowVersion)
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
@@ -107,7 +127,8 @@ const Agentflows = () => {
|
||||
useEffect(() => {
|
||||
if (getAllAgentflows.data) {
|
||||
try {
|
||||
const agentflows = getAllAgentflows.data
|
||||
const agentflows = getAllAgentflows.data?.data
|
||||
setTotal(getAllAgentflows.data?.total)
|
||||
const images = {}
|
||||
const icons = {}
|
||||
for (let i = 0; i < agentflows.length; i += 1) {
|
||||
@@ -189,6 +210,7 @@ const Agentflows = () => {
|
||||
<ToggleButtonGroup
|
||||
sx={{ borderRadius: 2, maxHeight: 40 }}
|
||||
value={view}
|
||||
disabled={total === 0}
|
||||
color='primary'
|
||||
exclusive
|
||||
onChange={handleChange}
|
||||
@@ -228,17 +250,11 @@ const Agentflows = () => {
|
||||
Add New
|
||||
</StyledPermissionButton>
|
||||
</ViewHeader>
|
||||
{!view || view === 'card' ? (
|
||||
{!isLoading && total > 0 && (
|
||||
<>
|
||||
{isLoading && !getAllAgentflows.data ? (
|
||||
{!view || view === 'card' ? (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
</Box>
|
||||
) : (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
{getAllAgentflows.data?.filter(filterFlows).map((data, index) => (
|
||||
{getAllAgentflows.data?.data.filter(filterFlows).map((data, index) => (
|
||||
<ItemCard
|
||||
key={index}
|
||||
onClick={() => goToCanvas(data)}
|
||||
@@ -248,22 +264,25 @@ const Agentflows = () => {
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
) : (
|
||||
<FlowListTable
|
||||
isAgentCanvas={true}
|
||||
isAgentflowV2={agentflowVersion === 'v2'}
|
||||
data={getAllAgentflows.data?.data}
|
||||
images={images}
|
||||
icons={icons}
|
||||
isLoading={isLoading}
|
||||
filterFunction={filterFlows}
|
||||
updateFlowsApi={getAllAgentflows}
|
||||
setError={setError}
|
||||
/>
|
||||
)}
|
||||
{/* Pagination and Page Size Controls */}
|
||||
<TablePagination currentPage={currentPage} limit={pageLimit} total={total} onChange={onChange} />
|
||||
</>
|
||||
) : (
|
||||
<FlowListTable
|
||||
isAgentCanvas={true}
|
||||
isAgentflowV2={agentflowVersion === 'v2'}
|
||||
data={getAllAgentflows.data}
|
||||
images={images}
|
||||
icons={icons}
|
||||
isLoading={isLoading}
|
||||
filterFunction={filterFlows}
|
||||
updateFlowsApi={getAllAgentflows}
|
||||
setError={setError}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && (!getAllAgentflows.data || getAllAgentflows.data.length === 0) && (
|
||||
|
||||
{!isLoading && total === 0 && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
|
||||
@@ -33,6 +33,8 @@ import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||
import ErrorBoundary from '@/ErrorBoundary'
|
||||
import { PermissionButton, StyledPermissionButton } from '@/ui-component/button/RBACButtons'
|
||||
import { Available } from '@/ui-component/rbac/available'
|
||||
import UploadJSONFileDialog from '@/views/apikey/UploadJSONFileDialog'
|
||||
import TablePagination, { DEFAULT_ITEMS_PER_PAGE } from '@/ui-component/pagination/TablePagination'
|
||||
|
||||
// API
|
||||
import apiKeyApi from '@/api/apikey'
|
||||
@@ -59,7 +61,6 @@ import {
|
||||
IconFileUpload
|
||||
} from '@tabler/icons-react'
|
||||
import APIEmptySVG from '@/assets/images/api_empty.svg'
|
||||
import UploadJSONFileDialog from '@/views/apikey/UploadJSONFileDialog'
|
||||
|
||||
// ==============================|| APIKey ||============================== //
|
||||
|
||||
@@ -222,6 +223,26 @@ const APIKey = () => {
|
||||
const [uploadDialogProps, setUploadDialogProps] = useState({})
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
/* Table Pagination */
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
|
||||
const [total, setTotal] = useState(0)
|
||||
|
||||
const onChange = (page, pageLimit) => {
|
||||
setCurrentPage(page)
|
||||
setPageLimit(pageLimit)
|
||||
refresh(page, pageLimit)
|
||||
}
|
||||
|
||||
const refresh = (page, limit) => {
|
||||
const params = {
|
||||
page: page || currentPage,
|
||||
limit: limit || pageLimit
|
||||
}
|
||||
getAllAPIKeysApi.request(params)
|
||||
}
|
||||
|
||||
const onSearchChange = (event) => {
|
||||
setSearch(event.target.value)
|
||||
}
|
||||
@@ -341,12 +362,11 @@ const APIKey = () => {
|
||||
const onConfirm = () => {
|
||||
setShowDialog(false)
|
||||
setShowUploadDialog(false)
|
||||
getAllAPIKeysApi.request()
|
||||
refresh(currentPage, pageLimit)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllAPIKeysApi.request()
|
||||
|
||||
refresh(currentPage, pageLimit)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
@@ -356,7 +376,8 @@ const APIKey = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllAPIKeysApi.data) {
|
||||
setAPIKeys(getAllAPIKeysApi.data)
|
||||
setAPIKeys(getAllAPIKeysApi.data?.data)
|
||||
setTotal(getAllAPIKeysApi.data?.total)
|
||||
}
|
||||
}, [getAllAPIKeysApi.data])
|
||||
|
||||
@@ -395,7 +416,7 @@ const APIKey = () => {
|
||||
Create Key
|
||||
</StyledPermissionButton>
|
||||
</ViewHeader>
|
||||
{!isLoading && apiKeys.length <= 0 ? (
|
||||
{!isLoading && apiKeys?.length <= 0 ? (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
@@ -407,104 +428,108 @@ const APIKey = () => {
|
||||
<div>No API Keys Yet</div>
|
||||
</Stack>
|
||||
) : (
|
||||
<TableContainer
|
||||
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
|
||||
component={Paper}
|
||||
>
|
||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||
<TableHead
|
||||
sx={{
|
||||
backgroundColor: customization.isDarkMode
|
||||
? theme.palette.common.black
|
||||
: theme.palette.grey[100],
|
||||
height: 56
|
||||
}}
|
||||
>
|
||||
<TableRow>
|
||||
<StyledTableCell>Key Name</StyledTableCell>
|
||||
<StyledTableCell>API Key</StyledTableCell>
|
||||
<StyledTableCell>Usage</StyledTableCell>
|
||||
<StyledTableCell>Updated</StyledTableCell>
|
||||
<Available permission={'apikeys:update,apikeys:create'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
<Available permission={'apikeys:delete'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<Available permission={'apikeys:update,apikeys:create'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
<Available permission={'apikeys:delete'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<Available permission={'apikeys:update,apikeys:create'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
<Available permission={'apikeys:delete'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{apiKeys.filter(filterKeys).map((key, index) => (
|
||||
<APIKeyRow
|
||||
key={index}
|
||||
apiKey={key}
|
||||
showApiKeys={showApiKeys}
|
||||
onCopyClick={(event) => {
|
||||
navigator.clipboard.writeText(key.apiKey)
|
||||
setAnchorEl(event.currentTarget)
|
||||
setTimeout(() => {
|
||||
handleClosePopOver()
|
||||
}, 1500)
|
||||
}}
|
||||
onShowAPIClick={() => onShowApiKeyClick(key.apiKey)}
|
||||
open={openPopOver}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClosePopOver}
|
||||
theme={theme}
|
||||
onEditClick={() => edit(key)}
|
||||
onDeleteClick={() => deleteKey(key)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<>
|
||||
<TableContainer
|
||||
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
|
||||
component={Paper}
|
||||
>
|
||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||
<TableHead
|
||||
sx={{
|
||||
backgroundColor: customization.isDarkMode
|
||||
? theme.palette.common.black
|
||||
: theme.palette.grey[100],
|
||||
height: 56
|
||||
}}
|
||||
>
|
||||
<TableRow>
|
||||
<StyledTableCell>Key Name</StyledTableCell>
|
||||
<StyledTableCell>API Key</StyledTableCell>
|
||||
<StyledTableCell>Usage</StyledTableCell>
|
||||
<StyledTableCell>Updated</StyledTableCell>
|
||||
<Available permission={'apikeys:update,apikeys:create'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
<Available permission={'apikeys:delete'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<Available permission={'apikeys:update,apikeys:create'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
<Available permission={'apikeys:delete'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<Available permission={'apikeys:update,apikeys:create'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
<Available permission={'apikeys:delete'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{apiKeys?.filter(filterKeys).map((key, index) => (
|
||||
<APIKeyRow
|
||||
key={index}
|
||||
apiKey={key}
|
||||
showApiKeys={showApiKeys}
|
||||
onCopyClick={(event) => {
|
||||
navigator.clipboard.writeText(key.apiKey)
|
||||
setAnchorEl(event.currentTarget)
|
||||
setTimeout(() => {
|
||||
handleClosePopOver()
|
||||
}, 1500)
|
||||
}}
|
||||
onShowAPIClick={() => onShowApiKeyClick(key.apiKey)}
|
||||
open={openPopOver}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClosePopOver}
|
||||
theme={theme}
|
||||
onEditClick={() => edit(key)}
|
||||
onDeleteClick={() => deleteKey(key)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{/* Pagination and Page Size Controls */}
|
||||
<TablePagination currentPage={currentPage} limit={pageLimit} total={total} onChange={onChange} />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -510,7 +510,8 @@ const CustomAssistantConfigurePreview = () => {
|
||||
} else if (setting === 'viewMessages') {
|
||||
setViewMessagesDialogProps({
|
||||
title: 'View Messages',
|
||||
chatflow: canvas.chatflow
|
||||
chatflow: canvas.chatflow,
|
||||
isChatflow: false
|
||||
})
|
||||
setViewMessagesDialogOpen(true)
|
||||
} else if (setting === 'viewLeads') {
|
||||
|
||||
@@ -76,7 +76,8 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, isAgentflowV2, handleSaveFlow,
|
||||
} else if (setting === 'viewMessages') {
|
||||
setViewMessagesDialogProps({
|
||||
title: 'View Messages',
|
||||
chatflow: chatflow
|
||||
chatflow: chatflow,
|
||||
isChatflow: isAgentflowV2 ? false : true
|
||||
})
|
||||
setViewMessagesDialogOpen(true)
|
||||
} else if (setting === 'viewLeads') {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { FlowListTable } from '@/ui-component/table/FlowListTable'
|
||||
import { StyledPermissionButton } from '@/ui-component/button/RBACButtons'
|
||||
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||
import ErrorBoundary from '@/ErrorBoundary'
|
||||
import TablePagination, { DEFAULT_ITEMS_PER_PAGE } from '@/ui-component/pagination/TablePagination'
|
||||
|
||||
// API
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
@@ -43,6 +44,25 @@ const Chatflows = () => {
|
||||
const getAllChatflowsApi = useApi(chatflowsApi.getAllChatflows)
|
||||
const [view, setView] = useState(localStorage.getItem('flowDisplayStyle') || 'card')
|
||||
|
||||
/* Table Pagination */
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
|
||||
const [total, setTotal] = useState(0)
|
||||
|
||||
const onChange = (page, pageLimit) => {
|
||||
setCurrentPage(page)
|
||||
setPageLimit(pageLimit)
|
||||
applyFilters(page, pageLimit)
|
||||
}
|
||||
|
||||
const applyFilters = (page, limit) => {
|
||||
const params = {
|
||||
page: page || currentPage,
|
||||
limit: limit || pageLimit
|
||||
}
|
||||
getAllChatflowsApi.request(params)
|
||||
}
|
||||
|
||||
const handleChange = (event, nextView) => {
|
||||
if (nextView === null) return
|
||||
localStorage.setItem('flowDisplayStyle', nextView)
|
||||
@@ -70,7 +90,7 @@ const Chatflows = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllChatflowsApi.request()
|
||||
applyFilters(currentPage, pageLimit)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
@@ -81,7 +101,9 @@ const Chatflows = () => {
|
||||
useEffect(() => {
|
||||
if (getAllChatflowsApi.data) {
|
||||
try {
|
||||
const chatflows = getAllChatflowsApi.data
|
||||
const chatflows = getAllChatflowsApi.data?.data
|
||||
const total = getAllChatflowsApi.data?.total
|
||||
setTotal(total)
|
||||
const images = {}
|
||||
for (let i = 0; i < chatflows.length; i += 1) {
|
||||
const flowDataStr = chatflows[i].flowData
|
||||
@@ -123,6 +145,7 @@ const Chatflows = () => {
|
||||
sx={{ borderRadius: 2, maxHeight: 40 }}
|
||||
value={view}
|
||||
color='primary'
|
||||
disabled={total === 0}
|
||||
exclusive
|
||||
onChange={handleChange}
|
||||
>
|
||||
@@ -161,41 +184,37 @@ const Chatflows = () => {
|
||||
Add New
|
||||
</StyledPermissionButton>
|
||||
</ViewHeader>
|
||||
{!view || view === 'card' ? (
|
||||
|
||||
{isLoading && (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
</Box>
|
||||
)}
|
||||
{!isLoading && total > 0 && (
|
||||
<>
|
||||
{isLoading && !getAllChatflowsApi.data ? (
|
||||
{!view || view === 'card' ? (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
{getAllChatflowsApi.data?.data?.filter(filterFlows).map((data, index) => (
|
||||
<ItemCard key={index} onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
|
||||
))}
|
||||
</Box>
|
||||
) : (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
{getAllChatflowsApi.data &&
|
||||
getAllChatflowsApi.data
|
||||
?.filter(filterFlows)
|
||||
.map((data, index) => (
|
||||
<ItemCard
|
||||
key={index}
|
||||
onClick={() => goToCanvas(data)}
|
||||
data={data}
|
||||
images={images[data.id]}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<FlowListTable
|
||||
data={getAllChatflowsApi.data?.data}
|
||||
images={images}
|
||||
isLoading={isLoading}
|
||||
filterFunction={filterFlows}
|
||||
updateFlowsApi={getAllChatflowsApi}
|
||||
setError={setError}
|
||||
/>
|
||||
)}
|
||||
{/* Pagination and Page Size Controls */}
|
||||
<TablePagination currentPage={currentPage} limit={pageLimit} total={total} onChange={onChange} />
|
||||
</>
|
||||
) : (
|
||||
<FlowListTable
|
||||
data={getAllChatflowsApi.data}
|
||||
images={images}
|
||||
isLoading={isLoading}
|
||||
filterFunction={filterFlows}
|
||||
updateFlowsApi={getAllChatflowsApi}
|
||||
setError={setError}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && (!getAllChatflowsApi.data || getAllChatflowsApi.data.length === 0) && (
|
||||
{!isLoading && (!getAllChatflowsApi.data?.data || getAllChatflowsApi.data?.data.length === 0) && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
|
||||
@@ -26,9 +26,11 @@ import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog'
|
||||
import AddEditDatasetRowDialog from './AddEditDatasetRowDialog'
|
||||
import UploadCSVFileDialog from '@/views/datasets/UploadCSVFileDialog'
|
||||
import ErrorBoundary from '@/ErrorBoundary'
|
||||
import { useError } from '@/store/context/ErrorContext'
|
||||
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||
import { PermissionButton, StyledPermissionButton } from '@/ui-component/button/RBACButtons'
|
||||
import AddEditDatasetDialog from '@/views/datasets/AddEditDatasetDialog'
|
||||
import TablePagination, { DEFAULT_ITEMS_PER_PAGE } from '@/ui-component/pagination/TablePagination'
|
||||
|
||||
// API
|
||||
import datasetsApi from '@/api/dataset'
|
||||
@@ -45,8 +47,6 @@ import empty_datasetSVG from '@/assets/images/empty_datasets.svg'
|
||||
import { IconTrash, IconPlus, IconX, IconUpload, IconArrowsDownUp } from '@tabler/icons-react'
|
||||
import DragIndicatorIcon from '@mui/icons-material/DragIndicator'
|
||||
|
||||
import { useError } from '@/store/context/ErrorContext'
|
||||
|
||||
// ==============================|| Dataset Items ||============================== //
|
||||
|
||||
const EvalDatasetRows = () => {
|
||||
@@ -85,6 +85,25 @@ const EvalDatasetRows = () => {
|
||||
const [startDragPos, setStartDragPos] = useState(-1)
|
||||
const [endDragPos, setEndDragPos] = useState(-1)
|
||||
|
||||
/* Table Pagination */
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
|
||||
const [total, setTotal] = useState(0)
|
||||
const onChange = (page, pageLimit) => {
|
||||
setCurrentPage(page)
|
||||
setPageLimit(pageLimit)
|
||||
refresh(page, pageLimit)
|
||||
}
|
||||
|
||||
const refresh = (page, limit) => {
|
||||
setLoading(true)
|
||||
const params = {
|
||||
page: page || currentPage,
|
||||
limit: limit || pageLimit
|
||||
}
|
||||
getDatasetRows.request(datasetId, params)
|
||||
}
|
||||
|
||||
const handleDragStart = (e, position) => {
|
||||
draggingItem.current = position
|
||||
setStartDragPos(position)
|
||||
@@ -242,11 +261,11 @@ const EvalDatasetRows = () => {
|
||||
setShowRowDialog(false)
|
||||
setShowUploadDialog(false)
|
||||
setShowDatasetDialog(false)
|
||||
getDatasetRows.request(datasetId)
|
||||
refresh(currentPage, pageLimit)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getDatasetRows.request(datasetId)
|
||||
refresh(currentPage, pageLimit)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
@@ -254,6 +273,7 @@ const EvalDatasetRows = () => {
|
||||
if (getDatasetRows.data) {
|
||||
const dataset = getDatasetRows.data
|
||||
setDataset(dataset)
|
||||
setTotal(dataset.total)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getDatasetRows.data])
|
||||
@@ -449,9 +469,11 @@ const EvalDatasetRows = () => {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Typography sx={{ color: theme.palette.grey[600] }} variant='subtitle2'>
|
||||
<Typography sx={{ color: theme.palette.grey[600], marginTop: -2 }} variant='subtitle2'>
|
||||
<i>Use the drag icon at (extreme right) to reorder the dataset items</i>
|
||||
</Typography>
|
||||
{/* Pagination and Page Size Controls */}
|
||||
<TablePagination currentPage={currentPage} limit={pageLimit} total={total} onChange={onChange} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -29,6 +29,7 @@ import ErrorBoundary from '@/ErrorBoundary'
|
||||
import { StyledTableCell, StyledTableRow } from '@/ui-component/table/TableStyles'
|
||||
import { StyledPermissionButton } from '@/ui-component/button/RBACButtons'
|
||||
import { Available } from '@/ui-component/rbac/available'
|
||||
import TablePagination, { DEFAULT_ITEMS_PER_PAGE } from '@/ui-component/pagination/TablePagination'
|
||||
|
||||
// API
|
||||
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions'
|
||||
@@ -70,8 +71,27 @@ const EvalDatasets = () => {
|
||||
const [datasetDialogProps, setDatasetDialogProps] = useState({})
|
||||
const getAllDatasets = useApi(datasetsApi.getAllDatasets)
|
||||
|
||||
/* Table Pagination */
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
|
||||
const [total, setTotal] = useState(0)
|
||||
const onChange = (page, pageLimit) => {
|
||||
setCurrentPage(page)
|
||||
setPageLimit(pageLimit)
|
||||
refresh(page, pageLimit)
|
||||
}
|
||||
|
||||
const refresh = (page, limit) => {
|
||||
setLoading(true)
|
||||
const params = {
|
||||
page: page || currentPage,
|
||||
limit: limit || pageLimit
|
||||
}
|
||||
getAllDatasets.request(params)
|
||||
}
|
||||
|
||||
const goToRows = (selectedDataset) => {
|
||||
navigate(`/dataset_rows/${selectedDataset.id}`)
|
||||
navigate(`/dataset_rows/${selectedDataset.id}?page=1&limit=10`)
|
||||
}
|
||||
|
||||
const onSearchChange = (event) => {
|
||||
@@ -149,7 +169,7 @@ const EvalDatasets = () => {
|
||||
|
||||
const onConfirm = () => {
|
||||
setShowDatasetDialog(false)
|
||||
getAllDatasets.request()
|
||||
refresh()
|
||||
}
|
||||
|
||||
function filterDatasets(data) {
|
||||
@@ -157,13 +177,14 @@ const EvalDatasets = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllDatasets.request()
|
||||
refresh(currentPage, pageLimit)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllDatasets.data) {
|
||||
setDatasets(getAllDatasets.data)
|
||||
setDatasets(getAllDatasets.data?.data)
|
||||
setTotal(getAllDatasets.data?.total)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getAllDatasets.data])
|
||||
@@ -209,118 +230,126 @@ const EvalDatasets = () => {
|
||||
<div>No Datasets Yet</div>
|
||||
</Stack>
|
||||
) : (
|
||||
<TableContainer
|
||||
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
|
||||
component={Paper}
|
||||
>
|
||||
<Table sx={{ minWidth: 650 }}>
|
||||
<TableHead
|
||||
sx={{
|
||||
backgroundColor: customization.isDarkMode
|
||||
? theme.palette.common.black
|
||||
: theme.palette.grey[100],
|
||||
height: 56
|
||||
}}
|
||||
>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
<TableCell>Rows</TableCell>
|
||||
<TableCell>Last Updated</TableCell>
|
||||
<Available permission={'datasets:update,datasets:create'}>
|
||||
<TableCell> </TableCell>
|
||||
</Available>
|
||||
<Available permission={'datasets:delete'}>
|
||||
<TableCell> </TableCell>
|
||||
</Available>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<Available permission={'datasets:update,datasets:create'}>
|
||||
<Skeleton variant='text' />
|
||||
</Available>
|
||||
<Available permission={'datasets:delete'}>
|
||||
<Skeleton variant='text' />
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<Available permission={'datasets:update,datasets:create'}>
|
||||
<Skeleton variant='text' />
|
||||
</Available>
|
||||
<Available permission={'datasets:delete'}>
|
||||
<Skeleton variant='text' />
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{datasets.filter(filterDatasets).map((ds, index) => (
|
||||
<StyledTableRow
|
||||
hover
|
||||
key={index}
|
||||
sx={{ cursor: 'pointer', '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
>
|
||||
<TableCell onClick={() => goToRows(ds)} component='th' scope='row'>
|
||||
{ds.name}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
onClick={() => goToRows(ds)}
|
||||
style={{ wordWrap: 'break-word', flexWrap: 'wrap', width: '40%' }}
|
||||
>
|
||||
{truncateString(ds?.description, 200)}
|
||||
</TableCell>
|
||||
<TableCell onClick={() => goToRows(ds)}>{ds?.rowCount}</TableCell>
|
||||
<TableCell onClick={() => goToRows(ds)}>
|
||||
{moment(ds.updatedDate).format('MMMM Do YYYY, hh:mm A')}
|
||||
</TableCell>
|
||||
<>
|
||||
<TableContainer
|
||||
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
|
||||
component={Paper}
|
||||
>
|
||||
<Table sx={{ minWidth: 650 }}>
|
||||
<TableHead
|
||||
sx={{
|
||||
backgroundColor: customization.isDarkMode
|
||||
? theme.palette.common.black
|
||||
: theme.palette.grey[100],
|
||||
height: 56
|
||||
}}
|
||||
>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
<TableCell>Rows</TableCell>
|
||||
<TableCell>Last Updated</TableCell>
|
||||
<Available permission={'datasets:update,datasets:create'}>
|
||||
<TableCell> </TableCell>
|
||||
</Available>
|
||||
<Available permission={'datasets:delete'}>
|
||||
<TableCell> </TableCell>
|
||||
</Available>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<Available permission={'datasets:update,datasets:create'}>
|
||||
<TableCell>
|
||||
<IconButton title='Edit' color='primary' onClick={() => edit(ds)}>
|
||||
<IconEdit />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<Skeleton variant='text' />
|
||||
</Available>
|
||||
<Available permission={'datasets:delete'}>
|
||||
<TableCell>
|
||||
<IconButton title='Delete' color='error' onClick={() => deleteDataset(ds)}>
|
||||
<IconTrash />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<Skeleton variant='text' />
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<Available permission={'datasets:update,datasets:create'}>
|
||||
<Skeleton variant='text' />
|
||||
</Available>
|
||||
<Available permission={'datasets:delete'}>
|
||||
<Skeleton variant='text' />
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{datasets.filter(filterDatasets).map((ds, index) => (
|
||||
<StyledTableRow
|
||||
hover
|
||||
key={index}
|
||||
sx={{ cursor: 'pointer', '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
>
|
||||
<TableCell onClick={() => goToRows(ds)} component='th' scope='row'>
|
||||
{ds.name}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
onClick={() => goToRows(ds)}
|
||||
style={{ wordWrap: 'break-word', flexWrap: 'wrap', width: '40%' }}
|
||||
>
|
||||
{truncateString(ds?.description, 200)}
|
||||
</TableCell>
|
||||
<TableCell onClick={() => goToRows(ds)}>{ds?.rowCount}</TableCell>
|
||||
<TableCell onClick={() => goToRows(ds)}>
|
||||
{moment(ds.updatedDate).format('MMMM Do YYYY, hh:mm A')}
|
||||
</TableCell>
|
||||
<Available permission={'datasets:update,datasets:create'}>
|
||||
<TableCell>
|
||||
<IconButton title='Edit' color='primary' onClick={() => edit(ds)}>
|
||||
<IconEdit />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</Available>
|
||||
<Available permission={'datasets:delete'}>
|
||||
<TableCell>
|
||||
<IconButton
|
||||
title='Delete'
|
||||
color='error'
|
||||
onClick={() => deleteDataset(ds)}
|
||||
>
|
||||
<IconTrash />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{/* Pagination and Page Size Controls */}
|
||||
<TablePagination currentPage={currentPage} limit={pageLimit} total={total} onChange={onChange} />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -1,32 +1,18 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
// material-ui
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
Typography
|
||||
} from '@mui/material'
|
||||
import { Box, Stack, ToggleButton, ToggleButtonGroup } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
|
||||
// project imports
|
||||
import ErrorBoundary from '@/ErrorBoundary'
|
||||
import { useError } from '@/store/context/ErrorContext'
|
||||
import MainCard from '@/ui-component/cards/MainCard'
|
||||
import TablePagination, { DEFAULT_ITEMS_PER_PAGE } from '@/ui-component/pagination/TablePagination'
|
||||
import DocumentStoreCard from '@/ui-component/cards/DocumentStoreCard'
|
||||
import AddDocStoreDialog from '@/views/docstore/AddDocStoreDialog'
|
||||
import ErrorBoundary from '@/ErrorBoundary'
|
||||
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||
import DocumentStoreStatus from '@/views/docstore/DocumentStoreStatus'
|
||||
import { StyledPermissionButton } from '@/ui-component/button/RBACButtons'
|
||||
|
||||
// API
|
||||
@@ -39,13 +25,12 @@ import doc_store_empty from '@/assets/images/doc_store_empty.svg'
|
||||
|
||||
// const
|
||||
import { baseURL, gridSpacing } from '@/store/constant'
|
||||
import { useError } from '@/store/context/ErrorContext'
|
||||
import { DocumentStoreTable } from '@/ui-component/table/DocumentStoreTable'
|
||||
|
||||
// ==============================|| DOCUMENTS ||============================== //
|
||||
|
||||
const Documents = () => {
|
||||
const theme = useTheme()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
|
||||
const navigate = useNavigate()
|
||||
const getAllDocumentStores = useApi(documentsApi.getAllDocumentStores)
|
||||
@@ -66,7 +51,9 @@ const Documents = () => {
|
||||
}
|
||||
|
||||
function filterDocStores(data) {
|
||||
return data.name.toLowerCase().indexOf(search.toLowerCase()) > -1
|
||||
return (
|
||||
data.name.toLowerCase().indexOf(search.toLowerCase()) > -1 || data.description.toLowerCase().indexOf(search.toLowerCase()) > -1
|
||||
)
|
||||
}
|
||||
|
||||
const onSearchChange = (event) => {
|
||||
@@ -90,41 +77,61 @@ const Documents = () => {
|
||||
|
||||
const onConfirm = () => {
|
||||
setShowDialog(false)
|
||||
getAllDocumentStores.request()
|
||||
applyFilters(currentPage, pageLimit)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllDocumentStores.request()
|
||||
applyFilters(currentPage, pageLimit)
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
/* Table Pagination */
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
|
||||
const [total, setTotal] = useState(0)
|
||||
const onChange = (page, pageLimit) => {
|
||||
setCurrentPage(page)
|
||||
setPageLimit(pageLimit)
|
||||
applyFilters(page, pageLimit)
|
||||
}
|
||||
|
||||
const applyFilters = (page, limit) => {
|
||||
setLoading(true)
|
||||
const params = {
|
||||
page: page || currentPage,
|
||||
limit: limit || pageLimit
|
||||
}
|
||||
getAllDocumentStores.request(params)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllDocumentStores.data) {
|
||||
try {
|
||||
const docStores = getAllDocumentStores.data
|
||||
if (!Array.isArray(docStores)) return
|
||||
const { data, total } = getAllDocumentStores.data
|
||||
if (!Array.isArray(data)) return
|
||||
const loaderImages = {}
|
||||
|
||||
for (let i = 0; i < docStores.length; i += 1) {
|
||||
const loaders = docStores[i].loaders ?? []
|
||||
for (let i = 0; i < data.length; i += 1) {
|
||||
const loaders = data[i].loaders ?? []
|
||||
|
||||
let totalChunks = 0
|
||||
let totalChars = 0
|
||||
loaderImages[docStores[i].id] = []
|
||||
loaderImages[data[i].id] = []
|
||||
for (let j = 0; j < loaders.length; j += 1) {
|
||||
const imageSrc = `${baseURL}/api/v1/node-icon/${loaders[j].loaderId}`
|
||||
if (!loaderImages[docStores[i].id].includes(imageSrc)) {
|
||||
loaderImages[docStores[i].id].push(imageSrc)
|
||||
if (!loaderImages[data[i].id].includes(imageSrc)) {
|
||||
loaderImages[data[i].id].push(imageSrc)
|
||||
}
|
||||
totalChunks += loaders[j]?.totalChunks ?? 0
|
||||
totalChars += loaders[j]?.totalChars ?? 0
|
||||
}
|
||||
docStores[i].totalDocs = loaders?.length ?? 0
|
||||
docStores[i].totalChunks = totalChunks
|
||||
docStores[i].totalChars = totalChars
|
||||
data[i].totalDocs = loaders?.length ?? 0
|
||||
data[i].totalChunks = totalChunks
|
||||
data[i].totalChars = totalChars
|
||||
}
|
||||
setDocStores(docStores)
|
||||
setDocStores(data)
|
||||
setTotal(total)
|
||||
setImages(loaderImages)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
@@ -136,6 +143,8 @@ const Documents = () => {
|
||||
setLoading(getAllDocumentStores.loading)
|
||||
}, [getAllDocumentStores.loading])
|
||||
|
||||
const hasDocStores = docStores && docStores.length > 0
|
||||
|
||||
return (
|
||||
<MainCard>
|
||||
{error ? (
|
||||
@@ -144,43 +153,45 @@ const Documents = () => {
|
||||
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
||||
<ViewHeader
|
||||
onSearchChange={onSearchChange}
|
||||
search={true}
|
||||
search={hasDocStores}
|
||||
searchPlaceholder='Search Name'
|
||||
title='Document Store'
|
||||
description='Store and upsert documents for LLM retrieval (RAG)'
|
||||
>
|
||||
<ToggleButtonGroup
|
||||
sx={{ borderRadius: 2, maxHeight: 40 }}
|
||||
value={view}
|
||||
color='primary'
|
||||
exclusive
|
||||
onChange={handleChange}
|
||||
>
|
||||
<ToggleButton
|
||||
sx={{
|
||||
borderColor: theme.palette.grey[900] + 25,
|
||||
borderRadius: 2,
|
||||
color: theme?.customization?.isDarkMode ? 'white' : 'inherit'
|
||||
}}
|
||||
variant='contained'
|
||||
value='card'
|
||||
title='Card View'
|
||||
{hasDocStores && (
|
||||
<ToggleButtonGroup
|
||||
sx={{ borderRadius: 2, maxHeight: 40 }}
|
||||
value={view}
|
||||
color='primary'
|
||||
exclusive
|
||||
onChange={handleChange}
|
||||
>
|
||||
<IconLayoutGrid />
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
sx={{
|
||||
borderColor: theme.palette.grey[900] + 25,
|
||||
borderRadius: 2,
|
||||
color: theme?.customization?.isDarkMode ? 'white' : 'inherit'
|
||||
}}
|
||||
variant='contained'
|
||||
value='list'
|
||||
title='List View'
|
||||
>
|
||||
<IconList />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
<ToggleButton
|
||||
sx={{
|
||||
borderColor: theme.palette.grey[900] + 25,
|
||||
borderRadius: 2,
|
||||
color: theme?.customization?.isDarkMode ? 'white' : 'inherit'
|
||||
}}
|
||||
variant='contained'
|
||||
value='card'
|
||||
title='Card View'
|
||||
>
|
||||
<IconLayoutGrid />
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
sx={{
|
||||
borderColor: theme.palette.grey[900] + 25,
|
||||
borderRadius: 2,
|
||||
color: theme?.customization?.isDarkMode ? 'white' : 'inherit'
|
||||
}}
|
||||
variant='contained'
|
||||
value='list'
|
||||
title='List View'
|
||||
>
|
||||
<IconList />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
)}
|
||||
<StyledPermissionButton
|
||||
permissionId={'documentStores:create'}
|
||||
variant='contained'
|
||||
@@ -192,142 +203,7 @@ const Documents = () => {
|
||||
Add New
|
||||
</StyledPermissionButton>
|
||||
</ViewHeader>
|
||||
{!view || view === 'card' ? (
|
||||
<>
|
||||
{isLoading && !docStores ? (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
</Box>
|
||||
) : (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
{docStores?.filter(filterDocStores).map((data, index) => (
|
||||
<DocumentStoreCard
|
||||
key={index}
|
||||
images={images[data.id]}
|
||||
data={data}
|
||||
onClick={() => goToDocumentStore(data.id)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<TableContainer sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }} component={Paper}>
|
||||
<Table aria-label='documents table'>
|
||||
<TableHead
|
||||
sx={{
|
||||
backgroundColor: customization.isDarkMode ? theme.palette.common.black : theme.palette.grey[100],
|
||||
height: 56
|
||||
}}
|
||||
>
|
||||
<TableRow>
|
||||
<TableCell> </TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
<TableCell>Connected flows</TableCell>
|
||||
<TableCell>Total characters</TableCell>
|
||||
<TableCell>Total chunks</TableCell>
|
||||
<TableCell>Loader types</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{docStores?.filter(filterDocStores).map((data, index) => (
|
||||
<TableRow
|
||||
onClick={() => goToDocumentStore(data.id)}
|
||||
hover
|
||||
key={index}
|
||||
sx={{ cursor: 'pointer', '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
>
|
||||
<TableCell align='center'>
|
||||
<DocumentStoreStatus isTableView={true} status={data.status} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
sx={{
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 5,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
{data.name}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
sx={{
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 5,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
{data?.description}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{data.whereUsed?.length ?? 0}</TableCell>
|
||||
<TableCell>{data.totalChars}</TableCell>
|
||||
<TableCell>{data.totalChunks}</TableCell>
|
||||
<TableCell>
|
||||
{images[data.id] && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'start',
|
||||
gap: 1
|
||||
}}
|
||||
>
|
||||
{images[data.id].slice(0, images.length > 3 ? 3 : images.length).map((img) => (
|
||||
<Box
|
||||
key={img}
|
||||
sx={{
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: customization.isDarkMode
|
||||
? theme.palette.common.white
|
||||
: theme.palette.grey[300] + 75
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 5,
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
alt=''
|
||||
src={img}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
{images.length > 3 && (
|
||||
<Typography
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
fontSize: '.9rem',
|
||||
fontWeight: 200
|
||||
}}
|
||||
>
|
||||
+ {images.length - 3} More
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
{!isLoading && (!docStores || docStores.length === 0) && (
|
||||
{!hasDocStores ? (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
@@ -338,6 +214,30 @@ const Documents = () => {
|
||||
</Box>
|
||||
<div>No Document Stores Created Yet</div>
|
||||
</Stack>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{!view || view === 'card' ? (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
{docStores?.filter(filterDocStores).map((data, index) => (
|
||||
<DocumentStoreCard
|
||||
key={index}
|
||||
images={images[data.id]}
|
||||
data={data}
|
||||
onClick={() => goToDocumentStore(data.id)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
) : (
|
||||
<DocumentStoreTable
|
||||
isLoading={isLoading}
|
||||
data={docStores?.filter(filterDocStores)}
|
||||
images={images}
|
||||
onRowClick={(row) => goToDocumentStore(row.id)}
|
||||
/>
|
||||
)}
|
||||
{/* Pagination and Page Size Controls */}
|
||||
<TablePagination currentPage={currentPage} limit={pageLimit} total={total} onChange={onChange} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -33,6 +33,7 @@ import useApi from '@/hooks/useApi'
|
||||
// Hooks
|
||||
import useConfirm from '@/hooks/useConfirm'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
import { useError } from '@/store/context/ErrorContext'
|
||||
|
||||
// project
|
||||
import MainCard from '@/ui-component/cards/MainCard'
|
||||
@@ -43,6 +44,7 @@ import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||
import { StyledTableCell, StyledTableRow } from '@/ui-component/table/TableStyles'
|
||||
import CreateEvaluationDialog from '@/views/evaluations/CreateEvaluationDialog'
|
||||
import { StyledPermissionButton } from '@/ui-component/button/RBACButtons'
|
||||
import TablePagination, { DEFAULT_ITEMS_PER_PAGE } from '@/ui-component/pagination/TablePagination'
|
||||
|
||||
// icons
|
||||
import {
|
||||
@@ -59,8 +61,6 @@ import {
|
||||
} from '@tabler/icons-react'
|
||||
import empty_evalSVG from '@/assets/images/empty_evals.svg'
|
||||
|
||||
import { useError } from '@/store/context/ErrorContext'
|
||||
|
||||
const EvalsEvaluation = () => {
|
||||
const theme = useTheme()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
@@ -83,6 +83,24 @@ const EvalsEvaluation = () => {
|
||||
const [selected, setSelected] = useState([])
|
||||
const [autoRefresh, setAutoRefresh] = useState(false)
|
||||
|
||||
/* Table Pagination */
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
|
||||
const [total, setTotal] = useState(0)
|
||||
const onChange = (page, pageLimit) => {
|
||||
setCurrentPage(page)
|
||||
setPageLimit(pageLimit)
|
||||
refresh(page, pageLimit)
|
||||
}
|
||||
|
||||
const refresh = (page, limit) => {
|
||||
const params = {
|
||||
page: page || currentPage,
|
||||
limit: limit || pageLimit
|
||||
}
|
||||
getAllEvaluations.request(params)
|
||||
}
|
||||
|
||||
const onSelectAllClick = (event) => {
|
||||
if (event.target.checked) {
|
||||
const newSelected = rows.filter((item) => item?.latestEval).map((n) => n.id)
|
||||
@@ -171,13 +189,14 @@ const EvalsEvaluation = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllEvaluations.request()
|
||||
refresh(currentPage, pageLimit)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllEvaluations.data) {
|
||||
const evalRows = getAllEvaluations.data
|
||||
const evalRows = getAllEvaluations.data.data
|
||||
setTotal(getAllEvaluations.data.total)
|
||||
if (evalRows) {
|
||||
// Prepare the data for the table
|
||||
for (let i = 0; i < evalRows.length; i++) {
|
||||
@@ -244,7 +263,8 @@ const EvalsEvaluation = () => {
|
||||
}, [createNewEvaluation.error])
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
getAllEvaluations.request()
|
||||
refresh(currentPage, pageLimit)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getAllEvaluations])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -358,111 +378,115 @@ const EvalsEvaluation = () => {
|
||||
<div>No Evaluations Yet</div>
|
||||
</Stack>
|
||||
) : (
|
||||
<TableContainer
|
||||
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
|
||||
component={Paper}
|
||||
>
|
||||
<Table sx={{ minWidth: 650 }}>
|
||||
<TableHead
|
||||
sx={{
|
||||
backgroundColor: customization.isDarkMode
|
||||
? theme.palette.common.black
|
||||
: theme.palette.grey[100],
|
||||
height: 56
|
||||
}}
|
||||
>
|
||||
<TableRow>
|
||||
<TableCell padding='checkbox'>
|
||||
<Checkbox
|
||||
color='primary'
|
||||
checked={selected.length === (rows.filter((item) => item?.latestEval) || []).length}
|
||||
onChange={onSelectAllClick}
|
||||
inputProps={{
|
||||
'aria-label': 'select all'
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell width={10}> </TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Latest Version</TableCell>
|
||||
<TableCell>Average Metrics</TableCell>
|
||||
<TableCell>Last Evaluated</TableCell>
|
||||
<TableCell>Flow(s)</TableCell>
|
||||
<TableCell>Dataset</TableCell>
|
||||
<TableCell> </TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isTableLoading ? (
|
||||
<>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</StyledTableRow>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{rows
|
||||
.filter((item) => item?.latestEval)
|
||||
.map((item, index) => (
|
||||
<EvaluationRunRow
|
||||
rows={rows.filter((row) => row.name === item.name)}
|
||||
item={item}
|
||||
key={index}
|
||||
theme={theme}
|
||||
selected={selected}
|
||||
customization={customization}
|
||||
onRefresh={onRefresh}
|
||||
handleSelect={handleSelect}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<>
|
||||
<TableContainer
|
||||
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
|
||||
component={Paper}
|
||||
>
|
||||
<Table sx={{ minWidth: 650 }}>
|
||||
<TableHead
|
||||
sx={{
|
||||
backgroundColor: customization.isDarkMode
|
||||
? theme.palette.common.black
|
||||
: theme.palette.grey[100],
|
||||
height: 56
|
||||
}}
|
||||
>
|
||||
<TableRow>
|
||||
<TableCell padding='checkbox'>
|
||||
<Checkbox
|
||||
color='primary'
|
||||
checked={selected.length === (rows.filter((item) => item?.latestEval) || []).length}
|
||||
onChange={onSelectAllClick}
|
||||
inputProps={{
|
||||
'aria-label': 'select all'
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell width={10}> </TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Latest Version</TableCell>
|
||||
<TableCell>Average Metrics</TableCell>
|
||||
<TableCell>Last Evaluated</TableCell>
|
||||
<TableCell>Flow(s)</TableCell>
|
||||
<TableCell>Dataset</TableCell>
|
||||
<TableCell> </TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isTableLoading ? (
|
||||
<>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</StyledTableRow>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{rows
|
||||
.filter((item) => item?.latestEval)
|
||||
.map((item, index) => (
|
||||
<EvaluationRunRow
|
||||
rows={rows.filter((row) => row.name === item.name)}
|
||||
item={item}
|
||||
key={index}
|
||||
theme={theme}
|
||||
selected={selected}
|
||||
customization={customization}
|
||||
onRefresh={onRefresh}
|
||||
handleSelect={handleSelect}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{/* Pagination and Page Size Controls */}
|
||||
<TablePagination currentPage={currentPage} limit={pageLimit} total={total} onChange={onChange} />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -14,6 +14,8 @@ import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog'
|
||||
import AddEditEvaluatorDialog from '@/views/evaluators/AddEditEvaluatorDialog'
|
||||
import { StyledTableCell, StyledTableRow } from '@/ui-component/table/TableStyles'
|
||||
import { PermissionIconButton, StyledPermissionButton } from '@/ui-component/button/RBACButtons'
|
||||
import TablePagination, { DEFAULT_ITEMS_PER_PAGE } from '@/ui-component/pagination/TablePagination'
|
||||
import { truncateString } from '@/utils/genericHelper'
|
||||
|
||||
// API
|
||||
import evaluatorsApi from '@/api/evaluators'
|
||||
@@ -23,15 +25,14 @@ import moment from 'moment/moment'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
import useConfirm from '@/hooks/useConfirm'
|
||||
import useApi from '@/hooks/useApi'
|
||||
import { useError } from '@/store/context/ErrorContext'
|
||||
|
||||
// icons
|
||||
import empty_evaluatorSVG from '@/assets/images/empty_evaluators.svg'
|
||||
import { IconTrash, IconPlus, IconJson, IconX, IconNumber123, IconAbc, IconAugmentedReality } from '@tabler/icons-react'
|
||||
import { truncateString } from '@/utils/genericHelper'
|
||||
|
||||
// const
|
||||
import { evaluators as evaluatorsOptions, numericOperators } from '../evaluators/evaluatorConstant'
|
||||
import { useError } from '@/store/context/ErrorContext'
|
||||
|
||||
// ==============================|| Evaluators ||============================== //
|
||||
|
||||
@@ -54,6 +55,24 @@ const Evaluators = () => {
|
||||
|
||||
const getAllEvaluators = useApi(evaluatorsApi.getAllEvaluators)
|
||||
|
||||
/* Table Pagination */
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
|
||||
const [total, setTotal] = useState(0)
|
||||
const onChange = (page, pageLimit) => {
|
||||
setCurrentPage(page)
|
||||
setPageLimit(pageLimit)
|
||||
refresh(page, pageLimit)
|
||||
}
|
||||
|
||||
const refresh = (page, limit) => {
|
||||
const params = {
|
||||
page: page || currentPage,
|
||||
limit: limit || pageLimit
|
||||
}
|
||||
getAllEvaluators.request(params)
|
||||
}
|
||||
|
||||
const onSearchChange = (event) => {
|
||||
setSearch(event.target.value)
|
||||
}
|
||||
@@ -129,7 +148,7 @@ const Evaluators = () => {
|
||||
|
||||
const onConfirm = () => {
|
||||
setShowEvaluatorDialog(false)
|
||||
getAllEvaluators.request()
|
||||
refresh(currentPage, pageLimit)
|
||||
}
|
||||
|
||||
function filterDatasets(data) {
|
||||
@@ -137,13 +156,14 @@ const Evaluators = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllEvaluators.request()
|
||||
refresh(currentPage, pageLimit)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllEvaluators.data) {
|
||||
setEvaluators(getAllEvaluators.data)
|
||||
setEvaluators(getAllEvaluators.data.data)
|
||||
setTotal(getAllEvaluators.data.total)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getAllEvaluators.data])
|
||||
@@ -189,327 +209,352 @@ const Evaluators = () => {
|
||||
<div>No Evaluators Yet</div>
|
||||
</Stack>
|
||||
) : (
|
||||
<TableContainer
|
||||
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
|
||||
component={Paper}
|
||||
>
|
||||
<Table sx={{ minWidth: 650 }}>
|
||||
<TableHead
|
||||
sx={{
|
||||
backgroundColor: customization.isDarkMode
|
||||
? theme.palette.common.black
|
||||
: theme.palette.grey[100],
|
||||
height: 56
|
||||
}}
|
||||
>
|
||||
<TableRow>
|
||||
<TableCell>Type</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Details</TableCell>
|
||||
<TableCell>Last Updated</TableCell>
|
||||
<TableCell> </TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</StyledTableRow>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{evaluators.filter(filterDatasets).map((ds, index) => (
|
||||
<>
|
||||
<StyledTableRow
|
||||
hover
|
||||
key={index}
|
||||
sx={{ cursor: 'pointer', '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
>
|
||||
<TableCell onClick={() => edit(ds)}>
|
||||
{ds?.type === 'numeric' && (
|
||||
<Stack flexDirection='row' sx={{ alignItems: 'center' }}>
|
||||
<Chip icon={<IconNumber123 />} label='Numeric' variant='outlined' />
|
||||
</Stack>
|
||||
)}
|
||||
{ds?.type === 'text' && (
|
||||
<Stack flexDirection='row' sx={{ alignItems: 'center' }}>
|
||||
<Chip icon={<IconAbc />} label='Text Based' variant='outlined' />
|
||||
</Stack>
|
||||
)}
|
||||
{ds?.type === 'json' && (
|
||||
<Stack flexDirection='row' sx={{ alignItems: 'center' }}>
|
||||
<Chip icon={<IconJson />} label='JSON Based' variant='outlined' />
|
||||
</Stack>
|
||||
)}
|
||||
{ds?.type === 'llm' && (
|
||||
<Stack flexDirection='row' sx={{ alignItems: 'center' }}>
|
||||
<Chip
|
||||
icon={<IconAugmentedReality />}
|
||||
label='LLM Based'
|
||||
variant='outlined'
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell onClick={() => edit(ds)} component='th' scope='row'>
|
||||
{ds.name}
|
||||
</TableCell>
|
||||
<TableCell style={{ width: '40%' }} onClick={() => edit(ds)}>
|
||||
{ds?.type === 'numeric' && (
|
||||
<Stack
|
||||
flexDirection='row'
|
||||
gap={1}
|
||||
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
|
||||
<>
|
||||
<TableContainer
|
||||
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
|
||||
component={Paper}
|
||||
>
|
||||
<Table sx={{ minWidth: 650 }}>
|
||||
<TableHead
|
||||
sx={{
|
||||
backgroundColor: customization.isDarkMode
|
||||
? theme.palette.common.black
|
||||
: theme.palette.grey[100],
|
||||
height: 56
|
||||
}}
|
||||
>
|
||||
<TableRow>
|
||||
<TableCell>Type</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Details</TableCell>
|
||||
<TableCell>Last Updated</TableCell>
|
||||
<TableCell> </TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</StyledTableRow>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{evaluators.filter(filterDatasets).map((ds, index) => (
|
||||
<>
|
||||
<StyledTableRow
|
||||
hover
|
||||
key={index}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
'&:last-child td, &:last-child th': { border: 0 }
|
||||
}}
|
||||
>
|
||||
<TableCell onClick={() => edit(ds)}>
|
||||
{ds?.type === 'numeric' && (
|
||||
<Stack flexDirection='row' sx={{ alignItems: 'center' }}>
|
||||
<Chip
|
||||
icon={<IconNumber123 />}
|
||||
label='Numeric'
|
||||
variant='outlined'
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{ds?.type === 'text' && (
|
||||
<Stack flexDirection='row' sx={{ alignItems: 'center' }}>
|
||||
<Chip
|
||||
icon={<IconAbc />}
|
||||
label='Text Based'
|
||||
variant='outlined'
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{ds?.type === 'json' && (
|
||||
<Stack flexDirection='row' sx={{ alignItems: 'center' }}>
|
||||
<Chip
|
||||
icon={<IconJson />}
|
||||
label='JSON Based'
|
||||
variant='outlined'
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{ds?.type === 'llm' && (
|
||||
<Stack flexDirection='row' sx={{ alignItems: 'center' }}>
|
||||
<Chip
|
||||
icon={<IconAugmentedReality />}
|
||||
label='LLM Based'
|
||||
variant='outlined'
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell onClick={() => edit(ds)} component='th' scope='row'>
|
||||
{ds.name}
|
||||
</TableCell>
|
||||
<TableCell style={{ width: '40%' }} onClick={() => edit(ds)}>
|
||||
{ds?.type === 'numeric' && (
|
||||
<Stack
|
||||
flexDirection='row'
|
||||
gap={1}
|
||||
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
|
||||
>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Measure</b>:{' '}
|
||||
{
|
||||
[
|
||||
...evaluatorsOptions,
|
||||
...numericOperators
|
||||
].find((item) => item.name === ds?.measure)
|
||||
?.label
|
||||
}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Operator</b>:{' '}
|
||||
{
|
||||
[
|
||||
...evaluatorsOptions,
|
||||
...numericOperators
|
||||
].find((item) => item.name === ds?.operator)
|
||||
?.label
|
||||
}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Value</b>: {ds?.value}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{ds?.type === 'text' && (
|
||||
<Stack
|
||||
flexDirection='row'
|
||||
gap={1}
|
||||
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
|
||||
>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Operator</b>:{' '}
|
||||
{
|
||||
[
|
||||
...evaluatorsOptions,
|
||||
...numericOperators
|
||||
].find((item) => item.name === ds?.operator)
|
||||
?.label
|
||||
}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Value</b>: {ds?.value}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{ds?.type === 'json' && (
|
||||
<Stack
|
||||
flexDirection='row'
|
||||
gap={1}
|
||||
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
|
||||
>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Operator</b>:{' '}
|
||||
{
|
||||
[...evaluatorsOptions].find(
|
||||
(item) => item.name === ds?.operator
|
||||
)?.label
|
||||
}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{ds?.type === 'llm' && (
|
||||
<Stack
|
||||
flexDirection='row'
|
||||
gap={1}
|
||||
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
|
||||
>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Prompt</b>: {truncateString(ds?.prompt, 100)}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Output Schema Elements</b>:{' '}
|
||||
{ds?.outputSchema.length > 0
|
||||
? ds?.outputSchema
|
||||
.map((item) => item.property)
|
||||
.join(', ')
|
||||
: 'None'}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell onClick={() => edit(ds)}>
|
||||
{moment(ds.updatedDate).format('MMMM Do YYYY, hh:mm A')}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<PermissionIconButton
|
||||
permissionId={'evaluators:delete'}
|
||||
title='Delete'
|
||||
color='error'
|
||||
onClick={() => deleteEvaluator(ds)}
|
||||
>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Measure</b>:{' '}
|
||||
{
|
||||
[...evaluatorsOptions, ...numericOperators].find(
|
||||
(item) => item.name === ds?.measure
|
||||
)?.label
|
||||
}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Operator</b>:{' '}
|
||||
{
|
||||
[...evaluatorsOptions, ...numericOperators].find(
|
||||
(item) => item.name === ds?.operator
|
||||
)?.label
|
||||
}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Value</b>: {ds?.value}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{ds?.type === 'text' && (
|
||||
<Stack
|
||||
flexDirection='row'
|
||||
gap={1}
|
||||
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
|
||||
>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Operator</b>:{' '}
|
||||
{
|
||||
[...evaluatorsOptions, ...numericOperators].find(
|
||||
(item) => item.name === ds?.operator
|
||||
)?.label
|
||||
}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Value</b>: {ds?.value}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{ds?.type === 'json' && (
|
||||
<Stack
|
||||
flexDirection='row'
|
||||
gap={1}
|
||||
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
|
||||
>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Operator</b>:{' '}
|
||||
{
|
||||
[...evaluatorsOptions].find(
|
||||
(item) => item.name === ds?.operator
|
||||
)?.label
|
||||
}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{ds?.type === 'llm' && (
|
||||
<Stack
|
||||
flexDirection='row'
|
||||
gap={1}
|
||||
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
|
||||
>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Prompt</b>: {truncateString(ds?.prompt, 100)}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
color='default'
|
||||
sx={{
|
||||
height: 'auto',
|
||||
'& .MuiChip-label': {
|
||||
display: 'block',
|
||||
whiteSpace: 'normal'
|
||||
},
|
||||
p: 0.5
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<b>Output Schema Elements</b>:{' '}
|
||||
{ds?.outputSchema.length > 0
|
||||
? ds?.outputSchema
|
||||
.map((item) => item.property)
|
||||
.join(', ')
|
||||
: 'None'}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell onClick={() => edit(ds)}>
|
||||
{moment(ds.updatedDate).format('MMMM Do YYYY, hh:mm A')}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<PermissionIconButton
|
||||
permissionId={'evaluators:delete'}
|
||||
title='Delete'
|
||||
color='error'
|
||||
onClick={() => deleteEvaluator(ds)}
|
||||
>
|
||||
<IconTrash />
|
||||
</PermissionIconButton>
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<IconTrash />
|
||||
</PermissionIconButton>
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{/* Pagination and Page Size Controls */}
|
||||
<TablePagination currentPage={currentPage} limit={pageLimit} total={total} onChange={onChange} />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -12,6 +12,7 @@ import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||
import ErrorBoundary from '@/ErrorBoundary'
|
||||
import { ToolsTable } from '@/ui-component/table/ToolsListTable'
|
||||
import { PermissionButton, StyledPermissionButton } from '@/ui-component/button/RBACButtons'
|
||||
import TablePagination, { DEFAULT_ITEMS_PER_PAGE } from '@/ui-component/pagination/TablePagination'
|
||||
|
||||
// API
|
||||
import toolsApi from '@/api/tools'
|
||||
@@ -39,6 +40,25 @@ const Tools = () => {
|
||||
|
||||
const inputRef = useRef(null)
|
||||
|
||||
/* Table Pagination */
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
|
||||
const [total, setTotal] = useState(0)
|
||||
|
||||
const onChange = (page, pageLimit) => {
|
||||
setCurrentPage(page)
|
||||
setPageLimit(pageLimit)
|
||||
refresh(page, pageLimit)
|
||||
}
|
||||
|
||||
const refresh = (page, limit) => {
|
||||
const params = {
|
||||
page: page || currentPage,
|
||||
limit: limit || pageLimit
|
||||
}
|
||||
getAllToolsApi.request(params)
|
||||
}
|
||||
|
||||
const handleChange = (event, nextView) => {
|
||||
if (nextView === null) return
|
||||
localStorage.setItem('toolsDisplayStyle', nextView)
|
||||
@@ -102,7 +122,7 @@ const Tools = () => {
|
||||
|
||||
const onConfirm = () => {
|
||||
setShowDialog(false)
|
||||
getAllToolsApi.request()
|
||||
refresh(currentPage, pageLimit)
|
||||
}
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
@@ -117,8 +137,7 @@ const Tools = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllToolsApi.request()
|
||||
|
||||
refresh(currentPage, pageLimit)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
@@ -126,6 +145,12 @@ const Tools = () => {
|
||||
setLoading(getAllToolsApi.loading)
|
||||
}, [getAllToolsApi.loading])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllToolsApi.data) {
|
||||
setTotal(getAllToolsApi.data.total)
|
||||
}
|
||||
}, [getAllToolsApi.data])
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainCard>
|
||||
@@ -144,6 +169,7 @@ const Tools = () => {
|
||||
sx={{ borderRadius: 2, maxHeight: 40 }}
|
||||
value={view}
|
||||
color='primary'
|
||||
disabled={total === 0}
|
||||
exclusive
|
||||
onChange={handleChange}
|
||||
>
|
||||
@@ -203,27 +229,29 @@ const Tools = () => {
|
||||
</StyledPermissionButton>
|
||||
</ButtonGroup>
|
||||
</ViewHeader>
|
||||
{!view || view === 'card' ? (
|
||||
{isLoading && (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
</Box>
|
||||
)}
|
||||
{!isLoading && total > 0 && (
|
||||
<>
|
||||
{isLoading ? (
|
||||
{!view || view === 'card' ? (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
{getAllToolsApi.data?.data?.filter(filterTools).map((data, index) => (
|
||||
<ItemCard data={data} key={index} onClick={() => edit(data)} />
|
||||
))}
|
||||
</Box>
|
||||
) : (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
{getAllToolsApi.data &&
|
||||
getAllToolsApi.data
|
||||
?.filter(filterTools)
|
||||
.map((data, index) => <ItemCard data={data} key={index} onClick={() => edit(data)} />)}
|
||||
</Box>
|
||||
<ToolsTable data={getAllToolsApi.data.data} isLoading={isLoading} onSelect={edit} />
|
||||
)}
|
||||
{/* Pagination and Page Size Controls */}
|
||||
<TablePagination currentPage={currentPage} limit={pageLimit} total={total} onChange={onChange} />
|
||||
</>
|
||||
) : (
|
||||
<ToolsTable data={getAllToolsApi.data} isLoading={isLoading} onSelect={edit} />
|
||||
)}
|
||||
{!isLoading && (!getAllToolsApi.data || getAllToolsApi.data.length === 0) && (
|
||||
{!isLoading && total === 0 && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
|
||||
@@ -33,6 +33,7 @@ import ErrorBoundary from '@/ErrorBoundary'
|
||||
import { StyledPermissionButton } from '@/ui-component/button/RBACButtons'
|
||||
import { Available } from '@/ui-component/rbac/available'
|
||||
import { refreshVariablesCache } from '@/ui-component/input/suggestionOption'
|
||||
import TablePagination, { DEFAULT_ITEMS_PER_PAGE } from '@/ui-component/pagination/TablePagination'
|
||||
|
||||
// API
|
||||
import variablesApi from '@/api/variables'
|
||||
@@ -91,8 +92,27 @@ const Variables = () => {
|
||||
const { confirm } = useConfirm()
|
||||
|
||||
const getAllVariables = useApi(variablesApi.getAllVariables)
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
/* Table Pagination */
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
|
||||
const [total, setTotal] = useState(0)
|
||||
|
||||
const onChange = (page, pageLimit) => {
|
||||
setCurrentPage(page)
|
||||
setPageLimit(pageLimit)
|
||||
refresh(page, pageLimit)
|
||||
}
|
||||
|
||||
const refresh = (page, limit) => {
|
||||
const params = {
|
||||
page: page || currentPage,
|
||||
limit: limit || pageLimit
|
||||
}
|
||||
getAllVariables.request(params)
|
||||
}
|
||||
|
||||
const onSearchChange = (event) => {
|
||||
setSearch(event.target.value)
|
||||
}
|
||||
@@ -172,12 +192,12 @@ const Variables = () => {
|
||||
|
||||
const onConfirm = () => {
|
||||
setShowVariableDialog(false)
|
||||
getAllVariables.request()
|
||||
refresh(currentPage, pageLimit)
|
||||
refreshVariablesCache()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllVariables.request()
|
||||
refresh(currentPage, pageLimit)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
@@ -187,7 +207,8 @@ const Variables = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllVariables.data) {
|
||||
setVariables(getAllVariables.data)
|
||||
setVariables(getAllVariables.data.data)
|
||||
setTotal(getAllVariables.data.total)
|
||||
}
|
||||
}, [getAllVariables.data])
|
||||
|
||||
@@ -231,162 +252,169 @@ const Variables = () => {
|
||||
<div>No Variables Yet</div>
|
||||
</Stack>
|
||||
) : (
|
||||
<TableContainer
|
||||
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
|
||||
component={Paper}
|
||||
>
|
||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||
<TableHead
|
||||
sx={{
|
||||
backgroundColor: customization.isDarkMode
|
||||
? theme.palette.common.black
|
||||
: theme.palette.grey[100],
|
||||
height: 56
|
||||
}}
|
||||
>
|
||||
<TableRow>
|
||||
<StyledTableCell>Name</StyledTableCell>
|
||||
<StyledTableCell>Value</StyledTableCell>
|
||||
<StyledTableCell>Type</StyledTableCell>
|
||||
<StyledTableCell>Last Updated</StyledTableCell>
|
||||
<StyledTableCell>Created</StyledTableCell>
|
||||
<Available permissionId={'variables:update'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
<Available permissionId={'variables:delete'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<Available permission={'variables:create,variables:update'}>
|
||||
<>
|
||||
<TableContainer
|
||||
sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }}
|
||||
component={Paper}
|
||||
>
|
||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||
<TableHead
|
||||
sx={{
|
||||
backgroundColor: customization.isDarkMode
|
||||
? theme.palette.common.black
|
||||
: theme.palette.grey[100],
|
||||
height: 56
|
||||
}}
|
||||
>
|
||||
<TableRow>
|
||||
<StyledTableCell>Name</StyledTableCell>
|
||||
<StyledTableCell>Value</StyledTableCell>
|
||||
<StyledTableCell>Type</StyledTableCell>
|
||||
<StyledTableCell>Last Updated</StyledTableCell>
|
||||
<StyledTableCell>Created</StyledTableCell>
|
||||
<Available permissionId={'variables:update'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
<Available permissionId={'variables:delete'}>
|
||||
<StyledTableCell> </StyledTableCell>
|
||||
</Available>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</Available>
|
||||
<Available permission={'variables:delete'}>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<Available permission={'variables:create,variables:update'}>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</Available>
|
||||
<Available permission={'variables:delete'}>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{variables.filter(filterVariables).map((variable, index) => (
|
||||
<StyledTableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||
<StyledTableCell component='th' scope='row'>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 25,
|
||||
height: 25,
|
||||
marginRight: 10,
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
>
|
||||
<IconVariable
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{variable.name}
|
||||
</div>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>{variable.value}</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Chip
|
||||
color={variable.type === 'static' ? 'info' : 'secondary'}
|
||||
size='small'
|
||||
label={variable.type}
|
||||
/>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
{moment(variable.updatedDate).format('MMMM Do, YYYY HH:mm:ss')}
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
{moment(variable.createdDate).format('MMMM Do, YYYY HH:mm:ss')}
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<Available permission={'variables:create,variables:update'}>
|
||||
<StyledTableCell>
|
||||
<IconButton title='Edit' color='primary' onClick={() => edit(variable)}>
|
||||
<IconEdit />
|
||||
</IconButton>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</Available>
|
||||
<Available permission={'variables:delete'}>
|
||||
<StyledTableCell>
|
||||
<IconButton
|
||||
title='Delete'
|
||||
color='error'
|
||||
onClick={() => deleteVariable(variable)}
|
||||
>
|
||||
<IconTrash />
|
||||
</IconButton>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<StyledTableRow>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
<Available permission={'variables:create,variables:update'}>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</Available>
|
||||
<Available permission={'variables:delete'}>
|
||||
<StyledTableCell>
|
||||
<Skeleton variant='text' />
|
||||
</StyledTableCell>
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{variables.filter(filterVariables).map((variable, index) => (
|
||||
<StyledTableRow
|
||||
key={index}
|
||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
>
|
||||
<StyledTableCell component='th' scope='row'>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 25,
|
||||
height: 25,
|
||||
marginRight: 10,
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
>
|
||||
<IconVariable
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{variable.name}
|
||||
</div>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>{variable.value}</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
<Chip
|
||||
color={variable.type === 'static' ? 'info' : 'secondary'}
|
||||
size='small'
|
||||
label={variable.type}
|
||||
/>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
{moment(variable.updatedDate).format('MMMM Do, YYYY HH:mm:ss')}
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
{moment(variable.createdDate).format('MMMM Do, YYYY HH:mm:ss')}
|
||||
</StyledTableCell>
|
||||
<Available permission={'variables:create,variables:update'}>
|
||||
<StyledTableCell>
|
||||
<IconButton title='Edit' color='primary' onClick={() => edit(variable)}>
|
||||
<IconEdit />
|
||||
</IconButton>
|
||||
</StyledTableCell>
|
||||
</Available>
|
||||
<Available permission={'variables:delete'}>
|
||||
<StyledTableCell>
|
||||
<IconButton
|
||||
title='Delete'
|
||||
color='error'
|
||||
onClick={() => deleteVariable(variable)}
|
||||
>
|
||||
<IconTrash />
|
||||
</IconButton>
|
||||
</StyledTableCell>
|
||||
</Available>
|
||||
</StyledTableRow>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{/* Pagination and Page Size Controls */}
|
||||
<TablePagination currentPage={currentPage} limit={pageLimit} total={total} onChange={onChange} />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user