Browse Source

Merge pull request #22994 from overleaf/jdt-diasble-assist

Add a toggle to hide all AI features on a users account

GitOrigin-RevId: 978b02609c40f3975daa267aa6c10ac49e13d6f3
Jimmy Domagala-Tang 1 year ago
parent
commit
0baaed6cdf

+ 6 - 3
services/web/app/src/Features/Project/ProjectController.js

@@ -364,7 +364,7 @@ const _ProjectController = {
           user: (async () => {
           user: (async () => {
             const user = await User.findById(
             const user = await User.findById(
               userId,
               userId,
-              'email first_name last_name referal_id signUpDate featureSwitches features featuresEpoch refProviders alphaProgram betaProgram isAdmin ace labsProgram completedTutorials writefull'
+              'email first_name last_name referal_id signUpDate featureSwitches features featuresEpoch refProviders alphaProgram betaProgram isAdmin ace labsProgram completedTutorials writefull aiErrorAssistant'
             ).exec()
             ).exec()
             // Handle case of deleted user
             // Handle case of deleted user
             if (!user) {
             if (!user) {
@@ -675,11 +675,14 @@ const _ProjectController = {
         subscription && !subscription.recurlySubscription_id
         subscription && !subscription.recurlySubscription_id
       const hasManuallyCollectedSubscription =
       const hasManuallyCollectedSubscription =
         subscription?.collectionMethod === 'manual'
         subscription?.collectionMethod === 'manual'
+      const cannotPurchaseAddons =
+        hasNonRecurlySubscription || hasManuallyCollectedSubscription
+      const assistantDisabled = user.aiErrorAssistant?.enabled === false // the assistant has been manually disabled by the user
       const canUseErrorAssistant =
       const canUseErrorAssistant =
         user.features?.aiErrorAssistant ||
         user.features?.aiErrorAssistant ||
         (splitTestAssignments['ai-add-on']?.variant === 'enabled' &&
         (splitTestAssignments['ai-add-on']?.variant === 'enabled' &&
-          !hasNonRecurlySubscription &&
-          !hasManuallyCollectedSubscription)
+          !cannotPurchaseAddons &&
+          !assistantDisabled)
 
 
       let featureUsage = {}
       let featureUsage = {}
 
 

+ 3 - 0
services/web/app/src/Features/User/UserPagesController.mjs

@@ -153,6 +153,9 @@ async function settingsPage(req, res) {
       writefull: {
       writefull: {
         enabled: Boolean(user.writefull?.enabled),
         enabled: Boolean(user.writefull?.enabled),
       },
       },
+      aiErrorAssistant: {
+        enabled: Boolean(user.aiErrorAssistant?.enabled),
+      },
     },
     },
     hasPassword: !!user.hashedPassword,
     hasPassword: !!user.hashedPassword,
     shouldAllowEditingDetails,
     shouldAllowEditingDetails,

+ 3 - 1
services/web/app/src/models/User.js

@@ -169,7 +169,6 @@ const UserSchema = new Schema(
           zotero: { type: Boolean },
           zotero: { type: Boolean },
           referencesSearch: { type: Boolean },
           referencesSearch: { type: Boolean },
           symbolPalette: { type: Boolean },
           symbolPalette: { type: Boolean },
-          compileAssistant: { type: Boolean },
         },
         },
       },
       },
     ],
     ],
@@ -196,6 +195,9 @@ const UserSchema = new Schema(
       enabled: { type: Boolean, default: null },
       enabled: { type: Boolean, default: null },
       autoCreatedAccount: { type: Boolean, default: false },
       autoCreatedAccount: { type: Boolean, default: false },
     },
     },
+    aiErrorAssistant: {
+      enabled: { type: Boolean, default: true },
+    },
     alphaProgram: { type: Boolean, default: false }, // experimental features
     alphaProgram: { type: Boolean, default: false }, // experimental features
     betaProgram: { type: Boolean, default: false },
     betaProgram: { type: Boolean, default: false },
     labsProgram: { type: Boolean, default: false },
     labsProgram: { type: Boolean, default: false },

+ 8 - 3
services/web/frontend/extracted-translations.json

@@ -18,6 +18,7 @@
   "a_new_reference_was_added_from_provider": "",
   "a_new_reference_was_added_from_provider": "",
   "a_new_reference_was_added_to_file": "",
   "a_new_reference_was_added_to_file": "",
   "a_new_reference_was_added_to_file_from_provider": "",
   "a_new_reference_was_added_to_file_from_provider": "",
+  "about_error_assist": "",
   "about_to_archive_projects": "",
   "about_to_archive_projects": "",
   "about_to_delete_cert": "",
   "about_to_delete_cert": "",
   "about_to_delete_projects": "",
   "about_to_delete_projects": "",
@@ -29,6 +30,7 @@
   "about_to_leave_project": "",
   "about_to_leave_project": "",
   "about_to_leave_projects": "",
   "about_to_leave_projects": "",
   "about_to_trash_projects": "",
   "about_to_trash_projects": "",
+  "about_writefull": "",
   "accept": "",
   "accept": "",
   "accept_all": "",
   "accept_all": "",
   "accept_and_continue": "",
   "accept_and_continue": "",
@@ -101,7 +103,10 @@
   "aggregate_changed": "",
   "aggregate_changed": "",
   "aggregate_to": "",
   "aggregate_to": "",
   "agree_with_the_terms": "",
   "agree_with_the_terms": "",
+  "ai_assistance_to_help_you": "",
+  "ai_based_language_tools": "",
   "ai_can_make_mistakes": "",
   "ai_can_make_mistakes": "",
+  "ai_features": "",
   "ai_feedback_do_you_have_any_thoughts_or_suggestions": "",
   "ai_feedback_do_you_have_any_thoughts_or_suggestions": "",
   "ai_feedback_tell_us_what_was_wrong_so_we_can_improve": "",
   "ai_feedback_tell_us_what_was_wrong_so_we_can_improve": "",
   "ai_feedback_the_answer_was_too_long": "",
   "ai_feedback_the_answer_was_too_long": "",
@@ -371,6 +376,7 @@
   "dictionary": "",
   "dictionary": "",
   "did_you_know_institution_providing_professional": "",
   "did_you_know_institution_providing_professional": "",
   "disable": "",
   "disable": "",
+  "disable_ai_features": "",
   "disable_equation_preview": "",
   "disable_equation_preview": "",
   "disable_equation_preview_confirm": "",
   "disable_equation_preview_confirm": "",
   "disable_equation_preview_enable": "",
   "disable_equation_preview_enable": "",
@@ -464,6 +470,7 @@
   "emails_and_affiliations_explanation": "",
   "emails_and_affiliations_explanation": "",
   "emails_and_affiliations_title": "",
   "emails_and_affiliations_title": "",
   "empty": "",
   "empty": "",
+  "enable_ai_features": "",
   "enable_managed_users": "",
   "enable_managed_users": "",
   "enable_single_sign_on": "",
   "enable_single_sign_on": "",
   "enable_sso": "",
   "enable_sso": "",
@@ -478,6 +485,7 @@
   "enter_the_number_of_users_youd_like_to_add_to_see_the_cost_breakdown": "",
   "enter_the_number_of_users_youd_like_to_add_to_see_the_cost_breakdown": "",
   "equation_preview": "",
   "equation_preview": "",
   "error": "",
   "error": "",
+  "error_assist": "",
   "error_opening_document": "",
   "error_opening_document": "",
   "error_opening_document_detail": "",
   "error_opening_document_detail": "",
   "error_performing_request": "",
   "error_performing_request": "",
@@ -808,7 +816,6 @@
   "labels_help_you_to_easily_reference_your_figures": "",
   "labels_help_you_to_easily_reference_your_figures": "",
   "labels_help_you_to_reference_your_tables": "",
   "labels_help_you_to_reference_your_tables": "",
   "language": "",
   "language": "",
-  "language_feedback": "",
   "large_or_high-resolution_images_taking_too_long": "",
   "large_or_high-resolution_images_taking_too_long": "",
   "last_active": "",
   "last_active": "",
   "last_active_description": "",
   "last_active_description": "",
@@ -1920,10 +1927,8 @@
   "work_with_non_overleaf_users": "",
   "work_with_non_overleaf_users": "",
   "work_with_other_github_users": "",
   "work_with_other_github_users": "",
   "writefull": "",
   "writefull": "",
-  "writefull_learn_more": "",
   "writefull_loading_error_body": "",
   "writefull_loading_error_body": "",
   "writefull_loading_error_title": "",
   "writefull_loading_error_title": "",
-  "writefull_settings_description": "",
   "x_changes_in": "",
   "x_changes_in": "",
   "x_changes_in_plural": "",
   "x_changes_in_plural": "",
   "x_libraries_accessed_in_this_project": "",
   "x_libraries_accessed_in_this_project": "",

+ 1 - 1
services/web/frontend/js/features/settings/components/linking-section.tsx

@@ -91,7 +91,7 @@ function LinkingSection() {
       {haslangFeedbackLinkingWidgets ? (
       {haslangFeedbackLinkingWidgets ? (
         <>
         <>
           <h3 id="language-feedback" className="text-capitalize">
           <h3 id="language-feedback" className="text-capitalize">
-            {t('language_feedback')}
+            {t('ai_features')}
           </h3>
           </h3>
           <div className="settings-widgets-container">
           <div className="settings-widgets-container">
             {langFeedbackLinkingWidgets.map(
             {langFeedbackLinkingWidgets.map(

+ 9 - 3
services/web/frontend/js/features/settings/components/linking/enable-widget.tsx

@@ -80,16 +80,22 @@ type ActionButtonProps = {
   handleUnlinkClick: () => void
   handleUnlinkClick: () => void
   handleLinkClick: () => void
   handleLinkClick: () => void
   disabled?: boolean
   disabled?: boolean
+  linkText?: string
+  unlinkText?: string
 }
 }
 
 
-function ActionButton({
+export function ActionButton({
   linked,
   linked,
   handleUnlinkClick,
   handleUnlinkClick,
   handleLinkClick,
   handleLinkClick,
   hasFeature,
   hasFeature,
   disabled,
   disabled,
+  linkText,
+  unlinkText,
 }: ActionButtonProps) {
 }: ActionButtonProps) {
   const { t } = useTranslation()
   const { t } = useTranslation()
+  const linkingText = linkText || t('turn_on')
+  const unlinkingText = unlinkText || t('turn_off')
   if (!hasFeature) {
   if (!hasFeature) {
     return (
     return (
       <OLButton
       <OLButton
@@ -107,7 +113,7 @@ function ActionButton({
         onClick={handleUnlinkClick}
         onClick={handleUnlinkClick}
         disabled={disabled}
         disabled={disabled}
       >
       >
-        {t('turn_off')}
+        {unlinkingText}
       </OLButton>
       </OLButton>
     )
     )
   } else {
   } else {
@@ -117,7 +123,7 @@ function ActionButton({
         disabled={disabled}
         disabled={disabled}
         onClick={handleLinkClick}
         onClick={handleLinkClick}
       >
       >
-        {t('turn_on')}
+        {linkingText}
       </OLButton>
       </OLButton>
     )
     )
   }
   }

+ 6 - 0
services/web/frontend/stylesheets/app/account-settings.less

@@ -173,6 +173,12 @@ tbody > tr.affiliations-table-warning-row > td {
     height: 40px;
     height: 40px;
   }
   }
 
 
+  .dual-logo {
+    display: flex;
+    justify-content: space-evenly;
+    height: 100%;
+  }
+
   .description-container {
   .description-container {
     flex-grow: 1;
     flex-grow: 1;
   }
   }

+ 6 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/account-settings.scss

@@ -100,6 +100,12 @@
     flex-grow: 1;
     flex-grow: 1;
   }
   }
 
 
+  .dual-logo {
+    display: flex;
+    justify-content: space-evenly;
+    height: 100%;
+  }
+
   .title-row {
   .title-row {
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;

+ 8 - 3
services/web/locales/en.json

@@ -21,6 +21,7 @@
   "a_new_reference_was_added_to_file": "A new reference was added to <0>__filePath__</0>",
   "a_new_reference_was_added_to_file": "A new reference was added to <0>__filePath__</0>",
   "a_new_reference_was_added_to_file_from_provider": "A new reference was added to <0>__filePath__</0> from __provider__",
   "a_new_reference_was_added_to_file_from_provider": "A new reference was added to <0>__filePath__</0> from __provider__",
   "about": "About",
   "about": "About",
+  "about_error_assist": "About Error Assist",
   "about_to_archive_projects": "You are about to archive the following projects:",
   "about_to_archive_projects": "You are about to archive the following projects:",
   "about_to_delete_cert": "You are about to delete the following certificate:",
   "about_to_delete_cert": "You are about to delete the following certificate:",
   "about_to_delete_projects": "You are about to delete the following projects:",
   "about_to_delete_projects": "You are about to delete the following projects:",
@@ -32,6 +33,7 @@
   "about_to_leave_project": "You are about to leave this project.",
   "about_to_leave_project": "You are about to leave this project.",
   "about_to_leave_projects": "You are about to leave the following projects:",
   "about_to_leave_projects": "You are about to leave the following projects:",
   "about_to_trash_projects": "You are about to trash the following projects:",
   "about_to_trash_projects": "You are about to trash the following projects:",
+  "about_writefull": "About Writefull",
   "abstract": "Abstract",
   "abstract": "Abstract",
   "accept": "Accept",
   "accept": "Accept",
   "accept_all": "Accept all",
   "accept_all": "Accept all",
@@ -120,7 +122,10 @@
   "aggregate_changed": "Changed",
   "aggregate_changed": "Changed",
   "aggregate_to": "to",
   "aggregate_to": "to",
   "agree_with_the_terms": "I agree with the Overleaf terms",
   "agree_with_the_terms": "I agree with the Overleaf terms",
+  "ai_assistance_to_help_you": "AI assistance to help you fix LaTeX errors",
+  "ai_based_language_tools": "AI-based language tools tailored to research writing",
   "ai_can_make_mistakes": "AI can make mistakes. Review fixes before you apply them.",
   "ai_can_make_mistakes": "AI can make mistakes. Review fixes before you apply them.",
+  "ai_features": "AI features",
   "ai_feedback_do_you_have_any_thoughts_or_suggestions": "Do you have any thoughts or suggestions for improving this feature?",
   "ai_feedback_do_you_have_any_thoughts_or_suggestions": "Do you have any thoughts or suggestions for improving this feature?",
   "ai_feedback_tell_us_what_was_wrong_so_we_can_improve": "Tell us what was wrong so we can improve.",
   "ai_feedback_tell_us_what_was_wrong_so_we_can_improve": "Tell us what was wrong so we can improve.",
   "ai_feedback_the_answer_was_too_long": "The answer was too long",
   "ai_feedback_the_answer_was_too_long": "The answer was too long",
@@ -490,6 +495,7 @@
   "dictionary": "Dictionary",
   "dictionary": "Dictionary",
   "did_you_know_institution_providing_professional": "Did you know that __institutionName__ is providing <0>free __appName__ Professional features</0> to everyone at __institutionName__?",
   "did_you_know_institution_providing_professional": "Did you know that __institutionName__ is providing <0>free __appName__ Professional features</0> to everyone at __institutionName__?",
   "disable": "Disable",
   "disable": "Disable",
+  "disable_ai_features": "Disable AI features",
   "disable_equation_preview": "Disable equation preview",
   "disable_equation_preview": "Disable equation preview",
   "disable_equation_preview_confirm": "This will disable equation preview for you in all projects.",
   "disable_equation_preview_confirm": "This will disable equation preview for you in all projects.",
   "disable_equation_preview_enable": "You can enable it again from the <b>Menu</b>.",
   "disable_equation_preview_enable": "You can enable it again from the <b>Menu</b>.",
@@ -610,6 +616,7 @@
   "empty": "Empty",
   "empty": "Empty",
   "empty_zip_file": "Zip doesn’t contain any file",
   "empty_zip_file": "Zip doesn’t contain any file",
   "en": "English",
   "en": "English",
+  "enable_ai_features": "Enable AI features",
   "enable_managed_users": "Enable Managed Users",
   "enable_managed_users": "Enable Managed Users",
   "enable_single_sign_on": "Enable single sign-on",
   "enable_single_sign_on": "Enable single sign-on",
   "enable_sso": "Enable SSO",
   "enable_sso": "Enable SSO",
@@ -627,6 +634,7 @@
   "enter_your_new_password": "Enter your new password",
   "enter_your_new_password": "Enter your new password",
   "equation_preview": "Equation preview",
   "equation_preview": "Equation preview",
   "error": "Error",
   "error": "Error",
+  "error_assist": "Error Assist",
   "error_opening_document": "Error opening document",
   "error_opening_document": "Error opening document",
   "error_opening_document_detail": "Sorry, something went wrong opening this document. Please try again.",
   "error_opening_document_detail": "Sorry, something went wrong opening this document. Please try again.",
   "error_performing_request": "An error has occurred while performing your request.",
   "error_performing_request": "An error has occurred while performing your request.",
@@ -1075,7 +1083,6 @@
   "labels_help_you_to_reference_your_tables": "Labels help you to reference your tables throughout your document easily. To reference a table within the text, reference the label using the <0>\\ref{...}</0> command. This makes it easy to reference tables without manually remembering the table numbering. <1>Read about labels and cross-references</1>.",
   "labels_help_you_to_reference_your_tables": "Labels help you to reference your tables throughout your document easily. To reference a table within the text, reference the label using the <0>\\ref{...}</0> command. This makes it easy to reference tables without manually remembering the table numbering. <1>Read about labels and cross-references</1>.",
   "labs_program_benefits": "By signing up for Overleaf Labs you can get your hands on in-development features and try them out as much as you like. All we ask in return is your honest feedback to help us develop and improve. It’s important to note that features available in this program are still being tested and actively developed. This means they could change, be removed, or become part of a premium plan.",
   "labs_program_benefits": "By signing up for Overleaf Labs you can get your hands on in-development features and try them out as much as you like. All we ask in return is your honest feedback to help us develop and improve. It’s important to note that features available in this program are still being tested and actively developed. This means they could change, be removed, or become part of a premium plan.",
   "language": "Language",
   "language": "Language",
-  "language_feedback": "Language Feedback",
   "large_or_high-resolution_images_taking_too_long": "Large or high-resolution images taking too long to process. You may be able to <0>optimize them</0>.",
   "large_or_high-resolution_images_taking_too_long": "Large or high-resolution images taking too long to process. You may be able to <0>optimize them</0>.",
   "last_active": "Last Active",
   "last_active": "Last Active",
   "last_active_description": "Last time a project was opened.",
   "last_active_description": "Last time a project was opened.",
@@ -2469,10 +2476,8 @@
   "work_with_other_github_users": "Work with other GitHub users",
   "work_with_other_github_users": "Work with other GitHub users",
   "write_and_collaborate_faster_with_features_like": "Write and collaborate faster with features like:",
   "write_and_collaborate_faster_with_features_like": "Write and collaborate faster with features like:",
   "writefull": "Writefull",
   "writefull": "Writefull",
-  "writefull_learn_more": "Learn more about Writefull for Overleaf",
   "writefull_loading_error_body": "Try refreshing the page. If this doesn’t work, try disabling any active browser extensions to check they aren’t blocking Writefull from loading.",
   "writefull_loading_error_body": "Try refreshing the page. If this doesn’t work, try disabling any active browser extensions to check they aren’t blocking Writefull from loading.",
   "writefull_loading_error_title": "Writefull didn’t load correctly",
   "writefull_loading_error_title": "Writefull didn’t load correctly",
-  "writefull_settings_description": "Get free AI-based language feedback specifically tailored for research writing with Writefull for Overleaf.",
   "x_changes_in": "__count__ change in",
   "x_changes_in": "__count__ change in",
   "x_changes_in_plural": "__count__ changes in",
   "x_changes_in_plural": "__count__ changes in",
   "x_libraries_accessed_in_this_project": "__provider__ libraries accessed in this project",
   "x_libraries_accessed_in_this_project": "__provider__ libraries accessed in this project",

+ 28 - 0
services/web/scripts/back_fill_hiding_ai_features.mjs

@@ -0,0 +1,28 @@
+import { db } from '../app/src/infrastructure/mongodb.js'
+import { batchedUpdate } from '@overleaf/mongo-utils/batchedUpdate.js'
+
+async function main() {
+  // update all applicable user models
+  await batchedUpdate(
+    db.users,
+    {
+      'writefull.enabled': false,
+    },
+    {
+      $set: {
+        'aiErrorAssistant.enabled': false,
+      },
+    }
+  )
+  console.log('completed syncing writefull state with error assist')
+}
+
+export default main
+
+try {
+  await main()
+  process.exit(0)
+} catch (error) {
+  console.error({ error })
+  process.exit(1)
+}

+ 3 - 0
services/web/types/user.ts

@@ -54,6 +54,9 @@ export type User = {
     autoCreatedAccount: boolean
     autoCreatedAccount: boolean
     firstAutoLoad: boolean
     firstAutoLoad: boolean
   }
   }
+  aiErrorAssistant?: {
+    enabled: boolean
+  }
   featureUsage?: FeatureUsage
   featureUsage?: FeatureUsage
 }
 }