@iblai/iblai-js 1.12.3 → 1.13.2

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.
@@ -7,7 +7,7 @@ import Link$2 from 'next/link';
7
7
  import * as ReactDOM from 'react-dom';
8
8
  import ReactDOM__default from 'react-dom';
9
9
  import { z as z$3 } from 'zod';
10
- import { useGetUserMetadataQuery, useGetUserMetadataEdxQuery, useUpdateUserMetadataMutation, useUpdateUserMetadataEdxMutation, useUploadProfileImageMutation, useResetPasswordMutation, useSelfRetireMutation, useCreateUserInstitutionMutation, useGetUserInstitutionsQuery, useCreateUserEducationMutation, useUpdateUserEducationMutation, useDeleteUserEducationMutation, useGetUserEducationQuery, useCreateUserCompanyMutation, useGetUserCompaniesQuery, useCreateUserExperienceMutation, useUpdateUserExperienceMutation, useDeleteUserExperienceMutation, useGetUserExperienceQuery, useGetUserResumeQuery, useCreateUserResumeMutation, useGetMySubscriptionsQuery, useGetItemSubscriptionQuery, useCancelSubscriptionMutation, useCreateGlobalMemoryMutation, useGetMemsearchStatusQuery, useGetUserMemorySettingsQuery, useUpdateUserMemorySettingsMutation, useGetGlobalMemoriesQuery, useDeleteGlobalMemoryMutation, useInviteUserMutation, usePlatformInvitationsQuery, useCreateCatalogInvitationCourseBulkMutation, useGetCatalogInvitationsCourseQuery, useLazyPlatformUsersQuery, useLazyPlatformUserGroupsQuery, useGetPersonnalizedSearchQuery, useCreateCatalogInvitationProgramBulkMutation, useGetCatalogInvitationsProgramQuery, useLazyGetCourseMetaDataQuery, useLazyGetCourseCompletionOutlinesQuery, useLazyGetCourseEligibilityQuery, useLazyGetEdxSSOTokenQuery, useCreateCourseEnrollmentMutation, useCreateStripeCheckoutSessionMutation, useLazyGetCourseProgressQuery, useLazyGetCourseCompletionQuery, useUpdateExamAttemptMutation, useStartExamMutation, useLazyGetExamInfoQuery, useGetAccountBillingInfoQuery, useUpdateAutoRechargeInfoMutation, useTriggerAutoRechargeMutation, useUpdateUserRoleMutation, useUpdateUserStatusMutation, useUpdatePlatformUserRoleWithPoliciesMutation, usePlatformUsersQuery, isPoliciesResponse, platformApiSlice, featureTags, useLazyGetRbacTeamsAccessListQuery, useCreateRbacTeamsAccessMutation, useGetRbacGroupsQuery, usePlatformUserGroupsQuery, useCreateRbacGroupMutation, useUpdateRbacGroupMutation, useDeleteRbacGroupMutation, useCreatePlatformUserGroupMutation, useUpdatePlatformUserGroupMutation, useDeletePlatformUserGroupMutation, useGetRbacGroupDetailsQuery, useGetPlatformUserGroupDetailsQuery, useGetRbacPermissionsMutation, useGetRbacRolesQuery, useCreateRbacRoleMutation, useUpdateRbacRoleMutation, useDeleteRbacRoleMutation, useGetRbacRoleDetailsQuery, useGetRbacPoliciesQuery, useCreateRbacPolicyMutation, useUpdateRbacPolicyMutation, useDeleteRbacPolicyMutation, useGetRbacPolicyDetailsQuery, useGetWatchedGroupsQuery, useCreateWatchedGroupMutation, useUpdateWatchedGroupMutation, useDeleteWatchedGroupMutation, useAddWatchedUserMutation, useRemoveWatchedUserMutation, useAddWatcherMutation, useUpdateWatcherMutation, useDeleteWatcherMutation, useGetWatchedUsersQuery, useGetWatchersQuery, WATCHER_NOTIFICATION_EVENTS, WATCHER_NOTIFICATION_EVENT_LABELS, useUploadLightLogoMutation, useUploadDarkLogoMutation, useUpdatePlatformInfoMutation, useUpdateTenantMetadataMutation, LOGO_ENDPOINTS, useDeleteApiKeyMutation, useGetApiKeysQuery, useCreateApiKeyMutation, useCreateLLMCredentialMutation, useGetCredentialsSchemaQuery, useGetMaskedLLMCredentialsQuery, useGetLlmsQuery, useDeleteIntegrationCredentialMutation, useDeleteCredentialMutation, useCreateIntegrationCredentialMutation, useGetIntegrationCredentialsSchemaQuery, useGetMaskedIntegrationCredentialsQuery, 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, useGetCustomMentorsQuery, useGetStripeConnectStatusQuery, useStartStripeConnectOnboardingMutation, useLazyGetStripeConnectDashboardQuery, useGetAiSearchMentorsQuery, useListPaywallsQuery, useListPricesQuery, useCreatePriceMutation, useUpdatePriceMutation, useDeletePriceMutation, useGetPaywallConfigQuery, useEnablePaywallMutation, useUpdatePaywallMutation, useGetUserProjectsQuery, useGetMentorsQuery, useGetPublicMentorsQuery, useCreateUserProjectMutation, useUpdateUserProjectMutation, useDeleteUserProjectMutation, useCreateSessionIdMutation, useGetMentorCategoriesQuery, useGetMentorSettingsQuery, useEditMentorMutation, useDeleteMentorMutation, useForkMentorMutation, useGetToolsQuery, useGetMentorMemoriesListQuery, useGetMemoryCategoriesAdminQuery, useDeleteMentorMemoryMutation, useUpdateMentorMemoryMutation, useCreateMentorMemoryMutation, useLazyGetMCPServersQuery, useOauthFindMutation, useLazyStartOAuthFlowQuery, useCreateMCPServerMutation, usePartialUpdateMCPServerMutation, useCreateMCPServerConnectionMutation, usePatchMCPServerConnectionMutation, useGetConnectedServicesQuery, useGetMCPServersQuery, useGetMCPServerConnectionsQuery, useUpdateMCPServerMutation, useDeleteMCPServerMutation, useEditMentorJsonMutation, useDisconnectServiceMutation, useGetPromptCategoriesQuery, PRIVACY_ACTIONS, PRIVACY_ENTITY_TYPES, useGetMentorPublicSettingsQuery, useGetChatHistoryFilterQuery, useGetChatHistoryQuery, useGetMentorSummariesQuery, useGetConversationMemoriesQuery, useEditTrainingDocumentMutation, useGetTrainingDocumentsQuery, useCreatePromptMutation, useGetPromptsSearchQuery, useUpdatePromptMutation, useCreateRedirectTokenMutation, useGetShareableLinkQuery, useCreateShareableLinkMutation, useUpdateShareableLinkMutation, useCreateDisclaimerMutation, useUpdateDisclaimerMutation, useGetDisclaimersQuery, useUpdateRbacMentorAccessMutation, useGetRbacMentorAccessListQuery, useStarMentorMutation, useUnstarMentorMutation, useGetPersonnalizedMentorsQuery, useGetVectorDocumentsQuery, useLazyGetConnectedServiceAuthUrlQuery, useCreateCallCredentialsMutation, useGetGuidedPromptsQuery, useUpdateChatSessionSharedMutation, useUpdateMessageFeedbackMutation, useLazyGetPromptsSearchQuery, useLazyGetGuidedPromptsQuery, useGetUserProjectDetailsQuery, useGetPeriodicAgentsQuery, useGetPeriodicAgentLogsListQuery, useCreatePeriodicAgentMutation, useDeletePeriodicAgentMutation, useCreateMemoryCategoryMutation, useUpdateMemoryCategoryMutation, useDeleteMemoryCategoryMutation, useDeleteTrainingDocumentMutation, useGetTrainingDocumentRetrainScheduleQuery, useCreateTrainingDocumentRetrainScheduleMutation, useDeletePromptMutation, useAddTrainingDocumentMutation, useLazyGetCredentialsQuery, useUpdateArtifactMutation, useLazyGetArtifactVersionQuery, useLazyListArtifactVersionsQuery, useSetCurrentVersionMutation, useListArtifactVersionsQuery, useLazyGetArtifactQuery, useLazyListArtifactsQuery, useEditSessionMutation } from '@iblai/data-layer';
10
+ import { useGetUserMetadataQuery, useGetUserMetadataEdxQuery, useUpdateUserMetadataMutation, useUpdateUserMetadataEdxMutation, useUploadProfileImageMutation, useResetPasswordMutation, useSelfRetireMutation, useCreateUserInstitutionMutation, useGetUserInstitutionsQuery, useCreateUserEducationMutation, useUpdateUserEducationMutation, useDeleteUserEducationMutation, useGetUserEducationQuery, useCreateUserCompanyMutation, useGetUserCompaniesQuery, useCreateUserExperienceMutation, useUpdateUserExperienceMutation, useDeleteUserExperienceMutation, useGetUserExperienceQuery, useGetUserResumeQuery, useCreateUserResumeMutation, useGetMySubscriptionsQuery, useGetItemSubscriptionQuery, useCancelSubscriptionMutation, useCreateGlobalMemoryMutation, useGetMemsearchStatusQuery, useGetUserMemorySettingsQuery, useUpdateUserMemorySettingsMutation, useGetGlobalMemoriesQuery, useDeleteGlobalMemoryMutation, useInviteUserMutation, usePlatformInvitationsQuery, useCreateCatalogInvitationCourseBulkMutation, useGetCatalogInvitationsCourseQuery, useLazyPlatformUsersQuery, useLazyPlatformUserGroupsQuery, useGetPersonnalizedSearchQuery, useCreateCatalogInvitationProgramBulkMutation, useGetCatalogInvitationsProgramQuery, useLazyGetCourseMetaDataQuery, useLazyGetCourseCompletionOutlinesQuery, useLazyGetCourseEligibilityQuery, useLazyGetEdxSSOTokenQuery, useCreateCourseEnrollmentMutation, useCreateStripeCheckoutSessionMutation, useLazyGetCourseProgressQuery, useLazyGetCourseCompletionQuery, useUpdateExamAttemptMutation, useStartExamMutation, useLazyGetExamInfoQuery, useGetAccountBillingInfoQuery, useUpdateAutoRechargeInfoMutation, useTriggerAutoRechargeMutation, useUpdateUserRoleMutation, useUpdateUserStatusMutation, useUpdatePlatformUserRoleWithPoliciesMutation, usePlatformUsersQuery, isPoliciesResponse, platformApiSlice, featureTags, useLazyGetRbacTeamsAccessListQuery, useCreateRbacTeamsAccessMutation, useGetRbacGroupsQuery, usePlatformUserGroupsQuery, useCreateRbacGroupMutation, useUpdateRbacGroupMutation, useDeleteRbacGroupMutation, useCreatePlatformUserGroupMutation, useUpdatePlatformUserGroupMutation, useDeletePlatformUserGroupMutation, useGetRbacGroupDetailsQuery, useGetPlatformUserGroupDetailsQuery, useGetRbacPermissionsMutation, useGetRbacRolesQuery, useCreateRbacRoleMutation, useUpdateRbacRoleMutation, useDeleteRbacRoleMutation, useGetRbacRoleDetailsQuery, useGetRbacPoliciesQuery, useCreateRbacPolicyMutation, useUpdateRbacPolicyMutation, useDeleteRbacPolicyMutation, useGetRbacPolicyDetailsQuery, useGetWatchedGroupsQuery, useCreateWatchedGroupMutation, useUpdateWatchedGroupMutation, useDeleteWatchedGroupMutation, useAddWatchedUserMutation, useRemoveWatchedUserMutation, useAddWatcherMutation, useUpdateWatcherMutation, useDeleteWatcherMutation, useGetWatchedUsersQuery, useGetWatchersQuery, WATCHER_NOTIFICATION_EVENTS, WATCHER_NOTIFICATION_EVENT_LABELS, useUploadLightLogoMutation, useUploadDarkLogoMutation, useUpdatePlatformInfoMutation, useUpdateTenantMetadataMutation, LOGO_ENDPOINTS, useDeleteApiKeyMutation, useGetApiKeysQuery, useCreateApiKeyMutation, useCreateLLMCredentialMutation, useGetCredentialsSchemaQuery, useGetMaskedLLMCredentialsQuery, useGetLlmsQuery, useDeleteIntegrationCredentialMutation, useDeleteCredentialMutation, useCreateIntegrationCredentialMutation, useGetIntegrationCredentialsSchemaQuery, useGetMaskedIntegrationCredentialsQuery, 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, useGetCustomMentorsQuery, useGetStripeConnectStatusQuery, useStartStripeConnectOnboardingMutation, useLazyGetStripeConnectDashboardQuery, useGetAiSearchMentorsQuery, useListPaywallsQuery, useListPricesQuery, useCreatePriceMutation, useUpdatePriceMutation, useDeletePriceMutation, useGetPaywallConfigQuery, useEnablePaywallMutation, useUpdatePaywallMutation, useGetUserProjectsQuery, useGetMentorsQuery, useGetPublicMentorsQuery, useCreateUserProjectMutation, useUpdateUserProjectMutation, useDeleteUserProjectMutation, useCreateSessionIdMutation, useGetMentorCategoriesQuery, useGetMentorSettingsQuery, useEditMentorMutation, useDeleteMentorMutation, useForkMentorMutation, useGetToolsQuery, useGetMentorMemoriesListQuery, useGetMemoryCategoriesAdminQuery, useDeleteMentorMemoryMutation, useUpdateMentorMemoryMutation, useCreateMentorMemoryMutation, useLazyGetMCPServersQuery, useOauthFindMutation, useLazyStartOAuthFlowQuery, useCreateMCPServerMutation, usePartialUpdateMCPServerMutation, useCreateMCPServerConnectionMutation, usePatchMCPServerConnectionMutation, useGetConnectedServicesQuery, useGetMCPServersQuery, useGetMCPServerConnectionsQuery, useUpdateMCPServerMutation, useDeleteMCPServerMutation, useEditMentorJsonMutation, useDisconnectServiceMutation, useGetPromptCategoriesQuery, PRIVACY_ACTIONS, PRIVACY_ENTITY_TYPES, useGetVoicesQuery, useGetVoiceQuery, useGetCallConfigurationsQuery, useCreateCallConfigurationMutation, useUpdateCallConfigurationMutation, useGetMentorPublicSettingsQuery, useGetChatHistoryFilterQuery, useGetChatHistoryQuery, useGetMentorSummariesQuery, useGetConversationMemoriesQuery, useEditTrainingDocumentMutation, useGetTrainingDocumentsQuery, useCreatePromptMutation, useGetPromptsSearchQuery, useUpdatePromptMutation, useCreateRedirectTokenMutation, useGetShareableLinkQuery, useCreateShareableLinkMutation, useUpdateShareableLinkMutation, useCreateDisclaimerMutation, useUpdateDisclaimerMutation, useGetDisclaimersQuery, useUpdateRbacMentorAccessMutation, useGetRbacMentorAccessListQuery, useStarMentorMutation, useUnstarMentorMutation, useGetPersonnalizedMentorsQuery, useGetVectorDocumentsQuery, useLazyGetConnectedServiceAuthUrlQuery, useCreateCallCredentialsMutation, useGetGuidedPromptsQuery, useUpdateChatSessionSharedMutation, useUpdateMessageFeedbackMutation, useLazyGetPromptsSearchQuery, useLazyGetGuidedPromptsQuery, useGetUserProjectDetailsQuery, useGetPeriodicAgentsQuery, useGetPeriodicAgentLogsListQuery, useCreatePeriodicAgentMutation, useDeletePeriodicAgentMutation, useCreateMemoryCategoryMutation, useUpdateMemoryCategoryMutation, useDeleteMemoryCategoryMutation, useDeleteTrainingDocumentMutation, useGetTrainingDocumentRetrainScheduleQuery, useCreateTrainingDocumentRetrainScheduleMutation, useDeletePromptMutation, useAddTrainingDocumentMutation, useLazyGetCredentialsQuery, useUpdateArtifactMutation, useLazyGetArtifactVersionQuery, useLazyListArtifactVersionsQuery, useSetCurrentVersionMutation, useListArtifactVersionsQuery, useLazyGetArtifactQuery, useLazyListArtifactsQuery, useEditSessionMutation } from '@iblai/data-layer';
11
11
  import { toast, Toaster as Toaster$1 } from 'sonner';
12
12
  import { getInitials, useTenantMetadata, isAlphaNumeric32, checkRbacPermission, WithPermissions, useTenantContext, useStripeUpgrade, CHAT_AREA_SIZE, useUsername, chatActions, TimeTracker, advancedTabsProperties, defaultSessionIds, WithFormPermissions, selectNumberOfActiveChatMessages, selectStreaming, isLoggedIn, TOOLS, isSafariBrowser, useShowAttachment, useShowVoiceCall, useShowVoiceRecorder, useMentorSettings, selectShowingSharedChat, selectRbacPermissions, useShowFreeTrialDialog, useEmbedMode, chatInputSliceSelectors, useResponsive, useAccessingPublicRoute, useVisitingTenant, useModelFileUploadCapabilities, useChatFileUpload, useVoiceChat, selectAttachedFiles, MENTOR_CHAT_DOCUMENTS_EXTENSIONS, removeFile, chatInputSliceActions, ANONYMOUS_USERNAME as ANONYMOUS_USERNAME$2, redirectToAuthSpaJoinTenant, selectActiveTab, useAxdToken, useWelcomeMessage, markdownToPlainText, useCachedSessionId, use402ErrorCheck, selectTokenEnabled, selectToken, useServiceWorker, useUserAgreement, useAdvancedChat, sendMessageToParentWebsite, useMentorTools, useFileDragDrop, eventBus, RemoteEvents, selectEnableChatActionsPopup, advancedTabs, isInIframe, getAuthSpaJoinUrl, addMessage, clearFiles, MENTOR_VISIBILITY } from '@iblai/web-utils';
13
13
  import { MentorVisibilityEnum, TransportEnum, PromptVisibilityEnum } from '@iblai/iblai-api';
@@ -197747,7 +197747,7 @@ function PoliciesTab({ tenant }) {
197747
197747
  }), className: "w-full text-left px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700", children: jsx("span", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: group.name }) }, group.id))) }))] })] })] })] }), jsxs(DialogFooter, { className: "mt-6", children: [jsx(Button$1, { variant: "outline", onClick: () => setIsOpen(false), children: "Cancel" }), jsx(TooltipProvider, { children: jsxs(Tooltip, { children: [jsx(TooltipTrigger, { asChild: true, children: jsx("span", { tabIndex: 0, children: jsx(Button$1, { onClick: onSubmit, disabled: policyDetails === null || policyDetails === void 0 ? void 0 : policyDetails.is_internal, className: "bg-gradient-to-r from-[#2563EB] to-[#93C5FD] hover:opacity-90 text-white", children: editing ? 'Save Policy' : 'Create Policy' }) }) }), editing && (policyDetails === null || policyDetails === void 0 ? void 0 : policyDetails.is_internal) && (jsx(TooltipContent, { children: jsx("p", { children: "Cannot edit internal policies" }) }))] }) })] })] }) })] }));
197748
197748
  }
197749
197749
 
197750
- const PAGE_SIZE$2 = 10;
197750
+ const PAGE_SIZE$3 = 10;
197751
197751
  function normalizeEvents(events) {
197752
197752
  if (!events)
197753
197753
  return [];
@@ -197795,8 +197795,8 @@ function AlertsTab({ tenant }) {
197795
197795
  const { data: watchedGroupsData, isLoading, isError, refetch, } = useGetWatchedGroupsQuery({
197796
197796
  params: {
197797
197797
  platform_key: tenant,
197798
- limit: PAGE_SIZE$2,
197799
- offset: (page - 1) * PAGE_SIZE$2,
197798
+ limit: PAGE_SIZE$3,
197799
+ offset: (page - 1) * PAGE_SIZE$3,
197800
197800
  },
197801
197801
  });
197802
197802
  const groups = useMemo(() => {
@@ -197809,7 +197809,7 @@ function AlertsTab({ tenant }) {
197809
197809
  return results;
197810
197810
  }, [watchedGroupsData, debouncedSearchQuery]);
197811
197811
  const totalPages = watchedGroupsData
197812
- ? Math.max(1, Math.ceil(watchedGroupsData.count / PAGE_SIZE$2))
197812
+ ? Math.max(1, Math.ceil(watchedGroupsData.count / PAGE_SIZE$3))
197813
197813
  : 0;
197814
197814
  const handlePageChange = async (newPage) => {
197815
197815
  setPage(newPage);
@@ -201249,7 +201249,7 @@ function PaywalledItemsList({ platformKey, onSelectItem, onAddCustomItem, }) {
201249
201249
  : 'No items configured yet. Search and select an item above.' }))] })] }));
201250
201250
  }
201251
201251
 
201252
- const EMPTY_FORM = {
201252
+ const EMPTY_FORM$1 = {
201253
201253
  name: '',
201254
201254
  amount: '',
201255
201255
  currency: 'usd',
@@ -201299,7 +201299,7 @@ function PriceManagement({ platformKey, itemType, itemId }) {
201299
201299
  const [deletePrice] = useDeletePriceMutation();
201300
201300
  const [showCreateForm, setShowCreateForm] = useState(false);
201301
201301
  const [editingPriceId, setEditingPriceId] = useState(null);
201302
- const [formData, setFormData] = useState(EMPTY_FORM);
201302
+ const [formData, setFormData] = useState(EMPTY_FORM$1);
201303
201303
  const [deletingId, setDeletingId] = useState(null);
201304
201304
  const handleCreate = async () => {
201305
201305
  try {
@@ -201318,7 +201318,7 @@ function PriceManagement({ platformKey, itemType, itemId }) {
201318
201318
  }).unwrap();
201319
201319
  toast.success('Price created');
201320
201320
  setShowCreateForm(false);
201321
- setFormData(EMPTY_FORM);
201321
+ setFormData(EMPTY_FORM$1);
201322
201322
  refetch();
201323
201323
  }
201324
201324
  catch (_a) {
@@ -201360,7 +201360,7 @@ function PriceManagement({ platformKey, itemType, itemId }) {
201360
201360
  }).unwrap();
201361
201361
  toast.success('Price updated');
201362
201362
  setEditingPriceId(null);
201363
- setFormData(EMPTY_FORM);
201363
+ setFormData(EMPTY_FORM$1);
201364
201364
  refetch();
201365
201365
  }
201366
201366
  catch (_a) {
@@ -201388,16 +201388,16 @@ function PriceManagement({ platformKey, itemType, itemId }) {
201388
201388
  };
201389
201389
  return (jsxs("div", { className: "space-y-3", children: [jsxs("div", { className: "flex items-center justify-between", children: [jsx("h4", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: "Pricing Tiers" }), !showCreateForm && !editingPriceId && (jsxs(Button$1, { onClick: () => {
201390
201390
  setShowCreateForm(true);
201391
- setFormData(EMPTY_FORM);
201391
+ setFormData(EMPTY_FORM$1);
201392
201392
  setEditingPriceId(null);
201393
201393
  }, variant: "outline", size: "sm", className: "cursor-pointer", children: [jsx(Plus, { className: "h-3 w-3 mr-1" }), "Add Price"] }))] }), showCreateForm && (jsx(PriceForm, { form: formData, onChange: setFormData, onSave: handleCreate, onCancel: () => {
201394
201394
  setShowCreateForm(false);
201395
- setFormData(EMPTY_FORM);
201395
+ setFormData(EMPTY_FORM$1);
201396
201396
  }, isSaving: isCreating, isEdit: false })), isLoading ? (jsx("div", { className: "space-y-2", children: [1, 2].map((i) => (jsx(Skeleton, { className: "h-20 w-full" }, i))) })) : prices && prices.length > 0 ? (jsx("div", { className: "space-y-2", children: prices.map((price) => {
201397
201397
  var _a;
201398
201398
  return editingPriceId === price.id ? (jsx(PriceForm, { form: formData, onChange: setFormData, onSave: handleUpdate, onCancel: () => {
201399
201399
  setEditingPriceId(null);
201400
- setFormData(EMPTY_FORM);
201400
+ setFormData(EMPTY_FORM$1);
201401
201401
  }, isSaving: isUpdating, isEdit: true }, price.id)) : (jsx(Card, { className: "shadow-sm border", style: { borderColor: 'oklch(.922 0 0)' }, children: jsx(CardContent, { className: "p-4", children: jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { className: "flex items-center gap-3", children: [jsx(Tag, { className: "h-4 w-4 text-gray-400" }), jsxs("div", { children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: price.name }), jsxs("span", { className: "text-sm font-semibold text-blue-600", children: ["$", price.amount, " ", formatInterval(price.interval)] }), !price.is_active && (jsx(Badge, { variant: "secondary", className: "text-xs", children: "Inactive" }))] }), price.description && (jsx("p", { className: "text-xs text-gray-400 mt-0.5", children: price.description })), ((_a = price.features) === null || _a === void 0 ? void 0 : _a.length) > 0 && (jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: price.features.map((f, i) => (jsx(Badge, { variant: "outline", className: "text-xs font-normal", children: f }, i))) }))] })] }), jsxs("div", { className: "flex gap-1 flex-shrink-0", children: [jsx(Button$1, { onClick: () => handleStartEdit(price), variant: "ghost", size: "sm", className: "h-8 w-8 p-0 cursor-pointer", children: jsx(Pencil, { className: "h-3 w-3" }) }), jsx(Button$1, { onClick: () => handleDelete(price.id), variant: "ghost", size: "sm", disabled: deletingId === price.id, className: "h-8 w-8 p-0 text-red-500 hover:text-red-700 cursor-pointer", children: deletingId === price.id ? (jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" })) : (jsx(Trash2, { className: "h-3 w-3" })) })] })] }) }) }, price.id));
201402
201402
  }) })) : (jsx("p", { className: "text-xs text-gray-400 text-center py-4", children: "No pricing tiers yet. Add one to start accepting payments." }))] }));
201403
201403
  }
@@ -206776,7 +206776,7 @@ const BulkDeleteMemoryModal$1 = dynamic(() => Promise.resolve().then(function ()
206776
206776
  const ManageCategoriesModal$1 = dynamic(() => Promise.resolve().then(function () { return manageCategoriesModal; }).then((module) => ({
206777
206777
  default: module.ManageCategoriesModal,
206778
206778
  })), { ssr: false });
206779
- const PAGE_SIZE$1 = 20;
206779
+ const PAGE_SIZE$2 = 20;
206780
206780
  const SNAPSHOT_PAGE_SIZE = 1000;
206781
206781
  function ManageMemories({ tenantKey, username, mentorId }) {
206782
206782
  var _a, _b, _c, _d, _e;
@@ -206790,7 +206790,7 @@ function ManageMemories({ tenantKey, username, mentorId }) {
206790
206790
  setPage(1);
206791
206791
  }, [selectedLearner, dateRange, selectedCategorySlug]);
206792
206792
  const listParams = useMemo(() => {
206793
- const params = { page, page_size: PAGE_SIZE$1 };
206793
+ const params = { page, page_size: PAGE_SIZE$2 };
206794
206794
  if (selectedLearner)
206795
206795
  params.email = selectedLearner;
206796
206796
  if (selectedCategorySlug !== 'all')
@@ -206809,7 +206809,7 @@ function ManageMemories({ tenantKey, username, mentorId }) {
206809
206809
  }, {
206810
206810
  skip: !tenantKey || !username || !mentorId,
206811
206811
  });
206812
- const totalPages = Math.max(1, Math.ceil(((_a = listResponse === null || listResponse === void 0 ? void 0 : listResponse.count) !== null && _a !== void 0 ? _a : 0) / PAGE_SIZE$1));
206812
+ const totalPages = Math.max(1, Math.ceil(((_a = listResponse === null || listResponse === void 0 ? void 0 : listResponse.count) !== null && _a !== void 0 ? _a : 0) / PAGE_SIZE$2));
206813
206813
  const { data: adminCategories } = useGetMemoryCategoriesAdminQuery({
206814
206814
  org: tenantKey,
206815
206815
  mentorId,
@@ -209108,9 +209108,9 @@ function AgentPrivacyTab({ labels: labelsOverride, tenantKey: tenantKeyProp, men
209108
209108
  }
209109
209109
  return (jsxs(Fragment$1, { children: [jsx("div", { className: "flex hidden h-[73px] flex-shrink-0 items-center border-b border-gray-200 bg-white p-4 lg:block", children: jsxs("div", { children: [jsx("h3", { className: "mb-1 text-base font-medium text-gray-900", children: labels.header.title }), jsx("p", { className: "text-xs text-gray-700", children: labels.header.description })] }) }), jsxs("div", { className: "flex-1 space-y-6 p-3 lg:p-4", style: { overflowY: 'auto', overflowX: 'hidden' }, children: [jsx(WithFormPermissions, { name: "enable_privacy_router",
209110
209110
  // @ts-ignore
209111
- permissions: (_h = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _h === void 0 ? void 0 : _h.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm font-medium text-[#646464]", children: labels.fields.enableRouter.label }), jsx(FieldTooltip, { text: labels.fields.enableRouter.tooltip })] }), jsx(CustomSwitch, { checked: enabled, onCheckedChange: (checked) => void updateField('enable_privacy_router', checked), disabled: isDisabled || disabled, "aria-label": `Privacy router ${enabled ? 'enabled' : 'disabled'}` })] })) }), enabled && (jsxs(Fragment$1, { children: [jsx(WithFormPermissions, { name: "privacy_action",
209111
+ permissions: (_h = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _h === void 0 ? void 0 : _h.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm font-medium text-[#646464]", children: labels.fields.enableRouter.label }), jsx(FieldTooltip$1, { text: labels.fields.enableRouter.tooltip })] }), jsx(CustomSwitch, { checked: enabled, onCheckedChange: (checked) => void updateField('enable_privacy_router', checked), disabled: isDisabled || disabled, "aria-label": `Privacy router ${enabled ? 'enabled' : 'disabled'}` })] })) }), enabled && (jsxs(Fragment$1, { children: [jsx(WithFormPermissions, { name: "privacy_action",
209112
209112
  // @ts-ignore
209113
- permissions: (_j = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _j === void 0 ? void 0 : _j.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-sm font-medium text-[#646464]", children: labels.fields.action.label }), jsx(FieldTooltip, { text: labels.fields.action.tooltip })] }), jsxs(Select$1, { value: action, onValueChange: (v) => void updateField('privacy_action', v), disabled: isDisabled || disabled, children: [jsx(SelectTrigger, { "aria-label": labels.fields.action.label, children: jsx(SelectValue, {}) }), jsx(SelectContent, { children: PRIVACY_ACTIONS.map((opt) => (jsx(SelectItem, { value: opt, children: labels.fields.action.options[opt] }, opt))) })] }), jsx("p", { className: "text-xs text-gray-500", children: labels.fields.action.descriptions[action] })] })) }), action === 'block' && (jsx(WithFormPermissions, { name: "privacy_response",
209113
+ permissions: (_j = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _j === void 0 ? void 0 : _j.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-sm font-medium text-[#646464]", children: labels.fields.action.label }), jsx(FieldTooltip$1, { text: labels.fields.action.tooltip })] }), jsxs(Select$1, { value: action, onValueChange: (v) => void updateField('privacy_action', v), disabled: isDisabled || disabled, children: [jsx(SelectTrigger, { "aria-label": labels.fields.action.label, children: jsx(SelectValue, {}) }), jsx(SelectContent, { children: PRIVACY_ACTIONS.map((opt) => (jsx(SelectItem, { value: opt, children: labels.fields.action.options[opt] }, opt))) })] }), jsx("p", { className: "text-xs text-gray-500", children: labels.fields.action.descriptions[action] })] })) }), action === 'block' && (jsx(WithFormPermissions, { name: "privacy_response",
209114
209114
  // @ts-ignore
209115
209115
  permissions: (_k = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _k === void 0 ? void 0 : _k.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "space-y-2", children: [jsx(Label, { className: "text-sm font-medium text-[#646464]", children: labels.fields.blockMessage.label }), jsx(Textarea, { value: localResponse, onChange: (e) => setLocalResponse(e.target.value), onBlur: () => {
209116
209116
  if (localResponse !== responseText) {
@@ -209118,14 +209118,14 @@ function AgentPrivacyTab({ labels: labelsOverride, tenantKey: tenantKeyProp, men
209118
209118
  }
209119
209119
  }, placeholder: labels.fields.blockMessage.placeholder, className: "min-h-[100px]", disabled: isDisabled || disabled, "aria-label": labels.fields.blockMessage.label })] })) })), jsx(WithFormPermissions, { name: "privacy_entities",
209120
209120
  // @ts-ignore
209121
- permissions: (_l = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _l === void 0 ? void 0 : _l.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-sm font-medium text-[#646464]", children: labels.fields.entities.label }), jsx(FieldTooltip, { text: labels.fields.entities.tooltip })] }), jsx("div", { className: "flex flex-wrap gap-2", children: PRIVACY_ENTITY_TYPES.map((entity) => {
209121
+ permissions: (_l = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _l === void 0 ? void 0 : _l.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-sm font-medium text-[#646464]", children: labels.fields.entities.label }), jsx(FieldTooltip$1, { text: labels.fields.entities.tooltip })] }), jsx("div", { className: "flex flex-wrap gap-2", children: PRIVACY_ENTITY_TYPES.map((entity) => {
209122
209122
  var _a;
209123
209123
  return (jsx(EntityChip, { entity: entity, label: (_a = labels.fields.entities.labels[entity]) !== null && _a !== void 0 ? _a : entity, selected: entities.includes(entity), disabled: isDisabled || disabled, onToggle: (checked) => toggleEntity(entity, checked) }, entity));
209124
209124
  }) }), entities.length === 0 && (jsx("p", { className: "text-xs text-gray-500", children: labels.fields.entities.emptyHint }))] })) }), jsx(WithFormPermissions, { name: "enable_privacy_output_filter",
209125
209125
  // @ts-ignore
209126
- permissions: (_m = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _m === void 0 ? void 0 : _m.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm font-medium text-[#646464]", children: labels.fields.outputFilter.label }), jsx(FieldTooltip, { text: labels.fields.outputFilter.tooltip })] }), jsx(CustomSwitch, { checked: outputFilter, onCheckedChange: (checked) => void updateField('enable_privacy_output_filter', checked), disabled: isDisabled || disabled, "aria-label": `Output filter ${outputFilter ? 'enabled' : 'disabled'}` })] })) })] }))] })] }));
209126
+ permissions: (_m = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _m === void 0 ? void 0 : _m.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm font-medium text-[#646464]", children: labels.fields.outputFilter.label }), jsx(FieldTooltip$1, { text: labels.fields.outputFilter.tooltip })] }), jsx(CustomSwitch, { checked: outputFilter, onCheckedChange: (checked) => void updateField('enable_privacy_output_filter', checked), disabled: isDisabled || disabled, "aria-label": `Output filter ${outputFilter ? 'enabled' : 'disabled'}` })] })) })] }))] })] }));
209127
209127
  }
209128
- function FieldTooltip({ text }) {
209128
+ function FieldTooltip$1({ text }) {
209129
209129
  return (jsx(TooltipProvider, { children: jsxs(Tooltip, { children: [jsx(TooltipTrigger, { type: "button", "aria-label": "More info", children: jsx(Info$3, { className: "h-4 w-4 text-gray-400" }) }), jsx(TooltipContent, { className: "ibl-tooltip-content", children: jsx("p", { children: text }) })] }) }));
209130
209130
  }
209131
209131
  function EntityChip({ entity, label, selected, disabled, onToggle }) {
@@ -209134,6 +209134,893 @@ function EntityChip({ entity, label, selected, disabled, onToggle }) {
209134
209134
  : 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50', disabled && 'cursor-not-allowed opacity-50'), "data-entity": entity, children: label }));
209135
209135
  }
209136
209136
 
209137
+ const AGENT_VOICE_TAB_LABELS = {
209138
+ header: {
209139
+ title: 'Voice',
209140
+ description: 'Choose the voice your agent uses and how voice calls work.',
209141
+ },
209142
+ subTabs: {
209143
+ voice: 'Voice',
209144
+ callConfig: 'Voice call',
209145
+ },
209146
+ mentorVoice: {
209147
+ title: 'Agent Voice',
209148
+ description: 'The voice your agent uses to read out replies in chat.',
209149
+ providerLabel: 'Voice source',
209150
+ providerTooltip: "Browser uses the listener's own device to speak. OpenAI and Google use a custom voice you pick below.",
209151
+ providerOptions: {
209152
+ browser: {
209153
+ label: 'Browser',
209154
+ description: "Speak through the user's own device. No setup needed.",
209155
+ },
209156
+ openai: {
209157
+ label: 'OpenAI',
209158
+ description: 'High-quality voices from OpenAI.',
209159
+ },
209160
+ google: {
209161
+ label: 'Google',
209162
+ description: 'Natural-sounding voices from Google.',
209163
+ },
209164
+ },
209165
+ voicePickerLabel: {
209166
+ openai: 'OpenAI voice',
209167
+ google: 'Google voice',
209168
+ },
209169
+ searchPlaceholder: 'Search voices…',
209170
+ emptyState: 'No voices match your search.',
209171
+ errorState: "Couldn't load voices. Try again.",
209172
+ previewAria: (voiceName, action) => `${action === 'play' ? 'Preview' : 'Stop'} ${voiceName} voice`,
209173
+ saveButton: 'Save voice',
209174
+ savingButton: 'Saving…',
209175
+ },
209176
+ callConfig: {
209177
+ title: 'Voice call',
209178
+ description: 'Customize how voice calls work. Screen sharing always uses live conversation.',
209179
+ info: 'Live conversation streams the call end-to-end and feels more natural. Step-by-step mode transcribes the user, has the agent think, then speaks — slower, with more time between turns. Screen sharing always uses live conversation.',
209180
+ fields: {
209181
+ mode: {
209182
+ label: 'Call style',
209183
+ tooltip: 'Live conversation feels faster and more natural. Step-by-step gives the agent more time to think between each turn.',
209184
+ options: { realtime: 'Live conversation', inference: 'Step-by-step' },
209185
+ },
209186
+ language: {
209187
+ label: 'Spoken language',
209188
+ tooltip: 'The language used during voice calls.',
209189
+ placeholder: 'English',
209190
+ },
209191
+ llmProvider: {
209192
+ label: 'AI provider',
209193
+ tooltip: 'Which company powers the AI during calls.',
209194
+ placeholder: 'Use agent default',
209195
+ },
209196
+ // TTS / STT are hidden in the UI today — labels kept for completeness
209197
+ // in case a host wants to surface them.
209198
+ ttsProvider: {
209199
+ label: 'Text-to-speech provider',
209200
+ tooltip: 'Auto-matched to the AI provider above.',
209201
+ placeholder: 'Match AI provider',
209202
+ },
209203
+ sttProvider: {
209204
+ label: 'Speech-to-text provider',
209205
+ tooltip: 'Auto-matched to the AI provider above.',
209206
+ placeholder: 'Match AI provider',
209207
+ },
209208
+ useFunctionCalling: {
209209
+ label: 'Look things up only when needed',
209210
+ tooltip: "On (recommended): the agent decides when to check its training materials before answering. Off: the agent checks before every reply — slower but never skips a lookup.",
209211
+ },
209212
+ enableVideo: {
209213
+ label: 'Allow screen sharing on a call',
209214
+ tooltip: "Let users share their screen so the agent can see what they're doing and guide them. Only works in Live conversation mode.",
209215
+ },
209216
+ },
209217
+ resetButton: 'Reset',
209218
+ saveCreate: 'Save',
209219
+ saveUpdate: 'Save changes',
209220
+ savingButton: 'Saving…',
209221
+ },
209222
+ toasts: {
209223
+ voiceSaved: 'Voice saved',
209224
+ voiceError: "Couldn't save voice settings",
209225
+ callConfigSaved: 'Voice call settings saved',
209226
+ callConfigError: "Couldn't save voice call settings",
209227
+ },
209228
+ };
209229
+ function resolveVoiceTabLabels(override) {
209230
+ if (!override)
209231
+ return AGENT_VOICE_TAB_LABELS;
209232
+ return deepMerge(AGENT_VOICE_TAB_LABELS, override);
209233
+ }
209234
+
209235
+ /**
209236
+ * Single-track audio preview hook. Calling `play()` for a new id stops the
209237
+ * currently-playing clip first; calling it again for the same id toggles
209238
+ * stop. Cleans up on unmount.
209239
+ */
209240
+ function useAudioPreview() {
209241
+ const audioRef = React__default.useRef(null);
209242
+ const [playingId, setPlayingId] = React__default.useState(null);
209243
+ const [isLoading, setIsLoading] = React__default.useState(false);
209244
+ React__default.useEffect(() => {
209245
+ return () => {
209246
+ if (audioRef.current) {
209247
+ audioRef.current.pause();
209248
+ audioRef.current.src = '';
209249
+ audioRef.current = null;
209250
+ }
209251
+ };
209252
+ }, []);
209253
+ const stop = React__default.useCallback(() => {
209254
+ if (audioRef.current) {
209255
+ audioRef.current.pause();
209256
+ audioRef.current.currentTime = 0;
209257
+ }
209258
+ setPlayingId(null);
209259
+ setIsLoading(false);
209260
+ }, []);
209261
+ const play = React__default.useCallback((id, url) => {
209262
+ if (!url)
209263
+ return;
209264
+ if (playingId === id && audioRef.current && !audioRef.current.paused) {
209265
+ stop();
209266
+ return;
209267
+ }
209268
+ if (audioRef.current) {
209269
+ audioRef.current.pause();
209270
+ }
209271
+ const audio = new Audio(url);
209272
+ audioRef.current = audio;
209273
+ setIsLoading(true);
209274
+ setPlayingId(id);
209275
+ audio.addEventListener('canplay', () => setIsLoading(false));
209276
+ audio.addEventListener('ended', () => setPlayingId(null));
209277
+ audio.addEventListener('error', () => {
209278
+ setIsLoading(false);
209279
+ setPlayingId(null);
209280
+ });
209281
+ // Older browsers / jsdom return void from play(); guard against
209282
+ // that so we don't crash with "Cannot read properties of undefined
209283
+ // (reading 'catch')".
209284
+ const result = audio.play();
209285
+ if (result && typeof result.catch === 'function') {
209286
+ result.catch(() => {
209287
+ setIsLoading(false);
209288
+ setPlayingId(null);
209289
+ });
209290
+ }
209291
+ }, [playingId, stop]);
209292
+ return { playingId, isLoading, play, stop };
209293
+ }
209294
+
209295
+ /**
209296
+ * Voices come back from the API in inconsistent casing (e.g. "alloy",
209297
+ * "Alloy", "ALLOY"). Display them title-cased so the UI stays consistent
209298
+ * regardless of the upstream catalogue.
209299
+ */
209300
+ function toTitleCase(name) {
209301
+ if (!name)
209302
+ return name;
209303
+ return name
209304
+ .split(/\s+/)
209305
+ .map((word) => word.length > 0 ? word[0].toUpperCase() + word.slice(1).toLowerCase() : word)
209306
+ .join(' ');
209307
+ }
209308
+
209309
+ const PAGE_SIZE$1 = 20;
209310
+ function VoicePicker({ org, userId, provider, selectedVoiceId, onSelect, disabled, labels, }) {
209311
+ var _a, _b;
209312
+ const [search, setSearch] = React__default.useState('');
209313
+ const [debouncedSearch, setDebouncedSearch] = React__default.useState('');
209314
+ const [page, setPage] = React__default.useState(1);
209315
+ // Reset paging when the user types a new search OR switches provider.
209316
+ React__default.useEffect(() => {
209317
+ setPage(1);
209318
+ }, [debouncedSearch, provider]);
209319
+ React__default.useEffect(() => {
209320
+ const handle = setTimeout(() => setDebouncedSearch(search), 300);
209321
+ return () => clearTimeout(handle);
209322
+ }, [search]);
209323
+ const { data, isFetching, isError } = useGetVoicesQuery({
209324
+ org,
209325
+ userId,
209326
+ provider,
209327
+ page,
209328
+ pageSize: PAGE_SIZE$1,
209329
+ ...(debouncedSearch ? { search: debouncedSearch } : {}),
209330
+ }, { skip: !org || !userId });
209331
+ const paginated = data;
209332
+ const voices = (_a = paginated === null || paginated === void 0 ? void 0 : paginated.results) !== null && _a !== void 0 ? _a : [];
209333
+ const total = (_b = paginated === null || paginated === void 0 ? void 0 : paginated.count) !== null && _b !== void 0 ? _b : 0;
209334
+ const totalPages = total > 0 ? Math.ceil(total / PAGE_SIZE$1) : 1;
209335
+ // Whenever a voice id is saved on the mentor settings, fetch it directly
209336
+ // so we can pin it at the top of the list. Otherwise the user can land
209337
+ // on a page that doesn't contain the selected voice and the selection
209338
+ // appears lost.
209339
+ const { data: pinnedVoiceData } = useGetVoiceQuery({
209340
+ id: selectedVoiceId !== null && selectedVoiceId !== void 0 ? selectedVoiceId : 0,
209341
+ org,
209342
+ userId,
209343
+ }, { skip: !org || !userId || selectedVoiceId == null });
209344
+ const pinnedVoice = pinnedVoiceData;
209345
+ // Only pin a voice from the same provider — otherwise switching from
209346
+ // google → openai would briefly show the leftover google voice.
209347
+ const showPinned = pinnedVoice != null &&
209348
+ pinnedVoice.provider === provider &&
209349
+ !voices.some((v) => v.id === pinnedVoice.id);
209350
+ const { playingId, isLoading: isPreviewLoading, play, stop } = useAudioPreview();
209351
+ React__default.useEffect(() => () => stop(), [provider, stop]);
209352
+ return (jsxs("div", { className: "space-y-3", children: [jsxs("div", { className: "relative", children: [jsx(Search, { className: "absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" }), jsx(Input, { type: "search", placeholder: labels.searchPlaceholder, className: "w-full pl-9", value: search, onChange: (e) => setSearch(e.target.value), disabled: disabled, "aria-label": labels.searchPlaceholder, "data-testid": "voice-picker-search" })] }), isError && jsx("p", { className: "text-sm text-red-500", children: labels.errorState }), isFetching ? (jsx("div", { className: "flex items-center justify-center py-8", children: jsx(Spinner, {}) })) : voices.length === 0 && !showPinned ? (jsx("div", { className: "rounded-md border border-dashed border-gray-200 py-8 text-center text-sm text-gray-500", children: labels.emptyState })) : (jsxs(Fragment$1, { children: [showPinned && pinnedVoice && (jsx("div", { className: "rounded-md bg-blue-50/40 p-2", "data-testid": "pinned-voice-row", children: jsx(VoiceRow, { voice: pinnedVoice, isSelected: true, isPlaying: playingId === pinnedVoice.id, showPlayLoader: playingId === pinnedVoice.id && isPreviewLoading, disabled: !!disabled, onSelect: onSelect, play: play, labels: labels }) })), jsx("div", {
209353
+ // Padding (`p-1`) prevents the selected border from being
209354
+ // clipped by `overflow-y-auto` on the first/last rows.
209355
+ className: "grid max-h-[360px] grid-cols-1 gap-2 overflow-y-auto p-1 sm:grid-cols-2", role: "radiogroup", "aria-label": "Available voices", children: voices.map((voice) => (jsx(VoiceRow, { voice: voice, isSelected: selectedVoiceId === voice.id, isPlaying: playingId === voice.id, showPlayLoader: playingId === voice.id && isPreviewLoading, disabled: !!disabled, onSelect: onSelect, play: play, labels: labels }, voice.id))) }), totalPages > 1 && (jsxs("div", { className: "flex items-center justify-between gap-2 border-t border-gray-100 pt-3 text-xs text-gray-600", "data-testid": "voice-pagination", children: [jsxs("span", { "aria-live": "polite", children: ["Page ", jsx("strong", { children: page }), " of ", jsx("strong", { children: totalPages }), jsxs("span", { className: "ml-1 text-gray-400", children: ["\u00B7 ", total, " voices"] })] }), jsxs("div", { className: "flex items-center gap-2", children: [jsxs(Button$1, { type: "button", variant: "outline", size: "sm", disabled: disabled || page <= 1, onClick: () => setPage((p) => Math.max(1, p - 1)), "aria-label": "Previous page of voices", "data-testid": "voice-prev-page", children: [jsx(ChevronLeft, { className: "h-4 w-4" }), "Prev"] }), jsxs(Button$1, { type: "button", variant: "outline", size: "sm", disabled: disabled || page >= totalPages, onClick: () => setPage((p) => Math.min(totalPages, p + 1)), "aria-label": "Next page of voices", "data-testid": "voice-next-page", children: ["Next", jsx(ChevronRight, { className: "h-4 w-4" })] })] })] }))] }))] }));
209356
+ }
209357
+ /**
209358
+ * A single voice row. Rendered as a `<div role="radio">` so the inner
209359
+ * preview `<button>` doesn't trip HTML's "no nested button" rule.
209360
+ * Keyboard behaviour mirrors a real radio: Space/Enter selects.
209361
+ *
209362
+ * Uses `border-2` (not `ring`) for the selected outline so it never gets
209363
+ * clipped by the parent scroll container's `overflow-y-auto`.
209364
+ */
209365
+ function VoiceRow({ voice, isSelected, isPlaying, showPlayLoader, disabled, onSelect, play, labels, }) {
209366
+ return (jsxs("div", { role: "radio", tabIndex: disabled ? -1 : 0, "aria-checked": isSelected, "aria-disabled": disabled || undefined, "aria-label": toTitleCase(voice.name), "data-voice-id": voice.id, "data-selected": isSelected || undefined, onClick: () => !disabled && onSelect(voice), onKeyDown: (e) => {
209367
+ if (disabled)
209368
+ return;
209369
+ if (e.key === ' ' || e.key === 'Enter') {
209370
+ e.preventDefault();
209371
+ onSelect(voice);
209372
+ }
209373
+ }, className: cn('flex cursor-pointer items-start gap-3 rounded-lg border-2 p-3 text-left transition-all hover:shadow-sm focus-visible:outline-blue-500', isSelected
209374
+ ? 'border-blue-500 bg-blue-50/40'
209375
+ : 'border-gray-200 bg-white hover:border-gray-300', disabled && 'cursor-not-allowed opacity-60'), children: [jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full bg-blue-50 text-blue-600", children: jsx(Volume2, { className: "h-4 w-4", "aria-hidden": "true" }) }), jsxs("div", { className: "min-w-0 flex-1", children: [jsxs("div", { className: "flex items-center justify-between gap-2", children: [jsx("span", { className: "truncate text-sm font-medium text-gray-900", children: toTitleCase(voice.name) }), voice.audio_url && (jsx(Button$1, { type: "button", variant: "ghost", size: "icon", className: "h-7 w-7 flex-shrink-0 text-gray-500 hover:text-blue-600", onClick: (e) => {
209376
+ e.stopPropagation();
209377
+ play(voice.id, voice.audio_url);
209378
+ }, disabled: disabled, "aria-label": labels.previewAria(toTitleCase(voice.name), isPlaying ? 'stop' : 'play'), children: showPlayLoader ? (jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" })) : isPlaying ? (jsx(Pause, { className: "h-4 w-4" })) : (jsx(Play, { className: "h-4 w-4" })) }))] }), (voice.language || voice.description) && (jsx("p", { className: "mt-1 line-clamp-2 text-xs text-gray-500", children: [voice.language, voice.description].filter(Boolean).join(' · ') }))] })] }));
209379
+ }
209380
+
209381
+ /**
209382
+ * Modal-style voice picker. Wraps the inline `VoicePicker` (search +
209383
+ * pagination + audio preview) in a Dialog. Used by both the Voice tab
209384
+ * (mentor voice) and the Call Configuration sub-tab so we don't end up
209385
+ * with two divergent UIs for choosing a voice.
209386
+ */
209387
+ function VoicePickerModal({ isOpen, onClose, org, userId, provider, selectedVoiceId, onSelect, labels, title = 'Select a voice', description = 'Search, browse, and listen to a sample before you pick.', }) {
209388
+ return (jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && onClose(), children: jsxs(DialogContent, { className: "max-w-3xl space-y-4 p-4", "data-testid": "voice-picker-modal", children: [jsxs(DialogHeader, { children: [jsx(DialogTitle, { className: "ibl-dialog-title", children: title }), jsx(DialogDescription, { className: "text-xs text-gray-600", children: description })] }), jsx(VoicePicker, { org: org, userId: userId, provider: provider, selectedVoiceId: selectedVoiceId,
209389
+ // Auto-close on selection — picking a voice in the modal is the
209390
+ // explicit "done" action. The parent receives the new voice and
209391
+ // closes us via `isOpen={false}`.
209392
+ onSelect: (v) => {
209393
+ onSelect(v);
209394
+ onClose();
209395
+ }, labels: labels })] }) }));
209396
+ }
209397
+ /**
209398
+ * Combobox-style trigger that shows the currently selected voice (name +
209399
+ * audio glyph + inline Play button) and opens the modal picker on click.
209400
+ * Visual pattern mirrors the LLM provider card on the LLM tab — a
209401
+ * card-style row with the currently picked item up front and a chevron at
209402
+ * the end.
209403
+ *
209404
+ * Clicking the Play button previews the voice without opening the modal;
209405
+ * clicking anywhere else opens it.
209406
+ */
209407
+ function VoicePickerTrigger({ org, userId, provider, selectedVoice, onSelect, disabled, labels, emptyLabel = 'Select a voice', modalTitle, modalDescription, testId, }) {
209408
+ var _a;
209409
+ const [isOpen, setIsOpen] = React__default.useState(false);
209410
+ const { playingId, isLoading: isPreviewLoading, play, stop } = useAudioPreview();
209411
+ // Stop preview when the trigger unmounts or when the modal opens —
209412
+ // the modal has its own preview state and we don't want both playing
209413
+ // at once.
209414
+ React__default.useEffect(() => {
209415
+ if (isOpen)
209416
+ stop();
209417
+ }, [isOpen, stop]);
209418
+ const displayName = selectedVoice ? toTitleCase(selectedVoice.name) : null;
209419
+ const language = (selectedVoice === null || selectedVoice === void 0 ? void 0 : selectedVoice.language) || null;
209420
+ const audioUrl = (selectedVoice === null || selectedVoice === void 0 ? void 0 : selectedVoice.audio_url) || '';
209421
+ const isPlaying = selectedVoice != null && playingId === selectedVoice.id;
209422
+ const showPlayLoader = isPlaying && isPreviewLoading;
209423
+ return (jsxs(Fragment$1, { children: [jsxs("div", { className: cn('flex w-full items-center gap-2 rounded-lg border-2 border-gray-200 bg-white p-3 transition-colors', !disabled &&
209424
+ 'hover:border-blue-300 focus-within:border-blue-500', disabled && 'cursor-not-allowed opacity-60'), "data-testid": testId !== null && testId !== void 0 ? testId : 'voice-picker-trigger', children: [jsxs("button", { type: "button", disabled: disabled, onClick: () => setIsOpen(true), "aria-haspopup": "dialog", "aria-expanded": isOpen, className: "flex min-w-0 flex-1 items-center gap-3 text-left focus-visible:outline-none", "data-testid": testId ? `${testId}-open` : 'voice-picker-trigger-open', children: [jsx("span", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full bg-blue-50 text-blue-600", children: jsx(Volume2, { className: "h-4 w-4", "aria-hidden": "true" }) }), jsxs("span", { className: "flex min-w-0 flex-col", children: [jsx("span", { className: "truncate text-sm font-medium text-gray-900", children: displayName !== null && displayName !== void 0 ? displayName : emptyLabel }), language && (jsx("span", { className: "truncate text-xs text-gray-500", children: language }))] })] }), audioUrl && selectedVoice && (jsx(Button$1, { type: "button", variant: "ghost", size: "icon", className: "h-8 w-8 flex-shrink-0 text-gray-500 hover:text-blue-600", disabled: disabled, onClick: (e) => {
209425
+ e.stopPropagation();
209426
+ play(selectedVoice.id, audioUrl);
209427
+ }, "aria-label": labels.previewAria(displayName !== null && displayName !== void 0 ? displayName : '', isPlaying ? 'stop' : 'play'), "data-testid": testId ? `${testId}-preview` : 'voice-picker-trigger-preview', children: showPlayLoader ? (jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" })) : isPlaying ? (jsx(Pause, { className: "h-4 w-4" })) : (jsx(Play, { className: "h-4 w-4" })) })), jsx(ChevronDown, { className: "h-4 w-4 flex-shrink-0 text-gray-400", "aria-hidden": "true" })] }), jsx(VoicePickerModal, { isOpen: isOpen, onClose: () => setIsOpen(false), org: org, userId: userId, provider: provider, selectedVoiceId: (_a = selectedVoice === null || selectedVoice === void 0 ? void 0 : selectedVoice.id) !== null && _a !== void 0 ? _a : null, onSelect: onSelect, labels: labels, title: modalTitle, description: modalDescription })] }));
209428
+ }
209429
+
209430
+ /**
209431
+ * Resolve a voice by its display name. The mentor settings endpoint
209432
+ * returns voice references as raw strings (e.g. `"alloy"`, `"Fenrir"`)
209433
+ * rather than ids, so we have to look them up in the voices catalogue
209434
+ * before we can render the trigger (with id, audio_url, etc.) or send
209435
+ * an id back on save.
209436
+ *
209437
+ * Returns `{ voice, isResolving }`. While the lookup is in flight the
209438
+ * caller should keep showing whatever placeholder makes sense.
209439
+ *
209440
+ * The query is scoped by provider so two different providers can each
209441
+ * have a "Standard" voice without colliding.
209442
+ */
209443
+ function useResolveVoiceByName(args) {
209444
+ var _a, _b;
209445
+ const { org, userId, provider, name } = args;
209446
+ const enabled = !!org && !!userId && !!name;
209447
+ const { data, isFetching } = useGetVoicesQuery({
209448
+ org,
209449
+ userId,
209450
+ provider,
209451
+ search: name !== null && name !== void 0 ? name : '',
209452
+ pageSize: 25, // search may return close matches; keep the window small
209453
+ }, { skip: !enabled });
209454
+ if (!enabled)
209455
+ return { voice: null, isResolving: false };
209456
+ const results = ((_a = data === null || data === void 0 ? void 0 : data.results) !== null && _a !== void 0 ? _a : []);
209457
+ const lowered = (name !== null && name !== void 0 ? name : '').trim().toLowerCase();
209458
+ const match = (_b = results.find((v) => { var _a; return ((_a = v.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === lowered && v.provider === provider; })) !== null && _b !== void 0 ? _b : null;
209459
+ return { voice: match, isResolving: isFetching };
209460
+ }
209461
+
209462
+ /**
209463
+ * Map an LLM provider to the voice provider used by the picker. Azure
209464
+ * OpenAI shares the same voice catalogue as OpenAI, so both surfaces
209465
+ * point at the `openai` voice list.
209466
+ */
209467
+ function llmProviderToVoiceProvider(llm) {
209468
+ if (llm === 'openai' || llm === 'azure_openai')
209469
+ return 'openai';
209470
+ if (llm === 'google')
209471
+ return 'google';
209472
+ return null;
209473
+ }
209474
+ /**
209475
+ * Derive a sensible TTS / STT provider from the LLM choice. The UI no
209476
+ * longer exposes these fields directly — keeping them aligned with the
209477
+ * LLM provider keeps the backend stack consistent and matches the
209478
+ * product expectation that "Microsoft" / OpenAI / Google all reach for
209479
+ * their native voice + speech APIs by default.
209480
+ *
209481
+ * If the user previously customised these manually they're kept as-is
209482
+ * (see `withDerivedProviders`); we only fill the field when it's empty.
209483
+ */
209484
+ function defaultTtsProviderFor(llm) {
209485
+ if (llm === 'azure_openai')
209486
+ return 'azure_openai';
209487
+ if (llm === 'google')
209488
+ return 'google';
209489
+ if (llm === 'openai')
209490
+ return 'openai';
209491
+ return '';
209492
+ }
209493
+ function defaultSttProviderFor(llm) {
209494
+ if (llm === 'azure_openai')
209495
+ return 'azure_openai';
209496
+ if (llm === 'google')
209497
+ return 'google';
209498
+ if (llm === 'openai')
209499
+ return 'openai';
209500
+ return '';
209501
+ }
209502
+ /**
209503
+ * Fill in any missing TTS / STT provider from the active LLM. Used on
209504
+ * save so the API always gets a complete triple even though the UI no
209505
+ * longer surfaces TTS/STT pickers.
209506
+ */
209507
+ function withDerivedProviders(form) {
209508
+ return {
209509
+ ...form,
209510
+ tts_provider: form.tts_provider || defaultTtsProviderFor(form.llm_provider),
209511
+ stt_provider: form.stt_provider || defaultSttProviderFor(form.llm_provider),
209512
+ };
209513
+ }
209514
+ /**
209515
+ * Friendly display name for an LLM provider. Used by both the field
209516
+ * label "Voice (Microsoft / OpenAI / Google)" and any tooltips that
209517
+ * mention the picked provider.
209518
+ */
209519
+ function llmProviderDisplayName(llm) {
209520
+ if (llm === 'azure_openai')
209521
+ return 'Microsoft';
209522
+ if (llm === 'google')
209523
+ return 'Google';
209524
+ if (llm === 'openai')
209525
+ return 'OpenAI';
209526
+ return '';
209527
+ }
209528
+ /**
209529
+ * Top languages we expose in the picker. The backend accepts any ISO 639
209530
+ * code as free text, but the dropdown keeps the common cases one click
209531
+ * away. Add to this list when product asks.
209532
+ */
209533
+ const LANGUAGE_OPTIONS = [
209534
+ { value: 'en', label: 'English' },
209535
+ { value: 'fr', label: 'French' },
209536
+ { value: 'es', label: 'Spanish' },
209537
+ { value: 'pt', label: 'Portuguese' },
209538
+ { value: 'nl', label: 'Dutch' },
209539
+ ];
209540
+ const EMPTY_FORM = {
209541
+ mode: 'realtime',
209542
+ tts_provider: '',
209543
+ stt_provider: '',
209544
+ llm_provider: '',
209545
+ language: 'en',
209546
+ use_function_calling_for_rag: false,
209547
+ enable_video: false,
209548
+ screensharing_system_prompt: '',
209549
+ screensharing_proactive_prompt: '',
209550
+ openai_voice_id: null,
209551
+ google_voice_id: null,
209552
+ };
209553
+ /**
209554
+ * The voice fields on `CallConfiguration` can arrive in two shapes:
209555
+ * - `openai_voice_id: number | null` (the canonical id)
209556
+ * - `openai_voice: Voice | number | null` (a fully serialized voice)
209557
+ *
209558
+ * `readCallConfigVoiceId` collapses both into a plain id.
209559
+ */
209560
+ function readCallConfigVoiceId(config, field) {
209561
+ if (!config)
209562
+ return null;
209563
+ const idField = field === 'openai' ? 'openai_voice_id' : 'google_voice_id';
209564
+ const objField = field === 'openai' ? 'openai_voice' : 'google_voice';
209565
+ const id = config[idField];
209566
+ if (typeof id === 'number')
209567
+ return id;
209568
+ const obj = config[objField];
209569
+ if (obj == null)
209570
+ return null;
209571
+ if (typeof obj === 'number')
209572
+ return obj;
209573
+ if (typeof obj === 'object' && 'id' in obj)
209574
+ return obj.id;
209575
+ return null;
209576
+ }
209577
+ function toFormState(config) {
209578
+ var _a, _b, _c, _d, _e, _f, _g;
209579
+ if (!config)
209580
+ return EMPTY_FORM;
209581
+ return {
209582
+ mode: (_a = config.mode) !== null && _a !== void 0 ? _a : 'realtime',
209583
+ tts_provider: (_b = config.tts_provider) !== null && _b !== void 0 ? _b : '',
209584
+ stt_provider: (_c = config.stt_provider) !== null && _c !== void 0 ? _c : '',
209585
+ llm_provider: (_d = config.llm_provider) !== null && _d !== void 0 ? _d : '',
209586
+ language: (_e = config.language) !== null && _e !== void 0 ? _e : 'en',
209587
+ use_function_calling_for_rag: !!config.use_function_calling_for_rag,
209588
+ enable_video: !!config.enable_video,
209589
+ screensharing_system_prompt: (_f = config.screensharing_system_prompt) !== null && _f !== void 0 ? _f : '',
209590
+ screensharing_proactive_prompt: (_g = config.screensharing_proactive_prompt) !== null && _g !== void 0 ? _g : '',
209591
+ openai_voice_id: readCallConfigVoiceId(config, 'openai'),
209592
+ google_voice_id: readCallConfigVoiceId(config, 'google'),
209593
+ };
209594
+ }
209595
+ function toRequestBody(form) {
209596
+ const derived = withDerivedProviders(form);
209597
+ return {
209598
+ mode: derived.mode,
209599
+ ...(derived.tts_provider ? { tts_provider: derived.tts_provider } : {}),
209600
+ ...(derived.stt_provider ? { stt_provider: derived.stt_provider } : {}),
209601
+ ...(derived.llm_provider ? { llm_provider: derived.llm_provider } : {}),
209602
+ language: derived.language || 'en',
209603
+ use_function_calling_for_rag: derived.use_function_calling_for_rag,
209604
+ enable_video: derived.enable_video,
209605
+ screensharing_system_prompt: derived.screensharing_system_prompt,
209606
+ screensharing_proactive_prompt: derived.screensharing_proactive_prompt,
209607
+ openai_voice_id: derived.openai_voice_id,
209608
+ google_voice_id: derived.google_voice_id,
209609
+ };
209610
+ }
209611
+ function CallConfigSection({ org, userId, mentorId, isDisabled, labels, voicePickerLabels, toastLabels, initialConfig, renderPromptContent: _renderPromptContent, // currently unused — kept for ABI stability
209612
+ }) {
209613
+ const hasInitial = initialConfig !== undefined;
209614
+ // Fall back to the list endpoint only when no inline config was supplied.
209615
+ // When `initialConfig` is provided (even as `null`), skip the network call
209616
+ // entirely so the section renders synchronously.
209617
+ const { data: configs, isLoading, isFetching, } = useGetCallConfigurationsQuery({ org, userId, mentor: mentorId }, { skip: hasInitial || !org || !userId || !mentorId });
209618
+ const existing = hasInitial
209619
+ ? initialConfig
209620
+ : configs === null || configs === void 0 ? void 0 : configs[0];
209621
+ const [form, setForm] = React__default.useState(() => toFormState(existing));
209622
+ const [initialForm, setInitialForm] = React__default.useState(() => toFormState(existing));
209623
+ // Track the full Voice object the user picked this session so we can
209624
+ // show its name + audio_url in the trigger immediately, before the
209625
+ // call-config refetches. When `null`, the trigger falls back to the
209626
+ // serialized voice object embedded in the call_configuration response
209627
+ // (`existing.openai_voice` / `existing.google_voice`).
209628
+ const [pickedOpenaiVoice, setPickedOpenaiVoice] = React__default.useState(null);
209629
+ const [pickedGoogleVoice, setPickedGoogleVoice] = React__default.useState(null);
209630
+ React__default.useEffect(() => {
209631
+ const next = toFormState(existing);
209632
+ setForm(next);
209633
+ setInitialForm(next);
209634
+ setPickedOpenaiVoice(null);
209635
+ setPickedGoogleVoice(null);
209636
+ }, [existing === null || existing === void 0 ? void 0 : existing.id, existing]);
209637
+ const [createCallConfig, { isLoading: isCreating }] = useCreateCallConfigurationMutation();
209638
+ const [updateCallConfig, { isLoading: isUpdating }] = useUpdateCallConfigurationMutation();
209639
+ const isSaving = isCreating || isUpdating;
209640
+ const disabled = Boolean(isDisabled) || isLoading || isSaving;
209641
+ const isDirty = JSON.stringify(form) !== JSON.stringify(initialForm);
209642
+ const updateField = (key, value) => {
209643
+ setForm((prev) => ({ ...prev, [key]: value }));
209644
+ };
209645
+ const handleSave = async () => {
209646
+ try {
209647
+ const body = toRequestBody(form);
209648
+ if (existing === null || existing === void 0 ? void 0 : existing.id) {
209649
+ await updateCallConfig({
209650
+ org,
209651
+ userId,
209652
+ id: existing.id,
209653
+ requestBody: body,
209654
+ }).unwrap();
209655
+ }
209656
+ else {
209657
+ await createCallConfig({
209658
+ org,
209659
+ userId,
209660
+ requestBody: { ...body, mentor: mentorId },
209661
+ }).unwrap();
209662
+ }
209663
+ toast.success(toastLabels.saved);
209664
+ setInitialForm(form);
209665
+ }
209666
+ catch (error) {
209667
+ console.error('[CallConfigSection] save error', error);
209668
+ toast.error(toastLabels.error);
209669
+ }
209670
+ };
209671
+ const handleReset = () => setForm(initialForm);
209672
+ if (isLoading) {
209673
+ return (jsx("div", { className: "flex items-center justify-center py-8", "data-testid": "call-config-loading", children: jsx(Spinner, {}) }));
209674
+ }
209675
+ return (jsxs("div", { className: "space-y-5", "data-testid": "call-config-form", children: [jsx("div", { className: "rounded-md border border-blue-100 bg-blue-50/50 p-3 text-xs text-gray-700", children: labels.info }), jsxs("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2", children: [jsx(FieldWrap, { label: labels.fields.mode.label, tooltip: labels.fields.mode.tooltip, children: jsxs(Select$1, { value: form.mode, onValueChange: (v) => updateField('mode', v), disabled: disabled, children: [jsx(SelectTrigger, { "aria-label": labels.fields.mode.label, children: jsx(SelectValue, {}) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: "realtime", children: labels.fields.mode.options.realtime }), jsx(SelectItem, { value: "inference", children: labels.fields.mode.options.inference })] })] }) }), jsx(FieldWrap, { label: labels.fields.language.label, tooltip: labels.fields.language.tooltip, children: jsxs(Select$1, { value: form.language, onValueChange: (v) => updateField('language', v), disabled: disabled, children: [jsx(SelectTrigger, { "aria-label": labels.fields.language.label, children: jsx(SelectValue, { placeholder: labels.fields.language.placeholder }) }), jsx(SelectContent, { children: LANGUAGE_OPTIONS.map((opt) => (jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value))) })] }) }), jsx(FieldWrap, { label: labels.fields.llmProvider.label, tooltip: labels.fields.llmProvider.tooltip, children: jsxs(Select$1, { value: form.llm_provider, onValueChange: (v) => updateField('llm_provider', v), disabled: disabled, children: [jsx(SelectTrigger, { "aria-label": labels.fields.llmProvider.label, children: jsx(SelectValue, { placeholder: labels.fields.llmProvider.placeholder }) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: "openai", children: "OpenAI" }), jsx(SelectItem, { value: "google", children: "Google" }), jsx(SelectItem, { value: "azure_openai", children: "Microsoft" })] })] }) })] }), (() => {
209676
+ const voiceProvider = llmProviderToVoiceProvider(form.llm_provider);
209677
+ if (!voiceProvider)
209678
+ return null;
209679
+ const isOpenAiVoice = voiceProvider === 'openai';
209680
+ const inlineVoice = (isOpenAiVoice
209681
+ ? existing === null || existing === void 0 ? void 0 : existing.openai_voice
209682
+ : existing === null || existing === void 0 ? void 0 : existing.google_voice);
209683
+ // The API can return either the full Voice object or just the id
209684
+ // depending on serializer version. We only pin it as the display
209685
+ // value when it has a `name` (i.e. the full-object shape).
209686
+ const inlineVoiceObject = inlineVoice != null && typeof inlineVoice === 'object' && 'name' in inlineVoice
209687
+ ? inlineVoice
209688
+ : null;
209689
+ const selectedVoice = isOpenAiVoice
209690
+ ? pickedOpenaiVoice !== null && pickedOpenaiVoice !== void 0 ? pickedOpenaiVoice : inlineVoiceObject
209691
+ : pickedGoogleVoice !== null && pickedGoogleVoice !== void 0 ? pickedGoogleVoice : inlineVoiceObject;
209692
+ const providerDisplayName = llmProviderDisplayName(form.llm_provider);
209693
+ return (jsxs("div", { className: "space-y-2 rounded-lg border border-gray-200 p-4", children: [jsx(Label, { className: "text-sm font-medium text-[#646464]", children: "Voice" }), jsxs("p", { className: "text-xs text-gray-500", children: ["The voice your agent uses on calls \u2014 from ", providerDisplayName, "."] }), jsx(VoicePickerTrigger, { org: org, userId: userId, provider: voiceProvider, selectedVoice: selectedVoice, onSelect: (v) => {
209694
+ if (isOpenAiVoice) {
209695
+ setPickedOpenaiVoice(v);
209696
+ updateField('openai_voice_id', v.id);
209697
+ }
209698
+ else {
209699
+ setPickedGoogleVoice(v);
209700
+ updateField('google_voice_id', v.id);
209701
+ }
209702
+ }, disabled: disabled, labels: voicePickerLabels, modalTitle: `Select a ${providerDisplayName} voice`, modalDescription: "Search, browse, and listen to a sample before you pick.", testId: "call-config-voice-trigger" })] }));
209703
+ })(), jsxs("div", { className: "flex items-center justify-end gap-2 border-t border-gray-100 pt-3", children: [isFetching && !isLoading && (jsx(LoaderCircle, { className: "h-4 w-4 animate-spin text-gray-400", "aria-label": "Refreshing" })), jsx(Button$1, { type: "button", variant: "outline", onClick: handleReset, disabled: disabled || !isDirty, children: labels.resetButton }), jsx(Button$1, { type: "button", onClick: handleSave, disabled: disabled || !isDirty, className: "bg-gradient-to-r from-[#2563EB] to-[#93C5FD] text-white hover:opacity-90", children: isSaving
209704
+ ? labels.savingButton
209705
+ : existing
209706
+ ? labels.saveUpdate
209707
+ : labels.saveCreate })] })] }));
209708
+ }
209709
+ function FieldWrap({ label, tooltip, children }) {
209710
+ return (jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-sm font-medium text-[#646464]", children: label }), tooltip && (jsx(TooltipProvider, { children: jsxs(Tooltip, { children: [jsx(TooltipTrigger, { type: "button", "aria-label": `More info about ${label}`, children: jsx(Info$3, { className: "h-4 w-4 text-gray-400" }) }), jsx(TooltipContent, { side: "top", align: "start", sideOffset: 6, collisionPadding: 16, className: "ibl-tooltip-content z-50 max-w-[300px]", children: jsx("p", { className: "text-xs leading-relaxed whitespace-normal", children: tooltip }) })] }) }))] }), children] }));
209711
+ }
209712
+
209713
+ const SUB_TAB_VALUES = {
209714
+ voice: 'voice',
209715
+ callConfig: 'call-config',
209716
+ };
209717
+ /**
209718
+ * Pull a voice **name** out of the mentor settings response, which can
209719
+ * return a plain string, a numeric id, or a serialized object with a
209720
+ * `name` key depending on the backend version.
209721
+ */
209722
+ function readVoiceName(value) {
209723
+ if (value == null)
209724
+ return null;
209725
+ if (typeof value === 'string')
209726
+ return value;
209727
+ if (typeof value === 'object' && 'name' in value && typeof value.name === 'string') {
209728
+ return value.name;
209729
+ }
209730
+ return null;
209731
+ }
209732
+ function AgentVoiceTab({ labels: labelsOverride, defaultSubTab = 'voice', renderPromptContent, tenantKey: tenantKeyProp, mentorId: mentorIdProp, username: usernameProp, enableRBAC: enableRBACProp, }) {
209733
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
209734
+ const ctx = useAgentSettingsOptional();
209735
+ const tenantKey = (_a = tenantKeyProp !== null && tenantKeyProp !== void 0 ? tenantKeyProp : ctx === null || ctx === void 0 ? void 0 : ctx.tenantKey) !== null && _a !== void 0 ? _a : '';
209736
+ const mentorId = (_b = mentorIdProp !== null && mentorIdProp !== void 0 ? mentorIdProp : ctx === null || ctx === void 0 ? void 0 : ctx.mentorId) !== null && _b !== void 0 ? _b : '';
209737
+ const username = (_c = usernameProp !== null && usernameProp !== void 0 ? usernameProp : ctx === null || ctx === void 0 ? void 0 : ctx.username) !== null && _c !== void 0 ? _c : '';
209738
+ const enableRBAC = (_d = enableRBACProp !== null && enableRBACProp !== void 0 ? enableRBACProp : ctx === null || ctx === void 0 ? void 0 : ctx.enableRBAC) !== null && _d !== void 0 ? _d : false;
209739
+ const labels = React__default.useMemo(() => resolveVoiceTabLabels(labelsOverride), [labelsOverride]);
209740
+ const { data: rawMentorSettings, isLoading: isMentorSettingsLoading } = useGetMentorSettingsQuery({
209741
+ mentor: mentorId,
209742
+ org: tenantKey,
209743
+ // @ts-expect-error userId is accepted at runtime
209744
+ userId: username,
209745
+ }, { skip: !username || !mentorId || !tenantKey });
209746
+ const mentorSettings = rawMentorSettings;
209747
+ const initialProvider = (_e = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.voice_provider) !== null && _e !== void 0 ? _e : 'browser';
209748
+ // Backend returns voice references as *name strings* on the mentor
209749
+ // settings endpoint (e.g. `"alloy"`, `"Fenrir"`). Resolve each one to a
209750
+ // full Voice via the voices catalogue so we can render the trigger
209751
+ // (with audio_url for the play button) and send the id on save.
209752
+ const savedOpenaiName = (_f = readVoiceName(mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.openai_voice)) !== null && _f !== void 0 ? _f : null;
209753
+ const savedGoogleName = (_g = readVoiceName(mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.google_voice)) !== null && _g !== void 0 ? _g : null;
209754
+ const { voice: resolvedOpenaiVoice } = useResolveVoiceByName({
209755
+ org: tenantKey,
209756
+ userId: username,
209757
+ provider: 'openai',
209758
+ name: savedOpenaiName,
209759
+ });
209760
+ const { voice: resolvedGoogleVoice } = useResolveVoiceByName({
209761
+ org: tenantKey,
209762
+ userId: username,
209763
+ provider: 'google',
209764
+ name: savedGoogleName,
209765
+ });
209766
+ // Picked voices override the resolved server values once the user
209767
+ // makes a choice. Null means "use what came from the server".
209768
+ const [provider, setProvider] = React__default.useState(initialProvider);
209769
+ const [pickedOpenaiVoice, setPickedOpenaiVoice] = React__default.useState(null);
209770
+ const [pickedGoogleVoice, setPickedGoogleVoice] = React__default.useState(null);
209771
+ React__default.useEffect(() => {
209772
+ setProvider(initialProvider);
209773
+ setPickedOpenaiVoice(null);
209774
+ setPickedGoogleVoice(null);
209775
+ // eslint-disable-next-line react-hooks/exhaustive-deps
209776
+ }, [mentorId, rawMentorSettings]);
209777
+ const effectiveOpenaiVoice = (_h = pickedOpenaiVoice !== null && pickedOpenaiVoice !== void 0 ? pickedOpenaiVoice : resolvedOpenaiVoice) !== null && _h !== void 0 ? _h : null;
209778
+ const effectiveGoogleVoice = (_j = pickedGoogleVoice !== null && pickedGoogleVoice !== void 0 ? pickedGoogleVoice : resolvedGoogleVoice) !== null && _j !== void 0 ? _j : null;
209779
+ const [editMentor, { isLoading: isSavingMentor }] = useEditMentorMutation();
209780
+ const isDisabled = isMentorSettingsLoading || isSavingMentor;
209781
+ const isDirty = provider !== initialProvider ||
209782
+ pickedOpenaiVoice != null ||
209783
+ pickedGoogleVoice != null;
209784
+ /**
209785
+ * Provider chips. We reuse the OpenAI / Google logos already shipped in
209786
+ * the host's static-root (see `getLLMProviderDetails` for the canonical
209787
+ * paths). Browser falls back to a Lucide globe — there's no vendor logo
209788
+ * for "device speech engine".
209789
+ */
209790
+ const providerOptions = [
209791
+ {
209792
+ value: 'browser',
209793
+ label: labels.mentorVoice.providerOptions.browser.label,
209794
+ description: labels.mentorVoice.providerOptions.browser.description,
209795
+ logo: { type: 'icon', Icon: Globe },
209796
+ },
209797
+ {
209798
+ value: 'openai',
209799
+ label: labels.mentorVoice.providerOptions.openai.label,
209800
+ description: labels.mentorVoice.providerOptions.openai.description,
209801
+ logo: { type: 'image', src: '/llm-openai-provider-2.svg', alt: 'OpenAI' },
209802
+ },
209803
+ {
209804
+ value: 'google',
209805
+ label: labels.mentorVoice.providerOptions.google.label,
209806
+ description: labels.mentorVoice.providerOptions.google.description,
209807
+ logo: { type: 'image', src: '/llm-google-provider.svg', alt: 'Google' },
209808
+ },
209809
+ ];
209810
+ const handleSaveVoice = async () => {
209811
+ try {
209812
+ const formData = { voice_provider: provider };
209813
+ // Only send a voice id when the user actually picked something this
209814
+ // session. If they didn't, leave the existing server-side value
209815
+ // alone — the backend interprets an absent field as "no change".
209816
+ if (provider === 'openai' && pickedOpenaiVoice) {
209817
+ formData.openai_voice = pickedOpenaiVoice.id;
209818
+ }
209819
+ if (provider === 'google' && pickedGoogleVoice) {
209820
+ formData.google_voice = pickedGoogleVoice.id;
209821
+ }
209822
+ await editMentor({
209823
+ mentor: mentorId,
209824
+ org: tenantKey,
209825
+ // @ts-expect-error userId is accepted at runtime
209826
+ userId: username,
209827
+ formData,
209828
+ }).unwrap();
209829
+ toast.success(labels.toasts.voiceSaved);
209830
+ }
209831
+ catch (error) {
209832
+ console.error('[AgentVoiceTab] save error', error);
209833
+ toast.error(labels.toasts.voiceError);
209834
+ }
209835
+ };
209836
+ const selectedVoice = provider === 'openai'
209837
+ ? effectiveOpenaiVoice
209838
+ : provider === 'google'
209839
+ ? effectiveGoogleVoice
209840
+ : null;
209841
+ const onSelectVoice = (voice) => {
209842
+ if (provider === 'openai')
209843
+ setPickedOpenaiVoice(voice);
209844
+ else if (provider === 'google')
209845
+ setPickedGoogleVoice(voice);
209846
+ };
209847
+ return (jsxs(Fragment$1, { children: [jsx("div", { className: "flex h-[73px] flex-shrink-0 items-center border-b border-gray-200 bg-white p-4 lg:block", children: jsxs("div", { children: [jsx("h3", { className: "mb-1 text-base font-medium text-gray-900", children: labels.header.title }), jsx("p", { className: "text-xs text-gray-600", children: labels.header.description })] }) }), jsx("div", { className: "flex-1 space-y-4 p-3 lg:p-4", style: { overflowY: 'auto', overflowX: 'hidden' }, "data-testid": "voice-tab-body", children: isMentorSettingsLoading ? (jsx("div", { className: "flex items-center justify-center py-12", "data-testid": "voice-tab-loading", children: jsx(Spinner, {}) })) : (jsxs(Tabs, { defaultValue: SUB_TAB_VALUES[defaultSubTab], className: "w-full", children: [jsxs(TabsList, { "aria-label": labels.header.title, "data-testid": "voice-sub-tabs", children: [jsx(TabsTrigger, { value: SUB_TAB_VALUES.voice, "data-testid": "voice-sub-tab-voice", children: labels.subTabs.voice }), jsx(TabsTrigger, { value: SUB_TAB_VALUES.callConfig, "data-testid": "voice-sub-tab-call-config", children: labels.subTabs.callConfig })] }), jsxs(TabsContent, { value: SUB_TAB_VALUES.voice, className: "space-y-4", children: [jsx("p", { className: "text-xs text-gray-600", children: labels.mentorVoice.description }), jsx(WithFormPermissions, { name: "voice_provider",
209848
+ // @ts-expect-error permissions.field is opaque
209849
+ permissions: (_k = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _k === void 0 ? void 0 : _k.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-sm font-medium text-[#646464]", children: labels.mentorVoice.providerLabel }), jsx(FieldTooltip, { text: labels.mentorVoice.providerTooltip })] }), jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", role: "radiogroup", "aria-label": labels.mentorVoice.providerLabel, children: providerOptions.map((option) => {
209850
+ const isSelected = provider === option.value;
209851
+ const isDisabledOption = isDisabled || disabled;
209852
+ return (jsxs("button", { type: "button", role: "radio", "aria-checked": isSelected, "aria-label": option.label, disabled: isDisabledOption, "data-testid": `voice-provider-${option.value}`, "data-voice-provider": option.value, onClick: () => setProvider(option.value), className: cn('flex flex-col items-start gap-2 rounded-lg border-2 p-3 text-left transition-all hover:shadow-sm', isSelected
209853
+ ? 'border-blue-500 bg-blue-50/40'
209854
+ : 'border-gray-200 bg-white hover:border-gray-300', isDisabledOption && 'cursor-not-allowed opacity-60'), children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center", children: option.logo.type === 'image' ? (jsx(Image$4, { src: option.logo.src, alt: option.logo.alt, width: 24, height: 24, className: "h-6 w-6 object-contain" })) : (jsx(option.logo.Icon, { className: "h-5 w-5 text-blue-600", "aria-hidden": "true" })) }), jsx("span", { className: "text-sm font-medium text-gray-900", children: option.label })] }), jsx("p", { className: "text-xs text-gray-500", children: option.description })] }, option.value));
209855
+ }) })] })) }), (provider === 'openai' || provider === 'google') && (jsx(WithFormPermissions, { name: provider === 'openai' ? 'openai_voice' : 'google_voice',
209856
+ // @ts-expect-error permissions.field is opaque
209857
+ permissions: (_l = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.permissions) === null || _l === void 0 ? void 0 : _l.field, enableRBAC: enableRBAC, children: ({ disabled }) => (jsxs("div", { className: "space-y-2", children: [jsx(Label, { className: "text-sm font-medium text-[#646464]", children: labels.mentorVoice.voicePickerLabel[provider] }), jsx(VoicePickerTrigger, { org: tenantKey, userId: username, provider: provider, selectedVoice: selectedVoice, onSelect: onSelectVoice, disabled: isDisabled || disabled, labels: labels.mentorVoice, modalTitle: `Select ${labels.mentorVoice.voicePickerLabel[provider]}`, modalDescription: "Search, browse, and listen to a sample before you pick.", testId: "mentor-voice-trigger" })] })) })), jsx("div", { className: "flex items-center justify-end border-t border-gray-100 pt-3", children: jsx(Button$1, { type: "button", onClick: handleSaveVoice, disabled: isDisabled || !isDirty, "data-testid": "voice-save-button", className: "bg-gradient-to-r from-[#2563EB] to-[#93C5FD] text-white hover:opacity-90", children: isSavingMentor
209858
+ ? labels.mentorVoice.savingButton
209859
+ : labels.mentorVoice.saveButton }) })] }), jsxs(TabsContent, { value: SUB_TAB_VALUES.callConfig, className: "space-y-4", children: [jsx("p", { className: "text-xs text-gray-600", children: labels.callConfig.description }), mentorId && username && tenantKey && (jsx(CallConfigSection, { org: tenantKey, userId: username, mentorId: mentorId, isDisabled: isDisabled, labels: labels.callConfig, voicePickerLabels: labels.mentorVoice, initialConfig: (_m = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.call_configuration) !== null && _m !== void 0 ? _m : null, renderPromptContent: renderPromptContent, toastLabels: {
209860
+ saved: labels.toasts.callConfigSaved,
209861
+ error: labels.toasts.callConfigError,
209862
+ } }))] })] })) })] }));
209863
+ }
209864
+ function FieldTooltip({ text }) {
209865
+ return (jsx(TooltipProvider, { delayDuration: 200, children: jsxs(Tooltip, { children: [jsx(TooltipTrigger, { type: "button", "aria-label": "More info", children: jsx(Info$3, { className: "h-4 w-4 text-gray-400" }) }), jsx(TooltipContent, { side: "top", align: "start", sideOffset: 6, collisionPadding: 16, className: "ibl-tooltip-content z-50 max-w-[300px]", children: jsx("p", { className: "text-xs leading-relaxed whitespace-normal", children: text }) })] }) }));
209866
+ }
209867
+
209868
+ /**
209869
+ * Display-only card for a single prompt. Mirrors the visual pattern
209870
+ * used by `AgentPromptsTab` (System Prompt / Proactive Prompt cards):
209871
+ * a header, a 📝-prefixed body, and Edit + Copy action buttons at the
209872
+ * bottom. "Edit" is wired by the parent (typically opens the shared
209873
+ * `EditPromptModal`); the new value is written back via the parent's
209874
+ * `onEdit` handler.
209875
+ */
209876
+ function PromptCard$1({ label, placeholder, value, disabled, renderContent, onEdit, }) {
209877
+ const isEmpty = !value || value.trim() === '';
209878
+ return (jsxs("div", { className: "overflow-hidden rounded-lg bg-gray-50", "data-testid": "screensharing-prompt-card", children: [jsx("div", { className: "flex items-center justify-between border-b border-gray-200 p-[1.12rem]", children: jsx("div", { className: "flex items-center gap-2", children: jsx("h3", { className: "text-sm font-medium text-gray-900", children: label }) }) }), jsxs("div", { className: "flex items-start gap-2 p-4", children: [jsx("div", { className: "mt-1 flex-shrink-0", "aria-hidden": "true", children: "\uD83D\uDCDD" }), jsx("div", { className: "flex h-[180px] flex-1 flex-col", children: jsx("div", { className: "mb-4 flex-grow overflow-y-auto", tabIndex: 0, role: "region", "aria-label": `${label} content`, children: isEmpty ? (jsx("p", { className: "text-sm text-gray-400 italic", children: placeholder })) : (renderContent(value)) }) })] }), jsxs("div", { className: "flex w-full gap-2 px-4 pb-4", children: [jsxs(Button$1, { variant: "outline", size: "sm", className: "h-8 flex-1 py-5", disabled: disabled, onClick: onEdit, "aria-label": `Edit ${label}`, children: [jsx(SquarePen, { className: "mr-2 h-4 w-4" }), "Edit"] }), jsx(CopyButton, { text: value, disabled: disabled })] })] }));
209879
+ }
209880
+
209881
+ const AGENT_SCREENSHARE_TAB_LABELS = {
209882
+ header: {
209883
+ title: 'Screen Share',
209884
+ description: 'How your agent behaves when a user shares their screen during a voice call.',
209885
+ },
209886
+ disabledHint: 'Screen sharing is currently turned off for this agent. These prompts will be saved but only take effect once it’s turned on.',
209887
+ fields: {
209888
+ systemPrompt: {
209889
+ label: 'Instructions during screen sharing',
209890
+ placeholder: "Tell the agent how to behave while a user shares their screen. E.g. 'Walk through one step at a time and wait for the user to confirm before moving on.'",
209891
+ },
209892
+ proactivePrompt: {
209893
+ label: 'What the agent says when screen sharing starts',
209894
+ placeholder: "E.g. 'I can see your screen now — what would you like help with first?'",
209895
+ },
209896
+ },
209897
+ saveButton: 'Save',
209898
+ savingButton: 'Saving…',
209899
+ toasts: {
209900
+ saved: 'Screen sharing prompts saved',
209901
+ error: "Couldn't save screen sharing prompts",
209902
+ },
209903
+ };
209904
+ function resolveScreenShareTabLabels(override) {
209905
+ if (!override)
209906
+ return AGENT_SCREENSHARE_TAB_LABELS;
209907
+ return deepMerge(AGENT_SCREENSHARE_TAB_LABELS, override);
209908
+ }
209909
+
209910
+ /**
209911
+ * Standalone top-level tab for editing the two screen-sharing prompts
209912
+ * (`screensharing_system_prompt`, `screensharing_proactive_prompt`) that
209913
+ * live on the mentor's CallConfiguration.
209914
+ *
209915
+ * Visibility in the host's edit-mentor modal is the host's call — this
209916
+ * component only renders an in-place off-state hint when
209917
+ * `call_configuration.enable_video` is false, so users know prompts they
209918
+ * author here won't take effect until screen sharing is turned on
209919
+ * (toggle lives on the Settings tab).
209920
+ */
209921
+ function AgentScreenShareTab({ labels: labelsOverride, renderPromptContent, tenantKey: tenantKeyProp, mentorId: mentorIdProp, username: usernameProp, }) {
209922
+ var _a, _b, _c, _d, _e, _f, _g;
209923
+ const ctx = useAgentSettingsOptional();
209924
+ const tenantKey = (_a = tenantKeyProp !== null && tenantKeyProp !== void 0 ? tenantKeyProp : ctx === null || ctx === void 0 ? void 0 : ctx.tenantKey) !== null && _a !== void 0 ? _a : '';
209925
+ const mentorId = (_b = mentorIdProp !== null && mentorIdProp !== void 0 ? mentorIdProp : ctx === null || ctx === void 0 ? void 0 : ctx.mentorId) !== null && _b !== void 0 ? _b : '';
209926
+ const username = (_c = usernameProp !== null && usernameProp !== void 0 ? usernameProp : ctx === null || ctx === void 0 ? void 0 : ctx.username) !== null && _c !== void 0 ? _c : '';
209927
+ const labels = React__default.useMemo(() => resolveScreenShareTabLabels(labelsOverride), [labelsOverride]);
209928
+ const { data: rawMentorSettings, isLoading: isMentorSettingsLoading } = useGetMentorSettingsQuery({
209929
+ mentor: mentorId,
209930
+ org: tenantKey,
209931
+ // @ts-expect-error userId is accepted at runtime
209932
+ userId: username,
209933
+ }, { skip: !username || !mentorId || !tenantKey });
209934
+ const mentorSettings = rawMentorSettings;
209935
+ const inlineConfig = (_d = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.call_configuration) !== null && _d !== void 0 ? _d : null;
209936
+ const hasInline = inlineConfig !== null;
209937
+ // Fall back to the list endpoint only when we don't have an inline
209938
+ // call-config from the mentor-settings response.
209939
+ const { data: configs, isLoading: isLoadingList } = useGetCallConfigurationsQuery({ org: tenantKey, userId: username, mentor: mentorId }, { skip: hasInline || !tenantKey || !username || !mentorId });
209940
+ const existing = hasInline
209941
+ ? inlineConfig
209942
+ : configs === null || configs === void 0 ? void 0 : configs[0];
209943
+ const initialSystemPrompt = (_e = existing === null || existing === void 0 ? void 0 : existing.screensharing_system_prompt) !== null && _e !== void 0 ? _e : '';
209944
+ const initialProactivePrompt = (_f = existing === null || existing === void 0 ? void 0 : existing.screensharing_proactive_prompt) !== null && _f !== void 0 ? _f : '';
209945
+ const enableVideo = (existing === null || existing === void 0 ? void 0 : existing.enable_video) === true;
209946
+ const [systemPrompt, setSystemPrompt] = React__default.useState(initialSystemPrompt);
209947
+ const [proactivePrompt, setProactivePrompt] = React__default.useState(initialProactivePrompt);
209948
+ const [selectedPrompt, setSelectedPrompt] = React__default.useState(null);
209949
+ // Rehydrate from server values whenever the underlying config changes.
209950
+ React__default.useEffect(() => {
209951
+ setSystemPrompt(initialSystemPrompt);
209952
+ setProactivePrompt(initialProactivePrompt);
209953
+ // eslint-disable-next-line react-hooks/exhaustive-deps
209954
+ }, [existing === null || existing === void 0 ? void 0 : existing.id, initialSystemPrompt, initialProactivePrompt]);
209955
+ const [createCallConfig, { isLoading: isCreating }] = useCreateCallConfigurationMutation();
209956
+ const [updateCallConfig, { isLoading: isUpdating }] = useUpdateCallConfigurationMutation();
209957
+ const isSaving = isCreating || isUpdating;
209958
+ const isLoading = isMentorSettingsLoading || isLoadingList;
209959
+ const isDirty = systemPrompt !== initialSystemPrompt || proactivePrompt !== initialProactivePrompt;
209960
+ const handleSave = async () => {
209961
+ try {
209962
+ const body = {
209963
+ screensharing_system_prompt: systemPrompt,
209964
+ screensharing_proactive_prompt: proactivePrompt,
209965
+ };
209966
+ if (existing === null || existing === void 0 ? void 0 : existing.id) {
209967
+ await updateCallConfig({
209968
+ org: tenantKey,
209969
+ userId: username,
209970
+ id: existing.id,
209971
+ requestBody: body,
209972
+ }).unwrap();
209973
+ }
209974
+ else {
209975
+ // No CallConfiguration row yet — POST one with these prompts.
209976
+ await createCallConfig({
209977
+ org: tenantKey,
209978
+ userId: username,
209979
+ requestBody: { ...body, mentor: mentorId },
209980
+ }).unwrap();
209981
+ }
209982
+ toast.success(labels.toasts.saved);
209983
+ }
209984
+ catch (error) {
209985
+ console.error('[AgentScreenShareTab] save error', error);
209986
+ toast.error(labels.toasts.error);
209987
+ }
209988
+ };
209989
+ const openEditor = (field) => {
209990
+ setSelectedPrompt({
209991
+ label: field === 'screensharing_system_prompt'
209992
+ ? labels.fields.systemPrompt.label
209993
+ : labels.fields.proactivePrompt.label,
209994
+ isSystem: true,
209995
+ name: field,
209996
+ prompt: field === 'screensharing_system_prompt' ? systemPrompt : proactivePrompt,
209997
+ });
209998
+ };
209999
+ const renderContent = (text) => {
210000
+ if (renderPromptContent)
210001
+ return renderPromptContent(text);
210002
+ return jsx("p", { className: "text-sm whitespace-pre-wrap text-gray-700", children: text });
210003
+ };
210004
+ return (jsxs(Fragment$1, { children: [jsx("div", { className: "flex h-[73px] flex-shrink-0 items-center border-b border-gray-200 bg-white p-4 lg:block", children: jsxs("div", { children: [jsx("h3", { className: "mb-1 text-base font-medium text-gray-900", children: labels.header.title }), jsx("p", { className: "text-xs text-gray-600", children: labels.header.description })] }) }), jsx("div", { className: "flex-1 space-y-4 p-3 lg:p-4", style: { overflowY: 'auto', overflowX: 'hidden' }, "data-testid": "screenshare-tab-body", children: isLoading ? (jsx("div", { className: "flex items-center justify-center py-12", "data-testid": "screenshare-tab-loading", children: jsx(Spinner, {}) })) : (jsxs(Fragment$1, { children: [!enableVideo && (jsx("div", { className: "rounded-md border border-amber-200 bg-amber-50 p-3 text-xs text-amber-800", "data-testid": "screenshare-disabled-hint", role: "status", children: labels.disabledHint })), jsxs("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2", children: [jsx(PromptCard$1, { label: labels.fields.systemPrompt.label, placeholder: labels.fields.systemPrompt.placeholder, value: systemPrompt, disabled: isSaving, renderContent: renderContent, onEdit: () => openEditor('screensharing_system_prompt') }), jsx(PromptCard$1, { label: labels.fields.proactivePrompt.label, placeholder: labels.fields.proactivePrompt.placeholder, value: proactivePrompt, disabled: isSaving, renderContent: renderContent, onEdit: () => openEditor('screensharing_proactive_prompt') })] }), jsx("div", { className: "flex items-center justify-end border-t border-gray-100 pt-3", children: jsx("button", { type: "button", onClick: handleSave, disabled: isSaving || !isDirty, "data-testid": "screenshare-save-button", className: "inline-flex items-center gap-2 rounded-md bg-gradient-to-r from-[#2563EB] to-[#93C5FD] px-4 py-2 text-sm font-medium text-white hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50", children: isSaving ? labels.savingButton : labels.saveButton }) }), selectedPrompt && (
210005
+ // EditPromptModal calls strict `useAgentSettings()` to read
210006
+ // `tenantKey` / `username` / `executeGatedAction`. The host
210007
+ // app may not have wrapped this tab in `<AgentSettingsProvider>`
210008
+ // (older integrations pass identity via props), so we wrap
210009
+ // the modal here with whatever we resolved — props,
210010
+ // surrounding context, or empty fallbacks. When the host
210011
+ // *did* wrap, this re-provider just re-emits the same
210012
+ // values (idempotent).
210013
+ jsx(AgentSettingsProvider, { tenantKey: tenantKey, mentorId: mentorId, username: username, enableRBAC: (_g = ctx === null || ctx === void 0 ? void 0 : ctx.enableRBAC) !== null && _g !== void 0 ? _g : false, rbacPermissions: ctx === null || ctx === void 0 ? void 0 : ctx.rbacPermissions, executeGatedAction: ctx === null || ctx === void 0 ? void 0 : ctx.executeGatedAction, visibilityOptions: ctx === null || ctx === void 0 ? void 0 : ctx.visibilityOptions, children: jsx(EditPromptModal$1, { isOpen: true, onClose: () => setSelectedPrompt(null), selectedPrompt: selectedPrompt, isEditing: isSaving, handleSave: (prompt, values) => {
210014
+ if (prompt.name === 'screensharing_system_prompt') {
210015
+ setSystemPrompt(values.prompt);
210016
+ }
210017
+ else if (prompt.name === 'screensharing_proactive_prompt') {
210018
+ setProactivePrompt(values.prompt);
210019
+ }
210020
+ setSelectedPrompt(null);
210021
+ } }) }))] })) })] }));
210022
+ }
210023
+
209137
210024
  function ApiKeyModal({ isOpen, onClose, apiKey, labels }) {
209138
210025
  const { copy, status } = useCopyToClipboard(800);
209139
210026
  const Icon = status === 'success' ? Check : Copy;
@@ -268725,5 +269612,5 @@ var index_es = /*#__PURE__*/Object.freeze({
268725
269612
  vectorsRatio: vectorsRatio
268726
269613
  });
268727
269614
 
268728
- export { AGENT_ACCESS_TAB_LABELS, AGENT_API_TAB_LABELS, AGENT_DATASETS_TAB_LABELS, AGENT_DISCLAIMERS_TAB_LABELS, AGENT_EMBED_TAB_LABELS, AGENT_HISTORY_TAB_LABELS, AGENT_LLM_TAB_LABELS, AGENT_MEMORY_TAB_LABELS, AGENT_PICKER_LABELS, AGENT_PRIVACY_TAB_LABELS, AGENT_PROMPTS_TAB_LABELS, AGENT_SAFETY_TAB_LABELS, AGENT_SEARCH_LABELS, AGENT_SETTINGS_TAB_LABELS, AGENT_TASKS_TAB_LABELS, AGENT_TOOLS_TAB_LABELS, Account, AgentAccessTab, AgentApiTab, AgentCard, AgentDatasetsTab, AgentDisclaimersTab, AgentEmbedTab, AgentEmptyState, AgentHistoryTab, AgentLLMTab, AgentMemoryTab, AgentPrivacyTab, AgentPromptsTab, AgentSafetyTab, AgentSearch, AgentSearchFilters, AgentSearchInput, AgentSettingsProvider, AgentSettingsTab, AgentTasksTab, AgentToolsTab, AppSidebar, Chat, ClientErrorPage, ConversationStarters$1 as ConversationStarters, CopyButton, CopyMentorModal, CourseBox, CourseContentLayout, CourseContentTabPage, CreatePathwayModal, CreateProjectModal, CredentialDetailModal, CredentialMiniBox, DeleteMentorModal, DeleteProjectModal, EMPTY_PROGRAM_SETTINGS_FORM, EdxIframe, ErrorPage, GreetingMethod, McpTab, MediaBox, MentorSelectionGrid, NavBar, OrganizationTab, PathwayDetailModal, ProfileInfoCards, ProfileTabs, ProgramDetailModal, ProjectsSidebarDropdown, RenameProjectModal, SidebarInset, SidebarProvider, SidebarTrigger, SsoLogin, StarButton, UserProfileDropdown, UserProfileModal, formatDateString, getLLMProviderDetails, initializeLocalStorageWithObject, resolveAgentPickerLabels, resolveAgentSearchLabels, resolveSettingsTabLabels, useAgentSearch, useAgentSearchWithPagination, useAgentSettings, useAgentStar, useGetChatDetails, useSidebar, useTimeTracker };
269615
+ export { AGENT_ACCESS_TAB_LABELS, AGENT_API_TAB_LABELS, AGENT_DATASETS_TAB_LABELS, AGENT_DISCLAIMERS_TAB_LABELS, AGENT_EMBED_TAB_LABELS, AGENT_HISTORY_TAB_LABELS, AGENT_LLM_TAB_LABELS, AGENT_MEMORY_TAB_LABELS, AGENT_PICKER_LABELS, AGENT_PRIVACY_TAB_LABELS, AGENT_PROMPTS_TAB_LABELS, AGENT_SAFETY_TAB_LABELS, AGENT_SCREENSHARE_TAB_LABELS, AGENT_SEARCH_LABELS, AGENT_SETTINGS_TAB_LABELS, AGENT_TASKS_TAB_LABELS, AGENT_TOOLS_TAB_LABELS, AGENT_VOICE_TAB_LABELS, Account, AgentAccessTab, AgentApiTab, AgentCard, AgentDatasetsTab, AgentDisclaimersTab, AgentEmbedTab, AgentEmptyState, AgentHistoryTab, AgentLLMTab, AgentMemoryTab, AgentPrivacyTab, AgentPromptsTab, AgentSafetyTab, AgentScreenShareTab, AgentSearch, AgentSearchFilters, AgentSearchInput, AgentSettingsProvider, AgentSettingsTab, AgentTasksTab, AgentToolsTab, AgentVoiceTab, AppSidebar, Chat, ClientErrorPage, ConversationStarters$1 as ConversationStarters, CopyButton, CopyMentorModal, CourseBox, CourseContentLayout, CourseContentTabPage, CreatePathwayModal, CreateProjectModal, CredentialDetailModal, CredentialMiniBox, DeleteMentorModal, DeleteProjectModal, EMPTY_PROGRAM_SETTINGS_FORM, EdxIframe, ErrorPage, GreetingMethod, McpTab, MediaBox, MentorSelectionGrid, NavBar, OrganizationTab, PathwayDetailModal, ProfileInfoCards, ProfileTabs, ProgramDetailModal, ProjectsSidebarDropdown, RenameProjectModal, SidebarInset, SidebarProvider, SidebarTrigger, SsoLogin, StarButton, UserProfileDropdown, UserProfileModal, formatDateString, getLLMProviderDetails, initializeLocalStorageWithObject, resolveAgentPickerLabels, resolveAgentSearchLabels, resolveSettingsTabLabels, useAgentSearch, useAgentSearchWithPagination, useAgentSettings, useAgentStar, useGetChatDetails, useSidebar, useTimeTracker };
268729
269616
  //# sourceMappingURL=index.esm.js.map