@iblai/iblai-js 2.17.0 → 2.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,8 +2,8 @@ import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import React__default, { useState, useEffect, forwardRef, createElement as createElement$3, useLayoutEffect, createContext, useContext, useMemo, useReducer, useRef, useImperativeHandle, useCallback, isValidElement, Children, PureComponent, cloneElement, Component as Component$1, useId as useId$2, useDebugValue, lazy, Suspense } from 'react';
4
4
  import * as dataLayer from '@iblai/data-layer';
5
- import { useGetUserMetadataQuery, useGetUserMetadataEdxQuery, useGetUserProfileSummaryQuery, useUpdateUserMetadataMutation, useUpdateUserMetadataEdxMutation, useUploadProfileImageMutation, useResetPasswordMutation, useSelfRetireMutation, useCreateUserInstitutionMutation, useGetUserInstitutionsQuery, useCreateUserEducationMutation, useUpdateUserEducationMutation, useDeleteUserEducationMutation, useGetUserEducationQuery, useCreateUserCompanyMutation, useGetUserCompaniesQuery, useCreateUserExperienceMutation, useUpdateUserExperienceMutation, useDeleteUserExperienceMutation, useGetUserExperienceQuery, useGetUserResumeQuery, useCreateUserResumeMutation, useGetMySubscriptionsQuery, useGetItemSubscriptionQuery, useCancelSubscriptionMutation, useGetLearnerDetailsQuery, useGetUserEnrolledCoursesQuery, useGetUserEnrolledProgramsQuery, useGetUserCatalogPathwaysQuery, useGetUsersAsAssertionsQuery, useGetUserEarnedSkillsQuery, useGetUserReportedSkillsQuery, useGetUserDesiredSkillsQuery, useCreateGlobalMemoryMutation, useUpdateGlobalMemoryMutation, useUpdateMentorMemoryMutation, useGetMemsearchStatusQuery, useGetUserMemorySettingsQuery, useUpdateUserMemorySettingsMutation, useGetGlobalMemoriesQuery, useGetAllMentorMemoriesQuery, useDeleteGlobalMemoryMutation, useDeleteMentorMemoryMutation, useGetAiSearchMentorsQuery, useStarMentorMutation, useUnstarMentorMutation, useGetUserChatHistoryReportsQuery, USER_CHAT_HISTORY_REPORT_ACTIVE_STATES, useCreateUserChatHistoryReportMutation, useLazyGetDownloadReportFromURLQuery, useGetUserChatHistoryReportStatusQuery, userChatHistoryApiSlice, USER_CHAT_HISTORY_QUERY_KEYS, useGetUserChatHistoryQuery, useGetUserChatHistoryFilterQuery, useGetMentorsQuery, useGetUserChatHistorySessionQuery, useInviteUserMutation, usePlatformInvitationsQuery, useCreateCatalogInvitationCourseBulkMutation, useGetCatalogInvitationsCourseQuery, useLazyPlatformUsersQuery, useLazyPlatformUserGroupsQuery, useGetPersonnalizedSearchQuery, useCreateCatalogInvitationProgramBulkMutation, useGetCatalogInvitationsProgramQuery, useUpdateUserRoleMutation, useUpdateUserStatusMutation, useUpdatePlatformUserRoleWithPoliciesMutation, usePlatformUsersQuery, useGetAccessibleUsersQuery, isPoliciesResponse, platformApiSlice, featureTags, useLazyGetRbacTeamsAccessListQuery, useCreateRbacTeamsAccessMutation, useGetRbacGroupsQuery, usePlatformUserGroupsQuery, useCreateRbacGroupMutation, useUpdateRbacGroupMutation, useDeleteRbacGroupMutation, useCreatePlatformUserGroupMutation, useUpdatePlatformUserGroupMutation, useDeletePlatformUserGroupMutation, useGetRbacGroupDetailsQuery, useGetPlatformUserGroupDetailsQuery, useGetRbacPermissionsMutation, useGetRbacRolesQuery, useCreateRbacRoleMutation, useUpdateRbacRoleMutation, useDeleteRbacRoleMutation, useGetRbacRoleDetailsQuery, useGetRbacActionsDefinitionQuery, useGetRbacResourceDiscoveryQuery, useGetRbacResourceListQuery, useGetRbacPoliciesQuery, useCreateRbacPolicyMutation, useUpdateRbacPolicyMutation, useDeleteRbacPolicyMutation, useGetRbacPolicyDetailsQuery, useGetWatchedGroupsQuery, useCreateWatchedGroupMutation, useUpdateWatchedGroupMutation, useDeleteWatchedGroupMutation, useAddWatchedUserMutation, useRemoveWatchedUserMutation, useAddWatcherMutation, useUpdateWatcherMutation, useDeleteWatcherMutation, useGetWatchedUsersQuery, useGetWatchersQuery, WATCHER_NOTIFICATION_EVENTS, WATCHER_NOTIFICATION_EVENT_LABELS, useGetApplicationFormsQuery, useGetApplicationFormDetailQuery, useCreateApplicationFormMutation, useUpdateApplicationFormMutation, useGetApplicationAdminDetailQuery, useInviteApplicationCandidateMutation, useLazyGetApplicationAttachmentFileQuery, useCreateApplicationWaiverMutation, useDeleteApplicationWaiverMutation, useSubmitApplicationOverrideMutation, useUpdateApplicationPlacementMutation, useDeleteApplicationCourseAssignmentMutation, useCreateApplicationNoteMutation, useUpdateApplicationNoteMutation, useDeleteApplicationNoteMutation, useTransitionApplicationMutation, useLinkApplicationCandidateMutation, useCreateApplicationPlacementsMutation, useGetCustomMentorsQuery, useCreateApplicationCourseAssignmentsMutation, useGetApplicationBlocksQuery, useDeleteApplicationBlockMutation, useGetApplicationsPipelineStatsQuery, useGetApplicationsPipelineQuery, useGetTenantMetadataQuery, useUpdateTenantMetadataMutation, useLazyGetTenantMetadataQuery, useDeleteApiKeyMutation, useGetApiKeyQuery, useGetApiKeyFieldPermissionsQuery, useUpdateApiKeyMutation, useGetApiKeysQuery, unwrapApiTokenList, useCreateApiKeyMutation, useCreateLLMCredentialMutation, useGetCredentialsSchemaQuery, useGetMaskedLLMCredentialsQuery, useGetLlmsQuery, useDeleteIntegrationCredentialMutation, useDeleteCredentialMutation, useCreateIntegrationCredentialMutation, useGetIntegrationCredentialsSchemaQuery, useGetMaskedIntegrationCredentialsQuery, useGetAccountBillingInfoQuery, useUpdateAutoRechargeInfoMutation, useTriggerAutoRechargeMutation, useGetAgentSpendCapQuery, useGetTenantSpendCapQuery, useListAgentUserSpendCapsQuery, useUpsertAgentSpendCapMutation, useDeleteAgentSpendCapMutation, useUpsertTenantSpendCapMutation, useDeleteTenantSpendCapMutation, useUpsertUserSpendCapMutation, useDeleteUserSpendCapMutation, useGetLlmUsageMetricsQuery, useListAgentSpendCapsQuery, useGetCreditTransactionsQuery, useCreateStripeCustomerPortalMutation, useSetPlatformConfigurationsMutation, useGetPlatformConfigurationsQuery, useUpdatePlatformMembershipMutation, useGetPlatformMembershipQuery, useGetCustomDomainsQuery, useCreateCustomDomainMutation, useDeleteCustomDomainMutation, useGetStudentMentorCreationStatusQuery, useSetStudentMentorCreationStatusMutation, coreApiSlice, recommendationPromptTypeEnum, useGetRecommendedPromptsListQuery, useCreateRecommendedPromptMutation, useUpdateRecommendedPromptMutation, useDeleteRecommendedPromptMutation, useLazyGetPublicPlatformImageAssetFileUrlQuery, useCreatePlatformImageAssetMutation, useGetProviderConfigQuery, useCreateProviderConfigMutation, useDeleteProviderConfigMutation, useGetExternalMappingQuery, useGetCredentialsListQuery, useCreateExternalMappingMutation, useDeleteExternalMappingMutation, useGetMemsearchConfigQuery, useUpdateMemsearchConfigMutation, useGetStripeConnectStatusQuery, useStartStripeConnectOnboardingMutation, useLazyGetStripeConnectDashboardQuery, useListPaywallsQuery, useListPricesQuery, useCreatePriceMutation, useUpdatePriceMutation, useDeletePriceMutation, useGetPaywallConfigQuery, useEnablePaywallMutation, useUpdatePaywallMutation, useGetMentorMemoriesListQuery, useGetMemoryCategoriesAdminQuery, useCreateMentorMemoryMutation, useLazyGetCourseMetaDataQuery, useLazyGetCourseCompletionOutlinesQuery, useLazyGetCourseEligibilityQuery, useLazyGetEdxSSOTokenQuery, useCreateCourseEnrollmentMutation, useCreateStripeCheckoutSessionMutation, useLazyGetCourseProgressQuery, useLazyGetCourseCompletionQuery, useUpdateExamAttemptMutation, useStartExamMutation, useLazyGetExamInfoQuery, useGetAgentSkillResourcesQuery, useCreateAgentSkillResourceMutation, useUpdateAgentSkillResourceMutation, useUploadAgentSkillResourceAssetMutation, useDeleteAgentSkillResourceMutation, useGetMentorSkillAssignmentsQuery, useGetAgentSkillsQuery, useCreateMentorSkillAssignmentMutation, useUpdateMentorSkillAssignmentMutation, useDeleteMentorSkillAssignmentMutation, useCreateAgentSkillMutation, useUpdateAgentSkillMutation, useDeleteAgentSkillMutation, filterSlashSkills, useGetMentorSkillAssignmentsInfiniteQuery, MENTOR_SKILL_ASSIGNMENTS_PAGE_SIZE, useUpdateUserProjectMutation, useGetTrainingDocumentsQuery, useGetVectorDocumentsQuery, useLazyGetConnectedServiceAuthUrlQuery, useCreateUserProjectMutation, useDeleteUserProjectMutation, useGetUserProjectDetailsQuery, useEditTrainingDocumentMutation, useAddTrainingDocumentMutation, useLazyGetCredentialsQuery, useGetMentorSettingsQuery, useEditMentorMutation, useUploadLightLogoMutation, useUploadDarkLogoMutation, useUpdatePlatformInfoMutation, LOGO_ENDPOINTS, useCreateEvalDatasetMutation, useAddEvalDatasetItemsMutation, useUploadEvalDatasetCsvMutation, useDeleteEvalDatasetItemMutation, useListEvalDatasetItemsQuery, evalTotalPages, useListEvalDatasetsQuery, useGetUserProjectsQuery, useCreateSessionIdMutation, useGetMentorCategoriesQuery, useGetTenantChatPrivacyConfigQuery as useGetTenantChatPrivacyConfigQuery$1, useGetCallConfigurationsQuery, useCreateCallConfigurationMutation, useUpdateCallConfigurationMutation, chatPrivacyApiSlice, useDeleteMentorMutation, useForkMentorMutation, useGetToolsQuery, useLazyGetMCPServersQuery, useOauthFindMutation, useLazyStartOAuthFlowQuery, useCreateMCPServerMutation, usePartialUpdateMCPServerMutation, useCreateMCPServerConnectionMutation, usePatchMCPServerConnectionMutation, useGetConnectedServicesQuery, useGetMCPServersQuery, useGetMCPServerConnectionsQuery, useUpdateMCPServerMutation, useDeleteMCPServerMutation, useEditMentorJsonMutation, useDisconnectServiceMutation, useGetPromptCategoriesQuery, PRIVACY_ACTIONS, PRIVACY_ENTITY_TYPES, useGetVoicesQuery, useGetVoiceQuery, isBaseAgentMentor, useGetGradeResultQuery, useOverrideGradeResultMutation, useGetChatHistoryFilterQuery, useListGradeResultsQuery, useGetMentorGraderConfigurationQuery, useListGraderCriteriaQuery, useCreateMentorGraderConfigurationMutation, useUpdateMentorGraderConfigurationMutation, useCreateGraderCriterionMutation, useUpdateGraderCriterionMutation, useDeleteGraderCriterionMutation, useGetSpendCapStatusQuery, useGetMentorPublicSettingsQuery, useGetChatHistoryQuery, useGetMentorSummariesQuery, useGetConversationMemoriesQuery, useGetSupportTicketsQuery, useGetSupportTicketMessagesQuery, useCreateSupportTicketMessageMutation, usePatchSupportTicketMutation, useCloseSupportTicketMutation, useCreateLtiMentorMutation, useUpdateLtiMentorMutation, useGetLtiMentorsQuery, useDeleteLtiMentorMutation, useCreateLtiKeyMutation, useUpdateLtiKeyMutation, useDeleteLtiKeyMutation, useGetLtiKeysQuery, useCreateLtiToolMutation, useUpdateLtiToolMutation, useGetLtiToolsQuery, useGetPlatformInfoQuery, useCreatePromptMutation, useGetPromptsSearchQuery, useUpdatePromptMutation, useCreateRedirectTokenMutation, useGetShareableLinkQuery, useCreateShareableLinkMutation, useUpdateShareableLinkMutation, useCreateDisclaimerMutation, useUpdateDisclaimerMutation, useGetDisclaimersQuery, useUpdateRbacMentorAccessMutation, useGetRbacMentorAccessListQuery, useStartEvalRunMutation, useEvaluateEvalRunMutation, useGetEvalRunQuery, useDeleteEvalScoreMutation, useListEvalRunJudgesQuery, useCreateEvalScoreMutation, useDeleteEvalRunMutation, useExportEvalRunMutation, useListEvalRunsQuery, useLazyGetEvalRunQuery, evaluationsApiSlice, EVALUATIONS_TAGS, useGetPersonnalizedMentorsQuery, useCreateCallCredentialsMutation, useGetGuidedPromptsQuery, useUpdateChatSessionSharedMutation, useUpdateMessageFeedbackMutation, useLazyGetPromptsSearchQuery, useLazyGetGuidedPromptsQuery, useGetPublicMentorsQuery, useGetPeriodicAgentsQuery, useGetPeriodicAgentLogsListQuery, useCreatePeriodicAgentMutation, useDeletePeriodicAgentMutation, useCreateMemoryCategoryMutation, useUpdateMemoryCategoryMutation, useDeleteMemoryCategoryMutation, useDeletePromptMutation, useDeleteTrainingDocumentMutation, useGetTrainingDocumentRetrainScheduleQuery, useCreateTrainingDocumentRetrainScheduleMutation, useUpdateArtifactMutation, useLazyGetArtifactVersionQuery, useLazyListArtifactVersionsQuery, useSetCurrentVersionMutation, useListArtifactVersionsQuery, useLazyGetArtifactQuery, useLazyListArtifactsQuery, useEditSessionMutation } from '@iblai/data-layer';
6
- import { removeAuthItem, setAuthItem, isTauriEmbeddedLLMHost, selectRbacPermissions, useCurrentTenant, canViewOtherUsersChatHistory, getInitials, useTenantMetadata, isPureWatcher, WithPermissions, canWriteField, useStripeUpgrade, openExternalUrl, CHAT_AREA_SIZE, isAlphaNumeric32, checkRbacPermission, selectNumberOfActiveChatMessages, useUsername, selectStreaming, isLoggedIn, TOOLS, hasRemoteAiConfig, isSafariBrowser, useShowAttachment, useShowVoiceCall, useShowVoiceRecorder, useMentorSettings, selectShowingSharedChat, useShowFreeTrialDialog, useEmbedMode, chatInputSliceSelectors, useResponsive, useAccessingPublicRoute, useVisitingTenant, useModelFileUploadCapabilities, useChatFileUpload, useVoiceChat, selectAttachedFiles, MENTOR_CHAT_DOCUMENTS_EXTENSIONS, removeFile, chatInputSliceActions, chatActions, WithFormPermissions, useTenantContext, TimeTracker, advancedTabsProperties, defaultSessionIds, pickWritableFields, ANONYMOUS_USERNAME as ANONYMOUS_USERNAME$3, redirectToAuthSpaJoinTenant, selectActiveTab, useAxdToken, useWelcomeMessage, markdownToPlainText, useCachedSessionId, use402ErrorCheck, use401TokenRefresh, selectTokenEnabled, selectToken, useServiceWorker, useUserAgreement, useAdvancedChat, sendMessageToParentWebsite, useMentorTools, useFileDragDrop, eventBus, RemoteEvents, selectEnableChatActionsPopup, advancedTabs, isInIframe, getAuthSpaJoinUrl, addMessage, clearFiles, MENTOR_VISIBILITY } from '@iblai/web-utils';
5
+ import { useGetUserMetadataQuery, useGetUserMetadataEdxQuery, useGetUserProfileSummaryQuery, useUpdateUserMetadataMutation, useUpdateUserMetadataEdxMutation, useUploadProfileImageMutation, useResetPasswordMutation, useSelfRetireMutation, useCreateUserInstitutionMutation, useGetUserInstitutionsQuery, useCreateUserEducationMutation, useUpdateUserEducationMutation, useDeleteUserEducationMutation, useGetUserEducationQuery, useCreateUserCompanyMutation, useGetUserCompaniesQuery, useCreateUserExperienceMutation, useUpdateUserExperienceMutation, useDeleteUserExperienceMutation, useGetUserExperienceQuery, useGetUserResumeQuery, useCreateUserResumeMutation, useGetMySubscriptionsQuery, useGetItemSubscriptionQuery, useCancelSubscriptionMutation, useGetLearnerDetailsQuery, useGetUserEnrolledCoursesQuery, useGetUserEnrolledProgramsQuery, useGetUserCatalogPathwaysQuery, useGetUsersAsAssertionsQuery, useGetUserEarnedSkillsQuery, useGetUserReportedSkillsQuery, useGetUserDesiredSkillsQuery, useCreateGlobalMemoryMutation, useUpdateGlobalMemoryMutation, useUpdateMentorMemoryMutation, useGetMemsearchStatusQuery, useGetUserMemorySettingsQuery, useUpdateUserMemorySettingsMutation, useGetGlobalMemoriesQuery, useGetAllMentorMemoriesQuery, useDeleteGlobalMemoryMutation, useDeleteMentorMemoryMutation, useGetAiSearchMentorsQuery, useStarMentorMutation, useUnstarMentorMutation, useGetUserChatHistoryReportsQuery, USER_CHAT_HISTORY_REPORT_ACTIVE_STATES, useCreateUserChatHistoryReportMutation, useLazyGetDownloadReportFromURLQuery, useGetUserChatHistoryReportStatusQuery, userChatHistoryApiSlice, USER_CHAT_HISTORY_QUERY_KEYS, useGetUserChatHistoryQuery, useGetUserChatHistoryFilterQuery, useGetMentorsQuery, useGetUserChatHistorySessionQuery, useInviteUserMutation, usePlatformInvitationsQuery, useCreateCatalogInvitationCourseBulkMutation, useGetCatalogInvitationsCourseQuery, useLazyPlatformUsersQuery, useLazyPlatformUserGroupsQuery, useGetPersonnalizedSearchQuery, useCreateCatalogInvitationProgramBulkMutation, useGetCatalogInvitationsProgramQuery, useGetVectorDocumentsQuery, useGetLlmsQuery, useUpdateUserRoleMutation, useUpdateUserStatusMutation, useUpdatePlatformUserRoleWithPoliciesMutation, usePlatformUsersQuery, useGetAccessibleUsersQuery, isPoliciesResponse, platformApiSlice, featureTags, useLazyGetRbacTeamsAccessListQuery, useCreateRbacTeamsAccessMutation, useGetRbacGroupsQuery, usePlatformUserGroupsQuery, useCreateRbacGroupMutation, useUpdateRbacGroupMutation, useDeleteRbacGroupMutation, useCreatePlatformUserGroupMutation, useUpdatePlatformUserGroupMutation, useDeletePlatformUserGroupMutation, useGetRbacGroupDetailsQuery, useGetPlatformUserGroupDetailsQuery, useGetRbacPermissionsMutation, useGetRbacRolesQuery, useCreateRbacRoleMutation, useUpdateRbacRoleMutation, useDeleteRbacRoleMutation, useGetRbacRoleDetailsQuery, useGetRbacActionsDefinitionQuery, useGetRbacResourceDiscoveryQuery, useGetRbacResourceListQuery, useGetRbacPoliciesQuery, useCreateRbacPolicyMutation, useUpdateRbacPolicyMutation, useDeleteRbacPolicyMutation, useGetRbacPolicyDetailsQuery, useGetWatchedGroupsQuery, useCreateWatchedGroupMutation, useUpdateWatchedGroupMutation, useDeleteWatchedGroupMutation, useAddWatchedUserMutation, useRemoveWatchedUserMutation, useAddWatcherMutation, useUpdateWatcherMutation, useDeleteWatcherMutation, useGetWatchedUsersQuery, useGetWatchersQuery, WATCHER_NOTIFICATION_EVENTS, WATCHER_NOTIFICATION_EVENT_LABELS, useGetApplicationFormsQuery, useGetApplicationFormDetailQuery, useCreateApplicationFormMutation, useUpdateApplicationFormMutation, useGetApplicationAdminDetailQuery, useInviteApplicationCandidateMutation, useLazyGetApplicationAttachmentFileQuery, useCreateApplicationWaiverMutation, useDeleteApplicationWaiverMutation, useSubmitApplicationOverrideMutation, useUpdateApplicationPlacementMutation, useDeleteApplicationCourseAssignmentMutation, useCreateApplicationNoteMutation, useUpdateApplicationNoteMutation, useDeleteApplicationNoteMutation, useTransitionApplicationMutation, useLinkApplicationCandidateMutation, useCreateApplicationPlacementsMutation, useGetCustomMentorsQuery, useCreateApplicationCourseAssignmentsMutation, useGetApplicationBlocksQuery, useDeleteApplicationBlockMutation, useGetApplicationsPipelineStatsQuery, useGetApplicationsPipelineQuery, useGetTenantMetadataQuery, useUpdateTenantMetadataMutation, useLazyGetTenantMetadataQuery, useDeleteApiKeyMutation, useGetApiKeyQuery, useGetApiKeyFieldPermissionsQuery, useUpdateApiKeyMutation, useGetApiKeysQuery, unwrapApiTokenList, useCreateApiKeyMutation, useCreateLLMCredentialMutation, useGetCredentialsSchemaQuery, useGetMaskedLLMCredentialsQuery, useDeleteIntegrationCredentialMutation, useDeleteCredentialMutation, useCreateIntegrationCredentialMutation, useGetIntegrationCredentialsSchemaQuery, useGetMaskedIntegrationCredentialsQuery, useGetAccountBillingInfoQuery, useUpdateAutoRechargeInfoMutation, useTriggerAutoRechargeMutation, useGetAgentSpendCapQuery, useGetTenantSpendCapQuery, useListAgentUserSpendCapsQuery, useUpsertAgentSpendCapMutation, useDeleteAgentSpendCapMutation, useUpsertTenantSpendCapMutation, useDeleteTenantSpendCapMutation, useUpsertUserSpendCapMutation, useDeleteUserSpendCapMutation, useGetLlmUsageMetricsQuery, useListAgentSpendCapsQuery, useGetCreditTransactionsQuery, useCreateStripeCustomerPortalMutation, useSetPlatformConfigurationsMutation, useGetPlatformConfigurationsQuery, useUpdatePlatformMembershipMutation, useGetPlatformMembershipQuery, useGetCustomDomainsQuery, useCreateCustomDomainMutation, useDeleteCustomDomainMutation, useGetStudentMentorCreationStatusQuery, useSetStudentMentorCreationStatusMutation, coreApiSlice, recommendationPromptTypeEnum, useGetRecommendedPromptsListQuery, useCreateRecommendedPromptMutation, useUpdateRecommendedPromptMutation, useDeleteRecommendedPromptMutation, useLazyGetPublicPlatformImageAssetFileUrlQuery, useCreatePlatformImageAssetMutation, useGetProviderConfigQuery, useCreateProviderConfigMutation, useDeleteProviderConfigMutation, useGetExternalMappingQuery, useGetCredentialsListQuery, useCreateExternalMappingMutation, useDeleteExternalMappingMutation, useGetMemsearchConfigQuery, useUpdateMemsearchConfigMutation, useGetStripeConnectStatusQuery, useStartStripeConnectOnboardingMutation, useLazyGetStripeConnectDashboardQuery, useListPaywallsQuery, useListPricesQuery, useCreatePriceMutation, useUpdatePriceMutation, useDeletePriceMutation, useGetPaywallConfigQuery, useEnablePaywallMutation, useUpdatePaywallMutation, useGetMentorMemoriesListQuery, useGetMemoryCategoriesAdminQuery, useCreateMentorMemoryMutation, useLazyGetCourseMetaDataQuery, useLazyGetCourseCompletionOutlinesQuery, useLazyGetCourseEligibilityQuery, useLazyGetEdxSSOTokenQuery, useCreateCourseEnrollmentMutation, useCreateStripeCheckoutSessionMutation, useLazyGetCourseProgressQuery, useLazyGetCourseCompletionQuery, useUpdateExamAttemptMutation, useStartExamMutation, useLazyGetExamInfoQuery, useGetAgentSkillResourcesQuery, useCreateAgentSkillResourceMutation, useUpdateAgentSkillResourceMutation, useUploadAgentSkillResourceAssetMutation, useDeleteAgentSkillResourceMutation, useGetMentorSkillAssignmentsQuery, useGetAgentSkillsQuery, useCreateMentorSkillAssignmentMutation, useUpdateMentorSkillAssignmentMutation, useDeleteMentorSkillAssignmentMutation, useCreateAgentSkillMutation, useUpdateAgentSkillMutation, useDeleteAgentSkillMutation, filterSlashSkills, useGetMentorSkillAssignmentsInfiniteQuery, MENTOR_SKILL_ASSIGNMENTS_PAGE_SIZE, useUpdateUserProjectMutation, useGetTrainingDocumentsQuery, useLazyGetConnectedServiceAuthUrlQuery, useCreateUserProjectMutation, useDeleteUserProjectMutation, useGetUserProjectDetailsQuery, useEditTrainingDocumentMutation, useAddTrainingDocumentMutation, useLazyGetCredentialsQuery, useGetMentorSettingsQuery, useEditMentorMutation, useUploadLightLogoMutation, useUploadDarkLogoMutation, useUpdatePlatformInfoMutation, LOGO_ENDPOINTS, useCreateEvalDatasetMutation, useAddEvalDatasetItemsMutation, useUploadEvalDatasetCsvMutation, useDeleteEvalDatasetItemMutation, useListEvalDatasetItemsQuery, evalTotalPages, useListEvalDatasetsQuery, useGetUserProjectsQuery, useCreateSessionIdMutation, useGetMentorCategoriesQuery, useGetTenantChatPrivacyConfigQuery as useGetTenantChatPrivacyConfigQuery$1, useGetCallConfigurationsQuery, useCreateCallConfigurationMutation, useUpdateCallConfigurationMutation, chatPrivacyApiSlice, useDeleteMentorMutation, useForkMentorMutation, useGetToolsQuery, useLazyGetMCPServersQuery, useOauthFindMutation, useLazyStartOAuthFlowQuery, useCreateMCPServerMutation, usePartialUpdateMCPServerMutation, useCreateMCPServerConnectionMutation, usePatchMCPServerConnectionMutation, useGetConnectedServicesQuery, useGetMCPServersQuery, useGetMCPServerConnectionsQuery, useUpdateMCPServerMutation, useDeleteMCPServerMutation, useEditMentorJsonMutation, useDisconnectServiceMutation, useGetPromptCategoriesQuery, PRIVACY_ACTIONS, PRIVACY_ENTITY_TYPES, useGetVoicesQuery, useGetVoiceQuery, isBaseAgentMentor, useGetGradeResultQuery, useOverrideGradeResultMutation, useGetChatHistoryFilterQuery, useListGradeResultsQuery, useGetMentorGraderConfigurationQuery, useListGraderCriteriaQuery, useCreateMentorGraderConfigurationMutation, useUpdateMentorGraderConfigurationMutation, useCreateGraderCriterionMutation, useUpdateGraderCriterionMutation, useDeleteGraderCriterionMutation, useGetSpendCapStatusQuery, useGetMentorPublicSettingsQuery, useGetChatHistoryQuery, useGetMentorSummariesQuery, useGetConversationMemoriesQuery, useGetSupportTicketsQuery, useGetSupportTicketMessagesQuery, useCreateSupportTicketMessageMutation, usePatchSupportTicketMutation, useCloseSupportTicketMutation, useCreateLtiMentorMutation, useUpdateLtiMentorMutation, useGetLtiMentorsQuery, useDeleteLtiMentorMutation, useCreateLtiKeyMutation, useUpdateLtiKeyMutation, useDeleteLtiKeyMutation, useGetLtiKeysQuery, useCreateLtiToolMutation, useUpdateLtiToolMutation, useGetLtiToolsQuery, useGetPlatformInfoQuery, useCreatePromptMutation, useGetPromptsSearchQuery, useUpdatePromptMutation, useCreateRedirectTokenMutation, useGetShareableLinkQuery, useCreateShareableLinkMutation, useUpdateShareableLinkMutation, useCreateDisclaimerMutation, useUpdateDisclaimerMutation, useGetDisclaimersQuery, useUpdateRbacMentorAccessMutation, useGetRbacMentorAccessListQuery, useStartEvalRunMutation, useEvaluateEvalRunMutation, useGetEvalRunQuery, useDeleteEvalScoreMutation, useListEvalRunJudgesQuery, useCreateEvalScoreMutation, useDeleteEvalRunMutation, useExportEvalRunMutation, useListEvalRunsQuery, useLazyGetEvalRunQuery, evaluationsApiSlice, EVALUATIONS_TAGS, useGetPersonnalizedMentorsQuery, useCreateCallCredentialsMutation, useGetGuidedPromptsQuery, useUpdateChatSessionSharedMutation, useUpdateMessageFeedbackMutation, useLazyGetPromptsSearchQuery, useLazyGetGuidedPromptsQuery, useGetPublicMentorsQuery, useGetPeriodicAgentsQuery, useGetPeriodicAgentLogsListQuery, useCreatePeriodicAgentMutation, useDeletePeriodicAgentMutation, useCreateMemoryCategoryMutation, useUpdateMemoryCategoryMutation, useDeleteMemoryCategoryMutation, useDeletePromptMutation, useDeleteTrainingDocumentMutation, useGetTrainingDocumentRetrainScheduleQuery, useCreateTrainingDocumentRetrainScheduleMutation, useUpdateArtifactMutation, useLazyGetArtifactVersionQuery, useLazyListArtifactVersionsQuery, useSetCurrentVersionMutation, useListArtifactVersionsQuery, useLazyGetArtifactQuery, useLazyListArtifactsQuery, useEditSessionMutation } from '@iblai/data-layer';
6
+ import { removeAuthItem, setAuthItem, isTauriEmbeddedLLMHost, selectRbacPermissions, useCurrentTenant, canViewOtherUsersChatHistory, getInitials, useTenantMetadata, useUsername, selectStreaming, isLoggedIn, TOOL_NAME_MAP, WRITE_TODOS_TOOL, isPureWatcher, WithPermissions, canWriteField, useStripeUpgrade, openExternalUrl, CHAT_AREA_SIZE, isAlphaNumeric32, checkRbacPermission, selectNumberOfActiveChatMessages, TOOLS, hasRemoteAiConfig, isSafariBrowser, useShowAttachment, useShowVoiceCall, useShowVoiceRecorder, useMentorSettings, selectShowingSharedChat, useShowFreeTrialDialog, useEmbedMode, chatInputSliceSelectors, useResponsive, useAccessingPublicRoute, useVisitingTenant, useModelFileUploadCapabilities, useChatFileUpload, useVoiceChat, selectAttachedFiles, MENTOR_CHAT_DOCUMENTS_EXTENSIONS, removeFile, chatInputSliceActions, chatActions, WithFormPermissions, useTenantContext, TimeTracker, advancedTabsProperties, defaultSessionIds, pickWritableFields, ANONYMOUS_USERNAME as ANONYMOUS_USERNAME$3, redirectToAuthSpaJoinTenant, selectActiveTab, useAxdToken, useWelcomeMessage, markdownToPlainText, useCachedSessionId, use402ErrorCheck, use401TokenRefresh, selectTokenEnabled, selectToken, useServiceWorker, useUserAgreement, useAdvancedChat, sendMessageToParentWebsite, useMentorTools, useFileDragDrop, eventBus, RemoteEvents, selectEnableChatActionsPopup, advancedTabs, isInIframe, getAuthSpaJoinUrl, addMessage, clearFiles, MENTOR_VISIBILITY } from '@iblai/web-utils';
7
7
  import { useDispatch, useSelector } from 'react-redux';
8
8
  import { toast } from 'sonner';
9
9
  import { useSearchParams, useParams, useRouter, usePathname } from 'next/navigation';
@@ -44326,7 +44326,7 @@ var isArray$h = function isArray(arr) {
44326
44326
  return toStr.call(arr) === '[object Array]';
44327
44327
  };
44328
44328
 
44329
- var isPlainObject$4 = function isPlainObject(obj) {
44329
+ var isPlainObject$5 = function isPlainObject(obj) {
44330
44330
  if (!obj || toStr.call(obj) !== '[object Object]') {
44331
44331
  return false;
44332
44332
  }
@@ -44405,12 +44405,12 @@ var extend$3 = function extend() {
44405
44405
  // Prevent never-ending loop
44406
44406
  if (target !== copy) {
44407
44407
  // Recurse if we're merging plain objects or arrays
44408
- if (deep && copy && (isPlainObject$4(copy) || (copyIsArray = isArray$h(copy)))) {
44408
+ if (deep && copy && (isPlainObject$5(copy) || (copyIsArray = isArray$h(copy)))) {
44409
44409
  if (copyIsArray) {
44410
44410
  copyIsArray = false;
44411
44411
  clone = src && isArray$h(src) ? src : [];
44412
44412
  } else {
44413
- clone = src && isPlainObject$4(src) ? src : {};
44413
+ clone = src && isPlainObject$5(src) ? src : {};
44414
44414
  }
44415
44415
 
44416
44416
  // Never move original objects, clone them
@@ -44431,7 +44431,7 @@ var extend$3 = function extend() {
44431
44431
 
44432
44432
  var extend$2 = /*@__PURE__*/getDefaultExportFromCjs(extend$3);
44433
44433
 
44434
- function isPlainObject$3(value) {
44434
+ function isPlainObject$4(value) {
44435
44435
  if (typeof value !== 'object' || value === null) {
44436
44436
  return false;
44437
44437
  }
@@ -45502,7 +45502,7 @@ class Processor extends CallableInstance {
45502
45502
  else if (parameters.length > 0) {
45503
45503
  let [primary, ...rest] = parameters;
45504
45504
  const currentPrimary = attachers[entryIndex][1];
45505
- if (isPlainObject$3(currentPrimary) && isPlainObject$3(primary)) {
45505
+ if (isPlainObject$4(currentPrimary) && isPlainObject$4(primary)) {
45506
45506
  primary = extend$2(true, currentPrimary, primary);
45507
45507
  }
45508
45508
 
@@ -45594,7 +45594,7 @@ function assertUnfrozen(name, frozen) {
45594
45594
  function assertNode(node) {
45595
45595
  // `isPlainObj` unfortunately uses `any` instead of `unknown`.
45596
45596
  // type-coverage:ignore-next-line
45597
- if (!isPlainObject$3(node) || typeof node.type !== 'string') {
45597
+ if (!isPlainObject$4(node) || typeof node.type !== 'string') {
45598
45598
  throw new TypeError('Expected node, got `' + node + '`')
45599
45599
  // Fine.
45600
45600
  }
@@ -97047,7 +97047,7 @@ var analyticsAnalyticsTranscriptsStats$3 = {
97047
97047
  labelUsername: "Username:",
97048
97048
  labelAgent: "Agent:",
97049
97049
  labelModel: "Model:",
97050
- labelModelCard: "model:",
97050
+ labelModelCard: "Model:",
97051
97051
  labelUser: "User:",
97052
97052
  roleUser: "User",
97053
97053
  roleAgent: "Agent",
@@ -97089,6 +97089,37 @@ var analyticsAnalyticsTranscriptsStats$3 = {
97089
97089
  avgCostPerConversationDescription: "What a typical conversation costs in model usage.",
97090
97090
  avgRatingDescription: "Average rating users gave their conversations, on a scale of 1 to 5."
97091
97091
  };
97092
+ var analyticsTranscriptTurnDetails$3 = {
97093
+ showDetails: "Show Details",
97094
+ hideDetails: "Hide Details",
97095
+ metadata: "Metadata",
97096
+ requestContext: "Request Context",
97097
+ model: "Model",
97098
+ provider: "Provider",
97099
+ temperature: "Temperature",
97100
+ client: "Client",
97101
+ sourcesBadge: "Documents · {count}",
97102
+ toolsBadge: "Tools · {count}",
97103
+ yes: "Yes",
97104
+ no: "No",
97105
+ redacted: "Redacted",
97106
+ fieldSession: "Session",
97107
+ fieldCallerVerified: "Caller Verified",
97108
+ fieldFiles: "Files",
97109
+ fieldFile: "File",
97110
+ fieldSystemPrompt: "System Prompt",
97111
+ fieldFlow: "Flow",
97112
+ fieldToken: "Token",
97113
+ fieldTenant: "Tenant",
97114
+ fieldPathway: "Pathway",
97115
+ fieldUsername: "Username",
97116
+ fieldName: "Name",
97117
+ fieldCanvas: "Canvas",
97118
+ fieldProactivePrompt: "Proactive Prompt",
97119
+ fieldGrader: "Grader",
97120
+ fieldDatasets: "Datasets",
97121
+ fieldDateTime: "Date & Time"
97122
+ };
97092
97123
  var analyticsAnalyticsUsersStats$3 = {
97093
97124
  usersLoggedInNow: "Users logged in right now",
97094
97125
  usersLoggedInPast30Days: "Users logged in past 30 days",
@@ -98579,7 +98610,8 @@ var historyTabLabels$3 = {
98579
98610
  noResponse: "No response available",
98580
98611
  selectPrompt: "Select a conversation to view details.",
98581
98612
  aiAgent: "AI Agent",
98582
- noConversations: "No conversations found"
98613
+ noConversations: "No conversations found",
98614
+ documentsChip: "Documents · {count}"
98583
98615
  },
98584
98616
  summary: {
98585
98617
  notAvailable: "Summary not available"
@@ -102982,6 +103014,7 @@ var en = {
102982
103014
  analyticsAnalyticsReports: analyticsAnalyticsReports$3,
102983
103015
  analyticsAnalyticsTopicsStats: analyticsAnalyticsTopicsStats$3,
102984
103016
  analyticsAnalyticsTranscriptsStats: analyticsAnalyticsTranscriptsStats$3,
103017
+ analyticsTranscriptTurnDetails: analyticsTranscriptTurnDetails$3,
102985
103018
  analyticsAnalyticsUsersStats: analyticsAnalyticsUsersStats$3,
102986
103019
  analyticsGroupsFilterDropdown: analyticsGroupsFilterDropdown$3,
102987
103020
  analyticsStatCard: analyticsStatCard$3,
@@ -104205,7 +104238,7 @@ var analyticsAnalyticsTranscriptsStats$2 = {
104205
104238
  labelUsername: "Nom d'utilisateur :",
104206
104239
  labelAgent: "Agent :",
104207
104240
  labelModel: "Modèle :",
104208
- labelModelCard: "modèle :",
104241
+ labelModelCard: "Modèle :",
104209
104242
  labelUser: "Utilisateur :",
104210
104243
  roleUser: "Utilisateur",
104211
104244
  roleAgent: "Agent",
@@ -104247,6 +104280,37 @@ var analyticsAnalyticsTranscriptsStats$2 = {
104247
104280
  avgCostPerConversationDescription: "Coût d’utilisation des modèles pour une conversation type.",
104248
104281
  avgRatingDescription: "Note moyenne attribuée par les utilisateurs à leurs conversations, sur une échelle de 1 à 5."
104249
104282
  };
104283
+ var analyticsTranscriptTurnDetails$2 = {
104284
+ showDetails: "Afficher les détails",
104285
+ hideDetails: "Masquer les détails",
104286
+ metadata: "Métadonnées",
104287
+ requestContext: "Contexte de la requête",
104288
+ model: "Modèle",
104289
+ provider: "Fournisseur",
104290
+ temperature: "Température",
104291
+ client: "Client",
104292
+ sourcesBadge: "Documents · {count}",
104293
+ toolsBadge: "Outils · {count}",
104294
+ yes: "Oui",
104295
+ no: "Non",
104296
+ redacted: "Masqué",
104297
+ fieldSession: "Session",
104298
+ fieldCallerVerified: "Appelant vérifié",
104299
+ fieldFiles: "Fichiers",
104300
+ fieldFile: "Fichier",
104301
+ fieldSystemPrompt: "Prompt système",
104302
+ fieldFlow: "Flux",
104303
+ fieldToken: "Jeton",
104304
+ fieldTenant: "Locataire",
104305
+ fieldPathway: "Parcours",
104306
+ fieldUsername: "Nom d’utilisateur",
104307
+ fieldName: "Nom",
104308
+ fieldCanvas: "Canvas",
104309
+ fieldProactivePrompt: "Prompt proactif",
104310
+ fieldGrader: "Correcteur",
104311
+ fieldDatasets: "Jeux de données",
104312
+ fieldDateTime: "Date et heure"
104313
+ };
104250
104314
  var analyticsAnalyticsUsersStats$2 = {
104251
104315
  usersLoggedInNow: "Utilisateurs connectés en ce moment",
104252
104316
  usersLoggedInPast30Days: "Utilisateurs connectés ces 30 derniers jours",
@@ -105903,7 +105967,8 @@ var historyTabLabels$2 = {
105903
105967
  noResponse: "Aucune réponse disponible",
105904
105968
  selectPrompt: "Sélectionnez une conversation pour afficher les détails.",
105905
105969
  aiAgent: "AI Agent",
105906
- noConversations: "Aucune conversation trouvée"
105970
+ noConversations: "Aucune conversation trouvée",
105971
+ documentsChip: "Documents · {count}"
105907
105972
  },
105908
105973
  summary: {
105909
105974
  notAvailable: "Résumé non disponible"
@@ -110140,6 +110205,7 @@ var fr = {
110140
110205
  analyticsAnalyticsReports: analyticsAnalyticsReports$2,
110141
110206
  analyticsAnalyticsTopicsStats: analyticsAnalyticsTopicsStats$2,
110142
110207
  analyticsAnalyticsTranscriptsStats: analyticsAnalyticsTranscriptsStats$2,
110208
+ analyticsTranscriptTurnDetails: analyticsTranscriptTurnDetails$2,
110143
110209
  analyticsAnalyticsUsersStats: analyticsAnalyticsUsersStats$2,
110144
110210
  analyticsGroupsFilterDropdown: analyticsGroupsFilterDropdown$2,
110145
110211
  analyticsStatCard: analyticsStatCard$2,
@@ -111363,7 +111429,7 @@ var analyticsAnalyticsTranscriptsStats$1 = {
111363
111429
  labelUsername: "Nombre de usuario:",
111364
111430
  labelAgent: "Agente:",
111365
111431
  labelModel: "Modelo:",
111366
- labelModelCard: "modelo:",
111432
+ labelModelCard: "Modelo:",
111367
111433
  labelUser: "Usuario:",
111368
111434
  roleUser: "Usuario",
111369
111435
  roleAgent: "Agente",
@@ -111405,6 +111471,37 @@ var analyticsAnalyticsTranscriptsStats$1 = {
111405
111471
  avgCostPerConversationDescription: "Lo que cuesta una conversación típica en uso de modelos.",
111406
111472
  avgRatingDescription: "Valoración media que los usuarios dieron a sus conversaciones, en una escala de 1 a 5."
111407
111473
  };
111474
+ var analyticsTranscriptTurnDetails$1 = {
111475
+ showDetails: "Mostrar detalles",
111476
+ hideDetails: "Ocultar detalles",
111477
+ metadata: "Metadatos",
111478
+ requestContext: "Contexto de la solicitud",
111479
+ model: "Modelo",
111480
+ provider: "Proveedor",
111481
+ temperature: "Temperatura",
111482
+ client: "Cliente",
111483
+ sourcesBadge: "Documentos · {count}",
111484
+ toolsBadge: "Herramientas · {count}",
111485
+ yes: "Sí",
111486
+ no: "No",
111487
+ redacted: "Oculto",
111488
+ fieldSession: "Sesión",
111489
+ fieldCallerVerified: "Llamante verificado",
111490
+ fieldFiles: "Archivos",
111491
+ fieldFile: "Archivo",
111492
+ fieldSystemPrompt: "Prompt del sistema",
111493
+ fieldFlow: "Flujo",
111494
+ fieldToken: "Token",
111495
+ fieldTenant: "Inquilino",
111496
+ fieldPathway: "Ruta",
111497
+ fieldUsername: "Nombre de usuario",
111498
+ fieldName: "Nombre",
111499
+ fieldCanvas: "Canvas",
111500
+ fieldProactivePrompt: "Prompt proactivo",
111501
+ fieldGrader: "Calificador",
111502
+ fieldDatasets: "Conjuntos de datos",
111503
+ fieldDateTime: "Fecha y hora"
111504
+ };
111408
111505
  var analyticsAnalyticsUsersStats$1 = {
111409
111506
  usersLoggedInNow: "Usuarios conectados ahora mismo",
111410
111507
  usersLoggedInPast30Days: "Usuarios conectados en los últimos 30 días",
@@ -113061,7 +113158,8 @@ var historyTabLabels$1 = {
113061
113158
  noResponse: "No hay respuesta disponible",
113062
113159
  selectPrompt: "Selecciona una conversación para ver los detalles.",
113063
113160
  aiAgent: "AI Agent",
113064
- noConversations: "No se encontraron conversaciones"
113161
+ noConversations: "No se encontraron conversaciones",
113162
+ documentsChip: "Documentos · {count}"
113065
113163
  },
113066
113164
  summary: {
113067
113165
  notAvailable: "Resumen no disponible"
@@ -117298,6 +117396,7 @@ var es = {
117298
117396
  analyticsAnalyticsReports: analyticsAnalyticsReports$1,
117299
117397
  analyticsAnalyticsTopicsStats: analyticsAnalyticsTopicsStats$1,
117300
117398
  analyticsAnalyticsTranscriptsStats: analyticsAnalyticsTranscriptsStats$1,
117399
+ analyticsTranscriptTurnDetails: analyticsTranscriptTurnDetails$1,
117301
117400
  analyticsAnalyticsUsersStats: analyticsAnalyticsUsersStats$1,
117302
117401
  analyticsGroupsFilterDropdown: analyticsGroupsFilterDropdown$1,
117303
117402
  analyticsStatCard: analyticsStatCard$1,
@@ -118563,6 +118662,37 @@ var analyticsAnalyticsTranscriptsStats = {
118563
118662
  avgCostPerConversationDescription: "一次典型对话所产生的模型使用成本。",
118564
118663
  avgRatingDescription: "用户对其对话给出的平均评分,满分 5 分。"
118565
118664
  };
118665
+ var analyticsTranscriptTurnDetails = {
118666
+ showDetails: "显示详情",
118667
+ hideDetails: "隐藏详情",
118668
+ metadata: "元数据",
118669
+ requestContext: "请求上下文",
118670
+ model: "模型",
118671
+ provider: "提供商",
118672
+ temperature: "温度",
118673
+ client: "客户端",
118674
+ sourcesBadge: "文档 · {count}",
118675
+ toolsBadge: "工具 · {count}",
118676
+ yes: "是",
118677
+ no: "否",
118678
+ redacted: "已隐藏",
118679
+ fieldSession: "会话",
118680
+ fieldCallerVerified: "调用者已验证",
118681
+ fieldFiles: "文件",
118682
+ fieldFile: "文件",
118683
+ fieldSystemPrompt: "系统提示",
118684
+ fieldFlow: "流程",
118685
+ fieldToken: "令牌",
118686
+ fieldTenant: "租户",
118687
+ fieldPathway: "路径",
118688
+ fieldUsername: "用户名",
118689
+ fieldName: "名称",
118690
+ fieldCanvas: "画布",
118691
+ fieldProactivePrompt: "主动提示",
118692
+ fieldGrader: "评分器",
118693
+ fieldDatasets: "数据集",
118694
+ fieldDateTime: "日期和时间"
118695
+ };
118566
118696
  var analyticsAnalyticsUsersStats = {
118567
118697
  usersLoggedInNow: "当前在线用户",
118568
118698
  usersLoggedInPast30Days: "过去 30 天登录用户",
@@ -120219,7 +120349,8 @@ var historyTabLabels = {
120219
120349
  noResponse: "暂无回复",
120220
120350
  selectPrompt: "选择一条对话以查看详情。",
120221
120351
  aiAgent: "AI Agent",
120222
- noConversations: "未找到对话"
120352
+ noConversations: "未找到对话",
120353
+ documentsChip: "文档 · {count}"
120223
120354
  },
120224
120355
  summary: {
120225
120356
  notAvailable: "摘要不可用"
@@ -124456,6 +124587,7 @@ var zh = {
124456
124587
  analyticsAnalyticsReports: analyticsAnalyticsReports,
124457
124588
  analyticsAnalyticsTopicsStats: analyticsAnalyticsTopicsStats,
124458
124589
  analyticsAnalyticsTranscriptsStats: analyticsAnalyticsTranscriptsStats,
124590
+ analyticsTranscriptTurnDetails: analyticsTranscriptTurnDetails,
124459
124591
  analyticsAnalyticsUsersStats: analyticsAnalyticsUsersStats,
124460
124592
  analyticsGroupsFilterDropdown: analyticsGroupsFilterDropdown,
124461
124593
  analyticsStatCard: analyticsStatCard,
@@ -173316,7 +173448,7 @@ var objectCtorString = funcToString.call(Object);
173316
173448
  * _.isPlainObject(Object.create(null));
173317
173449
  * // => true
173318
173450
  */
173319
- function isPlainObject$2(value) {
173451
+ function isPlainObject$3(value) {
173320
173452
  if (!isObjectLike$1(value) || baseGetTag$1(value) != objectTag) {
173321
173453
  return false;
173322
173454
  }
@@ -173329,9 +173461,9 @@ function isPlainObject$2(value) {
173329
173461
  funcToString.call(Ctor) == objectCtorString;
173330
173462
  }
173331
173463
 
173332
- var isPlainObject_1 = isPlainObject$2;
173464
+ var isPlainObject_1 = isPlainObject$3;
173333
173465
 
173334
- var isPlainObject$1 = /*@__PURE__*/getDefaultExportFromCjs(isPlainObject_1);
173466
+ var isPlainObject$2 = /*@__PURE__*/getDefaultExportFromCjs(isPlainObject_1);
173335
173467
 
173336
173468
  var baseGetTag = _baseGetTag,
173337
173469
  isObjectLike = isObjectLike_1;
@@ -173548,7 +173680,7 @@ function Shape(_ref2) {
173548
173680
  shape = /*#__PURE__*/cloneElement(option, _objectSpread$e(_objectSpread$e({}, props), getPropsFromShapeOption(option)));
173549
173681
  } else if (isFunction$3(option)) {
173550
173682
  shape = option(props);
173551
- } else if (isPlainObject$1(option) && !isBoolean(option)) {
173683
+ } else if (isPlainObject$2(option) && !isBoolean(option)) {
173552
173684
  var nextProps = propTransformer(option, props);
173553
173685
  shape = /*#__PURE__*/React__default.createElement(ShapeSelector, {
173554
173686
  shapeType: shapeType,
@@ -218053,6 +218185,100 @@ function Markdown({ children, className }) {
218053
218185
  return (jsx("div", { className: className, children: jsx(Markdown$1, { components: components, children: children }) }));
218054
218186
  }
218055
218187
 
218188
+ function FileCard({ fileName, fileType }) {
218189
+ const getFileIcon = (fileName, fileType) => {
218190
+ const type = fileType.toLowerCase();
218191
+ const name = fileName.toLowerCase();
218192
+ const extension = name.split('.').pop() || '';
218193
+ // Check by MIME type first
218194
+ if (type.includes('pdf')) {
218195
+ return jsx(File$1, { className: "h-5 w-5 text-blue-500" });
218196
+ }
218197
+ else if (type.includes('image')) {
218198
+ return jsx(FileImage, { className: "h-5 w-5 text-blue-500" });
218199
+ }
218200
+ else if (type.includes('audio')) {
218201
+ return jsx(FileAudio, { className: "h-5 w-5 text-blue-500" });
218202
+ }
218203
+ else if (type.includes('video')) {
218204
+ return jsx(FilePlay, { className: "h-5 w-5 text-blue-500" });
218205
+ }
218206
+ else if (type.includes('spreadsheet') || type.includes('excel') || type.includes('csv')) {
218207
+ return jsx(FileSpreadsheet, { className: "h-5 w-5 text-blue-500" });
218208
+ }
218209
+ else if (type.includes('zip') || type.includes('compressed') || type.includes('archive')) {
218210
+ return jsx(FileArchive, { className: "h-5 w-5 text-blue-500" });
218211
+ }
218212
+ else if (type.includes('javascript') ||
218213
+ type.includes('typescript') ||
218214
+ type.includes('html') ||
218215
+ type.includes('css') ||
218216
+ type.includes('json')) {
218217
+ return jsx(FileCode, { className: "h-5 w-5 text-blue-500" });
218218
+ }
218219
+ // If MIME type doesn't match, check by extension
218220
+ if (['doc', 'docx', 'txt', 'rtf'].includes(extension)) {
218221
+ return jsx(FileText, { className: "h-5 w-5 text-blue-500" });
218222
+ }
218223
+ else if (['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'].includes(extension)) {
218224
+ return jsx(FileImage, { className: "h-5 w-5 text-blue-500" });
218225
+ }
218226
+ else if (['pdf'].includes(extension)) {
218227
+ return jsx(File$1, { className: "h-5 w-5 text-blue-500" });
218228
+ }
218229
+ else if (['mp3', 'wav', 'ogg', 'flac'].includes(extension)) {
218230
+ return jsx(FileAudio, { className: "h-5 w-5 text-blue-500" });
218231
+ }
218232
+ else if (['mp4', 'webm', 'avi', 'mov'].includes(extension)) {
218233
+ return jsx(FilePlay, { className: "h-5 w-5 text-blue-500" });
218234
+ }
218235
+ else if (['xls', 'xlsx', 'csv'].includes(extension)) {
218236
+ return jsx(FileSpreadsheet, { className: "h-5 w-5 text-blue-500" });
218237
+ }
218238
+ else if (['zip', 'rar', '7z', 'tar', 'gz'].includes(extension)) {
218239
+ return jsx(FileArchive, { className: "h-5 w-5 text-blue-500" });
218240
+ }
218241
+ else if (['js', 'ts', 'jsx', 'tsx', 'html', 'css', 'json', 'py', 'java', 'c', 'cpp'].includes(extension)) {
218242
+ return jsx(FileCode, { className: "h-5 w-5 text-blue-500" });
218243
+ }
218244
+ // Default icon
218245
+ return jsx(File$1, { className: "h-5 w-5 text-blue-500" });
218246
+ };
218247
+ return (jsxs("div", { className: "mb-2 flex max-w-xs items-center rounded-lg border border-gray-200 bg-white p-3", children: [jsx("div", { className: "mr-3 shrink-0 rounded-lg bg-blue-100 p-2 text-blue-600", children: getFileIcon(fileName, fileType) }), jsxs("div", { className: "min-w-0 flex-1", children: [jsx("h3", { className: "truncate text-sm font-medium text-gray-900", children: fileName }), jsx("p", { className: "truncate text-xs text-gray-500", children: fileType })] })] }));
218248
+ }
218249
+
218250
+ function ImageMessage({ url, fileName, setPreviewImage }) {
218251
+ const [hasError, setHasError] = useState(false);
218252
+ if (hasError || !url) {
218253
+ return jsx(FileCard, { fileName: fileName, fileType: "image/*" });
218254
+ }
218255
+ return (jsx("div", { className: "h-full max-h-64 w-full max-w-96 overflow-hidden rounded-lg", children: jsx("div", { className: "relative", children: jsx("img", { src: url, alt: fileName, className: "h-auto w-full cursor-pointer object-contain p-2", onClick: () => setPreviewImage(url), onError: () => setHasError(true) }) }) }));
218256
+ }
218257
+
218258
+ const openInNewTab = (url) => {
218259
+ window.open(url, '_blank', 'noopener,noreferrer');
218260
+ };
218261
+ /**
218262
+ * Files on an admin transcript, in the chat's own attachment idiom: images
218263
+ * preview like a sent image, everything else is the chat's file card. A card
218264
+ * with a URL opens the file in a new tab (presigned links expire, so nothing
218265
+ * is cached here).
218266
+ */
218267
+ function TranscriptFileCards({ files, className, }) {
218268
+ const shown = files.filter((file) => file === null || file === void 0 ? void 0 : file.name);
218269
+ if (shown.length === 0)
218270
+ return null;
218271
+ return (jsx("div", { className: className !== null && className !== void 0 ? className : 'mt-2 flex flex-wrap items-start gap-2', "data-testid": "transcript-file-cards", children: shown.map((file, index) => {
218272
+ var _a;
218273
+ const contentType = (_a = file.contentType) !== null && _a !== void 0 ? _a : '';
218274
+ if (file.url && contentType.startsWith('image/')) {
218275
+ return (jsx(ImageMessage, { url: file.url, fileName: file.name, setPreviewImage: openInNewTab }, `${file.name}-${index}`));
218276
+ }
218277
+ const card = jsx(FileCard, { fileName: file.name, fileType: contentType || 'file' });
218278
+ return file.url ? (jsx("a", { href: file.url, target: "_blank", rel: "noopener noreferrer", className: "block hover:opacity-90", "aria-label": `Open ${file.name}`, onClick: (event) => event.stopPropagation(), children: card }, `${file.name}-${index}`)) : (jsx("div", { children: card }, `${file.name}-${index}`));
218279
+ }) }));
218280
+ }
218281
+
218056
218282
  /**
218057
218283
  * Whether the dropdown is open, closing on a pointer press outside `ref` or on
218058
218284
  * Escape.
@@ -218696,8 +218922,12 @@ function ConversationMessages({ messages, username, agent, fallbackEmail, }) {
218696
218922
  ? (userMetadata === null || userMetadata === void 0 ? void 0 : userMetadata.email) || fallbackEmail || username
218697
218923
  : t('chatHistoryChatHistoryTab.you');
218698
218924
  return (jsx("div", { className: "space-y-6", children: messages.map((message, index) => {
218699
- var _a, _b;
218700
- return (jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-start gap-3", children: [jsx(UserMessageAvatar, { username: username }), jsxs("div", { className: "min-w-0 flex-1 overflow-x-hidden", children: [jsx("div", { className: "font-medium break-all text-gray-700", children: humanLabel }), jsx(Markdown, { className: "mt-1 text-sm text-gray-500", children: (_a = message.human) !== null && _a !== void 0 ? _a : '' })] })] }), jsxs("div", { className: "flex items-start gap-3 border-t border-gray-200 pt-4", children: [jsx(AgentMessageAvatar, { agent: agent, fallback: t('chatHistoryChatHistoryTab.aiAvatarLabel') }), jsxs("div", { className: "min-w-0 flex-1 overflow-x-hidden", children: [jsx("div", { className: "font-medium text-gray-700", children: agent.name }), jsx(Markdown, { className: "mt-1 text-sm text-gray-500", children: (_b = message.ai) !== null && _b !== void 0 ? _b : '' })] })] })] }, index));
218925
+ var _a, _b, _c;
218926
+ return (jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-start gap-3", children: [jsx(UserMessageAvatar, { username: username }), jsxs("div", { className: "min-w-0 flex-1 overflow-x-hidden", children: [jsx("div", { className: "font-medium break-all text-gray-700", children: humanLabel }), jsx(Markdown, { className: "mt-1 text-sm text-gray-500", children: (_a = message.human) !== null && _a !== void 0 ? _a : '' }), jsx(TranscriptFileCards, { files: ((_b = message.human_files) !== null && _b !== void 0 ? _b : []).map((file) => ({
218927
+ name: file.name,
218928
+ contentType: file.content_type,
218929
+ url: file.url,
218930
+ })) })] })] }), jsxs("div", { className: "flex items-start gap-3 border-t border-gray-200 pt-4", children: [jsx(AgentMessageAvatar, { agent: agent, fallback: t('chatHistoryChatHistoryTab.aiAvatarLabel') }), jsxs("div", { className: "min-w-0 flex-1 overflow-x-hidden", children: [jsx("div", { className: "font-medium text-gray-700", children: agent.name }), jsx(Markdown, { className: "mt-1 text-sm text-gray-500", children: (_c = message.ai) !== null && _c !== void 0 ? _c : '' })] })] })] }, index));
218701
218931
  }) }));
218702
218932
  }
218703
218933
  /**
@@ -220517,6 +220747,624 @@ function InvitedUsersDialog({ tenant, onClose }) {
220517
220747
  }, children: jsx(DialogPortal, { forceMount: true, children: jsxs(DialogContent, { "aria-label": t('profileInvitedUsers.dialogAriaLabel'), className: "max-w-sm rounded-2xl px-6 py-8 pointer-events-auto", "aria-describedby": "invited-users-dialog", children: [jsx(DialogHeader, { className: "relative flex-row justify-between", children: jsx(DialogTitle, { className: "text-blue-600 text-xl font-semibold", children: t('profileInvitedUsers.title') }) }), jsxs("div", { className: "mt-4 space-y-1", children: [jsx("p", { className: "text-gray-500 text-sm", children: t('profileInvitedUsers.pendingDescription') }), jsx("form", { children: jsx(Input, { value: query, onChange: (event) => setQuery(event.target.value), placeholder: t('profileInvitedUsers.searchPlaceholder'), className: "mt-2" }) })] }), jsxs("div", { children: [jsx("h2", { className: "text-md text-gray-600 font-semibold mb-4", children: t('profileInvitedUsers.emailHeading') }), invitedUsers === null || invitedUsers === void 0 ? void 0 : invitedUsers.results.map((user) => (jsx("div", { className: "border-b-gray-50 pb-2 mb-4 text-sm text-gray-600 dark:text-gray-400", children: user.email }, user.id))), (invitedUsers === null || invitedUsers === void 0 ? void 0 : invitedUsers.results.length) === 0 && (jsx("p", { className: "text-gray", children: t('profileInvitedUsers.noRecords') }))] })] }) }) }));
220518
220748
  }
220519
220749
 
220750
+ function DocumentSidebar({ isModal = false, sessionId = '', documents, }) {
220751
+ const t = useT();
220752
+ const [isCollapsed, setIsCollapsed] = React.useState(false);
220753
+ const params = useParams();
220754
+ const username = useUsername();
220755
+ const isStreaming = useSelector(selectStreaming);
220756
+ const hasOwnDocuments = documents !== undefined;
220757
+ const { data: sessionDocuments, refetch, status, } = useGetVectorDocumentsQuery({
220758
+ org: params.tenantKey,
220759
+ sessionId: sessionId,
220760
+ // @ts-ignore
220761
+ userId: username,
220762
+ }, {
220763
+ skip: hasOwnDocuments || !params.tenantKey || !sessionId || !username || !isLoggedIn(),
220764
+ });
220765
+ const vectorDocuments = hasOwnDocuments
220766
+ ? (documents !== null && documents !== void 0 ? documents : undefined)
220767
+ : sessionDocuments;
220768
+ React.useEffect(() => {
220769
+ if (!hasOwnDocuments && !isStreaming && isLoggedIn() && status !== 'uninitialized') {
220770
+ refetch();
220771
+ }
220772
+ }, [hasOwnDocuments, isStreaming, refetch]);
220773
+ const toggleCollapse = () => {
220774
+ setIsCollapsed(!isCollapsed);
220775
+ };
220776
+ return (jsx(Fragment$1, { children: (vectorDocuments === null || vectorDocuments === void 0 ? void 0 : vectorDocuments.length) && vectorDocuments.length > 0 ? (jsx("aside", { className: cn('transition-all duration-300 ease-in-out', isModal
220777
+ ? 'h-full max-h-full w-full overflow-auto'
220778
+ : 'hidden rounded-tl-lg rounded-bl-lg md:hidden lg:block', !isModal && (isCollapsed ? 'w-[40px]' : 'w-[380px]')), style: {
220779
+ backgroundColor: isCollapsed && !isModal ? 'white' : '#F3F6FB',
220780
+ // In the dialog the parent bounds the height; the rail caps itself.
220781
+ maxHeight: isModal ? undefined : 'calc(100vh - 80px)',
220782
+ }, children: jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [!isModal && (jsxs("div", { className: cn('flex cursor-pointer items-center gap-2 border-b border-gray-200 p-4 transition-colors duration-200', isCollapsed && 'h-[80px] justify-center rounded-tl-[5px] rounded-bl-[5px] p-2', 'hover:bg-blue-100'), onClick: toggleCollapse, style: { backgroundColor: '#F3F6FB' }, children: [jsx(FileText, { className: "h-5 w-5 shrink-0 text-blue-600" }), !isCollapsed && (jsxs(Fragment$1, { children: [jsx("h2", { className: "text-base font-medium text-[#646464]", children: t('documentSidebar.retrievedDocuments') }), jsx(ChevronRight, { className: cn('ml-auto h-4 w-4 text-gray-400 transition-transform', isCollapsed ? 'rotate-180' : '') })] }))] })), (!isCollapsed || isModal) && (jsx(Fragment$1, { children: jsx("div", { className: "min-h-0 flex-1 overflow-auto", style: { backgroundColor: '#F3F6FB' }, children: jsx("div", { className: "mt-0 space-y-1 p-2", children: vectorDocuments.map((doc, index) => {
220783
+ var _a, _b;
220784
+ return (jsxs(React.Fragment, { children: [jsx("div", { className: "rounded-lg p-2 hover:bg-gray-50", children: jsxs("div", { className: "flex items-start gap-2", children: [jsx(Radio$1, { className: "mt-1 h-4 w-4 text-gray-400" }), jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [jsxs("div", { className: "flex items-center justify-between", children: [jsx(Link$2, { href: (_a = doc.source) !== null && _a !== void 0 ? _a : '', target: "_blank", className: "min-w-0", children: jsx("span", { className: "text-sm font-semibold break-words text-[#646464] hover:text-blue-600", children: doc.title }) }), jsx("div", { className: "flex items-center gap-2 self-center", children: jsx("span", { className: "text-xs text-gray-500", children: (_b = doc === null || doc === void 0 ? void 0 : doc.score) === null || _b === void 0 ? void 0 : _b.toFixed(2) }) })] }), jsx("p", { className: "line-clamp-6 pt-[10px] text-[14px] text-gray-600", children: doc.snippet })] })] }) }), index < vectorDocuments.length - 1 && (jsx("div", { className: "my-2 border-t border-gray-200" }))] }, index));
220785
+ }) }) }) }))] }) })) : null }));
220786
+ }
220787
+
220788
+ /**
220789
+ * The chat's "Retrieved Documents" control: an outline button opening the
220790
+ * documents dialog (`DocumentSidebar` in modal mode). Fed by the session's
220791
+ * vector results by default, or by `documents` the host already has — the
220792
+ * admin transcript surfaces pass a turn's or a conversation's documents, so
220793
+ * one component shows retrieved documents everywhere.
220794
+ */
220795
+ function RetrievedDocumentsButton({ sessionId = '', documents, className, label, }) {
220796
+ const t = useT();
220797
+ const [isOpen, setIsOpen] = useState(false);
220798
+ const params = useParams();
220799
+ const username = useUsername();
220800
+ const isStreaming = useSelector(selectStreaming);
220801
+ const hasOwnDocuments = documents !== undefined;
220802
+ const { data: sessionDocuments, refetch, status, } = useGetVectorDocumentsQuery({
220803
+ org: params.tenantKey,
220804
+ sessionId: sessionId,
220805
+ // @ts-ignore
220806
+ userId: username,
220807
+ }, {
220808
+ skip: hasOwnDocuments || !params.tenantKey || !sessionId || !username || !isLoggedIn(),
220809
+ });
220810
+ const vectorDocuments = hasOwnDocuments
220811
+ ? (documents !== null && documents !== void 0 ? documents : undefined)
220812
+ : sessionDocuments;
220813
+ useEffect(() => {
220814
+ if (!hasOwnDocuments && !isStreaming && isLoggedIn() && status !== 'uninitialized') {
220815
+ refetch();
220816
+ }
220817
+ }, [hasOwnDocuments, isStreaming, refetch]);
220818
+ if (!(vectorDocuments === null || vectorDocuments === void 0 ? void 0 : vectorDocuments.length)) {
220819
+ return null;
220820
+ }
220821
+ return (jsxs(Fragment$1, { children: [jsxs(Button$1, { variant: "outline", size: "sm", className: cn('flex items-center gap-2 border-gray-200 bg-white text-gray-700 hover:bg-gray-50', className), onClick: (event) => {
220822
+ // Safe inside clickable transcript rows: the row must not react.
220823
+ event.stopPropagation();
220824
+ setIsOpen(true);
220825
+ }, "data-testid": "retrieved-documents-button", children: [jsx(FileText, { className: "h-4 w-4 text-blue-600" }), jsx("span", { children: label !== null && label !== void 0 ? label : t('retrievedDocumentsButton.title') })] }), jsx(Dialog, { open: isOpen, onOpenChange: setIsOpen, children: jsxs(DialogContent, { className: "flex max-h-[80vh] flex-col gap-0 p-0 sm:max-w-[425px] md:max-w-[600px]", children: [jsx(DialogHeader, { className: "border-b px-4 py-2", children: jsxs(DialogTitle, { className: "flex items-center gap-2 text-base font-medium text-[#646464]", children: [jsx(FileText, { className: "h-5 w-5 text-blue-600" }), t('retrievedDocumentsButton.title')] }) }), jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", "data-testid": "retrieved-documents-scroll", children: jsx(DocumentSidebar, { isModal: true, sessionId: sessionId, documents: documents }) })] }) })] }));
220826
+ }
220827
+
220828
+ var COLLAPSIBLE_NAME = "Collapsible";
220829
+ var [createCollapsibleContext] = createContextScope$1(COLLAPSIBLE_NAME);
220830
+ var [CollapsibleProvider, useCollapsibleContext] = createCollapsibleContext(COLLAPSIBLE_NAME);
220831
+ var Collapsible$1 = React.forwardRef(
220832
+ (props, forwardedRef) => {
220833
+ const {
220834
+ __scopeCollapsible,
220835
+ open: openProp,
220836
+ defaultOpen,
220837
+ disabled,
220838
+ onOpenChange,
220839
+ ...collapsibleProps
220840
+ } = props;
220841
+ const [open, setOpen] = useControllableState$1({
220842
+ prop: openProp,
220843
+ defaultProp: defaultOpen ?? false,
220844
+ onChange: onOpenChange,
220845
+ caller: COLLAPSIBLE_NAME
220846
+ });
220847
+ return /* @__PURE__ */ jsx(
220848
+ CollapsibleProvider,
220849
+ {
220850
+ scope: __scopeCollapsible,
220851
+ disabled,
220852
+ contentId: useId$1(),
220853
+ open,
220854
+ onOpenToggle: React.useCallback(() => setOpen((prevOpen) => !prevOpen), [setOpen]),
220855
+ children: /* @__PURE__ */ jsx(
220856
+ Primitive$2.div,
220857
+ {
220858
+ "data-state": getState$1(open),
220859
+ "data-disabled": disabled ? "" : void 0,
220860
+ ...collapsibleProps,
220861
+ ref: forwardedRef
220862
+ }
220863
+ )
220864
+ }
220865
+ );
220866
+ }
220867
+ );
220868
+ Collapsible$1.displayName = COLLAPSIBLE_NAME;
220869
+ var TRIGGER_NAME$2 = "CollapsibleTrigger";
220870
+ var CollapsibleTrigger$1 = React.forwardRef(
220871
+ (props, forwardedRef) => {
220872
+ const { __scopeCollapsible, ...triggerProps } = props;
220873
+ const context = useCollapsibleContext(TRIGGER_NAME$2, __scopeCollapsible);
220874
+ return /* @__PURE__ */ jsx(
220875
+ Primitive$2.button,
220876
+ {
220877
+ type: "button",
220878
+ "aria-controls": context.contentId,
220879
+ "aria-expanded": context.open || false,
220880
+ "data-state": getState$1(context.open),
220881
+ "data-disabled": context.disabled ? "" : void 0,
220882
+ disabled: context.disabled,
220883
+ ...triggerProps,
220884
+ ref: forwardedRef,
220885
+ onClick: composeEventHandlers$1(props.onClick, context.onOpenToggle)
220886
+ }
220887
+ );
220888
+ }
220889
+ );
220890
+ CollapsibleTrigger$1.displayName = TRIGGER_NAME$2;
220891
+ var CONTENT_NAME$3 = "CollapsibleContent";
220892
+ var CollapsibleContent$1 = React.forwardRef(
220893
+ (props, forwardedRef) => {
220894
+ const { forceMount, ...contentProps } = props;
220895
+ const context = useCollapsibleContext(CONTENT_NAME$3, props.__scopeCollapsible);
220896
+ return /* @__PURE__ */ jsx(Presence$2, { present: forceMount || context.open, children: ({ present }) => /* @__PURE__ */ jsx(CollapsibleContentImpl, { ...contentProps, ref: forwardedRef, present }) });
220897
+ }
220898
+ );
220899
+ CollapsibleContent$1.displayName = CONTENT_NAME$3;
220900
+ var CollapsibleContentImpl = React.forwardRef((props, forwardedRef) => {
220901
+ const { __scopeCollapsible, present, children, ...contentProps } = props;
220902
+ const context = useCollapsibleContext(CONTENT_NAME$3, __scopeCollapsible);
220903
+ const [isPresent, setIsPresent] = React.useState(present);
220904
+ const ref = React.useRef(null);
220905
+ const composedRefs = useComposedRefs$1(forwardedRef, ref);
220906
+ const heightRef = React.useRef(0);
220907
+ const height = heightRef.current;
220908
+ const widthRef = React.useRef(0);
220909
+ const width = widthRef.current;
220910
+ const isOpen = context.open || isPresent;
220911
+ const isMountAnimationPreventedRef = React.useRef(isOpen);
220912
+ const originalStylesRef = React.useRef(void 0);
220913
+ React.useEffect(() => {
220914
+ const rAF = requestAnimationFrame(() => isMountAnimationPreventedRef.current = false);
220915
+ return () => cancelAnimationFrame(rAF);
220916
+ }, []);
220917
+ useLayoutEffect2$1(() => {
220918
+ const node = ref.current;
220919
+ if (node) {
220920
+ originalStylesRef.current = originalStylesRef.current || {
220921
+ transitionDuration: node.style.transitionDuration,
220922
+ animationName: node.style.animationName
220923
+ };
220924
+ node.style.transitionDuration = "0s";
220925
+ node.style.animationName = "none";
220926
+ const rect = node.getBoundingClientRect();
220927
+ heightRef.current = rect.height;
220928
+ widthRef.current = rect.width;
220929
+ if (!isMountAnimationPreventedRef.current) {
220930
+ node.style.transitionDuration = originalStylesRef.current.transitionDuration;
220931
+ node.style.animationName = originalStylesRef.current.animationName;
220932
+ }
220933
+ setIsPresent(present);
220934
+ }
220935
+ }, [context.open, present]);
220936
+ return /* @__PURE__ */ jsx(
220937
+ Primitive$2.div,
220938
+ {
220939
+ "data-state": getState$1(context.open),
220940
+ "data-disabled": context.disabled ? "" : void 0,
220941
+ id: context.contentId,
220942
+ hidden: !isOpen,
220943
+ ...contentProps,
220944
+ ref: composedRefs,
220945
+ style: {
220946
+ [`--radix-collapsible-content-height`]: height ? `${height}px` : void 0,
220947
+ [`--radix-collapsible-content-width`]: width ? `${width}px` : void 0,
220948
+ ...props.style
220949
+ },
220950
+ children: isOpen && children
220951
+ }
220952
+ );
220953
+ });
220954
+ function getState$1(open) {
220955
+ return open ? "open" : "closed";
220956
+ }
220957
+ var Root$5 = Collapsible$1;
220958
+
220959
+ const Collapsible = Root$5;
220960
+ const CollapsibleTrigger = CollapsibleTrigger$1;
220961
+ const CollapsibleContent = CollapsibleContent$1;
220962
+
220963
+ function getFriendlyToolName(toolName) {
220964
+ if (TOOL_NAME_MAP === null || TOOL_NAME_MAP === void 0 ? void 0 : TOOL_NAME_MAP[toolName])
220965
+ return TOOL_NAME_MAP[toolName];
220966
+ const words = toolName.replace(/_/g, ' ').trim();
220967
+ return words.charAt(0).toUpperCase() + words.slice(1);
220968
+ }
220969
+ // Extract a short human-readable query from tool input or log
220970
+ function getQueryLabel(toolCall) {
220971
+ var _a, _b, _c, _d, _e;
220972
+ // Prefer structured input
220973
+ if (toolCall.input && typeof toolCall.input === 'object') {
220974
+ const val = (_c = (_b = (_a = toolCall.input.query) !== null && _a !== void 0 ? _a : toolCall.input.q) !== null && _b !== void 0 ? _b : toolCall.input.input) !== null && _c !== void 0 ? _c : toolCall.input.text;
220975
+ if (val && typeof val === 'string')
220976
+ return val;
220977
+ // Fallback: first string value in input
220978
+ const firstStr = Object.values(toolCall.input).find((v) => typeof v === 'string');
220979
+ if (firstStr)
220980
+ return firstStr;
220981
+ }
220982
+ // Fallback: parse the "… with `{...}`" snippet of the log string. Located
220983
+ // with index scans rather than a lazy regex, which backtracks
220984
+ // polynomially on adversarial logs (CodeQL js/polynomial-redos).
220985
+ if (toolCall.log) {
220986
+ const snippet = extractLogPayload(toolCall.log);
220987
+ if (snippet) {
220988
+ try {
220989
+ const parsed = JSON.parse(snippet.replace(/'/g, '"'));
220990
+ const val = (_e = (_d = parsed.query) !== null && _d !== void 0 ? _d : parsed.q) !== null && _e !== void 0 ? _e : parsed.input;
220991
+ if (val)
220992
+ return val;
220993
+ }
220994
+ catch (_f) {
220995
+ // Not every log snippet is JSON (apostrophes inside the payload break
220996
+ // the quote swap above). That is expected input, not an error — and
220997
+ // this runs inside a memo comparator on every streaming tick, so
220998
+ // logging here flooded the console on phones.
220999
+ }
221000
+ }
221001
+ }
221002
+ return null;
221003
+ }
221004
+ /** The `{...}` inside the first "with `{...}`" of a tool log, or null. */
221005
+ function extractLogPayload(log) {
221006
+ let from = 0;
221007
+ while (from < log.length) {
221008
+ const open = log.indexOf('`{', from);
221009
+ if (open === -1)
221010
+ return null;
221011
+ // Must follow the word "with" plus at least one whitespace character.
221012
+ let i = open - 1;
221013
+ let sawSpace = false;
221014
+ while (i >= 0 && /\s/.test(log[i])) {
221015
+ sawSpace = true;
221016
+ i -= 1;
221017
+ }
221018
+ if (sawSpace && log.slice(Math.max(0, i - 3), i + 1) === 'with') {
221019
+ const close = log.indexOf('}`', open + 1);
221020
+ if (close === -1)
221021
+ return null;
221022
+ return log.slice(open + 1, close + 1);
221023
+ }
221024
+ from = open + 1;
221025
+ }
221026
+ return null;
221027
+ }
221028
+ function formatResult(result) {
221029
+ return result.replace(/\n{3,}/g, '\n\n').trim();
221030
+ }
221031
+
221032
+ const TOOL_ICONS = {
221033
+ web_search_call: Globe,
221034
+ vector_search: Search,
221035
+ code_executor: Code$1,
221036
+ file_reader: FileText,
221037
+ wikipedia: BookOpen,
221038
+ };
221039
+ function getToolIcon(toolName) {
221040
+ return (toolName && TOOL_ICONS[toolName]) || Wrench;
221041
+ }
221042
+ /**
221043
+ * Collapsible record of the tools a turn used — the "Used N tools" line of
221044
+ * the chat bubble.
221045
+ *
221046
+ * The header always reads as the completed record ("Used N tools") and the
221047
+ * individual rows never animate. Only while this row is the live phase does the
221048
+ * header sprout dots — at which point the working line has stood down, so there
221049
+ * is still exactly one animated "agent is working" signal on screen.
221050
+ */
221051
+ function ToolCallIndicator({ toolCalls, isActive = false, showResults = false, }) {
221052
+ const [isOpen, setIsOpen] = useState(false);
221053
+ // `write_todos` has a dedicated renderer (`AgentTodoList`), so it must never
221054
+ // appear as a generic tool card nor be counted in "Used N tools".
221055
+ const visibleToolCalls = (toolCalls !== null && toolCalls !== void 0 ? toolCalls : []).filter((tc) => (tc === null || tc === void 0 ? void 0 : tc.name) !== WRITE_TODOS_TOOL);
221056
+ if (visibleToolCalls.length === 0) {
221057
+ return null;
221058
+ }
221059
+ const uniqueToolCount = new Set(visibleToolCalls.map((tc) => tc.name)).size;
221060
+ const headerLabel = `Used ${uniqueToolCount} tool${uniqueToolCount === 1 ? '' : 's'}`;
221061
+ return (jsxs(Collapsible, { open: isOpen, onOpenChange: setIsOpen, className: "mb-2", children: [jsxs(CollapsibleTrigger, { className: "flex cursor-pointer items-center gap-1 pt-1 text-xs text-gray-600 transition-colors hover:text-gray-800", children: [jsx(ChevronRight, { className: cn('h-3 w-3 transition-transform duration-200', isOpen && 'rotate-90') }), jsx(Wrench, { className: "h-3 w-3" }), jsx("span", { children: headerLabel }), isActive && (jsxs("span", { className: "inline-flex gap-0.5", "aria-hidden": "true", children: [jsx("span", { className: "inline-block h-1 w-1 animate-bounce rounded-full bg-gray-600 [animation-delay:0ms] motion-reduce:animate-none" }), jsx("span", { className: "inline-block h-1 w-1 animate-bounce rounded-full bg-gray-600 [animation-delay:150ms] motion-reduce:animate-none" }), jsx("span", { className: "inline-block h-1 w-1 animate-bounce rounded-full bg-gray-600 [animation-delay:300ms] motion-reduce:animate-none" })] }))] }), jsx(CollapsibleContent, { className: "pb-2 pl-4", children: jsx("div", { className: "space-y-2.5 border-l-2 border-gray-300 pt-2.5 pl-3 text-xs leading-relaxed text-gray-600", children: visibleToolCalls.map((toolCall, index) => {
221062
+ var _a;
221063
+ const query = getQueryLabel(toolCall);
221064
+ const Icon = getToolIcon(toolCall === null || toolCall === void 0 ? void 0 : toolCall.name);
221065
+ const result = showResults && (toolCall === null || toolCall === void 0 ? void 0 : toolCall.result) ? formatResult(toolCall.result) : null;
221066
+ return (jsxs("div", { children: [jsxs("div", { className: "flex items-center gap-1.5 text-gray-700", children: [jsx(Icon, { className: "h-3 w-3 shrink-0 text-gray-500" }), jsx("span", { className: "font-medium", children: getFriendlyToolName((_a = toolCall === null || toolCall === void 0 ? void 0 : toolCall.name) !== null && _a !== void 0 ? _a : '') })] }), query && (jsx("div", { className: "mt-0.5 ml-[18px] text-gray-600", children: jsx(Markdown, { className: "prose prose-xs max-w-none [&_*]:text-xs [&_*]:text-gray-600 [&_p]:m-0", children: query }) })), result && (jsx("div", { className: "mt-1 ml-[18px] max-h-48 overflow-y-auto rounded-md border border-gray-200 bg-white px-2 py-1.5 leading-relaxed whitespace-pre-wrap text-gray-600", "data-testid": "tool-call-result", children: result }))] }, (toolCall === null || toolCall === void 0 ? void 0 : toolCall.id) || index));
221067
+ }) }) })] }));
221068
+ }
221069
+
221070
+ // Model wire keys whose raw form is not presentable, keyed lowercased and
221071
+ // alphanumerics-only.
221072
+ const IBL_AI_DISPLAY_NAME = 'ibl.ai';
221073
+ const MODEL_DISPLAY_NAME_BY_KEY = {
221074
+ iblai: IBL_AI_DISPLAY_NAME,
221075
+ };
221076
+ /**
221077
+ * The label to show for a model when only its wire key is known.
221078
+ *
221079
+ * The LLM picker gets a `display_name` per model straight from the API, but
221080
+ * mentor settings and transcripts persist only `llm_name` — a wire key — so
221081
+ * surfaces that read from those have no label to fall back on and would
221082
+ * render `iblai`. Most keys are already presentable (`gpt-4.1`), so this maps
221083
+ * only the ones that are not and passes everything else through untouched.
221084
+ */
221085
+ function getLLMModelDisplayName(llmName) {
221086
+ var _a;
221087
+ const raw = llmName !== null && llmName !== void 0 ? llmName : '';
221088
+ const normalized = raw.toLowerCase().replace(/[^a-z0-9]/g, '');
221089
+ // ibl.ai keys are the brand followed by an optional variant — `iblai`,
221090
+ // `iblai-pro`, `iblai-fast`, `IBL AI Pro`. Rebuild the label the model
221091
+ // picker gets from the API (`ibl.ai Pro`) rather than collapsing every
221092
+ // variant onto a bare `ibl.ai`, which would disagree with the picker.
221093
+ const brand = /^(iblai|ibl)(.*)$/.exec(normalized);
221094
+ const leadsWithBrand = brand &&
221095
+ (brand[1] === 'iblai' ||
221096
+ raw
221097
+ .toLowerCase()
221098
+ .split(/[^a-z0-9]+/)
221099
+ .filter(Boolean)[0] === 'ibl');
221100
+ if (leadsWithBrand) {
221101
+ const variant = brand[2];
221102
+ if (!variant)
221103
+ return IBL_AI_DISPLAY_NAME;
221104
+ // `ai` is part of the brand when the key spells it out (`ibl-ai-pro`).
221105
+ const suffix = variant.startsWith('ai') && brand[1] === 'ibl' ? variant.slice(2) : variant;
221106
+ if (!suffix)
221107
+ return IBL_AI_DISPLAY_NAME;
221108
+ return `${IBL_AI_DISPLAY_NAME} ${suffix.charAt(0).toUpperCase()}${suffix.slice(1)}`;
221109
+ }
221110
+ return (_a = MODEL_DISPLAY_NAME_BY_KEY[normalized]) !== null && _a !== void 0 ? _a : raw;
221111
+ }
221112
+
221113
+ /** Loose key for matching a model across spellings: lower-case, alphanumerics only. */
221114
+ const modelKey = (value) => (value !== null && value !== void 0 ? value : '').toLowerCase().replace(/[^a-z0-9]/g, '');
221115
+ /**
221116
+ * Model families → the provider keys the catalogue may list them under, for
221117
+ * a model the catalogue does not name (a dated or "-latest" wire key, or a
221118
+ * model the tenant enabled elsewhere). First key present in the catalogue
221119
+ * wins; hosted variants (Azure, Bedrock, Groq) come after the vendor.
221120
+ */
221121
+ const MODEL_FAMILY_PROVIDERS = [
221122
+ [/^(gpt|o[1-9]|chatgpt|davinci|text-embedding)/, ['openai', 'azure_openai', 'azure']],
221123
+ [/^claude/, ['anthropic', 'bedrock']],
221124
+ [/^(gemini|gemma|palm)/, ['google', 'vertex', 'vertexai', 'gemini']],
221125
+ [/^(llama|meta)/, ['llama', 'meta', 'bedrock', 'groq']],
221126
+ [/^(mistral|mixtral|codestral)/, ['mistral']],
221127
+ [/^deepseek/, ['deepseek']],
221128
+ [/^grok/, ['xai', 'grok']],
221129
+ [/^(command|aya)/, ['cohere']],
221130
+ [/^qwen/, ['qwen', 'alibaba']],
221131
+ [/^(nova|titan)/, ['bedrock', 'amazon']],
221132
+ ];
221133
+ /** Provider keys a model of this family is listed under, in order of preference. */
221134
+ function inferProviderKeysFromModel(model) {
221135
+ const key = modelKey(model);
221136
+ if (!key)
221137
+ return [];
221138
+ for (const [pattern, providers] of MODEL_FAMILY_PROVIDERS) {
221139
+ if (pattern.test(key))
221140
+ return providers;
221141
+ }
221142
+ return [];
221143
+ }
221144
+ /**
221145
+ * A resolver from a provider key (the `llm_provider` mentor settings persist,
221146
+ * e.g. `openai`) to the name and logo the backend publishes for it. A second,
221147
+ * optional model key finds the provider whose `chat_models` list it, for
221148
+ * payloads that name only the model (analytics transcript rows).
221149
+ *
221150
+ * Provider naming is backend-owned: the mentor-llms catalogue carries each
221151
+ * provider's `logo` (and `display_name` where the API supplies one), so this
221152
+ * reads the same RTK Query cache entry the LLM tab and model picker fill —
221153
+ * matched on `name`. A key the catalogue does not list (the catalogue is
221154
+ * still loading, or an on-device provider that has no backend row) resolves
221155
+ * to the raw key and no logo, so callers degrade to plain text.
221156
+ *
221157
+ * Returns a function rather than a value so list renderers (a mentors table,
221158
+ * a facet dropdown) can resolve many keys from one subscription.
221159
+ */
221160
+ function useLlmProviderCatalogue({ org, userId, mentorId }) {
221161
+ const skip = !org || !userId || !mentorId;
221162
+ const { data: providers } = useGetLlmsQuery({
221163
+ org: org !== null && org !== void 0 ? org : '',
221164
+ // @ts-expect-error - userId is not in the generated query args but the endpoint scopes by it
221165
+ userId: userId !== null && userId !== void 0 ? userId : '',
221166
+ mentorId: mentorId !== null && mentorId !== void 0 ? mentorId : '',
221167
+ }, { skip });
221168
+ // Indexed once per catalogue load so list renderers resolve each key in
221169
+ // O(1). First row wins on a duplicate name, as `Array.find` would.
221170
+ const rowsByName = useMemo(() => {
221171
+ const byName = new Map();
221172
+ if (!Array.isArray(providers))
221173
+ return byName;
221174
+ for (const row of providers) {
221175
+ if ((row === null || row === void 0 ? void 0 : row.name) && !byName.has(row.name))
221176
+ byName.set(row.name, row);
221177
+ }
221178
+ return byName;
221179
+ }, [providers]);
221180
+ // Model key → the provider row listing it. Keyed loosely (see `modelKey`):
221181
+ // transcripts persist the model as it was displayed ("GPT-5.2") while the
221182
+ // catalogue lists the wire key ("gpt-5.2"), so both spellings, and the
221183
+ // display name, must find the row.
221184
+ const rowsByModel = useMemo(() => {
221185
+ var _a;
221186
+ const byModel = new Map();
221187
+ for (const row of rowsByName.values()) {
221188
+ for (const model of (_a = row.chat_models) !== null && _a !== void 0 ? _a : []) {
221189
+ for (const candidate of [model === null || model === void 0 ? void 0 : model.llm_name, model === null || model === void 0 ? void 0 : model.display_name]) {
221190
+ const key = modelKey(candidate);
221191
+ if (key && !byModel.has(key))
221192
+ byModel.set(key, row);
221193
+ }
221194
+ }
221195
+ }
221196
+ return byModel;
221197
+ }, [rowsByName]);
221198
+ // Provider rows keyed loosely by name and display name ("OpenAI" → openai).
221199
+ const rowsByLooseName = useMemo(() => {
221200
+ const byLoose = new Map();
221201
+ for (const row of rowsByName.values()) {
221202
+ for (const candidate of [row.name, row.display_name]) {
221203
+ const key = modelKey(candidate);
221204
+ if (key && !byLoose.has(key))
221205
+ byLoose.set(key, row);
221206
+ }
221207
+ }
221208
+ return byLoose;
221209
+ }, [rowsByName]);
221210
+ const rowForModel = useCallback((llmName) => {
221211
+ var _a;
221212
+ const wanted = modelKey(llmName);
221213
+ if (!wanted)
221214
+ return undefined;
221215
+ const exact = rowsByModel.get(wanted);
221216
+ if (exact)
221217
+ return exact;
221218
+ // A dated or "-latest" wire key ("gpt-5.2-2026-03-01") still names
221219
+ // the family the catalogue lists ("gpt-5.2"), and vice versa.
221220
+ for (const [listed, row] of rowsByModel) {
221221
+ if (listed.startsWith(wanted) || wanted.startsWith(listed))
221222
+ return row;
221223
+ }
221224
+ for (const provider of inferProviderKeysFromModel(llmName)) {
221225
+ const row = (_a = rowsByName.get(provider)) !== null && _a !== void 0 ? _a : rowsByLooseName.get(modelKey(provider));
221226
+ if (row)
221227
+ return row;
221228
+ }
221229
+ return undefined;
221230
+ }, [rowsByModel, rowsByName, rowsByLooseName]);
221231
+ return useCallback((llmProvider, llmName) => {
221232
+ var _a, _b, _c, _d, _e;
221233
+ const key = llmProvider !== null && llmProvider !== void 0 ? llmProvider : '';
221234
+ const row = (_c = (_b = (_a = rowsByName.get(key)) !== null && _a !== void 0 ? _a : rowsByName.get(key.toLowerCase())) !== null && _b !== void 0 ? _b : rowsByLooseName.get(modelKey(key))) !== null && _c !== void 0 ? _c : rowForModel(llmName);
221235
+ return {
221236
+ logo: (row === null || row === void 0 ? void 0 : row.logo) || null,
221237
+ displayName: ((_d = row === null || row === void 0 ? void 0 : row.display_name) === null || _d === void 0 ? void 0 : _d.trim()) || ((_e = row === null || row === void 0 ? void 0 : row.name) === null || _e === void 0 ? void 0 : _e.trim()) || key,
221238
+ };
221239
+ }, [rowsByName, rowsByLooseName, rowForModel]);
221240
+ }
221241
+ const clean$1 = (value) => (value === null || value === void 0 ? void 0 : value.trim()) || undefined;
221242
+ /**
221243
+ * The label to show for a model.
221244
+ *
221245
+ * Settings persist only `llm_name` — a wire key like `iblai-pro` or
221246
+ * `claude-haiku-4-5-20251001` — while the model picker reads the catalogue,
221247
+ * which carries a `display_name` ("ibl.ai Pro", "Claude Haiku 4.5"). Resolve
221248
+ * the label in three steps:
221249
+ *
221250
+ * 1. `llm_config.display_name` from settings, when the caller has it.
221251
+ * 2. The mentor-llms catalogue, matched on `llm_name`. Only queried when step
221252
+ * 1 came back empty.
221253
+ * 3. `getLLMModelDisplayName`, which tidies the handful of keys whose raw
221254
+ * form is unpresentable — used while the catalogue is loading or failed.
221255
+ */
221256
+ function useLlmDisplayName({ llmName, llmConfig, org, userId, mentorId, }) {
221257
+ var _a;
221258
+ const fromConfig = clean$1(llmConfig === null || llmConfig === void 0 ? void 0 : llmConfig.display_name);
221259
+ const skip = Boolean(fromConfig) || !llmName || !org || !userId || !mentorId;
221260
+ const { data: providers } = useGetLlmsQuery({
221261
+ org: org !== null && org !== void 0 ? org : '',
221262
+ // @ts-expect-error - userId is not in the generated query args but the endpoint scopes by it
221263
+ userId: userId !== null && userId !== void 0 ? userId : '',
221264
+ mentorId: mentorId !== null && mentorId !== void 0 ? mentorId : '',
221265
+ }, { skip });
221266
+ const fromCatalogue = useMemo(() => {
221267
+ var _a;
221268
+ if (skip || !llmName || !Array.isArray(providers))
221269
+ return undefined;
221270
+ const wanted = modelKey(llmName);
221271
+ for (const provider of providers) {
221272
+ const match = (_a = provider === null || provider === void 0 ? void 0 : provider.chat_models) === null || _a === void 0 ? void 0 : _a.find((model) => modelKey(model === null || model === void 0 ? void 0 : model.llm_name) === wanted || modelKey(model === null || model === void 0 ? void 0 : model.display_name) === wanted);
221273
+ const label = clean$1(match === null || match === void 0 ? void 0 : match.display_name);
221274
+ if (label)
221275
+ return label;
221276
+ }
221277
+ return undefined;
221278
+ }, [skip, providers, llmName]);
221279
+ return (_a = fromConfig !== null && fromConfig !== void 0 ? fromConfig : fromCatalogue) !== null && _a !== void 0 ? _a : getLLMModelDisplayName(llmName);
221280
+ }
221281
+
221282
+ /**
221283
+ * A model the way the nav bar shows the mentor's: the provider logo in a
221284
+ * white circle (the catalogue's artwork, or the bot icon when it has none)
221285
+ * beside the model's display name. Read-only; used on admin transcripts to
221286
+ * name the model a turn ran on.
221287
+ */
221288
+ function LlmModelBadge({ model, provider, tenantKey, mentorId, className, }) {
221289
+ var _a, _b;
221290
+ const params = useParams();
221291
+ const username = useUsername();
221292
+ const org = (_a = tenantKey !== null && tenantKey !== void 0 ? tenantKey : params === null || params === void 0 ? void 0 : params.tenantKey) !== null && _a !== void 0 ? _a : null;
221293
+ const mentor = (_b = mentorId !== null && mentorId !== void 0 ? mentorId : params === null || params === void 0 ? void 0 : params.mentorId) !== null && _b !== void 0 ? _b : null;
221294
+ const resolveLlmProvider = useLlmProviderCatalogue({ org, userId: username, mentorId: mentor });
221295
+ // With no provider key (transcript rows carry only the model), the
221296
+ // catalogue row that lists this model supplies the logo and name.
221297
+ const providerDetails = resolveLlmProvider(provider, model);
221298
+ const displayName = useLlmDisplayName({
221299
+ llmName: model,
221300
+ org,
221301
+ userId: username,
221302
+ mentorId: mentor,
221303
+ });
221304
+ const label = displayName || providerDetails.displayName;
221305
+ if (!label)
221306
+ return null;
221307
+ return (jsxs("span", { className: cn('inline-flex max-w-full items-center gap-2 text-sm font-medium text-[#646464]', className), title: providerDetails.displayName && providerDetails.displayName !== label
221308
+ ? `${label} · ${providerDetails.displayName}`
221309
+ : label, "data-testid": "llm-model-badge", children: [jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-white", children: providerDetails.logo ? (jsx("img", { src: providerDetails.logo, alt: `${label} model logo`, className: "h-5 w-5 object-contain", height: 32, width: 32, loading: "lazy" })) : (jsx(Bot, { className: "h-4 w-4", "aria-hidden": "true" })) }), jsx("span", { className: "max-w-[220px] truncate", children: label })] }));
221310
+ }
221311
+
221312
+ /** SPAs whose users have coursework, so the profile leads with a Gradebook tab. */
221313
+ const GRADEBOOK_SPAS = ['lms', 'skills'];
221314
+ /**
221315
+ * The customization `Profile` is opened with: whatever the host passed, with
221316
+ * the Gradebook tab defaulted from the SPA it runs in. An explicit
221317
+ * `showGradebookTab` always wins, so a host can force it either way.
221318
+ */
221319
+ function profileCustomization(customization, currentSPA) {
221320
+ var _a;
221321
+ return {
221322
+ ...customization,
221323
+ showGradebookTab: (_a = customization === null || customization === void 0 ? void 0 : customization.showGradebookTab) !== null && _a !== void 0 ? _a : GRADEBOOK_SPAS.includes((currentSPA !== null && currentSPA !== void 0 ? currentSPA : '').trim().toLowerCase()),
221324
+ };
221325
+ }
221326
+ /**
221327
+ * The shared `Profile` viewer in a dialog — the same modal the tenant
221328
+ * Management tab opens when a user row is clicked. Controlled variant for
221329
+ * hosts that trigger it from a whole table row.
221330
+ *
221331
+ * Radix portals the dialog into `document.body`, but a React portal still
221332
+ * bubbles its events through the React tree — so without the guard below, a
221333
+ * click *inside* the open profile would reach the row this dialog is rendered
221334
+ * from and fire its `onClick` (e.g. opening the enrollment progress modal
221335
+ * behind the profile). The wrapper is `display: contents`, so it adds nothing
221336
+ * to the host's layout.
221337
+ */
221338
+ function UserProfileDialog({ tenantKey, username, isOpen, onClose, customization, currentSPA, }) {
221339
+ const t = useT();
221340
+ if (!isOpen)
221341
+ return null;
221342
+ return (jsx("span", { className: "contents", onClick: (event) => event.stopPropagation(), onMouseDown: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: jsx(Dialog, { open: true, onOpenChange: onClose, children: jsxs(DialogContent, { autoFocus: false, className: "profile-component-dialog max-w-7xl w-[85vw] gap-0 overflow-hidden bg-white p-0", style: { height: '85vh', display: 'flex', flexDirection: 'column' }, "data-testid": "user-profile-link-dialog", children: [jsx(DialogTitle, { className: "sr-only", children: t('tabsUserstab.profileDialogTitle') }), jsx(DialogDescription, { className: "sr-only", children: t('tabsUserstab.profileDialogDescription') }), jsx(Profile, { tenant: tenantKey, tenants: [], username: username, onClose: onClose, customization: profileCustomization(customization, currentSPA), currentSPA: currentSPA })] }) }) }));
221343
+ }
221344
+ /**
221345
+ * Clickable user identity that opens the shared `Profile` viewer in a dialog
221346
+ * — the same modal the tenant Management tab opens when a user row is
221347
+ * clicked. `Profile` drops into preview (read-only) mode on its own for anyone
221348
+ * but the signed-in user. Used by the spend-cap tables and every analytics
221349
+ * surface that names a user; safe inside clickable rows and cards, since
221350
+ * neither its clicks nor its key presses reach the host's handlers.
221351
+ */
221352
+ function UserProfileLink({ tenantKey, username, display, className, testId, customization, currentSPA, }) {
221353
+ const t = useT();
221354
+ const [isOpen, setIsOpen] = React__default.useState(false);
221355
+ if (!username) {
221356
+ // `inline-block`, or `truncate`'s `overflow: hidden` has nothing to clip:
221357
+ // an inline box takes its width from its own text.
221358
+ return (jsx("span", { className: cn('inline-block max-w-full truncate', className), "data-testid": testId, children: display }));
221359
+ }
221360
+ return (jsxs(Fragment$1, { children: [jsx("button", { type: "button", onClick: (event) => {
221361
+ event.stopPropagation();
221362
+ setIsOpen(true);
221363
+ },
221364
+ // Rows that open on Enter/Space would otherwise swallow the key press.
221365
+ onKeyDown: (event) => event.stopPropagation(), className: cn('max-w-full cursor-pointer truncate text-left hover:text-blue-600 hover:underline focus:text-blue-600 focus:outline-none', className), "aria-label": t('tabsUserstab.viewProfileAriaLabel', { name: display }), "data-testid": testId, children: display }), jsx(UserProfileDialog, { tenantKey: tenantKey, username: username, isOpen: isOpen, onClose: () => setIsOpen(false), customization: customization, currentSPA: currentSPA })] }));
221366
+ }
221367
+
220520
221368
  const AdvancedPagination = ({ currentPage, totalPages, onPageChange, totalCount, itemsPerPage, itemLabel = 'items', variant = 'default', className, }) => {
220521
221369
  const t = useT();
220522
221370
  if (totalPages <= 1) {
@@ -222427,141 +223275,6 @@ function trimTrailingSlashes(value) {
222427
223275
  return end === value.length ? value : value.slice(0, end);
222428
223276
  }
222429
223277
 
222430
- var COLLAPSIBLE_NAME = "Collapsible";
222431
- var [createCollapsibleContext] = createContextScope$1(COLLAPSIBLE_NAME);
222432
- var [CollapsibleProvider, useCollapsibleContext] = createCollapsibleContext(COLLAPSIBLE_NAME);
222433
- var Collapsible$1 = React.forwardRef(
222434
- (props, forwardedRef) => {
222435
- const {
222436
- __scopeCollapsible,
222437
- open: openProp,
222438
- defaultOpen,
222439
- disabled,
222440
- onOpenChange,
222441
- ...collapsibleProps
222442
- } = props;
222443
- const [open, setOpen] = useControllableState$1({
222444
- prop: openProp,
222445
- defaultProp: defaultOpen ?? false,
222446
- onChange: onOpenChange,
222447
- caller: COLLAPSIBLE_NAME
222448
- });
222449
- return /* @__PURE__ */ jsx(
222450
- CollapsibleProvider,
222451
- {
222452
- scope: __scopeCollapsible,
222453
- disabled,
222454
- contentId: useId$1(),
222455
- open,
222456
- onOpenToggle: React.useCallback(() => setOpen((prevOpen) => !prevOpen), [setOpen]),
222457
- children: /* @__PURE__ */ jsx(
222458
- Primitive$2.div,
222459
- {
222460
- "data-state": getState$1(open),
222461
- "data-disabled": disabled ? "" : void 0,
222462
- ...collapsibleProps,
222463
- ref: forwardedRef
222464
- }
222465
- )
222466
- }
222467
- );
222468
- }
222469
- );
222470
- Collapsible$1.displayName = COLLAPSIBLE_NAME;
222471
- var TRIGGER_NAME$2 = "CollapsibleTrigger";
222472
- var CollapsibleTrigger$1 = React.forwardRef(
222473
- (props, forwardedRef) => {
222474
- const { __scopeCollapsible, ...triggerProps } = props;
222475
- const context = useCollapsibleContext(TRIGGER_NAME$2, __scopeCollapsible);
222476
- return /* @__PURE__ */ jsx(
222477
- Primitive$2.button,
222478
- {
222479
- type: "button",
222480
- "aria-controls": context.contentId,
222481
- "aria-expanded": context.open || false,
222482
- "data-state": getState$1(context.open),
222483
- "data-disabled": context.disabled ? "" : void 0,
222484
- disabled: context.disabled,
222485
- ...triggerProps,
222486
- ref: forwardedRef,
222487
- onClick: composeEventHandlers$1(props.onClick, context.onOpenToggle)
222488
- }
222489
- );
222490
- }
222491
- );
222492
- CollapsibleTrigger$1.displayName = TRIGGER_NAME$2;
222493
- var CONTENT_NAME$3 = "CollapsibleContent";
222494
- var CollapsibleContent$1 = React.forwardRef(
222495
- (props, forwardedRef) => {
222496
- const { forceMount, ...contentProps } = props;
222497
- const context = useCollapsibleContext(CONTENT_NAME$3, props.__scopeCollapsible);
222498
- return /* @__PURE__ */ jsx(Presence$2, { present: forceMount || context.open, children: ({ present }) => /* @__PURE__ */ jsx(CollapsibleContentImpl, { ...contentProps, ref: forwardedRef, present }) });
222499
- }
222500
- );
222501
- CollapsibleContent$1.displayName = CONTENT_NAME$3;
222502
- var CollapsibleContentImpl = React.forwardRef((props, forwardedRef) => {
222503
- const { __scopeCollapsible, present, children, ...contentProps } = props;
222504
- const context = useCollapsibleContext(CONTENT_NAME$3, __scopeCollapsible);
222505
- const [isPresent, setIsPresent] = React.useState(present);
222506
- const ref = React.useRef(null);
222507
- const composedRefs = useComposedRefs$1(forwardedRef, ref);
222508
- const heightRef = React.useRef(0);
222509
- const height = heightRef.current;
222510
- const widthRef = React.useRef(0);
222511
- const width = widthRef.current;
222512
- const isOpen = context.open || isPresent;
222513
- const isMountAnimationPreventedRef = React.useRef(isOpen);
222514
- const originalStylesRef = React.useRef(void 0);
222515
- React.useEffect(() => {
222516
- const rAF = requestAnimationFrame(() => isMountAnimationPreventedRef.current = false);
222517
- return () => cancelAnimationFrame(rAF);
222518
- }, []);
222519
- useLayoutEffect2$1(() => {
222520
- const node = ref.current;
222521
- if (node) {
222522
- originalStylesRef.current = originalStylesRef.current || {
222523
- transitionDuration: node.style.transitionDuration,
222524
- animationName: node.style.animationName
222525
- };
222526
- node.style.transitionDuration = "0s";
222527
- node.style.animationName = "none";
222528
- const rect = node.getBoundingClientRect();
222529
- heightRef.current = rect.height;
222530
- widthRef.current = rect.width;
222531
- if (!isMountAnimationPreventedRef.current) {
222532
- node.style.transitionDuration = originalStylesRef.current.transitionDuration;
222533
- node.style.animationName = originalStylesRef.current.animationName;
222534
- }
222535
- setIsPresent(present);
222536
- }
222537
- }, [context.open, present]);
222538
- return /* @__PURE__ */ jsx(
222539
- Primitive$2.div,
222540
- {
222541
- "data-state": getState$1(context.open),
222542
- "data-disabled": context.disabled ? "" : void 0,
222543
- id: context.contentId,
222544
- hidden: !isOpen,
222545
- ...contentProps,
222546
- ref: composedRefs,
222547
- style: {
222548
- [`--radix-collapsible-content-height`]: height ? `${height}px` : void 0,
222549
- [`--radix-collapsible-content-width`]: width ? `${width}px` : void 0,
222550
- ...props.style
222551
- },
222552
- children: isOpen && children
222553
- }
222554
- );
222555
- });
222556
- function getState$1(open) {
222557
- return open ? "open" : "closed";
222558
- }
222559
- var Root$5 = Collapsible$1;
222560
-
222561
- const Collapsible = Root$5;
222562
- const CollapsibleTrigger = CollapsibleTrigger$1;
222563
- const CollapsibleContent = CollapsibleContent$1;
222564
-
222565
223278
  // Pure logic behind the application form (questions) editor. No React here so
222566
223279
  // it stays unit testable. The backend validates schemas on save and remains
222567
223280
  // authoritative; these helpers only shape the editor's local state.
@@ -226780,62 +227493,6 @@ function SpendLimitsSection({ tenantKey, labels: labelsOverride }) {
226780
227493
  return (jsxs(Fragment$1, { children: [jsxs("div", { "data-testid": "spend-limits-workspace-section", className: "rounded-lg border border-[#e5e5e5] bg-white shadow-[0_1px_2px_0_rgba(0,0,0,0.05)] dark:bg-zinc-900 dark:border-zinc-800", children: [jsx("div", { className: "flex flex-col gap-2 p-4 sm:flex-row sm:items-start sm:justify-between", children: jsxs("div", { className: "space-y-0.5", children: [jsx("h3", { className: "text-[16px] font-medium leading-6 text-[#101828] dark:text-gray-100", children: labels.tenantSections.workspace.title }), jsx("p", { className: "text-[13px] font-normal leading-5 text-[#4a5565] dark:text-gray-400", children: labels.tenantSections.workspace.description })] }) }), jsx("div", { className: "border-t border-[#e5e7eb] dark:border-zinc-800 px-4 py-3", children: isTenantCapLoading ? (jsx("div", { className: "flex items-center justify-center py-8", "data-testid": "tenant-cap-loading", children: jsx(Spinner, {}) })) : isTenantForbidden ? (jsxs("div", { className: "flex flex-col items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-10 text-center", "data-testid": "spend-limits-denied", children: [jsx(ShieldAlert, { className: "h-6 w-6 text-gray-400" }), jsx("p", { className: "text-sm font-medium text-gray-900", children: labels.denied.title }), jsx("p", { className: "max-w-sm text-xs text-gray-600", children: labels.denied.description })] })) : (jsxs("div", { className: "space-y-4", children: [!tenantCap && (monthlySpend != null || totalSpend != null) && (jsxs("div", { className: "space-y-1", "data-testid": "workspace-actual-spend-stats", children: [jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [monthlySpend != null && (jsxs("div", { className: "space-y-0.5", children: [jsx("p", { className: "text-[11px] font-medium uppercase tracking-wide text-[#6a7282] dark:text-gray-400", children: labels.tenantSections.noCapStats.spentThisMonth }), jsx("p", { className: "text-[18px] font-semibold leading-6 text-[#101828] dark:text-gray-100", "data-testid": "workspace-actual-spend-month", children: formatUsd$1(String(monthlySpend)) })] })), totalSpend != null && (jsxs("div", { className: "space-y-0.5", children: [jsx("p", { className: "text-[11px] font-medium uppercase tracking-wide text-[#6a7282] dark:text-gray-400", children: labels.tenantSections.noCapStats.spentAllTime }), jsx("p", { className: "text-[18px] font-semibold leading-6 text-[#101828] dark:text-gray-100", "data-testid": "workspace-actual-spend-total", children: formatUsd$1(String(totalSpend)) })] }))] }), jsx("p", { className: "text-[12px] font-normal leading-4 text-[#6a7282] dark:text-gray-500", children: labels.tenantSections.noCapStats.hint })] })), jsx(SpendCapForm, { labels: labels, cap: tenantCap, isSaving: isSavingTenantCap, isDeleting: isDeletingTenantCap, onSave: saveTenantCap, onDelete: () => setIsDeleteOpen(true), testId: "tenant-cap" })] })) })] }), jsx(Dialog, { open: isDeleteOpen, onOpenChange: () => setIsDeleteOpen(false), children: jsxs(DialogContent, { "aria-describedby": "tenant-cap-delete-description", "data-testid": "tenant-cap-delete-modal", children: [jsx(DialogHeader, { children: jsx(DialogTitle, { className: "ibl-dialog-title", children: labels.deleteModal.title }) }), jsx("div", { className: "my-5", id: "tenant-cap-delete-description", children: jsx("p", { className: "text-sm text-[#646464]", children: labels.deleteModal.description }) }), jsxs(DialogFooter, { className: "gap-3", children: [jsx(Button$1, { variant: "outline", onClick: () => setIsDeleteOpen(false), children: labels.deleteModal.cancel }), jsx(Button$1, { className: "bg-gradient-to-r from-[#2563EB] to-[#93C5FD] text-white hover:opacity-90", onClick: handleConfirmDelete, disabled: isDeletingTenantCap, "data-testid": "tenant-cap-delete-confirm", children: isDeletingTenantCap ? labels.form.removing : labels.deleteModal.confirm })] })] }) })] }));
226781
227494
  }
226782
227495
 
226783
- /** SPAs whose users have coursework, so the profile leads with a Gradebook tab. */
226784
- const GRADEBOOK_SPAS = ['lms', 'skills'];
226785
- /**
226786
- * The customization `Profile` is opened with: whatever the host passed, with
226787
- * the Gradebook tab defaulted from the SPA it runs in. An explicit
226788
- * `showGradebookTab` always wins, so a host can force it either way.
226789
- */
226790
- function profileCustomization(customization, currentSPA) {
226791
- var _a;
226792
- return {
226793
- ...customization,
226794
- showGradebookTab: (_a = customization === null || customization === void 0 ? void 0 : customization.showGradebookTab) !== null && _a !== void 0 ? _a : GRADEBOOK_SPAS.includes((currentSPA !== null && currentSPA !== void 0 ? currentSPA : '').trim().toLowerCase()),
226795
- };
226796
- }
226797
- /**
226798
- * The shared `Profile` viewer in a dialog — the same modal the tenant
226799
- * Management tab opens when a user row is clicked. Controlled variant for
226800
- * hosts that trigger it from a whole table row.
226801
- *
226802
- * Radix portals the dialog into `document.body`, but a React portal still
226803
- * bubbles its events through the React tree — so without the guard below, a
226804
- * click *inside* the open profile would reach the row this dialog is rendered
226805
- * from and fire its `onClick` (e.g. opening the enrollment progress modal
226806
- * behind the profile). The wrapper is `display: contents`, so it adds nothing
226807
- * to the host's layout.
226808
- */
226809
- function UserProfileDialog({ tenantKey, username, isOpen, onClose, customization, currentSPA, }) {
226810
- const t = useT();
226811
- if (!isOpen)
226812
- return null;
226813
- return (jsx("span", { className: "contents", onClick: (event) => event.stopPropagation(), onMouseDown: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: jsx(Dialog, { open: true, onOpenChange: onClose, children: jsxs(DialogContent, { autoFocus: false, className: "profile-component-dialog max-w-7xl w-[85vw] gap-0 overflow-hidden bg-white p-0", style: { height: '85vh', display: 'flex', flexDirection: 'column' }, "data-testid": "user-profile-link-dialog", children: [jsx(DialogTitle, { className: "sr-only", children: t('tabsUserstab.profileDialogTitle') }), jsx(DialogDescription, { className: "sr-only", children: t('tabsUserstab.profileDialogDescription') }), jsx(Profile, { tenant: tenantKey, tenants: [], username: username, onClose: onClose, customization: profileCustomization(customization, currentSPA), currentSPA: currentSPA })] }) }) }));
226814
- }
226815
- /**
226816
- * Clickable user identity that opens the shared `Profile` viewer in a dialog
226817
- * — the same modal the tenant Management tab opens when a user row is
226818
- * clicked. `Profile` drops into preview (read-only) mode on its own for anyone
226819
- * but the signed-in user. Used by the spend-cap tables and every analytics
226820
- * surface that names a user; safe inside clickable rows and cards, since
226821
- * neither its clicks nor its key presses reach the host's handlers.
226822
- */
226823
- function UserProfileLink({ tenantKey, username, display, className, testId, customization, currentSPA, }) {
226824
- const t = useT();
226825
- const [isOpen, setIsOpen] = React__default.useState(false);
226826
- if (!username) {
226827
- // `inline-block`, or `truncate`'s `overflow: hidden` has nothing to clip:
226828
- // an inline box takes its width from its own text.
226829
- return (jsx("span", { className: cn('inline-block max-w-full truncate', className), "data-testid": testId, children: display }));
226830
- }
226831
- return (jsxs(Fragment$1, { children: [jsx("button", { type: "button", onClick: (event) => {
226832
- event.stopPropagation();
226833
- setIsOpen(true);
226834
- },
226835
- // Rows that open on Enter/Space would otherwise swallow the key press.
226836
- onKeyDown: (event) => event.stopPropagation(), className: cn('max-w-full cursor-pointer truncate text-left hover:text-blue-600 hover:underline focus:text-blue-600 focus:outline-none', className), "aria-label": t('tabsUserstab.viewProfileAriaLabel', { name: display }), "data-testid": testId, children: display }), jsx(UserProfileDialog, { tenantKey: tenantKey, username: username, isOpen: isOpen, onClose: () => setIsOpen(false), customization: customization, currentSPA: currentSPA })] }));
226837
- }
226838
-
226839
227496
  /**
226840
227497
  * Add/edit dialog for one per-user spend limit, mirroring the grader
226841
227498
  * criterion modal: create mode starts with a searchable platform-user picker
@@ -247096,7 +247753,7 @@ function inputRulesPlugin(props) {
247096
247753
  function getType(value) {
247097
247754
  return Object.prototype.toString.call(value).slice(8, -1);
247098
247755
  }
247099
- function isPlainObject(value) {
247756
+ function isPlainObject$1(value) {
247100
247757
  if (getType(value) !== "Object") {
247101
247758
  return false;
247102
247759
  }
@@ -247106,9 +247763,9 @@ function isPlainObject(value) {
247106
247763
  // src/utilities/mergeDeep.ts
247107
247764
  function mergeDeep(target, source) {
247108
247765
  const output = { ...target };
247109
- if (isPlainObject(target) && isPlainObject(source)) {
247766
+ if (isPlainObject$1(target) && isPlainObject$1(source)) {
247110
247767
  Object.keys(source).forEach((key) => {
247111
- if (isPlainObject(source[key]) && isPlainObject(target[key])) {
247768
+ if (isPlainObject$1(source[key]) && isPlainObject$1(target[key])) {
247112
247769
  output[key] = mergeDeep(target[key], source[key]);
247113
247770
  } else {
247114
247771
  output[key] = source[key];
@@ -258125,6 +258782,492 @@ AnalyticsScopeContext.Provider;
258125
258782
 
258126
258783
  Array.from({ length: 24 }, (_, i) => i);
258127
258784
 
258785
+ const asArray = (value) => (Array.isArray(value) ? value : []);
258786
+ const hasEntries = (value) => !!value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length > 0;
258787
+ /** Rollup counts as the conversation-list endpoint reports them: numbers, numeric strings or `null`. */
258788
+ const toRollupCount = (value) => {
258789
+ const count = typeof value === 'string' ? Number(value) : (value !== null && value !== void 0 ? value : 0);
258790
+ return Number.isFinite(count) && count > 0 ? Math.floor(count) : 0;
258791
+ };
258792
+ /**
258793
+ * Sum the retrieved documents and tool calls over a conversation's turns, for
258794
+ * surfaces (the admin chat-history list) that carry the turns inline but have
258795
+ * no server-side rollup fields.
258796
+ */
258797
+ function summarizeTranscriptTurns(turns) {
258798
+ return asArray(turns).reduce((rollup, turn) => ({
258799
+ documentsCount: rollup.documentsCount + asArray(turn === null || turn === void 0 ? void 0 : turn.documents).length,
258800
+ toolCallsCount: rollup.toolCallsCount + asArray(turn === null || turn === void 0 ? void 0 : turn.tool_calls).length,
258801
+ }), { documentsCount: 0, toolCallsCount: 0 });
258802
+ }
258803
+ /** A transcript's retrieved documents in the shape the chat's documents dialog lists. */
258804
+ function toRetrievedDocuments(documents) {
258805
+ return documents.map((doc) => {
258806
+ var _a, _b, _c, _d;
258807
+ return ({
258808
+ title: (_b = (_a = doc.title) !== null && _a !== void 0 ? _a : doc.source) !== null && _b !== void 0 ? _b : null,
258809
+ snippet: (_c = doc.snippet) !== null && _c !== void 0 ? _c : null,
258810
+ source: (_d = doc.source) !== null && _d !== void 0 ? _d : null,
258811
+ score: typeof doc.score === 'number' ? doc.score : null,
258812
+ });
258813
+ });
258814
+ }
258815
+ /**
258816
+ * Every document a conversation retrieved, across its turns, exactly as the
258817
+ * backend reports them — a source retrieved again on a later turn appears
258818
+ * again, so the count matches the backend's per-conversation totals. Comes
258819
+ * from the admin history payload itself, so it needs no per-user session
258820
+ * lookup.
258821
+ */
258822
+ function conversationDocuments(turns) {
258823
+ return toRetrievedDocuments(asArray(turns).flatMap((turn) => asArray(turn === null || turn === void 0 ? void 0 : turn.documents)));
258824
+ }
258825
+ const asText = (value) => {
258826
+ if (value === null || value === undefined)
258827
+ return '';
258828
+ if (typeof value === 'string')
258829
+ return value;
258830
+ if (typeof value === 'number' || typeof value === 'boolean')
258831
+ return String(value);
258832
+ try {
258833
+ return JSON.stringify(value, null, 2);
258834
+ }
258835
+ catch (_a) {
258836
+ return String(value);
258837
+ }
258838
+ };
258839
+ /** A transcript's tool calls in the shape the chat's "Used N tools" record renders. */
258840
+ function toToolCallInfos(toolCalls) {
258841
+ return toolCalls.map((call, index) => {
258842
+ var _a;
258843
+ return ({
258844
+ id: `transcript-tool-${index}`,
258845
+ name: (_a = call.name) !== null && _a !== void 0 ? _a : '',
258846
+ input: call.input && typeof call.input === 'object' && !Array.isArray(call.input)
258847
+ ? call.input
258848
+ : typeof call.input === 'string'
258849
+ ? { input: call.input }
258850
+ : undefined,
258851
+ log: '',
258852
+ result: asText(call.output),
258853
+ });
258854
+ });
258855
+ }
258856
+ /** Whether a turn carries anything worth a details panel. */
258857
+ function hasTranscriptTurnDetails(turn) {
258858
+ if (!turn)
258859
+ return false;
258860
+ return (asArray(turn.documents).length > 0 ||
258861
+ asArray(turn.tool_calls).length > 0 ||
258862
+ hasEntries(turn.metadata) ||
258863
+ hasEntries(turn.request_context));
258864
+ }
258865
+ const isPlainObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
258866
+ /** Nothing worth a row: null, blank strings, empty arrays and empty objects. */
258867
+ const isPresent = (value) => {
258868
+ if (value === null || value === undefined)
258869
+ return false;
258870
+ if (typeof value === 'string')
258871
+ return value.trim() !== '';
258872
+ if (Array.isArray(value))
258873
+ return value.length > 0;
258874
+ if (isPlainObject(value))
258875
+ return Object.keys(value).length > 0;
258876
+ return true;
258877
+ };
258878
+ const FIELD_ABBREVIATIONS = new Set([
258879
+ 'ai',
258880
+ 'api',
258881
+ 'css',
258882
+ 'csv',
258883
+ 'html',
258884
+ 'http',
258885
+ 'https',
258886
+ 'id',
258887
+ 'ids',
258888
+ 'ip',
258889
+ 'json',
258890
+ 'llm',
258891
+ 'lti',
258892
+ 'mcp',
258893
+ 'mime',
258894
+ 'pdf',
258895
+ 'rag',
258896
+ 'sso',
258897
+ 'stt',
258898
+ 'tts',
258899
+ 'ui',
258900
+ 'uri',
258901
+ 'url',
258902
+ 'urls',
258903
+ 'uuid',
258904
+ ]);
258905
+ /**
258906
+ * "file_references" → "File References", "llmModel" → "LLM Model". Backend
258907
+ * keys are never shown raw: every unknown key goes through this, and the
258908
+ * common ones get a translated label on top (see `useFieldLabels`).
258909
+ */
258910
+ function humanizeFieldName(key) {
258911
+ const words = key
258912
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
258913
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
258914
+ .toLowerCase()
258915
+ .split(/[^a-z0-9]+/)
258916
+ .filter(Boolean);
258917
+ if (words.length === 0)
258918
+ return key;
258919
+ return words
258920
+ .map((word) => FIELD_ABBREVIATIONS.has(word)
258921
+ ? word.toUpperCase()
258922
+ : word.charAt(0).toUpperCase() + word.slice(1))
258923
+ .join(' ');
258924
+ }
258925
+ /** Translated labels for the keys the backend is known to send. */
258926
+ function useFieldLabels() {
258927
+ const t = useT();
258928
+ const known = {
258929
+ llm_model: t('analyticsTranscriptTurnDetails.model'),
258930
+ llm_provider: t('analyticsTranscriptTurnDetails.provider'),
258931
+ llm_temperature: t('analyticsTranscriptTurnDetails.temperature'),
258932
+ model: t('analyticsTranscriptTurnDetails.model'),
258933
+ provider: t('analyticsTranscriptTurnDetails.provider'),
258934
+ temperature: t('analyticsTranscriptTurnDetails.temperature'),
258935
+ client: t('analyticsTranscriptTurnDetails.client'),
258936
+ session_id: t('analyticsTranscriptTurnDetails.fieldSession'),
258937
+ // The product's own names: artifacts are "Canvas" in the chat, proactivity
258938
+ // is the "Proactive Prompt", grading is the "Grader" tab, document filters
258939
+ // are "Datasets".
258940
+ enable_artifacts: t('analyticsTranscriptTurnDetails.fieldCanvas'),
258941
+ artifact: t('analyticsTranscriptTurnDetails.fieldCanvas'),
258942
+ artifacts: t('analyticsTranscriptTurnDetails.fieldCanvas'),
258943
+ is_proactive: t('analyticsTranscriptTurnDetails.fieldProactivePrompt'),
258944
+ grading_context: t('analyticsTranscriptTurnDetails.fieldGrader'),
258945
+ document_filter: t('analyticsTranscriptTurnDetails.fieldDatasets'),
258946
+ current_datetime: t('analyticsTranscriptTurnDetails.fieldDateTime'),
258947
+ caller_verified: t('analyticsTranscriptTurnDetails.fieldCallerVerified'),
258948
+ file_references: t('analyticsTranscriptTurnDetails.fieldFiles'),
258949
+ file_id: t('analyticsTranscriptTurnDetails.fieldFile'),
258950
+ system_prompt: t('analyticsTranscriptTurnDetails.fieldSystemPrompt'),
258951
+ flow: t('analyticsTranscriptTurnDetails.fieldFlow'),
258952
+ token: t('analyticsTranscriptTurnDetails.fieldToken'),
258953
+ tenant: t('analyticsTranscriptTurnDetails.fieldTenant'),
258954
+ pathway: t('analyticsTranscriptTurnDetails.fieldPathway'),
258955
+ username: t('analyticsTranscriptTurnDetails.fieldUsername'),
258956
+ name: t('analyticsTranscriptTurnDetails.fieldName'),
258957
+ };
258958
+ return (key) => { var _a; return (_a = known[key]) !== null && _a !== void 0 ? _a : humanizeFieldName(key); };
258959
+ }
258960
+ const fileName = (value) => {
258961
+ var _a, _b, _c, _d;
258962
+ const name = (_b = (_a = value.file_name) !== null && _a !== void 0 ? _a : value.filename) !== null && _b !== void 0 ? _b : value.name;
258963
+ if (typeof name === 'string' && name.trim())
258964
+ return name.trim();
258965
+ const key = (_d = (_c = value.file_key) !== null && _c !== void 0 ? _c : value.key) !== null && _d !== void 0 ? _d : value.path;
258966
+ if (typeof key === 'string' && key.trim())
258967
+ return key.trim().split('/').pop() || key.trim();
258968
+ return undefined;
258969
+ };
258970
+ /** Backend payloads name files in a few shapes; anything with a name plus a file-ish fact counts. */
258971
+ function isFileReference(value) {
258972
+ if (!isPlainObject(value) || !fileName(value))
258973
+ return false;
258974
+ return ['file_id', 'file_key', 'file_size', 'content_type', 'mime_type', 'url', 'size'].some((key) => key in value && value[key] !== null && value[key] !== undefined);
258975
+ }
258976
+ const attachmentUrl = (name, attachments) => { var _a; return (_a = attachments === null || attachments === void 0 ? void 0 : attachments.find((file) => (file === null || file === void 0 ? void 0 : file.name) === name && !!file.url)) === null || _a === void 0 ? void 0 : _a.url; };
258977
+ /** Normalise a file reference; the turn's attachments lend their presigned `url` to a same-named file. */
258978
+ function toFileReference(value, attachments) {
258979
+ var _a, _b, _c, _d, _e, _f;
258980
+ const name = (_a = fileName(value)) !== null && _a !== void 0 ? _a : '';
258981
+ const contentType = (_c = (_b = value.content_type) !== null && _b !== void 0 ? _b : value.mime_type) !== null && _c !== void 0 ? _c : value.type;
258982
+ const size = (_d = value.file_size) !== null && _d !== void 0 ? _d : value.size;
258983
+ const own = (_f = (_e = value.url) !== null && _e !== void 0 ? _e : value.download_url) !== null && _f !== void 0 ? _f : value.href;
258984
+ const numericSize = typeof size === 'number'
258985
+ ? size
258986
+ : typeof size === 'string' && size.trim() && Number.isFinite(Number(size))
258987
+ ? Number(size)
258988
+ : undefined;
258989
+ return {
258990
+ name,
258991
+ contentType: typeof contentType === 'string' ? contentType : undefined,
258992
+ size: numericSize,
258993
+ url: typeof own === 'string' && own ? own : attachmentUrl(name, attachments),
258994
+ };
258995
+ }
258996
+ const REDACTED_PATTERN = /^\[?(redacted|hidden|masked|removed)\]?$|^\*{3,}$/i;
258997
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
258998
+ /** Past this depth nested objects flatten to "key: value" text rather than nesting further. */
258999
+ const MAX_NESTING = 4;
259000
+ const formatPrimitive = (value) => {
259001
+ if (value === null || value === undefined)
259002
+ return '';
259003
+ if (typeof value === 'string')
259004
+ return value;
259005
+ if (typeof value === 'number')
259006
+ return Number.isInteger(value) ? String(value) : String(value);
259007
+ return String(value);
259008
+ };
259009
+ /** Deeply nested leftovers as readable "Key: value" text — never JSON. */
259010
+ function compactText(value, labelFor) {
259011
+ if (Array.isArray(value)) {
259012
+ return value
259013
+ .filter(isPresent)
259014
+ .map((item) => compactText(item, labelFor))
259015
+ .join('; ');
259016
+ }
259017
+ if (isPlainObject(value)) {
259018
+ return Object.entries(value)
259019
+ .filter(([, v]) => isPresent(v))
259020
+ .map(([k, v]) => `${labelFor(k)}: ${compactText(v, labelFor)}`)
259021
+ .join(', ');
259022
+ }
259023
+ return formatPrimitive(value);
259024
+ }
259025
+ function BooleanValue({ value }) {
259026
+ const t = useT();
259027
+ return (jsx("span", { className: cn('inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium', value ? 'bg-green-50 text-green-700' : 'bg-gray-100 text-gray-600'), children: value ? t('analyticsTranscriptTurnDetails.yes') : t('analyticsTranscriptTurnDetails.no') }));
259028
+ }
259029
+ function StringValue({ value }) {
259030
+ const t = useT();
259031
+ const text = value.trim();
259032
+ if (REDACTED_PATTERN.test(text)) {
259033
+ return (jsx("span", { className: "text-gray-400 italic", children: t('analyticsTranscriptTurnDetails.redacted') }));
259034
+ }
259035
+ if (UUID_PATTERN.test(text)) {
259036
+ return jsx("span", { className: "font-mono text-xs break-all text-gray-700", children: text });
259037
+ }
259038
+ return (jsx("span", { className: "block max-h-48 overflow-y-auto break-words whitespace-pre-line", children: text }));
259039
+ }
259040
+ const DetailCtx = React__default.createContext({ labelFor: humanizeFieldName });
259041
+ function DetailValue({ value, depth, labelFor, }) {
259042
+ var _a, _b;
259043
+ const ctx = React__default.useContext(DetailCtx);
259044
+ if (typeof value === 'boolean')
259045
+ return jsx(BooleanValue, { value: value });
259046
+ if (typeof value === 'number')
259047
+ return jsx("span", { className: "tabular-nums", children: formatPrimitive(value) });
259048
+ if (typeof value === 'string')
259049
+ return jsx(StringValue, { value: value });
259050
+ if (depth >= MAX_NESTING) {
259051
+ return jsx("span", { className: "break-words", children: compactText(value, labelFor) });
259052
+ }
259053
+ if (isFileReference(value)) {
259054
+ const files = [toFileReference(value, ctx.attachments)];
259055
+ return jsx(Fragment$1, { children: ((_a = ctx.renderFiles) !== null && _a !== void 0 ? _a : defaultRenderFiles)(files) });
259056
+ }
259057
+ if (Array.isArray(value)) {
259058
+ const items = value.filter(isPresent);
259059
+ if (items.length === 0)
259060
+ return null;
259061
+ if (items.every(isFileReference)) {
259062
+ const files = items.map((item) => toFileReference(item, ctx.attachments));
259063
+ return jsx(Fragment$1, { children: ((_b = ctx.renderFiles) !== null && _b !== void 0 ? _b : defaultRenderFiles)(files) });
259064
+ }
259065
+ const allPrimitive = items.every((item) => !isPlainObject(item) && !Array.isArray(item));
259066
+ if (allPrimitive) {
259067
+ return (jsx("span", { className: "flex flex-wrap gap-1", children: items.map((item, index) => (jsx(Badge, { variant: "secondary", className: "max-w-full font-normal break-all whitespace-normal", children: typeof item === 'boolean' ? jsx(BooleanValue, { value: item }) : formatPrimitive(item) }, index))) }));
259068
+ }
259069
+ return (jsx("ol", { className: "space-y-2", children: items.map((item, index) => (jsxs("li", { className: "flex gap-2", children: [jsxs("span", { className: "mt-1 text-xs font-medium text-gray-400 tabular-nums", children: [index + 1, "."] }), jsx("div", { className: "min-w-0 flex-1 rounded-md border border-gray-200 px-3 py-1", children: isPlainObject(item) ? (jsx(DetailRows, { entries: item, depth: depth + 1, labelFor: labelFor })) : (jsx(DetailValue, { value: item, depth: depth + 1, labelFor: labelFor })) })] }, index))) }));
259070
+ }
259071
+ if (isPlainObject(value)) {
259072
+ return (jsx("div", { className: "border-l-2 border-gray-200 pl-3", children: jsx(DetailRows, { entries: value, depth: depth + 1, labelFor: labelFor }) }));
259073
+ }
259074
+ return jsx("span", { children: formatPrimitive(value) });
259075
+ }
259076
+ /**
259077
+ * Label/value rows for a metadata-style object. Keys are humanized (never
259078
+ * shown raw), blanks are skipped, booleans read Yes/No, lists become chips or
259079
+ * numbered cards, files become the chat's file cards and nested objects
259080
+ * indent — the same read-only detail style as the audit-log sheet, so nothing
259081
+ * here ever looks like a JSON dump.
259082
+ */
259083
+ function DetailRows({ entries, depth = 0, labelFor, }) {
259084
+ const rows = Object.entries(entries).filter(([, value]) => isPresent(value));
259085
+ if (rows.length === 0)
259086
+ return null;
259087
+ const top = depth === 0;
259088
+ return (jsx("dl", { className: cn(top ? 'divide-y divide-gray-100' : 'space-y-1'), children: rows.map(([key, value]) => (jsxs("div", { className: cn('grid gap-x-3', top
259089
+ ? 'grid-cols-[minmax(0,7rem)_minmax(0,1fr)] py-2'
259090
+ : 'grid-cols-[minmax(0,6rem)_minmax(0,1fr)] py-0.5'), children: [jsx("dt", { className: cn('break-words text-gray-500', top ? 'text-xs font-medium tracking-wide uppercase' : 'text-xs font-medium'), children: labelFor(key) }), jsx("dd", { className: "min-w-0 text-sm text-gray-800", children: jsx(DetailValue, { value: value, depth: depth, labelFor: labelFor }) })] }, key))) }));
259091
+ }
259092
+ function SectionHeading({ icon, children }) {
259093
+ return (jsxs("h4", { className: "mb-2 flex items-center gap-1.5 text-xs font-semibold tracking-wide text-gray-700 uppercase", children: [icon, children] }));
259094
+ }
259095
+ const MODEL_KEYS = new Set(['llm_model', 'llm_provider']);
259096
+ function MetadataSection({ metadata, renderModel, }) {
259097
+ var _a, _b;
259098
+ const t = useT();
259099
+ const labelFor = useFieldLabels();
259100
+ const rest = Object.fromEntries(Object.entries(metadata).filter(([key]) => !MODEL_KEYS.has(key)));
259101
+ const model = ((_a = metadata.llm_model) !== null && _a !== void 0 ? _a : '').trim() || undefined;
259102
+ const provider = ((_b = metadata.llm_provider) !== null && _b !== void 0 ? _b : '').trim() || undefined;
259103
+ const hasModel = !!(model || provider);
259104
+ return (jsxs("section", { "data-testid": "transcript-turn-metadata", children: [jsx(SectionHeading, { icon: jsx(Info$3, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), children: t('analyticsTranscriptTurnDetails.metadata') }), hasModel && (jsx("div", { "data-testid": "transcript-turn-model", className: "mb-2", children: (renderModel !== null && renderModel !== void 0 ? renderModel : defaultRenderModel)({ model, provider }) })), jsx(DetailRows, { entries: rest, labelFor: labelFor })] }));
259105
+ }
259106
+ function RequestContextSection({ requestContext }) {
259107
+ const t = useT();
259108
+ const labelFor = useFieldLabels();
259109
+ return (jsxs("section", { "data-testid": "transcript-turn-request-context", children: [jsx(SectionHeading, { icon: jsx(Info$3, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), children: t('analyticsTranscriptTurnDetails.requestContext') }), jsx(DetailRows, { entries: requestContext, labelFor: labelFor })] }));
259110
+ }
259111
+ /**
259112
+ * The panel's defaults are the chat's own components, so admins see exactly
259113
+ * what the conversation shows: the "Retrieved Documents" dialog button, the
259114
+ * "Used N tools" record (with results), the nav-bar model badge and the
259115
+ * attachment file cards.
259116
+ */
259117
+ const defaultRenderDocuments = (documents) => (jsx(RetrievedDocumentsButton, { documents: toRetrievedDocuments(documents) }));
259118
+ const defaultRenderToolCalls = (toolCalls) => (jsx(ToolCallIndicator, { toolCalls: toToolCallInfos(toolCalls), showResults: true }));
259119
+ const defaultRenderModel = ({ model, provider }) => (jsx(LlmModelBadge, { model: model, provider: provider }));
259120
+ const defaultRenderFiles = (files) => (jsx(TranscriptFileCards, { files: files }));
259121
+ /**
259122
+ * Collapsible "Sources / Tools / Metadata" panel for one AI turn of an admin
259123
+ * transcript (Perplexity/Claude-style citations). Driven by the extended
259124
+ * per-turn context the backend attaches on admin surfaces — the analytics
259125
+ * `messages/details/` endpoint and the admin `chat-history/` list — and
259126
+ * renders nothing at all when every field is null or empty, so it is safe to
259127
+ * drop under every AI bubble.
259128
+ */
259129
+ function TranscriptTurnDetails({ turn, className, defaultOpen = false, renderDocuments, renderToolCalls, renderModel, renderFiles, }) {
259130
+ const t = useT();
259131
+ const labelFor = useFieldLabels();
259132
+ const [open, setOpen] = React__default.useState(defaultOpen);
259133
+ const attachments = React__default.useMemo(() => [...asArray(turn === null || turn === void 0 ? void 0 : turn.human_files), ...asArray(turn === null || turn === void 0 ? void 0 : turn.ai_files)], [turn === null || turn === void 0 ? void 0 : turn.human_files, turn === null || turn === void 0 ? void 0 : turn.ai_files]);
259134
+ const detailContext = React__default.useMemo(() => ({ labelFor, attachments, renderFiles }), [labelFor, attachments, renderFiles]);
259135
+ if (!hasTranscriptTurnDetails(turn))
259136
+ return null;
259137
+ const documents = asArray(turn === null || turn === void 0 ? void 0 : turn.documents);
259138
+ const toolCalls = asArray(turn === null || turn === void 0 ? void 0 : turn.tool_calls);
259139
+ const metadata = hasEntries(turn === null || turn === void 0 ? void 0 : turn.metadata) ? turn === null || turn === void 0 ? void 0 : turn.metadata : null;
259140
+ const requestContext = hasEntries(turn === null || turn === void 0 ? void 0 : turn.request_context) ? turn === null || turn === void 0 ? void 0 : turn.request_context : null;
259141
+ return (jsx(DetailCtx.Provider, { value: detailContext, children: jsxs(Collapsible, { open: open, onOpenChange: setOpen, className: cn('mt-2', className), "data-testid": "transcript-turn-details", children: [jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [jsx(CollapsibleTrigger, { asChild: true, children: jsxs(Button$1, { type: "button", variant: "outline", size: "sm", className: "flex items-center gap-2 border-gray-200 bg-white text-gray-700 hover:bg-gray-50", "aria-expanded": open, children: [jsx(ChevronDown, { className: cn('h-4 w-4 transition-transform', open && 'rotate-180'), "aria-hidden": "true" }), open
259142
+ ? t('analyticsTranscriptTurnDetails.hideDetails')
259143
+ : t('analyticsTranscriptTurnDetails.showDetails')] }) }), documents.length > 0 && (jsx("div", { "data-testid": "transcript-turn-sources", className: "contents", children: (renderDocuments !== null && renderDocuments !== void 0 ? renderDocuments : defaultRenderDocuments)(documents) })), jsx(TranscriptRollupBadges, { documentsCount: 0, toolCallsCount: toolCalls.length })] }), jsxs(CollapsibleContent, { className: "mt-2 space-y-5 rounded-lg border border-gray-200 bg-white p-3", children: [toolCalls.length > 0 && (jsx("div", { "data-testid": "transcript-turn-tools", children: (renderToolCalls !== null && renderToolCalls !== void 0 ? renderToolCalls : defaultRenderToolCalls)(toolCalls) })), metadata && jsx(MetadataSection, { metadata: metadata, renderModel: renderModel }), requestContext && jsx(RequestContextSection, { requestContext: requestContext })] })] }) }));
259144
+ }
259145
+ /**
259146
+ * "Documents · N" / "Tools · N" chips for a conversation row, from the
259147
+ * conversation-list rollup fields (or a client-side `summarizeTranscriptTurns`).
259148
+ * A chip is hidden when its count is zero; nothing renders when both are.
259149
+ */
259150
+ function TranscriptRollupBadges({ documentsCount, toolCallsCount, className, }) {
259151
+ const t = useT();
259152
+ const documents = toRollupCount(documentsCount);
259153
+ const tools = toRollupCount(toolCallsCount);
259154
+ if (documents === 0 && tools === 0)
259155
+ return null;
259156
+ return (jsxs("span", { className: cn('inline-flex flex-wrap items-center gap-1.5', className), "data-testid": "transcript-rollup-badges", children: [documents > 0 && (jsxs(Badge, { variant: "outline", className: "gap-1 border-transparent bg-blue-50 font-normal text-blue-700", "data-testid": "transcript-sources-badge", children: [jsx(FileText, { className: "h-3 w-3", "aria-hidden": "true" }), t('analyticsTranscriptTurnDetails.sourcesBadge', { count: documents })] })), tools > 0 && (jsxs(Badge, { variant: "outline", className: "gap-1 border-transparent bg-amber-50 font-normal text-amber-800", "data-testid": "transcript-tools-badge", children: [jsx(Wrench, { className: "h-3 w-3", "aria-hidden": "true" }), t('analyticsTranscriptTurnDetails.toolsBadge', { count: tools })] }))] }));
259157
+ }
259158
+
259159
+ /**
259160
+ * One place that decides how a user is shown.
259161
+ *
259162
+ * Backend payloads name their user in a dozen ways — `email` / `lti_email` /
259163
+ * `user_email`, `username` / `student` / `lti_username`, `name` /
259164
+ * `full_name` / `user_full_name`, or a nested `user` object — and most of
259165
+ * them can be empty, `null`, or a placeholder like "None". Every surface used
259166
+ * to pick through those on its own. `resolveUserIdentity()` takes whatever
259167
+ * the payload is and returns what to show, what to open a profile on, and
259168
+ * the avatar initial, so the rule lives here and can be tweaked once.
259169
+ *
259170
+ * Default rule: email → full name → username → the anonymous label.
259171
+ */
259172
+ const DEFAULT_USER_IDENTITY_ORDER = ['email', 'name', 'username'];
259173
+ const DEFAULT_ANONYMOUS_LABEL = 'Anonymous';
259174
+ /** Values backends emit instead of "nothing". Compared case-insensitively. */
259175
+ const PLACEHOLDERS = new Set([
259176
+ '',
259177
+ 'none',
259178
+ 'null',
259179
+ 'undefined',
259180
+ 'n/a',
259181
+ 'na',
259182
+ '-',
259183
+ 'anonymous',
259184
+ 'anonymoususer',
259185
+ ]);
259186
+ /**
259187
+ * `local@domain.tld` with no whitespace, checked with a single pass rather than
259188
+ * a backtracking pattern (CodeQL flags `[^@]+@[^@]+\.[^@]+` as polynomial on
259189
+ * adversarial input).
259190
+ */
259191
+ const isEmailLike = (value) => {
259192
+ const text = value.trim();
259193
+ if (!text || /\s/.test(text))
259194
+ return false;
259195
+ const at = text.indexOf('@');
259196
+ if (at <= 0 || at !== text.lastIndexOf('@') || at === text.length - 1)
259197
+ return false;
259198
+ const domain = text.slice(at + 1);
259199
+ const dot = domain.indexOf('.');
259200
+ // A dot somewhere inside the domain, with something after the last one.
259201
+ return dot > 0 && !domain.endsWith('.');
259202
+ };
259203
+ /** Trimmed string, or undefined for blanks, non-strings and placeholders. */
259204
+ const clean = (value) => {
259205
+ if (typeof value !== 'string')
259206
+ return undefined;
259207
+ const trimmed = value.trim();
259208
+ return PLACEHOLDERS.has(trimmed.toLowerCase()) ? undefined : trimmed;
259209
+ };
259210
+ const firstOf = (...candidates) => {
259211
+ for (const candidate of candidates) {
259212
+ const value = clean(candidate);
259213
+ if (value)
259214
+ return value;
259215
+ }
259216
+ return undefined;
259217
+ };
259218
+ const nested = (source) => source.user && typeof source.user === 'object' ? source.user : undefined;
259219
+ /**
259220
+ * Resolve how a user should be displayed from any payload that names one.
259221
+ *
259222
+ * - `email`: `lti_email`, `email`, `user_email`, the nested `user`'s email —
259223
+ * or a username field that turns out to hold an email address.
259224
+ * - `username`: `lti_username`, `username`, `student`, `user_username`, the
259225
+ * nested `user`'s username, or a bare string/number `user` value.
259226
+ * - `name`: `user_full_name`, `full_name`, `name`, `first_name last_name`, or
259227
+ * the nested `user`'s name.
259228
+ * - `profileUsername`: the first *platform* username (never an LTI claim).
259229
+ *
259230
+ * A string source is treated as an email when it looks like one, else as a
259231
+ * username. Placeholder strings ("None", "-", "anonymous"…) count as absent.
259232
+ */
259233
+ function resolveUserIdentity(source, options = {}) {
259234
+ var _a;
259235
+ const anonymousLabel = options.anonymousLabel || DEFAULT_ANONYMOUS_LABEL;
259236
+ const order = ((_a = options.order) === null || _a === void 0 ? void 0 : _a.length) ? options.order : DEFAULT_USER_IDENTITY_ORDER;
259237
+ const record = source && typeof source === 'object' ? source : { user: source !== null && source !== void 0 ? source : null };
259238
+ const inner = nested(record);
259239
+ // A bare `user` value is an id or username; numeric ids are still shown.
259240
+ const bareUser = typeof record.user === 'number' && Number.isFinite(record.user)
259241
+ ? String(record.user)
259242
+ : typeof record.user === 'string'
259243
+ ? clean(record.user)
259244
+ : undefined;
259245
+ let email = firstOf(record.lti_email, record.email, record.user_email, inner === null || inner === void 0 ? void 0 : inner.lti_email, inner === null || inner === void 0 ? void 0 : inner.email, inner === null || inner === void 0 ? void 0 : inner.user_email);
259246
+ const platformUsername = firstOf(record.username, record.student, record.user_username, inner === null || inner === void 0 ? void 0 : inner.username, inner === null || inner === void 0 ? void 0 : inner.student, inner === null || inner === void 0 ? void 0 : inner.user_username, bareUser && !isEmailLike(bareUser) ? bareUser : undefined);
259247
+ let username = firstOf(record.lti_username, inner === null || inner === void 0 ? void 0 : inner.lti_username, platformUsername);
259248
+ const composed = [clean(record.first_name), clean(record.last_name)].filter(Boolean).join(' ');
259249
+ const name = firstOf(record.user_full_name, record.full_name, record.name, composed, inner === null || inner === void 0 ? void 0 : inner.user_full_name, inner === null || inner === void 0 ? void 0 : inner.full_name, inner === null || inner === void 0 ? void 0 : inner.name);
259250
+ // A bare string that looks like an email is an email, and so is a
259251
+ // "username" field that actually carries one.
259252
+ if (!email && bareUser && isEmailLike(bareUser))
259253
+ email = bareUser;
259254
+ if (!email && username && isEmailLike(username)) {
259255
+ email = username;
259256
+ username = undefined;
259257
+ }
259258
+ const facts = { email, username, name };
259259
+ const label = order.map((field) => facts[field]).find(Boolean);
259260
+ return {
259261
+ label: label !== null && label !== void 0 ? label : anonymousLabel,
259262
+ email,
259263
+ username,
259264
+ name,
259265
+ profileUsername: platformUsername,
259266
+ initial: (label !== null && label !== void 0 ? label : anonymousLabel).charAt(0).toUpperCase() || 'A',
259267
+ isAnonymous: !label,
259268
+ };
259269
+ }
259270
+
258128
259271
  const AnalyticsSettingsContext = createContext({});
258129
259272
  AnalyticsSettingsContext.Provider;
258130
259273
 
@@ -260865,64 +262008,6 @@ const AutoResizeTextarea = ({ allowEmptySubmit = false, value, onChange, onSubmi
260865
262008
  disabled, placeholder: placeholder, style: combinedStyle, className: `flex max-h-32 min-h-[36px] w-full resize-none rounded-2xl border-0 border-gray-200 bg-transparent px-4 py-2 pt-2 pb-1 text-base ring-offset-white placeholder:text-gray-400 focus:outline-none focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-800 dark:bg-gray-950 dark:ring-offset-gray-950 dark:placeholder:text-gray-400 ${className}`, ...props }));
260866
262009
  };
260867
262010
 
260868
- function DocumentSidebar({ isModal = false, sessionId }) {
260869
- const t = useT();
260870
- const [isCollapsed, setIsCollapsed] = React.useState(false);
260871
- const params = useParams();
260872
- const username = useUsername();
260873
- const isStreaming = useSelector(selectStreaming);
260874
- const { data: vectorDocuments, refetch, status, } = useGetVectorDocumentsQuery({
260875
- org: params.tenantKey,
260876
- sessionId: sessionId,
260877
- // @ts-ignore
260878
- userId: username,
260879
- }, {
260880
- skip: !params.tenantKey || !sessionId || !username || !isLoggedIn(),
260881
- });
260882
- React.useEffect(() => {
260883
- if (!isStreaming && isLoggedIn() && status !== 'uninitialized') {
260884
- refetch();
260885
- }
260886
- }, [isStreaming, refetch]);
260887
- const toggleCollapse = () => {
260888
- setIsCollapsed(!isCollapsed);
260889
- };
260890
- return (jsx(Fragment$1, { children: (vectorDocuments === null || vectorDocuments === void 0 ? void 0 : vectorDocuments.length) && vectorDocuments.length > 0 ? (jsx("aside", { className: cn('transition-all duration-300 ease-in-out', isModal
260891
- ? 'max-h-full w-full overflow-auto'
260892
- : 'hidden rounded-tl-lg rounded-bl-lg md:hidden lg:block', !isModal && (isCollapsed ? 'w-[40px]' : 'w-[380px]')), style: {
260893
- backgroundColor: isCollapsed && !isModal ? 'white' : '#F3F6FB',
260894
- maxHeight: 'calc(100vh - 80px)',
260895
- }, children: jsxs("div", { className: "flex h-full flex-col", children: [!isModal && (jsxs("div", { className: cn('flex cursor-pointer items-center gap-2 border-b border-gray-200 p-4 transition-colors duration-200', isCollapsed && 'h-[80px] justify-center rounded-tl-[5px] rounded-bl-[5px] p-2', 'hover:bg-blue-100'), onClick: toggleCollapse, style: { backgroundColor: '#F3F6FB' }, children: [jsx(FileText, { className: "h-5 w-5 shrink-0 text-blue-600" }), !isCollapsed && (jsxs(Fragment$1, { children: [jsx("h2", { className: "text-base font-medium text-[#646464]", children: t('documentSidebar.retrievedDocuments') }), jsx(ChevronRight, { className: cn('ml-auto h-4 w-4 text-gray-400 transition-transform', isCollapsed ? 'rotate-180' : '') })] }))] })), (!isCollapsed || isModal) && (jsx(Fragment$1, { children: jsx("div", { className: "flex-1 overflow-auto", style: { backgroundColor: '#F3F6FB' }, children: jsx("div", { className: "mt-0 space-y-1 p-2", children: vectorDocuments.map((doc, index) => {
260896
- var _a, _b;
260897
- return (jsxs(React.Fragment, { children: [jsx("div", { className: "rounded-lg p-2 hover:bg-gray-50", children: jsxs("div", { className: "flex items-start gap-2", children: [jsx(Radio$1, { className: "mt-1 h-4 w-4 text-gray-400" }), jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [jsxs("div", { className: "flex items-center justify-between", children: [jsx(Link$2, { href: (_a = doc.source) !== null && _a !== void 0 ? _a : '', target: "_blank", className: "min-w-0", children: jsx("span", { className: "text-sm font-semibold break-words text-[#646464] hover:text-blue-600", children: doc.title }) }), jsx("div", { className: "flex items-center gap-2 self-center", children: jsx("span", { className: "text-xs text-gray-500", children: (_b = doc === null || doc === void 0 ? void 0 : doc.score) === null || _b === void 0 ? void 0 : _b.toFixed(2) }) })] }), jsx("p", { className: "line-clamp-6 pt-[10px] text-[14px] text-gray-600", children: doc.snippet })] })] }) }), index < vectorDocuments.length - 1 && (jsx("div", { className: "my-2 border-t border-gray-200" }))] }, index));
260898
- }) }) }) }))] }) })) : null }));
260899
- }
260900
-
260901
- function RetrievedDocumentsButton({ sessionId }) {
260902
- const t = useT();
260903
- const [isOpen, setIsOpen] = useState(false);
260904
- const params = useParams();
260905
- const username = useUsername();
260906
- const isStreaming = useSelector(selectStreaming);
260907
- const { data: vectorDocuments, refetch, status, } = useGetVectorDocumentsQuery({
260908
- org: params.tenantKey,
260909
- sessionId: sessionId,
260910
- // @ts-ignore
260911
- userId: username,
260912
- }, {
260913
- skip: !params.tenantKey || !sessionId || !username || !isLoggedIn(),
260914
- });
260915
- useEffect(() => {
260916
- if (!isStreaming && isLoggedIn() && status !== 'uninitialized') {
260917
- refetch();
260918
- }
260919
- }, [isStreaming, refetch]);
260920
- if (!(vectorDocuments === null || vectorDocuments === void 0 ? void 0 : vectorDocuments.length)) {
260921
- return null;
260922
- }
260923
- return (jsxs(Fragment$1, { children: [jsxs(Button$1, { variant: "outline", size: "sm", className: "flex items-center gap-2 border-gray-200 bg-white text-gray-700 hover:bg-gray-50", onClick: () => setIsOpen(true), children: [jsx(FileText, { className: "h-4 w-4 text-blue-600" }), jsx("span", { children: t('retrievedDocumentsButton.title') })] }), jsx(Dialog, { open: isOpen, onOpenChange: setIsOpen, children: jsxs(DialogContent, { className: "flex max-h-[80vh] flex-col gap-0 p-0 sm:max-w-[425px] md:max-w-[600px]", children: [jsx(DialogHeader, { className: "border-b px-4 py-2", children: jsxs(DialogTitle, { className: "flex items-center gap-2 text-base font-medium text-[#646464]", children: [jsx(FileText, { className: "h-5 w-5 text-blue-600" }), t('retrievedDocumentsButton.title')] }) }), jsx("div", { className: "flex-1 overflow-hidden", children: jsx(DocumentSidebar, { isModal: true, sessionId: sessionId }) })] }) })] }));
260924
- }
260925
-
260926
262011
  const PAGE_SIZE$5 = 10;
260927
262012
  const MemoryMenu = ({ onClose, mentorId, tenantKey, username }) => {
260928
262013
  const t = useT();
@@ -276866,6 +277951,7 @@ const AGENT_HISTORY_TAB_LABELS = {
276866
277951
  selectPrompt: 'Select a conversation to view details.',
276867
277952
  aiAgent: 'AI Agent',
276868
277953
  noConversations: 'No conversations found',
277954
+ documentsChip: (count) => `Documents · ${count}`,
276869
277955
  },
276870
277956
  summary: {
276871
277957
  notAvailable: 'Summary not available',
@@ -276904,6 +277990,7 @@ function buildHistoryTabLabels(t) {
276904
277990
  selectPrompt: t('historyTabLabels.conversation.selectPrompt'),
276905
277991
  aiAgent: t('historyTabLabels.conversation.aiAgent'),
276906
277992
  noConversations: t('historyTabLabels.conversation.noConversations'),
277993
+ documentsChip: (count) => t('historyTabLabels.conversation.documentsChip', { count }),
276907
277994
  },
276908
277995
  summary: {
276909
277996
  notAvailable: t('historyTabLabels.summary.notAvailable'),
@@ -276923,6 +278010,8 @@ function resolveHistoryTabLabels(override, t) {
276923
278010
  }
276924
278011
 
276925
278012
  const ANONYMOUS_USERNAME$1 = 'anonymous';
278013
+ /** A turn's attachments in the shape the chat's file cards take. */
278014
+ const attachmentCards = (files) => (files !== null && files !== void 0 ? files : []).map((file) => ({ name: file.name, contentType: file.content_type, url: file.url }));
276926
278015
  function textTruncate$1(str, length, ending) {
276927
278016
  if (length == null) {
276928
278017
  length = 50;
@@ -276935,7 +278024,7 @@ function textTruncate$1(str, length, ending) {
276935
278024
  }
276936
278025
  return str;
276937
278026
  }
276938
- function AgentHistoryTab({ renderMessageContent, PaginationComponent, onExport, isExporting = false, labels: labelsOverride, }) {
278027
+ function AgentHistoryTab({ renderMessageContent, PaginationComponent, onExport, isExporting = false, labels: labelsOverride, currentSPA, }) {
276939
278028
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
276940
278029
  const t = useT();
276941
278030
  const { tenantKey, mentorId, username } = useAgentSettings();
@@ -276985,6 +278074,10 @@ function AgentHistoryTab({ renderMessageContent, PaginationComponent, onExport,
276985
278074
  // Use mentor summaries data for rating and tags
276986
278075
  const averageRating = (mentorSummaries === null || mentorSummaries === void 0 ? void 0 : mentorSummaries.rating) || 0;
276987
278076
  const topicTags = (mentorSummaries === null || mentorSummaries === void 0 ? void 0 : mentorSummaries.tags) || [];
278077
+ // Who the previewed conversation belongs to (email → full name → username → Anonymous).
278078
+ const identityOptions = { anonymousLabel: labels.conversation.anonymous };
278079
+ const selectedOwner = resolveUserIdentity(selectedConversation, identityOptions);
278080
+ const previewOwner = resolveUserIdentity(previewConversationContent, identityOptions);
276988
278081
  const renderAiContent = (content) => {
276989
278082
  if (renderMessageContent) {
276990
278083
  return renderMessageContent(content);
@@ -277016,7 +278109,10 @@ function AgentHistoryTab({ renderMessageContent, PaginationComponent, onExport,
277016
278109
  const conversation = _conversation;
277017
278110
  const messages = conversation.messages;
277018
278111
  const firstMessage = messages[0];
277019
- const name = conversation.lti_email || conversation.email || labels.conversation.anonymous;
278112
+ // Email → full name → username → Anonymous, decided in one place.
278113
+ const owner = resolveUserIdentity(conversation, {
278114
+ anonymousLabel: labels.conversation.anonymous,
278115
+ });
277020
278116
  const timeAgo = formatDistanceToNow(new Date(conversation.inserted_at), {
277021
278117
  addSuffix: true,
277022
278118
  });
@@ -277026,22 +278122,25 @@ function AgentHistoryTab({ renderMessageContent, PaginationComponent, onExport,
277026
278122
  const preview = (firstMessage === null || firstMessage === void 0 ? void 0 : firstMessage.ai)
277027
278123
  ? textTruncate$1(firstMessage.ai, 60)
277028
278124
  : labels.conversation.noResponse;
278125
+ // The admin list carries the turns inline (no server-side
278126
+ // rollups), so badge rows from the turns themselves.
278127
+ // The chip counts what the dialog lists: every document the
278128
+ // turns carry, as the backend reports them.
278129
+ const documents = conversationDocuments(messages);
278130
+ const rollup = summarizeTranscriptTurns(messages);
277029
278131
  return (jsxs("div", { className: cn('flex cursor-pointer items-center justify-between border-b p-4 last:border-b-0', (selectedConversation === null || selectedConversation === void 0 ? void 0 : selectedConversation.id) === conversation.id
277030
278132
  ? 'bg-gray-100'
277031
- : 'hover:bg-gray-50'), onClick: () => handleConversationClick(conversation), children: [jsxs("div", { className: "w-full", children: [jsxs("div", { className: "mb-2 flex items-center justify-between", children: [jsx("span", { className: "text-sm text-gray-600", children: timeAgo }), jsx("span", { className: "max-w-[100px] truncate text-sm text-gray-900 sm:max-w-[200px]", children: name })] }), jsx("div", { className: "font-medium text-gray-900", children: title }), jsx("p", { className: "line-clamp-1 text-sm text-gray-600", children: preview })] }), jsx("div", { className: "flex items-center gap-2 text-sm text-gray-600", children: jsx(ChevronRight, { className: "h-4 w-4" }) })] }, conversation.id));
277032
- }) }), totalPages > 1 && PaginationComponent && (jsx("div", { className: "my-2", children: jsx(PaginationComponent, { currentPage: currentPage, totalPages: totalPages, onPageChange: handlePageChange, disabled: isChatHistoryFetching || isChatHistoryLoading, disableNumberedButtons: isMobile }) }))] }), jsx("div", { className: "scrollbar-hide hidden max-h-[500px] flex-col overflow-y-auto rounded-lg border bg-white p-6 shadow-sm md:flex", tabIndex: 0, role: "region", "aria-label": t('tabsHistoryTab.conversationPreviewLabel'), children: selectedConversation && previewConversationContent ? (jsxs(Fragment$1, { children: [jsxs("div", { className: "mb-4", children: [jsx("h3", { className: "mb-1 text-base font-semibold text-gray-700", children: ((_m = selectedConversation.messages[0]) === null || _m === void 0 ? void 0 : _m.human) || labels.conversation.title }), jsx("span", { className: "text-sm text-gray-500", children: format$1(new Date(selectedConversation.inserted_at), "MMM dd, yyyy 'at' h:mm a") })] }), jsxs("div", { className: "space-y-6", children: [selectedConversation.messages.map((message, index) => {
277033
- var _a, _b;
277034
- return (jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-start gap-3", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-100", children: jsx("span", { className: "text-sm font-medium text-blue-600", children: ((_b = (_a = (selectedConversation.lti_email || selectedConversation.email)) === null || _a === void 0 ? void 0 : _a.charAt(0)) === null || _b === void 0 ? void 0 : _b.toUpperCase()) || 'A' }) }), jsxs("div", { className: "flex-1", children: [jsx("div", { className: "font-medium text-gray-700", children: selectedConversation.lti_email ||
277035
- selectedConversation.email ||
277036
- labels.conversation.anonymous }), jsx("p", { className: "mt-1 text-sm whitespace-pre-line text-gray-500", children: message.human })] })] }), jsxs("div", { className: "flex items-start gap-3 border-t border-gray-200 pt-4", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-gray-100", children: jsx("span", { className: "text-sm font-medium text-gray-600", children: t('tabsHistoryTab.aiAvatarLabel') }) }), jsxs("div", { className: "flex-1", children: [jsx("div", { className: "font-medium text-gray-700", children: labels.conversation.aiAgent }), jsx("div", { className: "mt-1 text-sm text-gray-500", children: renderAiContent(message.ai) })] })] })] }, index));
277037
- }), (selectedConversation === null || selectedConversation === void 0 ? void 0 : selectedConversation.memory_tracked) && (jsxs("div", { className: "border-t border-gray-200 pt-4", children: [jsxs(Button$1, { variant: "outline", onClick: () => setShowConversationMemory(!showConversationMemory), className: "flex w-full items-center justify-center gap-2 border-gray-200 text-gray-700 hover:bg-gray-50", children: [jsx(Brain, { className: "h-4 w-4" }), showConversationMemory ? labels.memory.hide : labels.memory.show] }), showConversationMemory && (jsx("div", { className: "mt-4 space-y-6", children: isConversationMemoryLoading ? (jsx("div", { className: "flex items-center justify-center py-4", children: jsx(Spinner, {}) })) : ((_o = conversationMemory === null || conversationMemory === void 0 ? void 0 : conversationMemory.entries) === null || _o === void 0 ? void 0 : _o.length) ? (jsx(Fragment$1, { children: conversationMemory.entries.map((entry, index) => (jsxs("div", { children: [jsx("h4", { className: "mb-3 font-semibold text-gray-900", children: entry.key }), jsx("ul", { className: "space-y-2", children: jsx("li", { className: "flex items-start gap-2 text-sm text-gray-600", children: entry.value }) })] }, index))) })) : (jsx("div", { className: "text-sm text-gray-500", children: labels.memory.notAvailable })) }))] }))] })] })) : (jsx("div", { className: "flex h-full items-center justify-center text-gray-500", children: labels.conversation.selectPrompt })) })] }))] }) }), jsx(Dialog, { open: isConversationPreviewModalOpen, onOpenChange: setIsConversationPreviewModalOpen, children: jsxs(DialogContent, { className: "scrollbar-hide mx-auto max-h-[80vh] max-w-[90vw] overflow-x-hidden overflow-y-auto", children: [jsx(DialogHeader, { children: jsx(DialogTitle, { className: "text-lg font-semibold text-gray-900", children: previewConversationContent
278133
+ : 'hover:bg-gray-50'), onClick: () => handleConversationClick(conversation), children: [jsxs("div", { className: "w-full", children: [jsxs("div", { className: "mb-2 flex items-center justify-between", children: [jsx("span", { className: "text-sm text-gray-600", children: timeAgo }), jsx(UserProfileLink, { tenantKey: tenantKey, username: owner.profileUsername, display: owner.label, currentSPA: currentSPA, className: "max-w-[100px] text-sm text-gray-900 sm:max-w-[200px]" })] }), jsx("div", { className: "font-medium text-gray-900", children: title }), jsx("p", { className: "line-clamp-1 text-sm text-gray-600", children: preview }), (rollup.documentsCount > 0 || rollup.toolCallsCount > 0) && (jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [rollup.documentsCount > 0 && (
278134
+ // The chat's documents dialog, as a chip: one
278135
+ // click shows what the conversation retrieved.
278136
+ jsx(RetrievedDocumentsButton, { documents: documents, label: labels.conversation.documentsChip(rollup.documentsCount), className: "h-6 gap-1 rounded-md border-transparent bg-blue-50 px-2 text-xs font-normal text-blue-700 hover:bg-blue-100 [&_svg]:h-3 [&_svg]:w-3" })), jsx(TranscriptRollupBadges, { documentsCount: 0, toolCallsCount: rollup.toolCallsCount })] }))] }), jsx("div", { className: "flex items-center gap-2 text-sm text-gray-600", children: jsx(ChevronRight, { className: "h-4 w-4" }) })] }, conversation.id));
278137
+ }) }), totalPages > 1 && PaginationComponent && (jsx("div", { className: "my-2", children: jsx(PaginationComponent, { currentPage: currentPage, totalPages: totalPages, onPageChange: handlePageChange, disabled: isChatHistoryFetching || isChatHistoryLoading, disableNumberedButtons: isMobile }) }))] }), jsx("div", { className: "scrollbar-hide hidden max-h-[500px] flex-col overflow-y-auto rounded-lg border bg-white p-6 shadow-sm md:flex", tabIndex: 0, role: "region", "aria-label": t('tabsHistoryTab.conversationPreviewLabel'), children: selectedConversation && previewConversationContent ? (jsxs(Fragment$1, { children: [jsxs("div", { className: "mb-4", children: [jsx("h3", { className: "mb-1 text-base font-semibold text-gray-700", children: ((_m = selectedConversation.messages[0]) === null || _m === void 0 ? void 0 : _m.human) || labels.conversation.title }), jsx("span", { className: "text-sm text-gray-500", children: format$1(new Date(selectedConversation.inserted_at), "MMM dd, yyyy 'at' h:mm a") }), jsx(RetrievedDocumentsButton, { sessionId: selectedConversation.id, documents: conversationDocuments(selectedConversation.messages).length > 0
278138
+ ? conversationDocuments(selectedConversation.messages)
278139
+ : undefined, className: "mt-2" })] }), jsxs("div", { className: "space-y-6", children: [selectedConversation.messages.map((message, index) => (jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-start gap-3", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-100", children: jsx("span", { className: "text-sm font-medium text-blue-600", children: selectedOwner.initial }) }), jsxs("div", { className: "flex-1", children: [jsx("div", { className: "font-medium text-gray-700", children: jsx(UserProfileLink, { tenantKey: tenantKey, username: selectedOwner.profileUsername, display: selectedOwner.label, currentSPA: currentSPA }) }), jsx("p", { className: "mt-1 text-sm whitespace-pre-line text-gray-500", children: message.human }), jsx(TranscriptFileCards, { files: attachmentCards(message.human_files) })] })] }), jsxs("div", { className: "flex items-start gap-3 border-t border-gray-200 pt-4", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-gray-100", children: jsx("span", { className: "text-sm font-medium text-gray-600", children: t('tabsHistoryTab.aiAvatarLabel') }) }), jsxs("div", { className: "flex-1", children: [jsx("div", { className: "font-medium text-gray-700", children: labels.conversation.aiAgent }), jsx("div", { className: "mt-1 text-sm text-gray-500", children: renderAiContent(message.ai) }), jsx(TranscriptFileCards, { files: attachmentCards(message.ai_files) }), jsx(TranscriptTurnDetails, { turn: message })] })] })] }, index))), (selectedConversation === null || selectedConversation === void 0 ? void 0 : selectedConversation.memory_tracked) && (jsxs("div", { className: "border-t border-gray-200 pt-4", children: [jsxs(Button$1, { variant: "outline", onClick: () => setShowConversationMemory(!showConversationMemory), className: "flex w-full items-center justify-center gap-2 border-gray-200 text-gray-700 hover:bg-gray-50", children: [jsx(Brain, { className: "h-4 w-4" }), showConversationMemory ? labels.memory.hide : labels.memory.show] }), showConversationMemory && (jsx("div", { className: "mt-4 space-y-6", children: isConversationMemoryLoading ? (jsx("div", { className: "flex items-center justify-center py-4", children: jsx(Spinner, {}) })) : ((_o = conversationMemory === null || conversationMemory === void 0 ? void 0 : conversationMemory.entries) === null || _o === void 0 ? void 0 : _o.length) ? (jsx(Fragment$1, { children: conversationMemory.entries.map((entry, index) => (jsxs("div", { children: [jsx("h4", { className: "mb-3 font-semibold text-gray-900", children: entry.key }), jsx("ul", { className: "space-y-2", children: jsx("li", { className: "flex items-start gap-2 text-sm text-gray-600", children: entry.value }) })] }, index))) })) : (jsx("div", { className: "text-sm text-gray-500", children: labels.memory.notAvailable })) }))] }))] })] })) : (jsx("div", { className: "flex h-full items-center justify-center text-gray-500", children: labels.conversation.selectPrompt })) })] }))] }) }), jsx(Dialog, { open: isConversationPreviewModalOpen, onOpenChange: setIsConversationPreviewModalOpen, children: jsxs(DialogContent, { className: "scrollbar-hide mx-auto max-h-[80vh] max-w-[90vw] overflow-x-hidden overflow-y-auto", children: [jsx(DialogHeader, { children: jsx(DialogTitle, { className: "text-lg font-semibold text-gray-900", children: previewConversationContent
277038
278140
  ? ((_p = previewConversationContent.messages[0]) === null || _p === void 0 ? void 0 : _p.human) || labels.conversation.title
277039
- : labels.conversation.title }) }), jsx("div", { className: "mx-auto max-w-full overflow-x-hidden", children: previewConversationContent && (jsxs("div", { className: "scrollbar-hide space-y-4 overflow-y-auto", children: [jsx("div", { className: "text-sm text-gray-500", children: format$1(new Date(previewConversationContent.inserted_at), "MMM dd, yyyy 'at' h:mm a") }), previewConversationContent.messages.map((message, index) => {
277040
- var _a, _b;
277041
- return (jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-start gap-3", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-100", children: jsx("span", { className: "text-sm font-medium text-blue-600", children: ((_b = (_a = (previewConversationContent.lti_email || previewConversationContent.email)) === null || _a === void 0 ? void 0 : _a.charAt(0)) === null || _b === void 0 ? void 0 : _b.toUpperCase()) || 'A' }) }), jsxs("div", { className: "overflow-x-hidden", children: [jsx("div", { className: "truncate font-medium text-gray-900", children: previewConversationContent.lti_email ||
277042
- previewConversationContent.email ||
277043
- labels.conversation.anonymous }), jsx("p", { className: "mt-1 text-sm whitespace-pre-line text-gray-900", children: message.human })] })] }), jsxs("div", { className: "flex items-start gap-3 border-t border-gray-200 pt-4", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-gray-100", children: jsx("span", { className: "text-sm font-medium text-gray-600", children: t('tabsHistoryTab.aiAvatarLabel') }) }), jsxs("div", { className: "overflow-x-hidden", children: [jsx("div", { className: "font-medium text-gray-900", children: labels.conversation.aiAgent }), jsx("div", { className: "mt-1 text-sm text-gray-900", children: renderAiContent(message.ai) })] })] })] }, index));
277044
- })] })) })] }) })] }));
278141
+ : labels.conversation.title }) }), jsx("div", { className: "mx-auto max-w-full overflow-x-hidden", children: previewConversationContent && (jsxs("div", { className: "scrollbar-hide space-y-4 overflow-y-auto", children: [jsx("div", { className: "text-sm text-gray-500", children: format$1(new Date(previewConversationContent.inserted_at), "MMM dd, yyyy 'at' h:mm a") }), jsx(RetrievedDocumentsButton, { sessionId: previewConversationContent.id, documents: conversationDocuments(previewConversationContent.messages).length > 0
278142
+ ? conversationDocuments(previewConversationContent.messages)
278143
+ : undefined }), previewConversationContent.messages.map((message, index) => (jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-start gap-3", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-100", children: jsx("span", { className: "text-sm font-medium text-blue-600", children: previewOwner.initial }) }), jsxs("div", { className: "overflow-x-hidden", children: [jsx("div", { className: "truncate font-medium text-gray-900", children: jsx(UserProfileLink, { tenantKey: tenantKey, username: previewOwner.profileUsername, display: previewOwner.label, currentSPA: currentSPA }) }), jsx("p", { className: "mt-1 text-sm whitespace-pre-line text-gray-900", children: message.human }), jsx(TranscriptFileCards, { files: attachmentCards(message.human_files) })] })] }), jsxs("div", { className: "flex items-start gap-3 border-t border-gray-200 pt-4", children: [jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-gray-100", children: jsx("span", { className: "text-sm font-medium text-gray-600", children: t('tabsHistoryTab.aiAvatarLabel') }) }), jsxs("div", { className: "overflow-x-hidden", children: [jsx("div", { className: "font-medium text-gray-900", children: labels.conversation.aiAgent }), jsx("div", { className: "mt-1 text-sm text-gray-900", children: renderAiContent(message.ai) }), jsx(TranscriptFileCards, { files: attachmentCards(message.ai_files) }), jsx(TranscriptTurnDetails, { turn: message })] })] })] }, index)))] })) })] }) })] }));
277045
278144
  }
277046
278145
 
277047
278146
  const ANONYMOUS_USERNAME = 'anonymous';
@@ -283839,76 +284938,6 @@ const AIMessageBubble = forwardRef(function AIMessageBubble({ content, profileIm
283839
284938
  }, className: "-ml-1 text-gray-500 hover:text-gray-700", children: [jsx("span", { className: "sr-only", children: t('chatAiMessageBubble.retryScreenReaderLabel') }), jsx(RefreshCcw, { className: "h-4 w-4" })] }) }), jsx(TooltipContent, { className: "ibl-tooltip-content", children: t('chatAiMessageBubble.retryTooltip') })] }))] })] })] }) }) }));
283840
284939
  });
283841
284940
 
283842
- function FileCard({ fileName, fileType }) {
283843
- const getFileIcon = (fileName, fileType) => {
283844
- const type = fileType.toLowerCase();
283845
- const name = fileName.toLowerCase();
283846
- const extension = name.split('.').pop() || '';
283847
- // Check by MIME type first
283848
- if (type.includes('pdf')) {
283849
- return jsx(File$1, { className: "h-5 w-5 text-blue-500" });
283850
- }
283851
- else if (type.includes('image')) {
283852
- return jsx(FileImage, { className: "h-5 w-5 text-blue-500" });
283853
- }
283854
- else if (type.includes('audio')) {
283855
- return jsx(FileAudio, { className: "h-5 w-5 text-blue-500" });
283856
- }
283857
- else if (type.includes('video')) {
283858
- return jsx(FilePlay, { className: "h-5 w-5 text-blue-500" });
283859
- }
283860
- else if (type.includes('spreadsheet') || type.includes('excel') || type.includes('csv')) {
283861
- return jsx(FileSpreadsheet, { className: "h-5 w-5 text-blue-500" });
283862
- }
283863
- else if (type.includes('zip') || type.includes('compressed') || type.includes('archive')) {
283864
- return jsx(FileArchive, { className: "h-5 w-5 text-blue-500" });
283865
- }
283866
- else if (type.includes('javascript') ||
283867
- type.includes('typescript') ||
283868
- type.includes('html') ||
283869
- type.includes('css') ||
283870
- type.includes('json')) {
283871
- return jsx(FileCode, { className: "h-5 w-5 text-blue-500" });
283872
- }
283873
- // If MIME type doesn't match, check by extension
283874
- if (['doc', 'docx', 'txt', 'rtf'].includes(extension)) {
283875
- return jsx(FileText, { className: "h-5 w-5 text-blue-500" });
283876
- }
283877
- else if (['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'].includes(extension)) {
283878
- return jsx(FileImage, { className: "h-5 w-5 text-blue-500" });
283879
- }
283880
- else if (['pdf'].includes(extension)) {
283881
- return jsx(File$1, { className: "h-5 w-5 text-blue-500" });
283882
- }
283883
- else if (['mp3', 'wav', 'ogg', 'flac'].includes(extension)) {
283884
- return jsx(FileAudio, { className: "h-5 w-5 text-blue-500" });
283885
- }
283886
- else if (['mp4', 'webm', 'avi', 'mov'].includes(extension)) {
283887
- return jsx(FilePlay, { className: "h-5 w-5 text-blue-500" });
283888
- }
283889
- else if (['xls', 'xlsx', 'csv'].includes(extension)) {
283890
- return jsx(FileSpreadsheet, { className: "h-5 w-5 text-blue-500" });
283891
- }
283892
- else if (['zip', 'rar', '7z', 'tar', 'gz'].includes(extension)) {
283893
- return jsx(FileArchive, { className: "h-5 w-5 text-blue-500" });
283894
- }
283895
- else if (['js', 'ts', 'jsx', 'tsx', 'html', 'css', 'json', 'py', 'java', 'c', 'cpp'].includes(extension)) {
283896
- return jsx(FileCode, { className: "h-5 w-5 text-blue-500" });
283897
- }
283898
- // Default icon
283899
- return jsx(File$1, { className: "h-5 w-5 text-blue-500" });
283900
- };
283901
- return (jsxs("div", { className: "mb-2 flex max-w-xs items-center rounded-lg border border-gray-200 bg-white p-3", children: [jsx("div", { className: "mr-3 shrink-0 rounded-lg bg-blue-100 p-2 text-blue-600", children: getFileIcon(fileName, fileType) }), jsxs("div", { className: "min-w-0 flex-1", children: [jsx("h3", { className: "truncate text-sm font-medium text-gray-900", children: fileName }), jsx("p", { className: "truncate text-xs text-gray-500", children: fileType })] })] }));
283902
- }
283903
-
283904
- function ImageMessage({ url, fileName, setPreviewImage }) {
283905
- const [hasError, setHasError] = useState(false);
283906
- if (hasError || !url) {
283907
- return jsx(FileCard, { fileName: fileName, fileType: "image/*" });
283908
- }
283909
- return (jsx("div", { className: "h-full max-h-64 w-full max-w-96 overflow-hidden rounded-lg", children: jsx("div", { className: "relative", children: jsx("img", { src: url, alt: fileName, className: "h-auto w-full cursor-pointer object-contain p-2", onClick: () => setPreviewImage(url), onError: () => setHasError(true) }) }) }));
283910
- }
283911
-
283912
284941
  function UserMessageBubble({ message, isHighlighted, profileImage, mentorName, messages, onHighlightMessage, onPreviewImage, }) {
283913
284942
  return (jsxs("div", { className: `message-container mb-4 flex flex-col items-end transition-all duration-300 ${isHighlighted ? 'rounded-2xl bg-blue-100' : ''}`, children: [message.fileAttachments && message.fileAttachments.length > 0 && (jsx("div", { className: "mb-2 flex flex-col items-end gap-2", children: message.fileAttachments.map((attachment, idx) => {
283914
284943
  if (attachment.fileType.startsWith('image/')) {