diff --git a/services/web/app/src/Features/Email/EmailBuilder.mjs b/services/web/app/src/Features/Email/EmailBuilder.mjs index d3d35bb74a..0ce977fcac 100644 --- a/services/web/app/src/Features/Email/EmailBuilder.mjs +++ b/services/web/app/src/Features/Email/EmailBuilder.mjs @@ -1122,6 +1122,42 @@ templates.groupMemberLimitWarning = ctaTemplate({ }, }) +templates.groupAiFeaturesDisabled = NoCTAEmailTemplate({ + subject() { + return `AI features have been disabled for your ${settings.appName} group` + }, + title() { + return `AI features disabled` + }, + greeting(opts) { + return opts.firstName ? `Hi ${opts.firstName},` : 'Hi there,' + }, + message() { + return [ + `Your organization has disabled AI features for your ${settings.appName} group. This means you won't see AI Assistant or other ${settings.appName} AI tools in the editor.`, + `If you have questions about this change, please contact your group administrator.`, + ] + }, +}) + +templates.groupAiFeaturesEnabled = NoCTAEmailTemplate({ + subject() { + return `AI features have been enabled for your ${settings.appName} group` + }, + title() { + return `AI features enabled` + }, + greeting(opts) { + return opts.firstName ? `Hi ${opts.firstName},` : 'Hi there,' + }, + message() { + return [ + `Your organization has enabled AI features for your ${settings.appName} group. You now have access to AI Assistant and other ${settings.appName} AI tools in the editor.`, + `If you have questions about this change, please contact your group administrator.`, + ] + }, +}) + templates.groupDomainCapturedByGroupChanged = ctaTemplate({ subject(opts) { return opts.domainCapturedByGroup diff --git a/services/web/app/src/Features/Subscription/SubscriptionLocator.mjs b/services/web/app/src/Features/Subscription/SubscriptionLocator.mjs index 11904b1843..b6d4e9c128 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionLocator.mjs +++ b/services/web/app/src/Features/Subscription/SubscriptionLocator.mjs @@ -34,13 +34,15 @@ const SubscriptionLocator = { return subscription }, - async getManagedGroupSubscriptions(userOrId) { + async getManagedGroupSubscriptions(userOrId, populate = []) { if (!Features.hasFeature('saas')) return [] + // eslint-disable-next-line no-restricted-syntax return await Subscription.find({ manager_ids: userOrId, groupPlan: true, }) .populate('admin_id', ['_id', 'email']) + .populate(populate) .exec() }, diff --git a/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.mjs b/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.mjs index 6cc04fa302..d3fbd4d267 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.mjs +++ b/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.mjs @@ -82,7 +82,11 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') { SubscriptionLocator.getMemberSubscriptions(user, cb) }, managedGroupSubscriptions(cb) { - SubscriptionLocator.getManagedGroupSubscriptions(user, cb) + SubscriptionLocator.getManagedGroupSubscriptions( + user, + ['groupPolicy'], + cb + ) }, currentInstitutionsWithLicence(cb) { InstitutionsGetter.getCurrentInstitutionsWithLicence( @@ -139,6 +143,14 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') { id => id.toString() === user._id.toString() ) + const groupPolicy = group.groupPolicy + ? { + userCannotUseChat: group.groupPolicy.userCannotUseChat, + userCannotUseDropbox: group.groupPolicy.userCannotUseDropbox, + userCannotUseAIFeatures: group.groupPolicy.userCannotUseAIFeatures, + } + : undefined + const groupDataForView = { _id: group._id, planCode: group.planCode, @@ -146,6 +158,8 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') { teamName: group.teamName, admin_id: { _id: group.admin_id._id, email: group.admin_id.email }, features: group.features, + managedUsersEnabled: !!group.managedUsersEnabled, + groupPolicy, userIsGroupMember, } diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index bfa250759a..2b5318e23c 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -122,6 +122,11 @@ "ai_assistant_disabled": "", "ai_can_make_mistakes": "", "ai_features": "", + "ai_features_couldnt_be_toggled": "", + "ai_features_managed_by_overleaf_support": "", + "ai_features_off": "", + "ai_features_on": "", + "ai_features_section_subtext": "", "ai_features_unavailable_on_this_project": "", "ai_feedback_please_provide_more_detail": "", "ai_feedback_tell_us_what_was_wrong_so_we_can_improve": "", @@ -539,7 +544,10 @@ "dropbox_duplicate_project_names": "", "dropbox_duplicate_project_names_suggestion": "", "dropbox_for_link_share_projs": "", + "dropbox_integration": "", "dropbox_integration_lowercase": "", + "dropbox_integration_managed_by_overleaf_support": "", + "dropbox_integration_section_description": "", "dropbox_sync_both": "", "dropbox_sync_description": "", "dropbox_sync_error": "", @@ -665,7 +673,10 @@ "fast": "", "fast_draft": "", "fast_optimize_images": "", + "feature_controls": "", + "feature_disabled_by_organization_contact_support": "", "feature_enabled_or_disabled": "", + "feature_settings_subtext": "", "features_like_track_changes": "", "feedback": "", "figure": "", @@ -943,6 +954,9 @@ "imported_from_zotero_at_date": "", "importing": "", "importing_and_merging_changes_in_github": "", + "in_editor_chat": "", + "in_editor_chat_managed_by_overleaf_support": "", + "in_editor_chat_section_description": "", "in_order_to_match_institutional_metadata_2": "", "in_order_to_match_institutional_metadata_associated": "", "inactive": "", diff --git a/services/web/frontend/js/features/subscription/components/dashboard/managed-group-subscriptions.tsx b/services/web/frontend/js/features/subscription/components/dashboard/managed-group-subscriptions.tsx index 740f54348a..19475b998e 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/managed-group-subscriptions.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/managed-group-subscriptions.tsx @@ -111,6 +111,16 @@ export default function ManagedGroupSubscriptions() { {managedGroupSubscriptions.map(subscription => { const isAdmin = usersEmail === subscription.admin_id.email + // Feature controls are currently rendered only for managed groups, where the admin can + // toggle AI Feature on/off. For non-managed groups, the section only displays notifications + // for features that have been disabled by Overleaf Support. + // This flag will be deleted once AI Features toggling is available to all groups. + const shouldDisplayFeatureControls = + subscription.managedUsersEnabled || + subscription.groupPolicy?.userCannotUseAIFeatures || + subscription.groupPolicy?.userCannotUseChat || + subscription.groupPolicy?.userCannotUseDropbox + return (