Procházet zdrojové kódy

Merge pull request #18682 from overleaf/jdt-ai-labs

Re-Release Labs Page for AI Error-Correction

GitOrigin-RevId: 2eef85dd3b5f86bedcf3339559a5fbbae2d4da87
Jimmy Domagala-Tang před 2 roky
rodič
revize
e5aa917f54

+ 16 - 0
services/web/app/src/Features/Newsletter/MailChimpProvider.js

@@ -23,6 +23,8 @@ function make(listName, listId) {
     subscribe: callbackify(provider.subscribe),
     subscribe: callbackify(provider.subscribe),
     unsubscribe: callbackify(provider.unsubscribe),
     unsubscribe: callbackify(provider.unsubscribe),
     changeEmail: callbackify(provider.changeEmail),
     changeEmail: callbackify(provider.changeEmail),
+    tag: callbackify(provider.tag),
+    removeTag: callbackify(provider.removeTag),
     promises: provider,
     promises: provider,
   }
   }
 }
 }
@@ -266,6 +268,8 @@ function makeNullProvider(listName) {
     subscribe,
     subscribe,
     unsubscribe,
     unsubscribe,
     changeEmail,
     changeEmail,
+    tag,
+    removeTag,
   }
   }
 
 
   async function subscribed(user) {
   async function subscribed(user) {
@@ -296,4 +300,16 @@ function makeNullProvider(listName) {
       'Not changing email in newsletter for user because no newsletter provider is configured'
       'Not changing email in newsletter for user because no newsletter provider is configured'
     )
     )
   }
   }
+  async function tag(user, tag) {
+    logger.debug(
+      { userId: user._id, tag, listName },
+      'Not tagging user because no newsletter provider is configured'
+    )
+  }
+  async function removeTag(user, tag) {
+    logger.debug(
+      { userId: user._id, tag, listName },
+      'Not removing tag for user because no newsletter provider is configured'
+    )
+  }
 }
 }

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

@@ -602,6 +602,11 @@ const _ProjectController = {
         !showPersonalAccessToken &&
         !showPersonalAccessToken &&
         splitTestAssignments['personal-access-token'].variant === 'enabled' // `?personal-access-token=enabled`
         splitTestAssignments['personal-access-token'].variant === 'enabled' // `?personal-access-token=enabled`
 
 
+      const showAiErrorAssistant =
+        userId &&
+        (!Features.hasFeature('saas') ||
+          (user.features && user.features.aiErrorAssistant))
+
       const template =
       const template =
         detachRole === 'detached'
         detachRole === 'detached'
           ? 'project/ide-react-detached'
           ? 'project/ide-react-detached'
@@ -670,7 +675,7 @@ const _ProjectController = {
         debugPdfDetach,
         debugPdfDetach,
         showSymbolPalette,
         showSymbolPalette,
         symbolPaletteAvailable: Features.hasFeature('symbol-palette'),
         symbolPaletteAvailable: Features.hasFeature('symbol-palette'),
-        showAiErrorAssistant: user.alphaProgram, // TODO: labs experiment
+        showAiErrorAssistant,
         detachRole,
         detachRole,
         metadata: { viewport: false },
         metadata: { viewport: false },
         showUpgradePrompt,
         showUpgradePrompt,

+ 5 - 0
services/web/app/src/models/User.js

@@ -126,6 +126,10 @@ const UserSchema = new Schema(
         type: Boolean,
         type: Boolean,
         default: Settings.defaultFeatures.symbolPalette,
         default: Settings.defaultFeatures.symbolPalette,
       },
       },
+      // labs feature, which shouldnt have a default as we havent decided pricing model yet
+      aiErrorAssistant: {
+        type: Boolean,
+      },
     },
     },
     featuresOverrides: [
     featuresOverrides: [
       {
       {
@@ -138,6 +142,7 @@ const UserSchema = new Schema(
         expiresAt: { type: Date },
         expiresAt: { type: Date },
         note: { type: String },
         note: { type: String },
         features: {
         features: {
+          aiErrorAssistant: { type: Boolean },
           collaborators: { type: Number },
           collaborators: { type: Number },
           versioning: { type: Boolean },
           versioning: { type: Boolean },
           dropbox: { type: Boolean },
           dropbox: { type: Boolean },

+ 1 - 0
services/web/config/settings.defaults.js

@@ -882,6 +882,7 @@ module.exports = {
     sourceEditorToolbarComponents: [],
     sourceEditorToolbarComponents: [],
     editorPromotions: [],
     editorPromotions: [],
     langFeedbackLinkingWidgets: [],
     langFeedbackLinkingWidgets: [],
+    labsExperiments: [],
     integrationLinkingWidgets: [],
     integrationLinkingWidgets: [],
     referenceLinkingWidgets: [],
     referenceLinkingWidgets: [],
     importProjectFromGithubModalWrapper: [],
     importProjectFromGithubModalWrapper: [],

+ 10 - 4
services/web/frontend/extracted-translations.json

@@ -80,6 +80,7 @@
   "all_premium_features_including": "",
   "all_premium_features_including": "",
   "all_projects": "",
   "all_projects": "",
   "all_projects_will_be_transferred_immediately": "",
   "all_projects_will_be_transferred_immediately": "",
+  "all_these_experiments_are_available_exclusively": "",
   "already_subscribed_try_refreshing_the_page": "",
   "already_subscribed_try_refreshing_the_page": "",
   "also": "",
   "also": "",
   "an_email_has_already_been_sent_to": "",
   "an_email_has_already_been_sent_to": "",
@@ -174,6 +175,7 @@
   "checking_project_github_status": "",
   "checking_project_github_status": "",
   "choose_a_custom_color": "",
   "choose_a_custom_color": "",
   "choose_from_group_members": "",
   "choose_from_group_members": "",
+  "choose_which_experiments": "",
   "clear_cached_files": "",
   "clear_cached_files": "",
   "clear_search": "",
   "clear_search": "",
   "click_here_to_view_sl_in_lng": "",
   "click_here_to_view_sl_in_lng": "",
@@ -380,6 +382,7 @@
   "enable_single_sign_on": "",
   "enable_single_sign_on": "",
   "enable_sso": "",
   "enable_sso": "",
   "enable_stop_on_first_error_under_recompile_dropdown_menu": "",
   "enable_stop_on_first_error_under_recompile_dropdown_menu": "",
+  "enabled": "",
   "enabling": "",
   "enabling": "",
   "end_of_document": "",
   "end_of_document": "",
   "enter_6_digit_code": "",
   "enter_6_digit_code": "",
@@ -458,12 +461,14 @@
   "get_collaborative_benefits": "",
   "get_collaborative_benefits": "",
   "get_discounted_plan": "",
   "get_discounted_plan": "",
   "get_dropbox_sync": "",
   "get_dropbox_sync": "",
+  "get_exclusive_access_to_labs": "",
   "get_full_project_history": "",
   "get_full_project_history": "",
   "get_git_integration": "",
   "get_git_integration": "",
   "get_github_sync": "",
   "get_github_sync": "",
   "get_in_touch": "",
   "get_in_touch": "",
   "get_more_compile_time": "",
   "get_more_compile_time": "",
   "get_most_subscription_by_checking_features": "",
   "get_most_subscription_by_checking_features": "",
+  "get_some_texnical_assistance": "",
   "get_symbol_palette": "",
   "get_symbol_palette": "",
   "get_track_changes": "",
   "get_track_changes": "",
   "git": "",
   "git": "",
@@ -651,6 +656,7 @@
   "is_email_affiliated": "",
   "is_email_affiliated": "",
   "issued_on": "",
   "issued_on": "",
   "join_now": "",
   "join_now": "",
+  "join_overleaf_labs": "",
   "join_project": "",
   "join_project": "",
   "join_team_explanation": "",
   "join_team_explanation": "",
   "joined_team": "",
   "joined_team": "",
@@ -662,9 +668,6 @@
   "keybindings": "",
   "keybindings": "",
   "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": "",
-  "labs_program_already_participating": "",
-  "labs_program_benefits": "",
-  "labs_program_not_participating": "",
   "language_feedback": "",
   "language_feedback": "",
   "large_or_high-resolution_images_taking_too_long": "",
   "large_or_high-resolution_images_taking_too_long": "",
   "last_active": "",
   "last_active": "",
@@ -755,7 +758,6 @@
   "manage_group_settings_subtext_group_sso": "",
   "manage_group_settings_subtext_group_sso": "",
   "manage_group_settings_subtext_managed_users": "",
   "manage_group_settings_subtext_managed_users": "",
   "manage_institution_managers": "",
   "manage_institution_managers": "",
-  "manage_labs_program_membership": "",
   "manage_managers_subtext": "",
   "manage_managers_subtext": "",
   "manage_members": "",
   "manage_members": "",
   "manage_newsletter": "",
   "manage_newsletter": "",
@@ -1229,6 +1231,7 @@
   "somthing_went_wrong_compiling": "",
   "somthing_went_wrong_compiling": "",
   "sorry_it_looks_like_that_didnt_work_this_time": "",
   "sorry_it_looks_like_that_didnt_work_this_time": "",
   "sorry_the_connection_to_the_server_is_down": "",
   "sorry_the_connection_to_the_server_is_down": "",
+  "sorry_there_are_no_experiments": "",
   "sorry_your_table_cant_be_displayed_at_the_moment": "",
   "sorry_your_table_cant_be_displayed_at_the_moment": "",
   "sort_by": "",
   "sort_by": "",
   "sort_by_x": "",
   "sort_by_x": "",
@@ -1556,6 +1559,7 @@
   "view_hub_subtext": "",
   "view_hub_subtext": "",
   "view_in_template_gallery": "",
   "view_in_template_gallery": "",
   "view_invitation": "",
   "view_invitation": "",
+  "view_labs_experiments": "",
   "view_less": "",
   "view_less": "",
   "view_logs": "",
   "view_logs": "",
   "view_metrics": "",
   "view_metrics": "",
@@ -1569,6 +1573,7 @@
   "visual_editor": "",
   "visual_editor": "",
   "visual_editor_is_only_available_for_tex_files": "",
   "visual_editor_is_only_available_for_tex_files": "",
   "want_change_to_apply_before_plan_end": "",
   "want_change_to_apply_before_plan_end": "",
+  "we_are_unable_to_opt_you_into_this_experiment": "",
   "we_cant_find_any_sections_or_subsections_in_this_file": "",
   "we_cant_find_any_sections_or_subsections_in_this_file": "",
   "we_do_not_share_personal_information": "",
   "we_do_not_share_personal_information": "",
   "we_logged_you_in": "",
   "we_logged_you_in": "",
@@ -1658,6 +1663,7 @@
   "your_role": "",
   "your_role": "",
   "your_subscription": "",
   "your_subscription": "",
   "your_subscription_has_expired": "",
   "your_subscription_has_expired": "",
+  "youre_a_member_of_overleaf_labs": "",
   "youre_about_to_disable_single_sign_on": "",
   "youre_about_to_disable_single_sign_on": "",
   "youre_about_to_enable_single_sign_on": "",
   "youre_about_to_enable_single_sign_on": "",
   "youre_about_to_enable_single_sign_on_sso_only": "",
   "youre_about_to_enable_single_sign_on_sso_only": "",

+ 19 - 14
services/web/frontend/js/features/settings/components/labs-program-section.tsx

@@ -1,25 +1,30 @@
-import { Trans, useTranslation } from 'react-i18next'
+import { useTranslation } from 'react-i18next'
 import { useUserContext } from '../../../shared/context/user-context'
 import { useUserContext } from '../../../shared/context/user-context'
 
 
 function LabsProgramSection() {
 function LabsProgramSection() {
   const { t } = useTranslation()
   const { t } = useTranslation()
-  const { labsProgram } = useUserContext()
+  const { labsProgram, alphaProgram } = useUserContext()
+
+  // keeping alpha only as we test AI experiment in preparation for general labs release
+  if (!alphaProgram) {
+    return null
+  }
+
+  const labsStatusText = labsProgram
+    ? t('youre_a_member_of_overleaf_labs')
+    : t('get_exclusive_access_to_labs')
+  const labsRedirectText = labsProgram
+    ? t('view_labs_experiments')
+    : t('join_overleaf_labs')
 
 
   return (
   return (
     <>
     <>
       <h3>{t('overleaf_labs')}</h3>
       <h3>{t('overleaf_labs')}</h3>
-      {labsProgram ? null : (
-        <p className="small">
-          {/* eslint-disable-next-line react/jsx-key */}
-          <Trans i18nKey="labs_program_benefits" components={[<span />]} />
-        </p>
-      )}
-      <p className="small">
-        {labsProgram
-          ? t('labs_program_already_participating')
-          : t('labs_program_not_participating')}
-      </p>
-      <a href="/labs/participate">{t('manage_labs_program_membership')}</a>
+
+      <p className="small">{labsStatusText}</p>
+
+      <a href="/labs/participate">{labsRedirectText}</a>
+      <hr />
     </>
     </>
   )
   )
 }
 }

+ 0 - 1
services/web/frontend/js/features/settings/components/root.tsx

@@ -80,7 +80,6 @@ function SettingsPageContent() {
           {labsEnabled ? (
           {labsEnabled ? (
             <>
             <>
               <LabsProgramSection />
               <LabsProgramSection />
-              <hr />
             </>
             </>
           ) : null}
           ) : null}
           <SessionsSection />
           <SessionsSection />

+ 127 - 0
services/web/frontend/js/shared/components/labs/labs-experiments-widget.tsx

@@ -0,0 +1,127 @@
+import { ReactNode, useCallback, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import Badge from '@/shared/components/badge'
+import { postJSON } from '@/infrastructure/fetch-json'
+import { Button } from 'react-bootstrap'
+import getMeta from '@/utils/meta'
+
+export type UserFeatures = {
+  [key: string]: boolean
+}
+
+type IntegrationLinkingWidgetProps = {
+  logo: ReactNode
+  title: string
+  description: string
+  helpPath?: string
+  labsEnabled?: boolean
+  experimentName: string
+  setErrorMessage: (message: string) => void
+}
+
+export function LabsExperimentWidget({
+  logo,
+  title,
+  description,
+  helpPath,
+  labsEnabled,
+  experimentName,
+  setErrorMessage,
+}: IntegrationLinkingWidgetProps) {
+  const { t } = useTranslation()
+  const userFeatures = getMeta('ol-features') as UserFeatures
+
+  const [enabled, setEnabled] = useState(() => {
+    return userFeatures[experimentName] === true
+  })
+
+  const experimentsErrorMessage = t(
+    'we_are_unable_to_opt_you_into_this_experiment'
+  )
+
+  const handleEnable = useCallback(async () => {
+    try {
+      const enablePath = `/labs/participate/experiments/${experimentName}/opt-in`
+      await postJSON(enablePath)
+      setEnabled(true)
+    } catch (err) {
+      setErrorMessage(experimentsErrorMessage)
+    }
+  }, [experimentName, setErrorMessage, experimentsErrorMessage])
+  const handleDisable = useCallback(async () => {
+    try {
+      const disablePath = `/labs/participate/experiments/${experimentName}/opt-out`
+      await postJSON(disablePath)
+      setEnabled(false)
+    } catch (err) {
+      setErrorMessage(experimentsErrorMessage)
+    }
+  }, [experimentName, setErrorMessage, experimentsErrorMessage])
+
+  return (
+    <div className="labs-experiment-widget-container">
+      <div className="p-2">{logo}</div>
+      <div className="description-container">
+        <div className="title-row">
+          <h3 className="h4">{title}</h3>
+          {enabled && <Badge bsStyle="info">{t('enabled')}</Badge>}
+        </div>
+        <p className="small">
+          {description}{' '}
+          {helpPath && (
+            <a href={helpPath} target="_blank" rel="noreferrer">
+              {t('learn_more')}
+            </a>
+          )}
+        </p>
+      </div>
+      <div>
+        {labsEnabled && (
+          <ActionButton
+            enabled={enabled}
+            handleDisable={handleDisable}
+            handleEnable={handleEnable}
+          />
+        )}
+      </div>
+    </div>
+  )
+}
+
+type ActionButtonProps = {
+  enabled?: boolean
+  handleEnable: () => void
+  handleDisable: () => void
+}
+
+function ActionButton({
+  enabled,
+  handleEnable,
+  handleDisable,
+}: ActionButtonProps) {
+  const { t } = useTranslation()
+
+  if (enabled) {
+    return (
+      <Button
+        bsStyle="secondary"
+        onClick={handleDisable}
+        className="btn btn-secondary"
+      >
+        {t('turn_off')}
+      </Button>
+    )
+  } else {
+    return (
+      <Button
+        bsStyle="primary"
+        onClick={handleEnable}
+        className="btn btn-primary"
+      >
+        {t('turn_on')}
+      </Button>
+    )
+  }
+}
+
+export default LabsExperimentWidget

+ 3 - 1
services/web/frontend/js/utils/meta.ts

@@ -1,4 +1,4 @@
-import { User } from '../../../types/user'
+import { User, Features } from '../../../types/user'
 import { User as MinimalUser } from '../../../types/admin/user'
 import { User as MinimalUser } from '../../../types/admin/user'
 import { User as ManagedUser } from '../../../types/group-management/user'
 import { User as ManagedUser } from '../../../types/group-management/user'
 import { UserSettings } from '../../../types/user-settings'
 import { UserSettings } from '../../../types/user-settings'
@@ -82,6 +82,7 @@ export interface Meta {
   'ol-emailAddressLimit': number
   'ol-emailAddressLimit': number
   'ol-error': { name: string } | undefined
   'ol-error': { name: string } | undefined
   'ol-expired': boolean
   'ol-expired': boolean
+  'ol-features': Features
   'ol-fromPlansPage': boolean
   'ol-fromPlansPage': boolean
   'ol-gitBridgeEnabled': boolean
   'ol-gitBridgeEnabled': boolean
   'ol-gitBridgePublicBaseUrl': string
   'ol-gitBridgePublicBaseUrl': string
@@ -118,6 +119,7 @@ export interface Meta {
   'ol-itm_campaign': string
   'ol-itm_campaign': string
   'ol-itm_content': string
   'ol-itm_content': string
   'ol-itm_referrer': string
   'ol-itm_referrer': string
+  'ol-labs': boolean
   'ol-languages': SpellCheckLanguage[]
   'ol-languages': SpellCheckLanguage[]
   'ol-learnedWords': string[]
   'ol-learnedWords': string[]
   'ol-legacyEditorThemes': string[]
   'ol-legacyEditorThemes': string[]

+ 1 - 0
services/web/frontend/stylesheets/main-style.less

@@ -158,3 +158,4 @@
 @import 'modules/overleaf-integration.less';
 @import 'modules/overleaf-integration.less';
 @import 'modules/writefull.less';
 @import 'modules/writefull.less';
 @import 'modules/third-party-references.less';
 @import 'modules/third-party-references.less';
+@import 'modules/labs.less';

+ 78 - 0
services/web/frontend/stylesheets/modules/labs.less

@@ -0,0 +1,78 @@
+.labs-opt-in-wrapper {
+  color: @ol-blue-gray-3;
+
+  h1,
+  h2,
+  h3,
+  h4 {
+    color: @ol-blue-gray-3;
+  }
+
+  #experiments-container {
+    .title-row {
+      display: flex;
+    }
+    .labs-experiment-widget-container {
+      border: 1px solid @gray-lighter;
+    }
+  }
+  .labs-icon {
+    background-color: @green-50;
+    color: @white;
+    border-radius: 20%;
+    vertical-align: text-bottom;
+  }
+  p {
+    font-size: @font-size-small;
+  }
+  strong {
+    font-size: @font-size-small;
+  }
+
+  .labs-experiment-widget-container {
+    display: grid;
+    grid-template-columns: 40px 1fr auto;
+    gap: 20px;
+    align-items: center;
+    padding: 10px;
+
+    > div {
+      display: flex;
+      flex-direction: column;
+      padding-right: 20px;
+
+      &:last-child {
+        padding-right: 0px;
+      }
+    }
+
+    img {
+      width: 36px;
+      height: 36px;
+    }
+
+    .title-row {
+      margin: 0;
+      margin-bottom: 10px;
+
+      > h3 {
+        margin: 0;
+        margin-right: 10px;
+      }
+    }
+
+    p {
+      margin-bottom: 10px;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+  }
+
+  @media (max-width: @screen-xs-max) {
+    .labs-experiment-widget-container {
+      grid-template-columns: 1fr;
+    }
+  }
+}

+ 17 - 10
services/web/locales/en.json

@@ -190,6 +190,7 @@
   "bulk_reject_confirm": "Are you sure you want to reject the selected __nChanges__ changes?",
   "bulk_reject_confirm": "Are you sure you want to reject the selected __nChanges__ changes?",
   "buy_now_no_exclamation_mark": "Buy now",
   "buy_now_no_exclamation_mark": "Buy now",
   "by": "by",
   "by": "by",
+  "by_joining_labs": "By joining Labs, you agree to receive occasional emails and updates from Overleaf—for example, to request your feedback. You also agree to our <0>terms of service</0> and <1>privacy notice</1>.",
   "by_registering_you_agree_to_our_terms_of_service": "By registering, you agree to our <0>terms of service</0> and <1>privacy notice</1>.",
   "by_registering_you_agree_to_our_terms_of_service": "By registering, you agree to our <0>terms of service</0> and <1>privacy notice</1>.",
   "by_subscribing_you_agree_to_our_terms_of_service": "By subscribing, you agree to our <0>terms of service</0>.",
   "by_subscribing_you_agree_to_our_terms_of_service": "By subscribing, you agree to our <0>terms of service</0>.",
   "can_edit": "Can Edit",
   "can_edit": "Can Edit",
@@ -248,6 +249,7 @@
   "checking_project_github_status": "Checking project status in GitHub",
   "checking_project_github_status": "Checking project status in GitHub",
   "choose_a_custom_color": "Choose a custom color",
   "choose_a_custom_color": "Choose a custom color",
   "choose_from_group_members": "Choose from group members",
   "choose_from_group_members": "Choose from group members",
+  "choose_which_experiments": "Choose which experiments you’d like to try.",
   "choose_your_plan": "Choose your plan",
   "choose_your_plan": "Choose your plan",
   "city": "City",
   "city": "City",
   "clear_cached_files": "Clear cached files",
   "clear_cached_files": "Clear cached files",
@@ -363,7 +365,6 @@
   "credit_card": "Credit Card",
   "credit_card": "Credit Card",
   "cs": "Czech",
   "cs": "Czech",
   "currency": "Currency",
   "currency": "Currency",
-  "current_experiments": "Current Experiments",
   "current_file": "Current file",
   "current_file": "Current file",
   "current_password": "Current Password",
   "current_password": "Current Password",
   "current_session": "Current Session",
   "current_session": "Current Session",
@@ -537,6 +538,7 @@
   "enable_single_sign_on": "Enable single sign-on",
   "enable_single_sign_on": "Enable single sign-on",
   "enable_sso": "Enable SSO",
   "enable_sso": "Enable SSO",
   "enable_stop_on_first_error_under_recompile_dropdown_menu": "Enable <0>“Stop on first error”</0> under the <1>Recompile</1> drop-down menu to help you find and fix errors right away.",
   "enable_stop_on_first_error_under_recompile_dropdown_menu": "Enable <0>“Stop on first error”</0> under the <1>Recompile</1> drop-down menu to help you find and fix errors right away.",
+  "enabled": "Enabled",
   "enabling": "Enabling",
   "enabling": "Enabling",
   "end_of_document": "End of document",
   "end_of_document": "End of document",
   "enter_6_digit_code": "Enter 6-digit code",
   "enter_6_digit_code": "Enter 6-digit code",
@@ -560,6 +562,7 @@
   "example": "Example",
   "example": "Example",
   "example_project": "Example Project",
   "example_project": "Example Project",
   "examples": "Examples",
   "examples": "Examples",
+  "exclusive_access_with_labs": "Exclusive access to early-stage experiments",
   "existing_plan_active_until_term_end": "Your existing plan and its features will remain active until the end of the current billing period.",
   "existing_plan_active_until_term_end": "Your existing plan and its features will remain active until the end of the current billing period.",
   "expand": "Expand",
   "expand": "Expand",
   "expired": "Expired",
   "expired": "Expired",
@@ -691,6 +694,7 @@
   "get_collaborative_benefits": "Get the collaborative benefits from __appName__, even if you prefer to work offline",
   "get_collaborative_benefits": "Get the collaborative benefits from __appName__, even if you prefer to work offline",
   "get_discounted_plan": "Get discounted plan",
   "get_discounted_plan": "Get discounted plan",
   "get_dropbox_sync": "Get Dropbox Sync",
   "get_dropbox_sync": "Get Dropbox Sync",
+  "get_exclusive_access_to_labs": "Get exclusive access to early-stage experiments when you join Overleaf Labs. All we ask in return is your honest feedback to help us develop and improve.",
   "get_full_project_history": "Get full project history",
   "get_full_project_history": "Get full project history",
   "get_git_integration": "Get Git integration",
   "get_git_integration": "Get Git integration",
   "get_github_sync": "Get GitHub Sync",
   "get_github_sync": "Get GitHub Sync",
@@ -699,6 +703,7 @@
   "get_involved": "Get involved",
   "get_involved": "Get involved",
   "get_more_compile_time": "Get more compile time",
   "get_more_compile_time": "Get more compile time",
   "get_most_subscription_by_checking_features": "Get the most out of your __appName__ subscription by checking out <0>__appName__’s features</0>.",
   "get_most_subscription_by_checking_features": "Get the most out of your __appName__ subscription by checking out <0>__appName__’s features</0>.",
+  "get_some_texnical_assistance": "Get some TeXnical assistance from AI to fix errors in your project.",
   "get_symbol_palette": "Get Symbol Palette",
   "get_symbol_palette": "Get Symbol Palette",
   "get_the_best_overleaf_experience": "Get the best Overleaf experience",
   "get_the_best_overleaf_experience": "Get the best Overleaf experience",
   "get_the_best_writing_experience": "Get the best writing experience",
   "get_the_best_writing_experience": "Get the best writing experience",
@@ -953,7 +958,9 @@
   "ja": "Japanese",
   "ja": "Japanese",
   "january": "January",
   "january": "January",
   "join_beta_program": "Join beta program",
   "join_beta_program": "Join beta program",
+  "join_labs": "Join Labs",
   "join_now": "Join now",
   "join_now": "Join now",
+  "join_overleaf_labs": "Join Overleaf Labs",
   "join_project": "Join Project",
   "join_project": "Join Project",
   "join_sl_to_view_project": "Join __appName__ to view this project",
   "join_sl_to_view_project": "Join __appName__ to view this project",
   "join_team_explanation": "Please click the button below to join the group subscription and enjoy the benefits of an upgraded __appName__ account",
   "join_team_explanation": "Please click the button below to join the group subscription and enjoy the benefits of an upgraded __appName__ account",
@@ -973,10 +980,7 @@
   "ko": "Korean",
   "ko": "Korean",
   "labels_help_you_to_easily_reference_your_figures": "Labels help you to easily reference your figures throughout your document. To reference a figure within the text, reference the label using the <0>\\ref{...}</0> command. This makes it easy to reference figures without needing to manually remember the figure numbering. <1>Learn more</1>",
   "labels_help_you_to_easily_reference_your_figures": "Labels help you to easily reference your figures throughout your document. To reference a figure within the text, reference the label using the <0>\\ref{...}</0> command. This makes it easy to reference figures without needing to manually remember the figure numbering. <1>Learn more</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>.",
   "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_already_participating": "You are enrolled in Labs",
-  "labs_program_badge_description": "While using __appName__, you will see Labs features marked with this badge:",
-  "labs_program_benefits": "__appName__ is always looking for new ways to help users work more quickly and effectively. By joining Overleaf Labs, you can participate in experiments that explore innovative ideas in the space of collaborative writing and publishing.",
-  "labs_program_not_participating": "You are not enrolled in Labs",
+  "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",
   "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>.",
@@ -1016,6 +1020,7 @@
   "leave": "Leave",
   "leave": "Leave",
   "leave_any_group_subscriptions": "Leave any group subscriptions other than the one that will be managing your account. <0>Leave them from the Subscription page.</0>",
   "leave_any_group_subscriptions": "Leave any group subscriptions other than the one that will be managing your account. <0>Leave them from the Subscription page.</0>",
   "leave_group": "Leave group",
   "leave_group": "Leave group",
+  "leave_labs": "Leave Overleaf Labs",
   "leave_now": "Leave now",
   "leave_now": "Leave now",
   "leave_projects": "Leave Projects",
   "leave_projects": "Leave Projects",
   "left": "Left",
   "left": "Left",
@@ -1113,7 +1118,6 @@
   "manage_group_settings_subtext_group_sso": "Configure and manage SSO",
   "manage_group_settings_subtext_group_sso": "Configure and manage SSO",
   "manage_group_settings_subtext_managed_users": "Turn on Managed Users",
   "manage_group_settings_subtext_managed_users": "Turn on Managed Users",
   "manage_institution_managers": "Manage institution managers",
   "manage_institution_managers": "Manage institution managers",
-  "manage_labs_program_membership": "Manage Labs Program Membership",
   "manage_managers_subtext": "Assign or remove manager privileges",
   "manage_managers_subtext": "Assign or remove manager privileges",
   "manage_members": "Manage members",
   "manage_members": "Manage members",
   "manage_newsletter": "Manage Your Newsletter Preferences",
   "manage_newsletter": "Manage Your Newsletter Preferences",
@@ -1250,7 +1254,6 @@
   "not_managed": "Not managed",
   "not_managed": "Not managed",
   "not_now": "Not now",
   "not_now": "Not now",
   "not_registered": "Not registered",
   "not_registered": "Not registered",
-  "note_experiments_under_development": "<0>Please note</0> that experiments in this program are still being tested and actively developed. This means that they might <0>change</0>, be <0>removed</0> or <0>become part of a paid plan</0>",
   "note_features_under_development": "<0>Please note</0> that features in this program are still being tested and actively developed. This means that they might <0>change</0>, be <0>removed</0> or <0>become part of a premium plan</0>",
   "note_features_under_development": "<0>Please note</0> that features in this program are still being tested and actively developed. This means that they might <0>change</0>, be <0>removed</0> or <0>become part of a premium plan</0>",
   "notification_features_upgraded_by_affiliation": "Good news! Your affiliated organization __institutionName__ has an Overleaf subscription, and you now have access to all of Overleaf’s Professional features.",
   "notification_features_upgraded_by_affiliation": "Good news! Your affiliated organization __institutionName__ has an Overleaf subscription, and you now have access to all of Overleaf’s Professional features.",
   "notification_ieee_collabratec_retirement_message": "As of April 2, 2024, free access to Overleaf Professional subscriptions via Collabratec or IEEE accounts has ended. Need to upgrade? <0>View our plans</0>. Contact <1>authors@ieee.org</1> with any questions.",
   "notification_ieee_collabratec_retirement_message": "As of April 2, 2024, free access to Overleaf Professional subscriptions via Collabratec or IEEE accounts has ended. Need to upgrade? <0>View our plans</0>. Contact <1>authors@ieee.org</1> with any questions.",
@@ -1279,6 +1282,7 @@
   "one_per_project": "1 per project",
   "one_per_project": "1 per project",
   "one_step_away_from_professional_features": "You are one step away from accessing <0>Overleaf Professional features</0>!",
   "one_step_away_from_professional_features": "You are one step away from accessing <0>Overleaf Professional features</0>!",
   "one_user": "1 user",
   "one_user": "1 user",
+  "ongoing_experiments": "Ongoing experiments",
   "online_latex_editor": "Online LaTeX Editor",
   "online_latex_editor": "Online LaTeX Editor",
   "only_group_admin_or_managers_can_delete_your_account_1": "By becoming a managed user, your organization will have admin rights over your account and control over your stuff, including the right to close your account and access, delete and share your stuff. As a result:",
   "only_group_admin_or_managers_can_delete_your_account_1": "By becoming a managed user, your organization will have admin rights over your account and control over your stuff, including the right to close your account and access, delete and share your stuff. As a result:",
   "only_group_admin_or_managers_can_delete_your_account_2": "Only your group admin or group managers will be able to delete your account.",
   "only_group_admin_or_managers_can_delete_your_account_2": "Only your group admin or group managers will be able to delete your account.",
@@ -1773,6 +1777,7 @@
   "sorry_it_looks_like_that_didnt_work_this_time": "Sorry! It looks like that didn’t work this time. Please try again.",
   "sorry_it_looks_like_that_didnt_work_this_time": "Sorry! It looks like that didn’t work this time. Please try again.",
   "sorry_something_went_wrong_opening_the_document_please_try_again": "Sorry, an unexpected error occurred when trying to open this content on Overleaf. Please try again.",
   "sorry_something_went_wrong_opening_the_document_please_try_again": "Sorry, an unexpected error occurred when trying to open this content on Overleaf. Please try again.",
   "sorry_the_connection_to_the_server_is_down": "Sorry, the connection to the server is down.",
   "sorry_the_connection_to_the_server_is_down": "Sorry, the connection to the server is down.",
+  "sorry_there_are_no_experiments": "Sorry, there are no experiments currently running in Overleaf Labs.",
   "sorry_this_account_has_been_suspended": "Sorry, this account has been suspended.",
   "sorry_this_account_has_been_suspended": "Sorry, this account has been suspended.",
   "sorry_your_table_cant_be_displayed_at_the_moment": "Sorry, your table can’t be displayed at the moment.",
   "sorry_your_table_cant_be_displayed_at_the_moment": "Sorry, your table can’t be displayed at the moment.",
   "sorry_your_token_expired": "Sorry, your token expired",
   "sorry_your_token_expired": "Sorry, your token expired",
@@ -1932,7 +1937,6 @@
   "thank_you_email_confirmed": "Thank you, your email is now confirmed",
   "thank_you_email_confirmed": "Thank you, your email is now confirmed",
   "thank_you_exclamation": "Thank you!",
   "thank_you_exclamation": "Thank you!",
   "thank_you_for_being_part_of_our_beta_program": "Thank you for being part of our Beta Program, where you can have <0>early access to new features</0> and help us understand your needs better",
   "thank_you_for_being_part_of_our_beta_program": "Thank you for being part of our Beta Program, where you can have <0>early access to new features</0> and help us understand your needs better",
-  "thank_you_for_being_part_of_our_labs_program": "Thank you for being part of our Labs program, where you can have <0>early access to experimental features</0> and help us explore innovative ideas that help you work more quickly and effectively",
   "thank_you_for_your_feedback": "Thank you for your feedback!",
   "thank_you_for_your_feedback": "Thank you for your feedback!",
   "thanks": "Thanks",
   "thanks": "Thanks",
   "thanks_for_confirming_your_email_address": "Thanks for confirming your email address",
   "thanks_for_confirming_your_email_address": "Thanks for confirming your email address",
@@ -2185,6 +2189,7 @@
   "view_hub_subtext": "Access and download subscription statistics and a list of users",
   "view_hub_subtext": "Access and download subscription statistics and a list of users",
   "view_in_template_gallery": "View it in the template gallery",
   "view_in_template_gallery": "View it in the template gallery",
   "view_invitation": "View Invitation",
   "view_invitation": "View Invitation",
+  "view_labs_experiments": "View Labs Experiments",
   "view_less": "View less",
   "view_less": "View less",
   "view_logs": "View logs",
   "view_logs": "View logs",
   "view_metrics": "View metrics",
   "view_metrics": "View metrics",
@@ -2199,6 +2204,7 @@
   "visual_editor": "Visual Editor",
   "visual_editor": "Visual Editor",
   "visual_editor_is_only_available_for_tex_files": "Visual Editor is only available for TeX files",
   "visual_editor_is_only_available_for_tex_files": "Visual Editor is only available for TeX files",
   "want_change_to_apply_before_plan_end": "If you wish this change to apply before the end of your current billing period, please contact us.",
   "want_change_to_apply_before_plan_end": "If you wish this change to apply before the end of your current billing period, please contact us.",
+  "we_are_unable_to_opt_you_into_this_experiment": "We are unable to opt you into this experiment at this time, please ensure your organization has allowed this feature, or try again later.",
   "we_cant_confirm_this_email": "We can’t confirm this email",
   "we_cant_confirm_this_email": "We can’t confirm this email",
   "we_cant_find_any_sections_or_subsections_in_this_file": "We can’t find any sections or subsections in this file",
   "we_cant_find_any_sections_or_subsections_in_this_file": "We can’t find any sections or subsections in this file",
   "we_do_not_share_personal_information": "See our <0>Privacy Notice</0> for details of how we treat your personal data",
   "we_do_not_share_personal_information": "See our <0>Privacy Notice</0> for details of how we treat your personal data",
@@ -2218,6 +2224,7 @@
   "what_does_this_mean_for_you": "This means:",
   "what_does_this_mean_for_you": "This means:",
   "what_happens_when_sso_is_enabled": "What happens when SSO is enabled?",
   "what_happens_when_sso_is_enabled": "What happens when SSO is enabled?",
   "what_should_we_call_you": "What should we call you?",
   "what_should_we_call_you": "What should we call you?",
+  "when_you_join_labs": "When you join Labs, you can choose which experiments you want to be part of. Once you’ve done that, you can use Overleaf as normal, but you’ll see any labs features marked with this badge:",
   "when_you_tick_the_include_caption_box": "When you tick the box “Include caption” the image will be inserted into your document with a placeholder caption. To edit it, you simply select the placeholder text and type to replace it with your own.",
   "when_you_tick_the_include_caption_box": "When you tick the box “Include caption” the image will be inserted into your document with a placeholder caption. To edit it, you simply select the placeholder text and type to replace it with your own.",
   "why_latex": "Why LaTeX?",
   "why_latex": "Why LaTeX?",
   "wide": "Wide",
   "wide": "Wide",
@@ -2270,9 +2277,7 @@
   "you_are_on_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are on our <0>__planName__</0> plan as a <1>member</1> of the group subscription <1>__groupName__</1> administered by <1>__adminEmail__</1>",
   "you_are_on_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are on our <0>__planName__</0> plan as a <1>member</1> of the group subscription <1>__groupName__</1> administered by <1>__adminEmail__</1>",
   "you_can_now_enable_sso": "You can now enable SSO on your Group settings page.",
   "you_can_now_enable_sso": "You can now enable SSO on your Group settings page.",
   "you_can_now_log_in_sso": "You can now log in through your institution and if eligible you will receive <0>__appName__ Professional features</0>.",
   "you_can_now_log_in_sso": "You can now log in through your institution and if eligible you will receive <0>__appName__ Professional features</0>.",
-  "you_can_opt_in_and_out_of_overleaf_labs_at_any_time_on_this_page": "You can <0>opt in and out</0> of Overleaf Labs at any time on this page",
   "you_can_opt_in_and_out_of_the_program_at_any_time_on_this_page": "You can <0>opt in and out</0> of the program at any time on this page",
   "you_can_opt_in_and_out_of_the_program_at_any_time_on_this_page": "You can <0>opt in and out</0> of the program at any time on this page",
-  "you_can_opt_in_to_individual_experiments": "You will be asked to opt in and out of individual experiments; each experiment may have unique partners, requirements, terms and conditions, etc. that must be opted in to for that specific experiment",
   "you_can_request_a_maximum_of_limit_fixes_per_day": "You can request a maximum of __limit__ fixes per day. Please try again tomorrow.",
   "you_can_request_a_maximum_of_limit_fixes_per_day": "You can request a maximum of __limit__ fixes per day. Please try again tomorrow.",
   "you_cant_add_or_change_password_due_to_sso": "You can’t add or change your password because your group or organization uses <0>single sign-on (SSO)</0>.",
   "you_cant_add_or_change_password_due_to_sso": "You can’t add or change your password because your group or organization uses <0>single sign-on (SSO)</0>.",
   "you_cant_join_this_group_subscription": "You can’t join this group subscription",
   "you_cant_join_this_group_subscription": "You can’t join this group subscription",
@@ -2320,12 +2325,14 @@
   "your_sessions": "Your Sessions",
   "your_sessions": "Your Sessions",
   "your_subscription": "Your Subscription",
   "your_subscription": "Your Subscription",
   "your_subscription_has_expired": "Your subscription has expired.",
   "your_subscription_has_expired": "Your subscription has expired.",
+  "youre_a_member_of_overleaf_labs": "You’re a member of Overleaf Labs. Don’t forget to check in regularly to see what experiments you can sign up to.",
   "youre_about_to_disable_single_sign_on": "You’re about to disable single sign-on for all group members.",
   "youre_about_to_disable_single_sign_on": "You’re about to disable single sign-on for all group members.",
   "youre_about_to_enable_single_sign_on": "You’re about to enable single sign-on (SSO). Before you do this, you should ensure you’re confident the SSO configuration is correct and all your group members have managed user accounts.",
   "youre_about_to_enable_single_sign_on": "You’re about to enable single sign-on (SSO). Before you do this, you should ensure you’re confident the SSO configuration is correct and all your group members have managed user accounts.",
   "youre_about_to_enable_single_sign_on_sso_only": "You’re about to enable single sign-on (SSO). Before you do this, you should ensure you’re confident the SSO configuration is correct.",
   "youre_about_to_enable_single_sign_on_sso_only": "You’re about to enable single sign-on (SSO). Before you do this, you should ensure you’re confident the SSO configuration is correct.",
   "youre_already_setup_for_sso": "You’re already set up for SSO",
   "youre_already_setup_for_sso": "You’re already set up for SSO",
   "youre_on_free_trial_which_ends_on": "You’re on a free trial which ends on <0>__date__</0>.",
   "youre_on_free_trial_which_ends_on": "You’re on a free trial which ends on <0>__date__</0>.",
   "youre_signed_in_as_logout": "You’re signed in as <0>__email__</0>. <1>Log out.</1>",
   "youre_signed_in_as_logout": "You’re signed in as <0>__email__</0>. <1>Log out.</1>",
+  "youre_signed_up": "You’re signed up",
   "youve_unlinked_all_users": "You’ve unlinked all users",
   "youve_unlinked_all_users": "You’ve unlinked all users",
   "zh-CN": "Chinese",
   "zh-CN": "Chinese",
   "zip_contents_too_large": "Zip contents too large",
   "zip_contents_too_large": "Zip contents too large",

+ 19 - 16
services/web/types/user.ts

@@ -7,6 +7,24 @@ export type RefProviders = {
 
 
 export type UserId = Brand<string, 'UserId'>
 export type UserId = Brand<string, 'UserId'>
 
 
+export type Features = {
+  aiErrorAssistant?: boolean
+  collaborators?: number
+  compileGroup?: 'standard' | 'priority'
+  compileTimeout?: number
+  dropbox?: boolean
+  gitBridge?: boolean
+  github?: boolean
+  mendeley?: boolean
+  references?: boolean
+  referencesSearch?: boolean
+  symbolPalette?: boolean
+  templates?: boolean
+  trackChanges?: boolean
+  versioning?: boolean
+  zotero?: boolean
+}
+
 export type User = {
 export type User = {
   id: UserId
   id: UserId
   isAdmin?: boolean
   isAdmin?: boolean
@@ -18,22 +36,7 @@ export type User = {
   betaProgram?: boolean
   betaProgram?: boolean
   labsProgram?: boolean
   labsProgram?: boolean
   signUpDate?: string // date string
   signUpDate?: string // date string
-  features?: {
-    collaborators?: number
-    compileGroup?: 'standard' | 'priority'
-    compileTimeout?: number
-    dropbox?: boolean
-    gitBridge?: boolean
-    github?: boolean
-    mendeley?: boolean
-    references?: boolean
-    referencesSearch?: boolean
-    symbolPalette?: boolean
-    templates?: boolean
-    trackChanges?: boolean
-    versioning?: boolean
-    zotero?: boolean
-  }
+  features?: Features
   refProviders?: RefProviders
   refProviders?: RefProviders
   writefull?: {
   writefull?: {
     enabled: boolean
     enabled: boolean