diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index 5137807719..322d0df083 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -219,7 +219,6 @@
"buy_licenses": "",
"buy_more_licenses": "",
"buy_now_no_exclamation_mark": "",
- "by_continuing_you_agree_to_use_this_feature_in_line_with_your_institutions_policies": "",
"by_email": "",
"by_sharing_link": "",
"by_subscribing_you_agree_to_our_terms_of_service": "",
@@ -284,7 +283,6 @@
"choose_how_you_search_your_references": "",
"choose_which_experiments": "",
"citation": "",
- "citation_checks_are_powered_by_trusted_sources_from_dimensions": "",
"cite_a_reference_from_your_reference_manager_to_automatically_add_it_to_your_bib_file": "",
"cite_directly_or_import_references": "",
"cite_faster": "",
@@ -756,6 +754,7 @@
"github_workflow_files_delete_github_repo": "",
"github_workflow_files_error": "",
"github_workflow_files_error_non_owner": "",
+ "give-ai-consent": "",
"give_feedback": "",
"go_next_page": "",
"go_page": "",
@@ -1839,7 +1838,6 @@
"suggestion_applied": "",
"suggests_code_completions_while_typing": "",
"support_for_your_browser_is_ending_soon": "",
- "supporting_your_research_respecting_your_privacy": "",
"supports_up_to_x_licenses": "",
"sure_you_want_to_cancel_plan_change": "",
"sure_you_want_to_change_plan": "",
@@ -2318,7 +2316,6 @@
"you_have_x_licenses_and_your_plan_supports_up_to_y": "",
"you_have_x_licenses_on_your_subscription": "",
"you_need_to_configure_your_sso_settings": "",
- "you_remain_in_control_please_review_all_suggestions_carefully": "",
"you_unpaused_your_subscription": "",
"you_will_be_able_to_reassign_subscription": "",
"youll_get_best_results_in_visual_but_can_be_used_in_source": "",
@@ -2355,7 +2352,6 @@
"your_plan_is_limited_to_n_editors": "",
"your_plan_is_limited_to_n_editors_plural": "",
"your_premium_plan_is_paused": "",
- "your_project_content_is_never_used_for_model_training": "",
"your_project_exceeded_collaborator_limit": "",
"your_project_exceeded_compile_timeout_limit_on_free_plan": "",
"your_project_near_compile_timeout_limit": "",
diff --git a/services/web/frontend/js/shared/components/ai-consent-prompt-message.tsx b/services/web/frontend/js/shared/components/ai-consent-prompt-message.tsx
new file mode 100644
index 0000000000..50ecc4f4b2
--- /dev/null
+++ b/services/web/frontend/js/shared/components/ai-consent-prompt-message.tsx
@@ -0,0 +1,59 @@
+import React from 'react'
+
+interface ConsentPromptMessageBodyProps {
+ className?: string
+ listClassName?: string
+}
+
+interface ConsentPromptMessageHeaderProps {
+ className?: string
+}
+
+export const AiConsentPromptMessageHeader = ({
+ className,
+}: ConsentPromptMessageHeaderProps) => {
+ return (
+
+ Supporting your research and protecting your privacy
+
+ )
+}
+
+export const AiConsentPromptMessageBody = ({
+ className,
+ listClassName,
+}: ConsentPromptMessageBodyProps) => {
+ return (
+
+
+ -
+ AI features in Overleaf, like this one, can help you with writing your
+ document and fixing errors.
+
+ -
+ As some AI features are powered by third parties, your project
+ content/data will be sent to those third parties in order to provide
+ those features to you. However, your project content/data will not be
+ used for model training. There are more details about how we use your
+ data in{' '}
+
+ our docs
+
+ .
+
+ -
+ Like any AI, AI features in Overleaf can make mistakes, so please
+ review all suggestions carefully before accepting them.
+
+
+
+ By using these features, you approve the use and sharing of your data in
+ this way.
+
+
+ )
+}
diff --git a/services/web/frontend/js/shared/hooks/use-ai-consent.ts b/services/web/frontend/js/shared/hooks/use-ai-consent.ts
new file mode 100644
index 0000000000..e890232f4f
--- /dev/null
+++ b/services/web/frontend/js/shared/hooks/use-ai-consent.ts
@@ -0,0 +1,33 @@
+import { useCallback, useMemo, useState } from 'react'
+import useTutorial from '@/shared/hooks/promotions/use-tutorial'
+
+const AI_CONSENT_TUTORIAL_KEY = 'workbench-consent-release'
+const eventData = { name: AI_CONSENT_TUTORIAL_KEY }
+
+export { AI_CONSENT_TUTORIAL_KEY }
+
+export default function useAiConsent() {
+ const { completeTutorial, checkCompletion } = useTutorial(
+ AI_CONSENT_TUTORIAL_KEY,
+ eventData
+ )
+
+ const hasGivenAiConsent = useMemo(() => checkCompletion(), [checkCompletion])
+ const [consentError, setConsentError] = useState(false)
+
+ const giveAiConsent = useCallback(async () => {
+ setConsentError(false)
+ try {
+ await completeTutorial(
+ { event: 'promo-click', action: 'complete' },
+ { failSilently: false }
+ )
+ return true
+ } catch {
+ setConsentError(true)
+ return false
+ }
+ }, [completeTutorial])
+
+ return { hasGivenAiConsent, giveAiConsent, consentError }
+}
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 14a7aec202..5f5160044f 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -277,7 +277,6 @@
"buy_more_licenses": "Buy more licenses",
"buy_now_no_exclamation_mark": "Buy now",
"by": "by",
- "by_continuing_you_agree_to_use_this_feature_in_line_with_your_institutions_policies": "By continuing, you agree to use this feature in line with your institution’s policies.",
"by_email": "By email",
"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 service0> and <1>privacy notice1>.",
"by_registering_you_agree_to_our_terms_of_service": "By registering, you agree to our <0>terms of service0> and <1>privacy notice1>.",
@@ -356,7 +355,6 @@
"choose_which_experiments": "Choose which experiments you’d like to try.",
"choose_your_plan": "Choose your plan",
"citation": "Citation",
- "citation_checks_are_powered_by_trusted_sources_from_dimensions": "Citation checks are powered by trusted sources from Dimensions.",
"cite_a_reference_from_your_reference_manager_to_automatically_add_it_to_your_bib_file": "Cite a reference from your reference manager to automatically add it to your .bib file.",
"cite_directly_or_import_references": "Cite directly or import references",
"cite_faster": "Cite faster",
@@ -963,6 +961,7 @@
"github_workflow_files_delete_github_repo": "The repository has been created on GitHub but linking was unsuccessful. You will have to delete GitHub repository or choose a new name.",
"github_workflow_files_error": "The __appName__ GitHub sync service couldn’t sync GitHub Workflow files (in .github/workflows/). Please authorize __appName__ to edit your GitHub workflow files and try again.",
"github_workflow_files_error_non_owner": "The __appName__ GitHub sync service couldn’t sync GitHub Workflow files (in .github/workflows/). Please ensure the project owner has authorized __appName__ to edit this project’s GitHub workflow files and try again.",
+ "give-ai-consent": "Give AI consent",
"give_feedback": "Give feedback",
"global": "global",
"go_back_and_link_accts": "Go back and link your accounts",
@@ -2343,7 +2342,6 @@
"suggests_code_completions_while_typing": "Suggests code completions while typing",
"support": "Support",
"support_for_your_browser_is_ending_soon": "Support for your browser is ending soon",
- "supporting_your_research_respecting_your_privacy": "Supporting your research, respecting your privacy",
"supports_up_to_x_licenses": "Supports up to <0>__count__ licenses0>",
"sure_you_want_to_cancel_plan_change": "Are you sure you want to revert your scheduled plan change? You will remain subscribed to the <0>__planName__0> plan.",
"sure_you_want_to_change_plan": "Are you sure you want to change plan to <0>__planName__0>?",
@@ -2891,7 +2889,6 @@
"you_have_x_licenses_and_your_plan_supports_up_to_y": "You have allocated __addedUsersSize__ licenses and your plan supports up to __groupSize__.",
"you_have_x_licenses_on_your_subscription": "You have __groupSize__ licenses on your subscription.",
"you_need_to_configure_your_sso_settings": "You need to configure and test your SSO settings before enabling SSO",
- "you_remain_in_control_please_review_all_suggestions_carefully": "You remain in control, so please review all suggestions carefully before accepting them.",
"you_unpaused_your_subscription": "You unpaused your subscription.",
"you_will_be_able_to_contact_us_any_time_to_share_your_feedback": "<0>You will be able to contact us0> any time to share your feedback",
"you_will_be_able_to_reassign_subscription": "You will be able to reassign their subscription membership to another person in your organization",
@@ -2932,7 +2929,6 @@
"your_plan_is_limited_to_n_editors": "Your plan allows __count__ collaborator with edit access and unlimited viewers.",
"your_plan_is_limited_to_n_editors_plural": "Your plan allows __count__ collaborators with edit access and unlimited viewers.",
"your_premium_plan_is_paused": "Your Premium plan is <0>paused0>.",
- "your_project_content_is_never_used_for_model_training": "Your project content is never used for model training. There are more details about how we use your data in <0>our docs0>.",
"your_project_exceeded_collaborator_limit": "Your project exceeded the collaborator limit and access levels were changed. Select a new access level for your collaborators, or upgrade to add more editors or reviewers.",
"your_project_exceeded_compile_timeout_limit_on_free_plan": "Your project exceeded the compile timeout limit on our free plan.",
"your_project_near_compile_timeout_limit": "Your project is near the compile timeout limit for our free plan.",
diff --git a/services/web/test/frontend/features/workbench/chat.spec.tsx b/services/web/test/frontend/features/workbench/chat.spec.tsx
index 3f9ae43f3f..5c95bd29d1 100644
--- a/services/web/test/frontend/features/workbench/chat.spec.tsx
+++ b/services/web/test/frontend/features/workbench/chat.spec.tsx
@@ -1,4 +1,4 @@
-import { consentTutorialKey } from '@modules/workbench/frontend/js/components/chat'
+import { AI_CONSENT_TUTORIAL_KEY } from '@/shared/hooks/use-ai-consent'
import Workbench from '@modules/workbench/frontend/js/components/workbench'
import { EditorProviders } from '../../helpers/editor-providers'
import { EditorView } from '@codemirror/view'
@@ -13,7 +13,9 @@ describe('Workbench', { scrollBehavior: false }, function () {
win.metaAttributesCache.set('ol-splitTestVariants', {
'ai-workbench-release': 'enabled',
})
- win.metaAttributesCache.set('ol-inactiveTutorials', [consentTutorialKey])
+ win.metaAttributesCache.set('ol-inactiveTutorials', [
+ AI_CONSENT_TUTORIAL_KEY,
+ ])
})
})
diff --git a/services/web/test/frontend/helpers/editor-providers.tsx b/services/web/test/frontend/helpers/editor-providers.tsx
index 6c3da56ae0..a3186c4ad5 100644
--- a/services/web/test/frontend/helpers/editor-providers.tsx
+++ b/services/web/test/frontend/helpers/editor-providers.tsx
@@ -55,7 +55,6 @@ import { DetachCompileContext } from '@/shared/context/detach-compile-context'
import { type CompileContext } from '@/shared/context/local-compile-context'
import { EditorContext } from '@/shared/context/editor-context'
import { Cobranding } from '@ol-types/cobranding'
-import { TutorialContext } from '@/shared/context/tutorial-context'
import { EDITOR_SESSION_ID } from '@/features/pdf-preview/util/metrics'
// these constants can be imported in tests instead of
@@ -311,25 +310,6 @@ export function makeEditorProvider({
return EditorProvider
}
-export const makeTutorialProvider = (opts?: {
- inactiveTutorials: string[]
-}) => {
- const TutorialProvider: FC = ({ children }) => {
- const value = {
- deactivateTutorial: () => {},
- inactiveTutorials: opts?.inactiveTutorials ?? [],
- currentPopup: null,
- setCurrentPopup: () => {},
- }
- return (
-
- {children}
-
- )
- }
- return TutorialProvider
-}
-
const makeReferencesProvider = () => {
const ReferencesProvider: FC = ({ children }) => {
return (
diff --git a/services/web/test/frontend/helpers/make-tutorial-provider.tsx b/services/web/test/frontend/helpers/make-tutorial-provider.tsx
new file mode 100644
index 0000000000..01dff51702
--- /dev/null
+++ b/services/web/test/frontend/helpers/make-tutorial-provider.tsx
@@ -0,0 +1,32 @@
+import React, {
+ type FC,
+ type PropsWithChildren,
+ useCallback,
+ useState,
+} from 'react'
+import { TutorialContext } from '@/shared/context/tutorial-context'
+
+export const makeTutorialProvider = (opts?: {
+ inactiveTutorials: string[]
+}) => {
+ const TutorialProvider: FC = ({ children }) => {
+ const [inactiveTutorials, setInactiveTutorials] = useState(
+ opts?.inactiveTutorials ?? []
+ )
+ const deactivateTutorial = useCallback((key: string) => {
+ setInactiveTutorials(prev => (prev.includes(key) ? prev : [...prev, key]))
+ }, [])
+ const value = {
+ deactivateTutorial,
+ inactiveTutorials,
+ currentPopup: null,
+ setCurrentPopup: () => {},
+ }
+ return (
+
+ {children}
+
+ )
+ }
+ return TutorialProvider
+}
diff --git a/services/web/test/frontend/shared/hooks/use-tutorial.spec.tsx b/services/web/test/frontend/shared/hooks/use-tutorial.spec.tsx
index 698441aff1..21329bef06 100644
--- a/services/web/test/frontend/shared/hooks/use-tutorial.spec.tsx
+++ b/services/web/test/frontend/shared/hooks/use-tutorial.spec.tsx
@@ -1,9 +1,7 @@
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
import { useEffect, useState } from 'react'
-import {
- EditorProviders,
- makeTutorialProvider,
-} from '../../helpers/editor-providers'
+import { EditorProviders } from '../../helpers/editor-providers'
+import { makeTutorialProvider } from '../../helpers/make-tutorial-provider'
const TutorialTester = ({
tutorial,