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:
parent
6baec93860
commit
bf05f25f7e
|
|
@ -290,6 +290,9 @@ export class DocumentStoreDTO {
|
|||
}
|
||||
|
||||
static fromEntities(entities: DocumentStore[]): DocumentStoreDTO[] {
|
||||
if (entities.length === 0) {
|
||||
return []
|
||||
}
|
||||
return entities.map((entity) => this.fromEntity(entity))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ import { Request, Response, NextFunction } from 'express'
|
|||
import { StatusCodes } from 'http-status-codes'
|
||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import apikeyService from '../../services/apikey'
|
||||
import { getPageAndLimitParams } from '../../utils/pagination'
|
||||
|
||||
// Get api keys
|
||||
const getAllApiKeys = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const autoCreateNewKey = true
|
||||
const apiResponse = await apikeyService.getAllApiKeys(req.user?.activeWorkspaceId, autoCreateNewKey)
|
||||
const { page, limit } = getPageAndLimitParams(req)
|
||||
const apiResponse = await apikeyService.getAllApiKeys(req.user?.activeWorkspaceId, autoCreateNewKey, page, limit)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { ChatMessage } from '../../database/entities/ChatMessage'
|
|||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { utilGetChatMessage } from '../../utils/getChatMessage'
|
||||
import { getPageAndLimitParams } from '../../utils/pagination'
|
||||
|
||||
const getFeedbackTypeFilters = (_feedbackTypeFilters: ChatMessageRatingType[]): ChatMessageRatingType[] | undefined => {
|
||||
try {
|
||||
|
|
@ -71,6 +72,9 @@ const getAllChatMessages = async (req: Request, res: Response, next: NextFunctio
|
|||
const startDate = req.query?.startDate as string | undefined
|
||||
const endDate = req.query?.endDate as string | undefined
|
||||
const feedback = req.query?.feedback as boolean | undefined
|
||||
|
||||
const { page, limit } = getPageAndLimitParams(req)
|
||||
|
||||
let feedbackTypeFilters = req.query?.feedbackType as ChatMessageRatingType[] | undefined
|
||||
if (feedbackTypeFilters) {
|
||||
feedbackTypeFilters = getFeedbackTypeFilters(feedbackTypeFilters)
|
||||
|
|
@ -93,7 +97,9 @@ const getAllChatMessages = async (req: Request, res: Response, next: NextFunctio
|
|||
messageId,
|
||||
feedback,
|
||||
feedbackTypeFilters,
|
||||
activeWorkspaceId
|
||||
activeWorkspaceId,
|
||||
page,
|
||||
limit
|
||||
)
|
||||
return res.json(parseAPIResponse(apiResponse))
|
||||
} catch (error) {
|
||||
|
|
@ -202,7 +208,8 @@ const removeAllChatMessages = async (req: Request, res: Response, next: NextFunc
|
|||
startDate,
|
||||
endDate,
|
||||
feedback: isFeedback,
|
||||
feedbackTypes: feedbackTypeFilters
|
||||
feedbackTypes: feedbackTypeFilters,
|
||||
activeWorkspaceId: workspaceId
|
||||
})
|
||||
const messageIds = messages.map((message) => message.id)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import chatflowsService from '../../services/chatflows'
|
|||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||
import { checkUsageLimit } from '../../utils/quotaUsage'
|
||||
import { RateLimiterManager } from '../../utils/rateLimit'
|
||||
import { getPageAndLimitParams } from '../../utils/pagination'
|
||||
|
||||
const checkIfChatflowIsValidForStreaming = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
|
|
@ -67,7 +68,14 @@ const deleteChatflow = async (req: Request, res: Response, next: NextFunction) =
|
|||
|
||||
const getAllChatflows = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const apiResponse = await chatflowsService.getAllChatflows(req.query?.type as ChatflowType, req.user?.activeWorkspaceId)
|
||||
const { page, limit } = getPageAndLimitParams(req)
|
||||
|
||||
const apiResponse = await chatflowsService.getAllChatflows(
|
||||
req.query?.type as ChatflowType,
|
||||
req.user?.activeWorkspaceId,
|
||||
page,
|
||||
limit
|
||||
)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import { Request, Response, NextFunction } from 'express'
|
|||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import datasetService from '../../services/dataset'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { getPageAndLimitParams } from '../../utils/pagination'
|
||||
|
||||
const getAllDatasets = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const apiResponse = await datasetService.getAllDatasets(req.user?.activeWorkspaceId)
|
||||
const { page, limit } = getPageAndLimitParams(req)
|
||||
const apiResponse = await datasetService.getAllDatasets(req.user?.activeWorkspaceId, page, limit)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
|
|
@ -17,7 +19,8 @@ const getDataset = async (req: Request, res: Response, next: NextFunction) => {
|
|||
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: datasetService.getDataset - id not provided!`)
|
||||
}
|
||||
const apiResponse = await datasetService.getDataset(req.params.id)
|
||||
const { page, limit } = getPageAndLimitParams(req)
|
||||
const apiResponse = await datasetService.getDataset(req.params.id, page, limit)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
|||
import { DocumentStoreDTO } from '../../Interface'
|
||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||
import { FLOWISE_COUNTER_STATUS, FLOWISE_METRIC_COUNTERS } from '../../Interface.Metrics'
|
||||
import { getPageAndLimitParams } from '../../utils/pagination'
|
||||
|
||||
const createDocumentStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
|
|
@ -37,8 +38,17 @@ const createDocumentStore = async (req: Request, res: Response, next: NextFuncti
|
|||
|
||||
const getAllDocumentStores = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const apiResponse = await documentStoreService.getAllDocumentStores(req.user?.activeWorkspaceId)
|
||||
return res.json(DocumentStoreDTO.fromEntities(apiResponse))
|
||||
const { page, limit } = getPageAndLimitParams(req)
|
||||
|
||||
const apiResponse: any = await documentStoreService.getAllDocumentStores(req.user?.activeWorkspaceId, page, limit)
|
||||
if (apiResponse?.total >= 0) {
|
||||
return res.json({
|
||||
total: apiResponse.total,
|
||||
data: DocumentStoreDTO.fromEntities(apiResponse.data)
|
||||
})
|
||||
} else {
|
||||
return res.json(DocumentStoreDTO.fromEntities(apiResponse))
|
||||
}
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Request, Response, NextFunction } from 'express'
|
|||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import evaluationsService from '../../services/evaluations'
|
||||
import { getPageAndLimitParams } from '../../utils/pagination'
|
||||
|
||||
const createEvaluation = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
|
|
@ -81,7 +82,8 @@ const deleteEvaluation = async (req: Request, res: Response, next: NextFunction)
|
|||
|
||||
const getAllEvaluations = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const apiResponse = await evaluationsService.getAllEvaluations(req.user?.activeWorkspaceId)
|
||||
const { page, limit } = getPageAndLimitParams(req)
|
||||
const apiResponse = await evaluationsService.getAllEvaluations(req.user?.activeWorkspaceId, page, limit)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import { Request, Response, NextFunction } from 'express'
|
|||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import evaluatorService from '../../services/evaluator'
|
||||
import { getPageAndLimitParams } from '../../utils/pagination'
|
||||
|
||||
const getAllEvaluators = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const apiResponse = await evaluatorService.getAllEvaluators(req.user?.activeWorkspaceId)
|
||||
const { page, limit } = getPageAndLimitParams(req)
|
||||
const apiResponse = await evaluatorService.getAllEvaluators(req.user?.activeWorkspaceId, page, limit)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,16 @@ const getChatflowStats = async (req: Request, res: Response, next: NextFunction)
|
|||
return res.status(500).send(e)
|
||||
}
|
||||
}
|
||||
const apiResponse = await statsService.getChatflowStats(chatflowid, chatTypes, startDate, endDate, '', true, feedbackTypeFilters)
|
||||
const apiResponse = await statsService.getChatflowStats(
|
||||
chatflowid,
|
||||
chatTypes,
|
||||
startDate,
|
||||
endDate,
|
||||
'',
|
||||
true,
|
||||
feedbackTypeFilters,
|
||||
req.user?.activeWorkspaceId
|
||||
)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { NextFunction, Request, Response } from 'express'
|
|||
import toolsService from '../../services/tools'
|
||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { getPageAndLimitParams } from '../../utils/pagination'
|
||||
|
||||
const createTool = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
|
|
@ -40,7 +41,8 @@ const deleteTool = async (req: Request, res: Response, next: NextFunction) => {
|
|||
|
||||
const getAllTools = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const apiResponse = await toolsService.getAllTools(req.user?.activeWorkspaceId)
|
||||
const { page, limit } = getPageAndLimitParams(req)
|
||||
const apiResponse = await toolsService.getAllTools(req.user?.activeWorkspaceId, page, limit)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import variablesService from '../../services/variables'
|
|||
import { Variable } from '../../database/entities/Variable'
|
||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { getPageAndLimitParams } from '../../utils/pagination'
|
||||
|
||||
const createVariable = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
|
|
@ -45,7 +46,8 @@ const deleteVariable = async (req: Request, res: Response, next: NextFunction) =
|
|||
|
||||
const getAllVariables = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const apiResponse = await variablesService.getAllVariables(req.user?.activeWorkspaceId)
|
||||
const { page, limit } = getPageAndLimitParams(req)
|
||||
const apiResponse = await variablesService.getAllVariables(req.user?.activeWorkspaceId, page, limit)
|
||||
return res.json(apiResponse)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { WHITELIST_URLS } from './utils/constants'
|
|||
import { initializeJwtCookieMiddleware, verifyToken } from './enterprise/middleware/passport'
|
||||
import { IdentityManager } from './IdentityManager'
|
||||
import { SSEStreamer } from './utils/SSEStreamer'
|
||||
import { getAPIKeyWorkspaceID, validateAPIKey } from './utils/validateKey'
|
||||
import { validateAPIKey } from './utils/validateKey'
|
||||
import { LoggedInUser } from './enterprise/Interface.Enterprise'
|
||||
import { IMetricsProvider } from './Interface.Metrics'
|
||||
import { Prometheus } from './metrics/Prometheus'
|
||||
|
|
@ -217,58 +217,55 @@ export class App {
|
|||
return res.status(401).json({ error: 'Unauthorized Access' })
|
||||
}
|
||||
}
|
||||
const isKeyValidated = await validateAPIKey(req)
|
||||
if (!isKeyValidated) {
|
||||
|
||||
const { isValid, workspaceId: apiKeyWorkSpaceId } = await validateAPIKey(req)
|
||||
if (!isValid) {
|
||||
return res.status(401).json({ error: 'Unauthorized Access' })
|
||||
}
|
||||
const apiKeyWorkSpaceId = await getAPIKeyWorkspaceID(req)
|
||||
if (apiKeyWorkSpaceId) {
|
||||
// Find workspace
|
||||
const workspace = await this.AppDataSource.getRepository(Workspace).findOne({
|
||||
where: { id: apiKeyWorkSpaceId }
|
||||
})
|
||||
if (!workspace) {
|
||||
return res.status(401).json({ error: 'Unauthorized Access' })
|
||||
}
|
||||
|
||||
// Find owner role
|
||||
const ownerRole = await this.AppDataSource.getRepository(Role).findOne({
|
||||
where: { name: GeneralRole.OWNER, organizationId: IsNull() }
|
||||
})
|
||||
if (!ownerRole) {
|
||||
return res.status(401).json({ error: 'Unauthorized Access' })
|
||||
}
|
||||
|
||||
// Find organization
|
||||
const activeOrganizationId = workspace.organizationId as string
|
||||
const org = await this.AppDataSource.getRepository(Organization).findOne({
|
||||
where: { id: activeOrganizationId }
|
||||
})
|
||||
if (!org) {
|
||||
return res.status(401).json({ error: 'Unauthorized Access' })
|
||||
}
|
||||
const subscriptionId = org.subscriptionId as string
|
||||
const customerId = org.customerId as string
|
||||
const features = await this.identityManager.getFeaturesByPlan(subscriptionId)
|
||||
const productId = await this.identityManager.getProductIdFromSubscription(subscriptionId)
|
||||
|
||||
// @ts-ignore
|
||||
req.user = {
|
||||
permissions: [...JSON.parse(ownerRole.permissions)],
|
||||
features,
|
||||
activeOrganizationId: activeOrganizationId,
|
||||
activeOrganizationSubscriptionId: subscriptionId,
|
||||
activeOrganizationCustomerId: customerId,
|
||||
activeOrganizationProductId: productId,
|
||||
isOrganizationAdmin: true,
|
||||
activeWorkspaceId: apiKeyWorkSpaceId,
|
||||
activeWorkspace: workspace.name,
|
||||
isApiKeyValidated: true
|
||||
}
|
||||
next()
|
||||
} else {
|
||||
// Find workspace
|
||||
const workspace = await this.AppDataSource.getRepository(Workspace).findOne({
|
||||
where: { id: apiKeyWorkSpaceId }
|
||||
})
|
||||
if (!workspace) {
|
||||
return res.status(401).json({ error: 'Unauthorized Access' })
|
||||
}
|
||||
|
||||
// Find owner role
|
||||
const ownerRole = await this.AppDataSource.getRepository(Role).findOne({
|
||||
where: { name: GeneralRole.OWNER, organizationId: IsNull() }
|
||||
})
|
||||
if (!ownerRole) {
|
||||
return res.status(401).json({ error: 'Unauthorized Access' })
|
||||
}
|
||||
|
||||
// Find organization
|
||||
const activeOrganizationId = workspace.organizationId as string
|
||||
const org = await this.AppDataSource.getRepository(Organization).findOne({
|
||||
where: { id: activeOrganizationId }
|
||||
})
|
||||
if (!org) {
|
||||
return res.status(401).json({ error: 'Unauthorized Access' })
|
||||
}
|
||||
const subscriptionId = org.subscriptionId as string
|
||||
const customerId = org.customerId as string
|
||||
const features = await this.identityManager.getFeaturesByPlan(subscriptionId)
|
||||
const productId = await this.identityManager.getProductIdFromSubscription(subscriptionId)
|
||||
|
||||
// @ts-ignore
|
||||
req.user = {
|
||||
permissions: [...JSON.parse(ownerRole.permissions)],
|
||||
features,
|
||||
activeOrganizationId: activeOrganizationId,
|
||||
activeOrganizationSubscriptionId: subscriptionId,
|
||||
activeOrganizationCustomerId: customerId,
|
||||
activeOrganizationProductId: productId,
|
||||
isOrganizationAdmin: true,
|
||||
activeWorkspaceId: apiKeyWorkSpaceId!,
|
||||
activeWorkspace: workspace.name,
|
||||
isApiKeyValidated: true
|
||||
}
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
return res.status(401).json({ error: 'Unauthorized Access' })
|
||||
|
|
|
|||
|
|
@ -9,19 +9,31 @@ import { Not, IsNull } from 'typeorm'
|
|||
import { getWorkspaceSearchOptions } from '../../enterprise/utils/ControllerServiceUtils'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
const getAllApiKeysFromDB = async (workspaceId?: string) => {
|
||||
const getAllApiKeysFromDB = async (workspaceId?: string, page: number = -1, limit: number = -1) => {
|
||||
const appServer = getRunningExpressApp()
|
||||
const keys = await appServer.AppDataSource.getRepository(ApiKey).findBy(getWorkspaceSearchOptions(workspaceId))
|
||||
const keysWithChatflows = await addChatflowsCount(keys)
|
||||
return keysWithChatflows
|
||||
const queryBuilder = appServer.AppDataSource.getRepository(ApiKey).createQueryBuilder('api_key').orderBy('api_key.updatedDate', 'DESC')
|
||||
if (page > 0 && limit > 0) {
|
||||
queryBuilder.skip((page - 1) * limit)
|
||||
queryBuilder.take(limit)
|
||||
}
|
||||
if (workspaceId) queryBuilder.andWhere('api_key.workspaceId = :workspaceId', { workspaceId })
|
||||
const [data, total] = await queryBuilder.getManyAndCount()
|
||||
const keysWithChatflows = await addChatflowsCount(data)
|
||||
|
||||
if (page > 0 && limit > 0) {
|
||||
return { total, data: keysWithChatflows }
|
||||
} else {
|
||||
return keysWithChatflows
|
||||
}
|
||||
}
|
||||
|
||||
const getAllApiKeys = async (workspaceId?: string, autoCreateNewKey?: boolean) => {
|
||||
const getAllApiKeys = async (workspaceId?: string, autoCreateNewKey?: boolean, page: number = -1, limit: number = -1) => {
|
||||
try {
|
||||
let keys = await getAllApiKeysFromDB(workspaceId)
|
||||
if (keys.length === 0 && autoCreateNewKey) {
|
||||
let keys = await getAllApiKeysFromDB(workspaceId, page, limit)
|
||||
const isEmpty = keys?.total === 0 || (Array.isArray(keys) && keys?.length === 0)
|
||||
if (isEmpty && autoCreateNewKey) {
|
||||
await createApiKey('DefaultKey', workspaceId)
|
||||
keys = await getAllApiKeysFromDB(workspaceId)
|
||||
keys = await getAllApiKeysFromDB(workspaceId, page, limit)
|
||||
}
|
||||
return keys
|
||||
} catch (error) {
|
||||
|
|
@ -44,6 +56,21 @@ const getApiKey = async (apiKey: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
const getApiKeyById = async (apiKeyId: string) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const currentKey = await appServer.AppDataSource.getRepository(ApiKey).findOneBy({
|
||||
id: apiKeyId
|
||||
})
|
||||
if (!currentKey) {
|
||||
return undefined
|
||||
}
|
||||
return currentKey
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: apikeyService.getApiKeyById - ${getErrorMessage(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
const createApiKey = async (keyName: string, workspaceId?: string) => {
|
||||
try {
|
||||
const apiKey = generateAPIKey()
|
||||
|
|
@ -231,5 +258,6 @@ export default {
|
|||
updateApiKey,
|
||||
verifyApiKey,
|
||||
getApiKey,
|
||||
getApiKeyById,
|
||||
importKeys
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ const getAllChatMessages = async (
|
|||
messageId?: string,
|
||||
feedback?: boolean,
|
||||
feedbackTypes?: ChatMessageRatingType[],
|
||||
activeWorkspaceId?: string
|
||||
activeWorkspaceId?: string,
|
||||
page?: number,
|
||||
pageSize?: number
|
||||
): Promise<ChatMessage[]> => {
|
||||
try {
|
||||
const dbResponse = await utilGetChatMessage({
|
||||
|
|
@ -54,7 +56,9 @@ const getAllChatMessages = async (
|
|||
messageId,
|
||||
feedback,
|
||||
feedbackTypes,
|
||||
activeWorkspaceId
|
||||
activeWorkspaceId,
|
||||
page,
|
||||
pageSize
|
||||
})
|
||||
return dbResponse
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -127,21 +127,36 @@ const deleteChatflow = async (chatflowId: string, orgId: string, workspaceId: st
|
|||
}
|
||||
}
|
||||
|
||||
const getAllChatflows = async (type?: ChatflowType, workspaceId?: string): Promise<ChatFlow[]> => {
|
||||
const getAllChatflows = async (type?: ChatflowType, workspaceId?: string, page: number = -1, limit: number = -1) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).findBy(getWorkspaceSearchOptions(workspaceId))
|
||||
|
||||
const queryBuilder = appServer.AppDataSource.getRepository(ChatFlow)
|
||||
.createQueryBuilder('chat_flow')
|
||||
.orderBy('chat_flow.updatedDate', 'DESC')
|
||||
|
||||
if (page > 0 && limit > 0) {
|
||||
queryBuilder.skip((page - 1) * limit)
|
||||
queryBuilder.take(limit)
|
||||
}
|
||||
if (type === 'MULTIAGENT') {
|
||||
return dbResponse.filter((chatflow) => chatflow.type === 'MULTIAGENT')
|
||||
queryBuilder.andWhere('chat_flow.type = :type', { type: 'MULTIAGENT' })
|
||||
} else if (type === 'AGENTFLOW') {
|
||||
return dbResponse.filter((chatflow) => chatflow.type === 'AGENTFLOW')
|
||||
queryBuilder.andWhere('chat_flow.type = :type', { type: 'AGENTFLOW' })
|
||||
} else if (type === 'ASSISTANT') {
|
||||
return dbResponse.filter((chatflow) => chatflow.type === 'ASSISTANT')
|
||||
queryBuilder.andWhere('chat_flow.type = :type', { type: 'ASSISTANT' })
|
||||
} else if (type === 'CHATFLOW') {
|
||||
// fetch all chatflows that are not agentflow
|
||||
return dbResponse.filter((chatflow) => chatflow.type === 'CHATFLOW' || !chatflow.type)
|
||||
queryBuilder.andWhere('chat_flow.type = :type', { type: 'CHATFLOW' })
|
||||
}
|
||||
if (workspaceId) queryBuilder.andWhere('chat_flow.workspaceId = :workspaceId', { workspaceId })
|
||||
const [data, total] = await queryBuilder.getManyAndCount()
|
||||
|
||||
if (page > 0 && limit > 0) {
|
||||
return { data, total }
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
return dbResponse
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
|
|
|
|||
|
|
@ -8,22 +8,33 @@ import { Readable } from 'stream'
|
|||
import { In } from 'typeorm'
|
||||
|
||||
import csv from 'csv-parser'
|
||||
import { getWorkspaceSearchOptions } from '../../enterprise/utils/ControllerServiceUtils'
|
||||
|
||||
const getAllDatasets = async (workspaceId?: string) => {
|
||||
const getAllDatasets = async (workspaceId?: string, page: number = -1, limit: number = -1) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const queryBuilder = appServer.AppDataSource.getRepository(Dataset).createQueryBuilder('ds').orderBy('ds.updatedDate', 'DESC')
|
||||
if (page > 0 && limit > 0) {
|
||||
queryBuilder.skip((page - 1) * limit)
|
||||
queryBuilder.take(limit)
|
||||
}
|
||||
if (workspaceId) queryBuilder.andWhere('ds.workspaceId = :workspaceId', { workspaceId })
|
||||
|
||||
const [data, total] = await queryBuilder.getManyAndCount()
|
||||
|
||||
const returnObj: Dataset[] = []
|
||||
const datasets = await appServer.AppDataSource.getRepository(Dataset).findBy(getWorkspaceSearchOptions(workspaceId))
|
||||
|
||||
// TODO: This is a hack to get the row count for each dataset. Need to find a better way to do this
|
||||
for (const dataset of datasets) {
|
||||
for (const dataset of data) {
|
||||
;(dataset as any).rowCount = await appServer.AppDataSource.getRepository(DatasetRow).count({
|
||||
where: { datasetId: dataset.id }
|
||||
})
|
||||
returnObj.push(dataset)
|
||||
}
|
||||
return returnObj
|
||||
if (page > 0 && limit > 0) {
|
||||
return { total, data: returnObj }
|
||||
} else {
|
||||
return returnObj
|
||||
}
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
|
|
@ -32,36 +43,45 @@ const getAllDatasets = async (workspaceId?: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
const getDataset = async (id: string) => {
|
||||
const getDataset = async (id: string, page: number = -1, limit: number = -1) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const dataset = await appServer.AppDataSource.getRepository(Dataset).findOneBy({
|
||||
id: id
|
||||
})
|
||||
let items = await appServer.AppDataSource.getRepository(DatasetRow).find({
|
||||
where: { datasetId: id },
|
||||
order: { sequenceNo: 'asc' }
|
||||
})
|
||||
const queryBuilder = appServer.AppDataSource.getRepository(DatasetRow).createQueryBuilder('dsr').orderBy('dsr.sequenceNo', 'ASC')
|
||||
queryBuilder.andWhere('dsr.datasetId = :datasetId', { datasetId: id })
|
||||
if (page > 0 && limit > 0) {
|
||||
queryBuilder.skip((page - 1) * limit)
|
||||
queryBuilder.take(limit)
|
||||
}
|
||||
let [data, total] = await queryBuilder.getManyAndCount()
|
||||
// special case for sequence numbers == -1 (this happens when the update script is run and all rows are set to -1)
|
||||
// check if there are any sequence numbers == -1, if so set them to the max sequence number + 1
|
||||
const missingSequenceNumbers = items.filter((item) => item.sequenceNo === -1)
|
||||
const missingSequenceNumbers = data.filter((item) => item.sequenceNo === -1)
|
||||
if (missingSequenceNumbers.length > 0) {
|
||||
const maxSequenceNumber = items.reduce((prev, current) => (prev.sequenceNo > current.sequenceNo ? prev : current))
|
||||
const maxSequenceNumber = data.reduce((prev, current) => (prev.sequenceNo > current.sequenceNo ? prev : current))
|
||||
let sequenceNo = maxSequenceNumber.sequenceNo + 1
|
||||
for (const zeroSequenceNumber of missingSequenceNumbers) {
|
||||
zeroSequenceNumber.sequenceNo = sequenceNo++
|
||||
}
|
||||
await appServer.AppDataSource.getRepository(DatasetRow).save(missingSequenceNumbers)
|
||||
// now get the items again
|
||||
items = await appServer.AppDataSource.getRepository(DatasetRow).find({
|
||||
where: { datasetId: id },
|
||||
order: { sequenceNo: 'asc' }
|
||||
})
|
||||
const queryBuilder2 = appServer.AppDataSource.getRepository(DatasetRow)
|
||||
.createQueryBuilder('dsr')
|
||||
.orderBy('dsr.sequenceNo', 'ASC')
|
||||
queryBuilder2.andWhere('dsr.datasetId = :datasetId', { datasetId: id })
|
||||
if (page > 0 && limit > 0) {
|
||||
queryBuilder2.skip((page - 1) * limit)
|
||||
queryBuilder2.take(limit)
|
||||
}
|
||||
;[data, total] = await queryBuilder2.getManyAndCount()
|
||||
}
|
||||
|
||||
return {
|
||||
...dataset,
|
||||
rows: items
|
||||
rows: data,
|
||||
total
|
||||
}
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: datasetService.getDataset - ${getErrorMessage(error)}`)
|
||||
|
|
|
|||
|
|
@ -77,11 +77,26 @@ const createDocumentStore = async (newDocumentStore: DocumentStore, orgId: strin
|
|||
}
|
||||
}
|
||||
|
||||
const getAllDocumentStores = async (workspaceId?: string) => {
|
||||
const getAllDocumentStores = async (workspaceId?: string, page: number = -1, limit: number = -1) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const entities = await appServer.AppDataSource.getRepository(DocumentStore).findBy(getWorkspaceSearchOptions(workspaceId))
|
||||
return entities
|
||||
const queryBuilder = appServer.AppDataSource.getRepository(DocumentStore)
|
||||
.createQueryBuilder('doc_store')
|
||||
.orderBy('doc_store.updatedDate', 'DESC')
|
||||
|
||||
if (page > 0 && limit > 0) {
|
||||
queryBuilder.skip((page - 1) * limit)
|
||||
queryBuilder.take(limit)
|
||||
}
|
||||
if (workspaceId) queryBuilder.andWhere('doc_store.workspaceId = :workspaceId', { workspaceId })
|
||||
|
||||
const [data, total] = await queryBuilder.getManyAndCount()
|
||||
|
||||
if (page > 0 && limit > 0) {
|
||||
return { data, total }
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
|
|
|
|||
|
|
@ -338,42 +338,80 @@ const createEvaluation = async (body: ICommonObject, baseURL: string, orgId: str
|
|||
}
|
||||
}
|
||||
|
||||
const getAllEvaluations = async (workspaceId?: string) => {
|
||||
const getAllEvaluations = async (workspaceId?: string, page: number = -1, limit: number = -1) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const findAndOrderBy: any = {
|
||||
where: getWorkspaceSearchOptions(workspaceId),
|
||||
order: {
|
||||
runDate: 'DESC'
|
||||
}
|
||||
}
|
||||
const evaluations = await appServer.AppDataSource.getRepository(Evaluation).find(findAndOrderBy)
|
||||
|
||||
// First, get the count of distinct evaluation names for the total
|
||||
// needed as the The getCount() method in TypeORM doesn't respect the GROUP BY clause and will return the total count of records
|
||||
const countQuery = appServer.AppDataSource.getRepository(Evaluation)
|
||||
.createQueryBuilder('ev')
|
||||
.select('COUNT(DISTINCT(ev.name))', 'count')
|
||||
.where('ev.workspaceId = :workspaceId', { workspaceId: workspaceId })
|
||||
|
||||
const totalResult = await countQuery.getRawOne()
|
||||
const total = totalResult ? parseInt(totalResult.count) : 0
|
||||
|
||||
// Then get the distinct evaluation names with their counts and latest run date
|
||||
const namesQueryBuilder = appServer.AppDataSource.getRepository(Evaluation)
|
||||
.createQueryBuilder('ev')
|
||||
.select('DISTINCT(ev.name)', 'name')
|
||||
.addSelect('COUNT(ev.name)', 'count')
|
||||
.addSelect('MAX(ev.runDate)', 'latestRunDate')
|
||||
.andWhere('ev.workspaceId = :workspaceId', { workspaceId: workspaceId })
|
||||
.groupBy('ev.name')
|
||||
.orderBy('max(ev.runDate)', 'DESC') // Order by the latest run date
|
||||
|
||||
if (page > 0 && limit > 0) {
|
||||
namesQueryBuilder.skip((page - 1) * limit)
|
||||
namesQueryBuilder.take(limit)
|
||||
}
|
||||
|
||||
const evaluationNames = await namesQueryBuilder.getRawMany()
|
||||
// Get all evaluations for all names at once in a single query
|
||||
const returnResults: IEvaluationResult[] = []
|
||||
// mark the first evaluation with a unique name as the latestEval and then reset the version number
|
||||
for (let i = 0; i < evaluations.length; i++) {
|
||||
const evaluation = evaluations[i] as IEvaluationResult
|
||||
returnResults.push(evaluation)
|
||||
// find the first index with this name in the evaluations array
|
||||
// as it is sorted desc, make the first evaluation with this name as the latestEval
|
||||
const currentIndex = evaluations.indexOf(evaluation)
|
||||
if (evaluations.findIndex((e) => e.name === evaluation.name) === currentIndex) {
|
||||
returnResults[i].latestEval = true
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < returnResults.length; i++) {
|
||||
const evaluation = returnResults[i]
|
||||
if (evaluation.latestEval) {
|
||||
const versions = returnResults.filter((e) => e.name === evaluation.name)
|
||||
let descVersion = versions.length
|
||||
for (let j = 0; j < versions.length; j++) {
|
||||
versions[j].version = descVersion--
|
||||
if (evaluationNames.length > 0) {
|
||||
const names = evaluationNames.map((item) => item.name)
|
||||
// Fetch all evaluations for these names in a single query
|
||||
const allEvaluations = await appServer.AppDataSource.getRepository(Evaluation)
|
||||
.createQueryBuilder('ev')
|
||||
.where('ev.name IN (:...names)', { names })
|
||||
.andWhere('ev.workspaceId = :workspaceId', { workspaceId })
|
||||
.orderBy('ev.name', 'ASC')
|
||||
.addOrderBy('ev.runDate', 'DESC')
|
||||
.getMany()
|
||||
|
||||
// Process the results by name
|
||||
const evaluationsByName = new Map<string, Evaluation[]>()
|
||||
// Group evaluations by name
|
||||
for (const evaluation of allEvaluations) {
|
||||
if (!evaluationsByName.has(evaluation.name)) {
|
||||
evaluationsByName.set(evaluation.name, [])
|
||||
}
|
||||
evaluationsByName.get(evaluation.name)!.push(evaluation)
|
||||
}
|
||||
|
||||
// Process each name's evaluations
|
||||
for (const item of evaluationNames) {
|
||||
const evaluationsForName = evaluationsByName.get(item.name) || []
|
||||
for (let i = 0; i < evaluationsForName.length; i++) {
|
||||
const evaluation = evaluationsForName[i] as IEvaluationResult
|
||||
evaluation.latestEval = i === 0
|
||||
evaluation.version = parseInt(item.count) - i
|
||||
returnResults.push(evaluation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnResults
|
||||
if (page > 0 && limit > 0) {
|
||||
return {
|
||||
total: total,
|
||||
data: returnResults
|
||||
}
|
||||
} else {
|
||||
return returnResults
|
||||
}
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
|
|
|
|||
|
|
@ -4,13 +4,25 @@ import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
|||
import { getErrorMessage } from '../../errors/utils'
|
||||
import { Evaluator } from '../../database/entities/Evaluator'
|
||||
import { EvaluatorDTO } from '../../Interface.Evaluation'
|
||||
import { getWorkspaceSearchOptions } from '../../enterprise/utils/ControllerServiceUtils'
|
||||
|
||||
const getAllEvaluators = async (workspaceId?: string) => {
|
||||
const getAllEvaluators = async (workspaceId?: string, page: number = -1, limit: number = -1) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const results: Evaluator[] = await appServer.AppDataSource.getRepository(Evaluator).findBy(getWorkspaceSearchOptions(workspaceId))
|
||||
return EvaluatorDTO.fromEntities(results)
|
||||
const queryBuilder = appServer.AppDataSource.getRepository(Evaluator).createQueryBuilder('ev').orderBy('ev.updatedDate', 'DESC')
|
||||
if (workspaceId) queryBuilder.andWhere('ev.workspaceId = :workspaceId', { workspaceId })
|
||||
if (page > 0 && limit > 0) {
|
||||
queryBuilder.skip((page - 1) * limit)
|
||||
queryBuilder.take(limit)
|
||||
}
|
||||
const [data, total] = await queryBuilder.getManyAndCount()
|
||||
if (page > 0 && limit > 0) {
|
||||
return {
|
||||
total,
|
||||
data: EvaluatorDTO.fromEntities(data)
|
||||
}
|
||||
} else {
|
||||
return EvaluatorDTO.fromEntities(data)
|
||||
}
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ const getPublicExecutionById = async (executionId: string): Promise<Execution |
|
|||
const getAllExecutions = async (filters: ExecutionFilters = {}): Promise<{ data: Execution[]; total: number }> => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const { id, agentflowId, sessionId, state, startDate, endDate, page = 1, limit = 10, workspaceId } = filters
|
||||
const { id, agentflowId, sessionId, state, startDate, endDate, page = 1, limit = 12, workspaceId } = filters
|
||||
|
||||
// Handle UUID fields properly using raw parameters to avoid type conversion issues
|
||||
// This uses the query builder instead of direct objects for compatibility with UUID fields
|
||||
|
|
|
|||
|
|
@ -90,16 +90,20 @@ const convertExportInput = (body: any): ExportInput => {
|
|||
const FileDefaultName = 'ExportData.json'
|
||||
const exportData = async (exportInput: ExportInput, activeWorkspaceId?: string): Promise<{ FileDefaultName: string } & ExportData> => {
|
||||
try {
|
||||
let AgentFlow: ChatFlow[] =
|
||||
let AgentFlow: ChatFlow[] | { data: ChatFlow[]; total: number } =
|
||||
exportInput.agentflow === true ? await chatflowService.getAllChatflows('MULTIAGENT', activeWorkspaceId) : []
|
||||
AgentFlow = 'data' in AgentFlow ? AgentFlow.data : AgentFlow
|
||||
|
||||
let AgentFlowV2: ChatFlow[] =
|
||||
let AgentFlowV2: ChatFlow[] | { data: ChatFlow[]; total: number } =
|
||||
exportInput.agentflowv2 === true ? await chatflowService.getAllChatflows('AGENTFLOW', activeWorkspaceId) : []
|
||||
AgentFlowV2 = 'data' in AgentFlowV2 ? AgentFlowV2.data : AgentFlowV2
|
||||
|
||||
let AssistantCustom: Assistant[] =
|
||||
exportInput.assistantCustom === true ? await assistantService.getAllAssistants('CUSTOM', activeWorkspaceId) : []
|
||||
let AssistantFlow: ChatFlow[] =
|
||||
|
||||
let AssistantFlow: ChatFlow[] | { data: ChatFlow[]; total: number } =
|
||||
exportInput.assistantCustom === true ? await chatflowService.getAllChatflows('ASSISTANT', activeWorkspaceId) : []
|
||||
AssistantFlow = 'data' in AssistantFlow ? AssistantFlow.data : AssistantFlow
|
||||
|
||||
let AssistantOpenAI: Assistant[] =
|
||||
exportInput.assistantOpenAI === true ? await assistantService.getAllAssistants('OPENAI', activeWorkspaceId) : []
|
||||
|
|
@ -107,12 +111,15 @@ const exportData = async (exportInput: ExportInput, activeWorkspaceId?: string):
|
|||
let AssistantAzure: Assistant[] =
|
||||
exportInput.assistantAzure === true ? await assistantService.getAllAssistants('AZURE', activeWorkspaceId) : []
|
||||
|
||||
let ChatFlow: ChatFlow[] = exportInput.chatflow === true ? await chatflowService.getAllChatflows('CHATFLOW', activeWorkspaceId) : []
|
||||
let ChatFlow: ChatFlow[] | { data: ChatFlow[]; total: number } =
|
||||
exportInput.chatflow === true ? await chatflowService.getAllChatflows('CHATFLOW', activeWorkspaceId) : []
|
||||
ChatFlow = 'data' in ChatFlow ? ChatFlow.data : ChatFlow
|
||||
|
||||
const allChatflow: ChatFlow[] =
|
||||
let allChatflow: ChatFlow[] | { data: ChatFlow[]; total: number } =
|
||||
exportInput.chat_message === true || exportInput.chat_feedback === true
|
||||
? await chatflowService.getAllChatflows(undefined, activeWorkspaceId)
|
||||
: []
|
||||
allChatflow = 'data' in allChatflow ? allChatflow.data : allChatflow
|
||||
const chatflowIds = allChatflow.map((chatflow) => chatflow.id)
|
||||
|
||||
let ChatMessage: ChatMessage[] =
|
||||
|
|
@ -124,8 +131,10 @@ const exportData = async (exportInput: ExportInput, activeWorkspaceId?: string):
|
|||
let CustomTemplate: CustomTemplate[] =
|
||||
exportInput.custom_template === true ? await marketplacesService.getAllCustomTemplates(activeWorkspaceId) : []
|
||||
|
||||
let DocumentStore: DocumentStore[] =
|
||||
let DocumentStore: DocumentStore[] | { data: DocumentStore[]; total: number } =
|
||||
exportInput.document_store === true ? await documenStoreService.getAllDocumentStores(activeWorkspaceId) : []
|
||||
DocumentStore = 'data' in DocumentStore ? DocumentStore.data : DocumentStore
|
||||
|
||||
const documentStoreIds = DocumentStore.map((documentStore) => documentStore.id)
|
||||
|
||||
let DocumentStoreFileChunk: DocumentStoreFileChunk[] =
|
||||
|
|
@ -137,9 +146,13 @@ const exportData = async (exportInput: ExportInput, activeWorkspaceId?: string):
|
|||
const { data: totalExecutions } = exportInput.execution === true ? await executionService.getAllExecutions(filters) : { data: [] }
|
||||
let Execution: Execution[] = exportInput.execution === true ? totalExecutions : []
|
||||
|
||||
let Tool: Tool[] = exportInput.tool === true ? await toolsService.getAllTools(activeWorkspaceId) : []
|
||||
let Tool: Tool[] | { data: Tool[]; total: number } =
|
||||
exportInput.tool === true ? await toolsService.getAllTools(activeWorkspaceId) : []
|
||||
Tool = 'data' in Tool ? Tool.data : Tool
|
||||
|
||||
let Variable: Variable[] = exportInput.variable === true ? await variableService.getAllVariables(activeWorkspaceId) : []
|
||||
let Variable: Variable[] | { data: Variable[]; total: number } =
|
||||
exportInput.variable === true ? await variableService.getAllVariables(activeWorkspaceId) : []
|
||||
Variable = 'data' in Variable ? Variable.data : Variable
|
||||
|
||||
return {
|
||||
FileDefaultName,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ const getChatflowStats = async (
|
|||
endDate?: string,
|
||||
messageId?: string,
|
||||
feedback?: boolean,
|
||||
feedbackTypes?: ChatMessageRatingType[]
|
||||
feedbackTypes?: ChatMessageRatingType[],
|
||||
activeWorkspaceId?: string
|
||||
): Promise<any> => {
|
||||
try {
|
||||
const chatmessages = (await utilGetChatMessage({
|
||||
|
|
@ -24,15 +25,20 @@ const getChatflowStats = async (
|
|||
endDate,
|
||||
messageId,
|
||||
feedback,
|
||||
feedbackTypes
|
||||
feedbackTypes,
|
||||
activeWorkspaceId
|
||||
})) as Array<ChatMessage & { feedback?: ChatMessageFeedback }>
|
||||
const totalMessages = chatmessages.length
|
||||
const totalFeedback = chatmessages.filter((message) => message?.feedback).length
|
||||
const positiveFeedback = chatmessages.filter((message) => message?.feedback?.rating === 'THUMBS_UP').length
|
||||
// count the number of unique sessions in the chatmessages - count unique sessionId
|
||||
const uniqueSessions = new Set(chatmessages.map((message) => message.sessionId))
|
||||
const totalSessions = uniqueSessions.size
|
||||
const dbResponse = {
|
||||
totalMessages,
|
||||
totalFeedback,
|
||||
positiveFeedback
|
||||
positiveFeedback,
|
||||
totalSessions
|
||||
}
|
||||
|
||||
return dbResponse
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { Tool } from '../../database/entities/Tool'
|
|||
import { getAppVersion } from '../../utils'
|
||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { getErrorMessage } from '../../errors/utils'
|
||||
import { getWorkspaceSearchOptions } from '../../enterprise/utils/ControllerServiceUtils'
|
||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||
import { FLOWISE_METRIC_COUNTERS, FLOWISE_COUNTER_STATUS } from '../../Interface.Metrics'
|
||||
import { QueryRunner } from 'typeorm'
|
||||
|
|
@ -44,11 +43,23 @@ const deleteTool = async (toolId: string): Promise<any> => {
|
|||
}
|
||||
}
|
||||
|
||||
const getAllTools = async (workspaceId?: string): Promise<Tool[]> => {
|
||||
const getAllTools = async (workspaceId?: string, page: number = -1, limit: number = -1) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const dbResponse = await appServer.AppDataSource.getRepository(Tool).findBy(getWorkspaceSearchOptions(workspaceId))
|
||||
return dbResponse
|
||||
const queryBuilder = appServer.AppDataSource.getRepository(Tool).createQueryBuilder('tool').orderBy('tool.updatedDate', 'DESC')
|
||||
|
||||
if (page > 0 && limit > 0) {
|
||||
queryBuilder.skip((page - 1) * limit)
|
||||
queryBuilder.take(limit)
|
||||
}
|
||||
if (workspaceId) queryBuilder.andWhere('tool.workspaceId = :workspaceId', { workspaceId })
|
||||
const [data, total] = await queryBuilder.getManyAndCount()
|
||||
|
||||
if (page > 0 && limit > 0) {
|
||||
return { data, total }
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: toolsService.getAllTools - ${getErrorMessage(error)}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { Variable } from '../../database/entities/Variable'
|
|||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||
import { getErrorMessage } from '../../errors/utils'
|
||||
import { getAppVersion } from '../../utils'
|
||||
import { getWorkspaceSearchOptions } from '../../enterprise/utils/ControllerServiceUtils'
|
||||
import { QueryRunner } from 'typeorm'
|
||||
import { validate } from 'uuid'
|
||||
|
||||
|
|
@ -44,11 +43,26 @@ const deleteVariable = async (variableId: string): Promise<any> => {
|
|||
}
|
||||
}
|
||||
|
||||
const getAllVariables = async (workspaceId?: string) => {
|
||||
const getAllVariables = async (workspaceId?: string, page: number = -1, limit: number = -1) => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const dbResponse = await appServer.AppDataSource.getRepository(Variable).findBy(getWorkspaceSearchOptions(workspaceId))
|
||||
return dbResponse
|
||||
const queryBuilder = appServer.AppDataSource.getRepository(Variable)
|
||||
.createQueryBuilder('variable')
|
||||
.orderBy('variable.updatedDate', 'DESC')
|
||||
|
||||
if (page > 0 && limit > 0) {
|
||||
queryBuilder.skip((page - 1) * limit)
|
||||
queryBuilder.take(limit)
|
||||
}
|
||||
if (workspaceId) queryBuilder.andWhere('variable.workspaceId = :workspaceId', { workspaceId })
|
||||
|
||||
const [data, total] = await queryBuilder.getManyAndCount()
|
||||
|
||||
if (page > 0 && limit > 0) {
|
||||
return { data, total }
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
} catch (error) {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ import {
|
|||
constructGraphs,
|
||||
getAPIOverrideConfig
|
||||
} from '../utils'
|
||||
import { validateChatflowAPIKey } from './validateKey'
|
||||
import { validateFlowAPIKey } from './validateKey'
|
||||
import logger from './logger'
|
||||
import { utilAddChatMessage } from './addChatMesage'
|
||||
import { checkPredictions, checkStorage, updatePredictionsUsage, updateStorageUsage } from './quotaUsage'
|
||||
|
|
@ -923,7 +923,7 @@ export const utilBuildChatflow = async (req: Request, isInternal: boolean = fals
|
|||
try {
|
||||
// Validate API Key if its external API request
|
||||
if (!isInternal) {
|
||||
const isKeyValidated = await validateChatflowAPIKey(req, chatflow)
|
||||
const isKeyValidated = await validateFlowAPIKey(req, chatflow)
|
||||
if (!isKeyValidated) {
|
||||
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { ChatMessage } from '../database/entities/ChatMessage'
|
|||
import { ChatMessageFeedback } from '../database/entities/ChatMessageFeedback'
|
||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
||||
import { aMonthAgo } from '.'
|
||||
|
||||
/**
|
||||
* Method that get chat messages.
|
||||
|
|
@ -19,6 +18,7 @@ import { aMonthAgo } from '.'
|
|||
* @param {boolean} feedback
|
||||
* @param {ChatMessageRatingType[]} feedbackTypes
|
||||
*/
|
||||
|
||||
interface GetChatMessageParams {
|
||||
chatflowid: string
|
||||
chatTypes?: ChatType[]
|
||||
|
|
@ -32,6 +32,8 @@ interface GetChatMessageParams {
|
|||
feedback?: boolean
|
||||
feedbackTypes?: ChatMessageRatingType[]
|
||||
activeWorkspaceId?: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export const utilGetChatMessage = async ({
|
||||
|
|
@ -46,72 +48,44 @@ export const utilGetChatMessage = async ({
|
|||
messageId,
|
||||
feedback,
|
||||
feedbackTypes,
|
||||
activeWorkspaceId
|
||||
activeWorkspaceId,
|
||||
page = -1,
|
||||
pageSize = -1
|
||||
}: GetChatMessageParams): Promise<ChatMessage[]> => {
|
||||
if (!page) page = -1
|
||||
if (!pageSize) pageSize = -1
|
||||
|
||||
const appServer = getRunningExpressApp()
|
||||
|
||||
// Check if chatflow workspaceId is same as activeWorkspaceId
|
||||
if (activeWorkspaceId) {
|
||||
const chatflow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
|
||||
id: chatflowid
|
||||
id: chatflowid,
|
||||
workspaceId: activeWorkspaceId
|
||||
})
|
||||
if (chatflow?.workspaceId !== activeWorkspaceId) {
|
||||
if (!chatflow) {
|
||||
throw new Error('Unauthorized access')
|
||||
}
|
||||
} else {
|
||||
throw new Error('Unauthorized access')
|
||||
}
|
||||
|
||||
if (feedback) {
|
||||
const query = await appServer.AppDataSource.getRepository(ChatMessage).createQueryBuilder('chat_message')
|
||||
|
||||
// do the join with chat message feedback based on messageId for each chat message in the chatflow
|
||||
query
|
||||
.leftJoinAndSelect('chat_message.execution', 'execution')
|
||||
.leftJoinAndMapOne('chat_message.feedback', ChatMessageFeedback, 'feedback', 'feedback.messageId = chat_message.id')
|
||||
.where('chat_message.chatflowid = :chatflowid', { chatflowid })
|
||||
|
||||
// based on which parameters are available add `andWhere` clauses to the query
|
||||
if (chatTypes && chatTypes.length > 0) {
|
||||
query.andWhere('chat_message.chatType IN (:...chatTypes)', { chatTypes })
|
||||
}
|
||||
if (chatId) {
|
||||
query.andWhere('chat_message.chatId = :chatId', { chatId })
|
||||
}
|
||||
if (memoryType) {
|
||||
query.andWhere('chat_message.memoryType = :memoryType', { memoryType })
|
||||
}
|
||||
if (sessionId) {
|
||||
query.andWhere('chat_message.sessionId = :sessionId', { sessionId })
|
||||
}
|
||||
|
||||
// set date range
|
||||
if (startDate) {
|
||||
query.andWhere('chat_message.createdDate >= :startDateTime', { startDateTime: startDate ? new Date(startDate) : aMonthAgo() })
|
||||
}
|
||||
if (endDate) {
|
||||
query.andWhere('chat_message.createdDate <= :endDateTime', { endDateTime: endDate ? new Date(endDate) : new Date() })
|
||||
}
|
||||
|
||||
// sort
|
||||
query.orderBy('chat_message.createdDate', sortOrder === 'DESC' ? 'DESC' : 'ASC')
|
||||
|
||||
const messages = (await query.getMany()) as Array<ChatMessage & { feedback: ChatMessageFeedback }>
|
||||
|
||||
if (feedbackTypes && feedbackTypes.length > 0) {
|
||||
// just applying a filter to the messages array will only return the messages that have feedback,
|
||||
// but we also want the message before the feedback message which is the user message.
|
||||
const indicesToKeep = new Set()
|
||||
|
||||
messages.forEach((message, index) => {
|
||||
if (message.role === 'apiMessage' && message.feedback && feedbackTypes.includes(message.feedback.rating)) {
|
||||
if (index > 0) indicesToKeep.add(index - 1)
|
||||
indicesToKeep.add(index)
|
||||
}
|
||||
})
|
||||
|
||||
return messages.filter((_, index) => indicesToKeep.has(index))
|
||||
}
|
||||
|
||||
return messages
|
||||
// Handle feedback queries with improved efficiency
|
||||
return await handleFeedbackQuery({
|
||||
chatflowid,
|
||||
chatTypes,
|
||||
sortOrder,
|
||||
chatId,
|
||||
memoryType,
|
||||
sessionId,
|
||||
startDate,
|
||||
endDate,
|
||||
messageId,
|
||||
feedbackTypes,
|
||||
page,
|
||||
pageSize
|
||||
})
|
||||
}
|
||||
|
||||
let createdDateQuery
|
||||
|
|
@ -146,3 +120,226 @@ export const utilGetChatMessage = async ({
|
|||
|
||||
return messages
|
||||
}
|
||||
|
||||
async function handleFeedbackQuery(params: {
|
||||
chatflowid: string
|
||||
chatTypes?: ChatType[]
|
||||
sortOrder: string
|
||||
chatId?: string
|
||||
memoryType?: string
|
||||
sessionId?: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
messageId?: string
|
||||
feedbackTypes?: ChatMessageRatingType[]
|
||||
page: number
|
||||
pageSize: number
|
||||
}): Promise<ChatMessage[]> {
|
||||
const {
|
||||
chatflowid,
|
||||
chatTypes,
|
||||
sortOrder,
|
||||
chatId,
|
||||
memoryType,
|
||||
sessionId,
|
||||
startDate,
|
||||
endDate,
|
||||
messageId,
|
||||
feedbackTypes,
|
||||
page,
|
||||
pageSize
|
||||
} = params
|
||||
|
||||
const appServer = getRunningExpressApp()
|
||||
|
||||
// For specific session/message queries, no pagination needed
|
||||
if (sessionId || messageId) {
|
||||
return await getMessagesWithFeedback(params, false)
|
||||
}
|
||||
|
||||
// For paginated queries, handle session-based pagination efficiently
|
||||
if (page > -1 && pageSize > -1) {
|
||||
// First get session IDs with pagination
|
||||
const sessionQuery = appServer.AppDataSource.getRepository(ChatMessage)
|
||||
.createQueryBuilder('chat_message')
|
||||
.select('DISTINCT chat_message.sessionId', 'sessionId')
|
||||
.where('chat_message.chatflowid = :chatflowid', { chatflowid })
|
||||
|
||||
// Apply basic filters
|
||||
if (chatTypes && chatTypes.length > 0) {
|
||||
sessionQuery.andWhere('chat_message.chatType IN (:...chatTypes)', { chatTypes })
|
||||
}
|
||||
if (chatId) {
|
||||
sessionQuery.andWhere('chat_message.chatId = :chatId', { chatId })
|
||||
}
|
||||
if (memoryType) {
|
||||
sessionQuery.andWhere('chat_message.memoryType = :memoryType', { memoryType })
|
||||
}
|
||||
if (startDate && typeof startDate === 'string') {
|
||||
sessionQuery.andWhere('chat_message.createdDate >= :startDateTime', {
|
||||
startDateTime: new Date(startDate)
|
||||
})
|
||||
}
|
||||
if (endDate && typeof endDate === 'string') {
|
||||
sessionQuery.andWhere('chat_message.createdDate <= :endDateTime', {
|
||||
endDateTime: new Date(endDate)
|
||||
})
|
||||
}
|
||||
|
||||
// If feedback types are specified, only get sessions with those feedback types
|
||||
if (feedbackTypes && feedbackTypes.length > 0) {
|
||||
sessionQuery
|
||||
.leftJoin(ChatMessageFeedback, 'feedback', 'feedback.messageId = chat_message.id')
|
||||
.andWhere('feedback.rating IN (:...feedbackTypes)', { feedbackTypes })
|
||||
}
|
||||
|
||||
const startIndex = pageSize * (page - 1)
|
||||
const sessionIds = await sessionQuery
|
||||
.orderBy('MAX(chat_message.createdDate)', sortOrder === 'DESC' ? 'DESC' : 'ASC')
|
||||
.groupBy('chat_message.sessionId')
|
||||
.offset(startIndex)
|
||||
.limit(pageSize)
|
||||
.getRawMany()
|
||||
|
||||
if (sessionIds.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Get all messages for these sessions
|
||||
const sessionIdList = sessionIds.map((s) => s.sessionId)
|
||||
return await getMessagesWithFeedback(
|
||||
{
|
||||
...params,
|
||||
sessionId: undefined // Clear specific sessionId since we're using list
|
||||
},
|
||||
true,
|
||||
sessionIdList
|
||||
)
|
||||
}
|
||||
|
||||
// No pagination - get all feedback messages
|
||||
return await getMessagesWithFeedback(params, false)
|
||||
}
|
||||
|
||||
async function getMessagesWithFeedback(
|
||||
params: {
|
||||
chatflowid: string
|
||||
chatTypes?: ChatType[]
|
||||
sortOrder: string
|
||||
chatId?: string
|
||||
memoryType?: string
|
||||
sessionId?: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
messageId?: string
|
||||
feedbackTypes?: ChatMessageRatingType[]
|
||||
},
|
||||
useSessionList: boolean = false,
|
||||
sessionIdList?: string[]
|
||||
): Promise<ChatMessage[]> {
|
||||
const { chatflowid, chatTypes, sortOrder, chatId, memoryType, sessionId, startDate, endDate, messageId, feedbackTypes } = params
|
||||
|
||||
const appServer = getRunningExpressApp()
|
||||
const query = appServer.AppDataSource.getRepository(ChatMessage).createQueryBuilder('chat_message')
|
||||
|
||||
query
|
||||
.leftJoinAndSelect('chat_message.execution', 'execution')
|
||||
.leftJoinAndMapOne('chat_message.feedback', ChatMessageFeedback, 'feedback', 'feedback.messageId = chat_message.id')
|
||||
.where('chat_message.chatflowid = :chatflowid', { chatflowid })
|
||||
|
||||
// Apply filters
|
||||
if (useSessionList && sessionIdList && sessionIdList.length > 0) {
|
||||
query.andWhere('chat_message.sessionId IN (:...sessionIds)', { sessionIds: sessionIdList })
|
||||
}
|
||||
|
||||
if (chatTypes && chatTypes.length > 0) {
|
||||
query.andWhere('chat_message.chatType IN (:...chatTypes)', { chatTypes })
|
||||
}
|
||||
if (chatId) {
|
||||
query.andWhere('chat_message.chatId = :chatId', { chatId })
|
||||
}
|
||||
if (memoryType) {
|
||||
query.andWhere('chat_message.memoryType = :memoryType', { memoryType })
|
||||
}
|
||||
if (sessionId) {
|
||||
query.andWhere('chat_message.sessionId = :sessionId', { sessionId })
|
||||
}
|
||||
if (messageId) {
|
||||
query.andWhere('chat_message.id = :messageId', { messageId })
|
||||
}
|
||||
if (startDate && typeof startDate === 'string') {
|
||||
query.andWhere('chat_message.createdDate >= :startDateTime', {
|
||||
startDateTime: new Date(startDate)
|
||||
})
|
||||
}
|
||||
if (endDate && typeof endDate === 'string') {
|
||||
query.andWhere('chat_message.createdDate <= :endDateTime', {
|
||||
endDateTime: new Date(endDate)
|
||||
})
|
||||
}
|
||||
|
||||
// Pre-filter by feedback types if specified (more efficient than post-processing)
|
||||
if (feedbackTypes && feedbackTypes.length > 0) {
|
||||
query.andWhere('(feedback.rating IN (:...feedbackTypes) OR feedback.rating IS NULL)', { feedbackTypes })
|
||||
}
|
||||
|
||||
query.orderBy('chat_message.createdDate', sortOrder === 'DESC' ? 'DESC' : 'ASC')
|
||||
|
||||
const messages = (await query.getMany()) as Array<ChatMessage & { feedback: ChatMessageFeedback }>
|
||||
|
||||
// Apply feedback type filtering with previous message inclusion
|
||||
if (feedbackTypes && feedbackTypes.length > 0) {
|
||||
return filterMessagesWithFeedback(messages, feedbackTypes)
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
function filterMessagesWithFeedback(
|
||||
messages: Array<ChatMessage & { feedback: ChatMessageFeedback }>,
|
||||
feedbackTypes: ChatMessageRatingType[]
|
||||
): ChatMessage[] {
|
||||
// Group messages by session for proper filtering
|
||||
const sessionGroups = new Map<string, Array<ChatMessage & { feedback: ChatMessageFeedback }>>()
|
||||
|
||||
messages.forEach((message) => {
|
||||
const sessionId = message.sessionId
|
||||
if (!sessionId) return // Skip messages without sessionId
|
||||
|
||||
if (!sessionGroups.has(sessionId)) {
|
||||
sessionGroups.set(sessionId, [])
|
||||
}
|
||||
sessionGroups.get(sessionId)!.push(message)
|
||||
})
|
||||
|
||||
const result: ChatMessage[] = []
|
||||
|
||||
// Process each session group
|
||||
sessionGroups.forEach((sessionMessages) => {
|
||||
// Sort by creation date to ensure proper order
|
||||
sessionMessages.sort((a, b) => new Date(a.createdDate).getTime() - new Date(b.createdDate).getTime())
|
||||
|
||||
const toInclude = new Set<number>()
|
||||
|
||||
sessionMessages.forEach((message, index) => {
|
||||
if (message.role === 'apiMessage' && message.feedback && feedbackTypes.includes(message.feedback.rating)) {
|
||||
// Include the feedback message
|
||||
toInclude.add(index)
|
||||
// Include the previous message (user message) if it exists
|
||||
if (index > 0) {
|
||||
toInclude.add(index - 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Add filtered messages to result
|
||||
sessionMessages.forEach((message, index) => {
|
||||
if (toInclude.has(index)) {
|
||||
result.push(message)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Sort final result by creation date
|
||||
return result.sort((a, b) => new Date(a.createdDate).getTime() - new Date(b.createdDate).getTime())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { InternalFlowiseError } from '../errors/internalFlowiseError'
|
||||
import { StatusCodes } from 'http-status-codes'
|
||||
import { Request } from 'express'
|
||||
|
||||
type Pagination = {
|
||||
page: number
|
||||
limit: number
|
||||
}
|
||||
|
||||
export const getPageAndLimitParams = (req: Request): Pagination => {
|
||||
// by default assume no pagination
|
||||
let page = -1
|
||||
let limit = -1
|
||||
if (req.query.page) {
|
||||
// if page is provided, make sure it's a positive number
|
||||
page = parseInt(req.query.page as string)
|
||||
if (page < 0) {
|
||||
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: page cannot be negative!`)
|
||||
}
|
||||
}
|
||||
if (req.query.limit) {
|
||||
// if limit is provided, make sure it's a positive number
|
||||
limit = parseInt(req.query.limit as string)
|
||||
if (limit < 0) {
|
||||
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: limit cannot be negative!`)
|
||||
}
|
||||
}
|
||||
return { page, limit }
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ import {
|
|||
getStartingNodes,
|
||||
getAPIOverrideConfig
|
||||
} from '../utils'
|
||||
import { validateChatflowAPIKey } from './validateKey'
|
||||
import { validateFlowAPIKey } from './validateKey'
|
||||
import { IncomingInput, INodeDirectedGraph, IReactFlowObject, ChatType, IExecuteFlowParams, MODE } from '../Interface'
|
||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
||||
|
|
@ -251,7 +251,7 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
|
|||
const files = (req.files as Express.Multer.File[]) || []
|
||||
|
||||
if (!isInternal) {
|
||||
const isKeyValidated = await validateChatflowAPIKey(req, chatflow)
|
||||
const isKeyValidated = await validateFlowAPIKey(req, chatflow)
|
||||
if (!isKeyValidated) {
|
||||
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import { Request } from 'express'
|
||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||
import { ApiKey } from '../database/entities/ApiKey'
|
||||
import { compareKeys } from './apiKey'
|
||||
import apikeyService from '../services/apikey'
|
||||
|
||||
/**
|
||||
* Validate Chatflow API Key
|
||||
* Validate flow API Key, this is needed because Prediction/Upsert API is public
|
||||
* @param {Request} req
|
||||
* @param {ChatFlow} chatflow
|
||||
*/
|
||||
export const validateChatflowAPIKey = async (req: Request, chatflow: ChatFlow) => {
|
||||
export const validateFlowAPIKey = async (req: Request, chatflow: ChatFlow): Promise<boolean> => {
|
||||
const chatFlowApiKeyId = chatflow?.apikeyid
|
||||
if (!chatFlowApiKeyId) return true
|
||||
|
||||
|
|
@ -16,48 +17,52 @@ export const validateChatflowAPIKey = async (req: Request, chatflow: ChatFlow) =
|
|||
if (chatFlowApiKeyId && !authorizationHeader) return false
|
||||
|
||||
const suppliedKey = authorizationHeader.split(`Bearer `).pop()
|
||||
if (suppliedKey) {
|
||||
const keys = await apikeyService.getAllApiKeys()
|
||||
const apiSecret = keys.find((key: any) => key.id === chatFlowApiKeyId)?.apiSecret
|
||||
if (!apiSecret) return false
|
||||
if (!compareKeys(apiSecret, suppliedKey)) return false
|
||||
if (!suppliedKey) return false
|
||||
|
||||
try {
|
||||
const apiKey = await apikeyService.getApiKeyById(chatFlowApiKeyId)
|
||||
if (!apiKey) return false
|
||||
|
||||
const apiKeyWorkSpaceId = apiKey.workspaceId
|
||||
if (!apiKeyWorkSpaceId) return false
|
||||
|
||||
if (apiKeyWorkSpaceId !== chatflow.workspaceId) return false
|
||||
|
||||
const apiSecret = apiKey.apiSecret
|
||||
if (!apiSecret || !compareKeys(apiSecret, suppliedKey)) return false
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate API Key
|
||||
* Validate and Get API Key Information
|
||||
* @param {Request} req
|
||||
* @returns {Promise<{isValid: boolean, apiKey?: ApiKey, workspaceId?: string}>}
|
||||
*/
|
||||
export const validateAPIKey = async (req: Request) => {
|
||||
export const validateAPIKey = async (req: Request): Promise<{ isValid: boolean; apiKey?: ApiKey; workspaceId?: string }> => {
|
||||
const authorizationHeader = (req.headers['Authorization'] as string) ?? (req.headers['authorization'] as string) ?? ''
|
||||
if (!authorizationHeader) return false
|
||||
if (!authorizationHeader) return { isValid: false }
|
||||
|
||||
const suppliedKey = authorizationHeader.split(`Bearer `).pop()
|
||||
if (!suppliedKey) return { isValid: false }
|
||||
|
||||
if (suppliedKey) {
|
||||
const keys = await apikeyService.getAllApiKeys()
|
||||
const apiSecret = keys.find((key: any) => key.apiKey === suppliedKey)?.apiSecret
|
||||
if (!apiSecret) return false
|
||||
if (!compareKeys(apiSecret, suppliedKey)) return false
|
||||
return true
|
||||
try {
|
||||
const apiKey = await apikeyService.getApiKey(suppliedKey)
|
||||
if (!apiKey) return { isValid: false }
|
||||
|
||||
const apiKeyWorkSpaceId = apiKey.workspaceId
|
||||
if (!apiKeyWorkSpaceId) return { isValid: false }
|
||||
|
||||
const apiSecret = apiKey.apiSecret
|
||||
if (!apiSecret || !compareKeys(apiSecret, suppliedKey)) {
|
||||
return { isValid: false, apiKey, workspaceId: apiKey.workspaceId }
|
||||
}
|
||||
|
||||
return { isValid: true, apiKey, workspaceId: apiKey.workspaceId }
|
||||
} catch (error) {
|
||||
return { isValid: false }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API Key WorkspaceID
|
||||
* @param {Request} req
|
||||
*/
|
||||
export const getAPIKeyWorkspaceID = async (req: Request) => {
|
||||
const authorizationHeader = (req.headers['Authorization'] as string) ?? (req.headers['authorization'] as string) ?? ''
|
||||
if (!authorizationHeader) return false
|
||||
|
||||
const suppliedKey = authorizationHeader.split(`Bearer `).pop()
|
||||
if (suppliedKey) {
|
||||
const key = await apikeyService.getApiKey(suppliedKey)
|
||||
return key?.workspaceId
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in New Issue