Merge pull request #31737 from overleaf/mg-shared-ai-consent

Migrate Writefull AI consent to shared tutorial system

GitOrigin-RevId: c9298a177c9f1aa1a941c96599d6d854591f8a76
This commit is contained in:
Malik Glossop
2026-03-20 09:08:31 +01:00
committed by Copybot
parent 6901a2a8de
commit a598c552a5
8 changed files with 132 additions and 36 deletions

View File

@@ -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": "",

View File

@@ -0,0 +1,59 @@
import React from 'react'
interface ConsentPromptMessageBodyProps {
className?: string
listClassName?: string
}
interface ConsentPromptMessageHeaderProps {
className?: string
}
export const AiConsentPromptMessageHeader = ({
className,
}: ConsentPromptMessageHeaderProps) => {
return (
<div className={className}>
Supporting your research and protecting your privacy
</div>
)
}
export const AiConsentPromptMessageBody = ({
className,
listClassName,
}: ConsentPromptMessageBodyProps) => {
return (
<div className={className}>
<ul className={listClassName}>
<li>
AI features in Overleaf, like this one, can help you with writing your
document and fixing errors.
</li>
<li>
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{' '}
<a
href="https://docs.overleaf.com/integrations-and-add-ons/ai-features#data-privacy-and-responsible-use"
target="_blank"
rel="noreferrer"
>
our docs
</a>
.
</li>
<li>
Like any AI, AI features in Overleaf can make mistakes, so please
review all suggestions carefully before accepting them.
</li>
</ul>
<p>
By using these features, you approve the use and sharing of your data in
this way.
</p>
</div>
)
}

View File

@@ -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 }
}

View File

@@ -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 institutions 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 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>.",
@@ -356,7 +355,6 @@
"choose_which_experiments": "Choose which experiments youd 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 couldnt 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 couldnt sync GitHub Workflow files (in .github/workflows/). Please ensure the project owner has authorized __appName__ to edit this projects GitHub workflow files and try again.",
"give-ai-consent": "Give AI consent",
"give_feedback": "Give feedback",
"global": "global",
"go_back_and_link_accts": "<a href=\"__link__\">Go back</a> 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__ licenses</0>",
"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 us</0> 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>paused</0>.",
"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 docs</0>.",
"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.",

View File

@@ -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,
])
})
})

View File

@@ -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<PropsWithChildren> = ({ children }) => {
const value = {
deactivateTutorial: () => {},
inactiveTutorials: opts?.inactiveTutorials ?? [],
currentPopup: null,
setCurrentPopup: () => {},
}
return (
<TutorialContext.Provider value={value}>
{children}
</TutorialContext.Provider>
)
}
return TutorialProvider
}
const makeReferencesProvider = () => {
const ReferencesProvider: FC<PropsWithChildren> = ({ children }) => {
return (

View File

@@ -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<PropsWithChildren> = ({ children }) => {
const [inactiveTutorials, setInactiveTutorials] = useState<string[]>(
opts?.inactiveTutorials ?? []
)
const deactivateTutorial = useCallback((key: string) => {
setInactiveTutorials(prev => (prev.includes(key) ? prev : [...prev, key]))
}, [])
const value = {
deactivateTutorial,
inactiveTutorials,
currentPopup: null,
setCurrentPopup: () => {},
}
return (
<TutorialContext.Provider value={value}>
{children}
</TutorialContext.Provider>
)
}
return TutorialProvider
}

View File

@@ -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,