Merge pull request #21107 from overleaf/jdt-error-assistant-freemium

Convert Ai Error Assistant to a Freemium Model

GitOrigin-RevId: 348c19262928d7dde8236baf37663c85d91f101a
This commit is contained in:
Jimmy Domagala-Tang 2024-11-04 08:44:40 -08:00 committed by Copybot
parent 745695650c
commit 811e935ced
6 changed files with 84 additions and 8 deletions

View file

@ -348,6 +348,9 @@ const _ProjectController = {
'write-and-cite-ars', 'write-and-cite-ars',
'default-visual-for-beginners', 'default-visual-for-beginners',
'hotjar', 'hotjar',
'spell-check-client',
'spell-check-no-server',
'ai-add-on',
].filter(Boolean) ].filter(Boolean)
const getUserValues = async userId => const getUserValues = async userId =>
@ -646,6 +649,21 @@ const _ProjectController = {
aiFeaturesAllowed = false aiFeaturesAllowed = false
} }
} }
const canUseErrorAssistant =
user.features?.aiErrorAssistant ||
splitTestAssignments['ai-add-on']?.variant === 'enabled'
let featureUsage = {}
if (Features.hasFeature('saas')) {
const usagesLeft = await Modules.promises.hooks.fire(
'remainingFeatureAllocation',
userId
)
usagesLeft?.forEach(usage => {
featureUsage = { ...featureUsage, ...usage }
})
}
// check if a user has never tried writefull before (writefull.enabled will be null) // check if a user has never tried writefull before (writefull.enabled will be null)
// if they previously accepted writefull, or are have been already assigned to a trial, user.writefull will be true, // if they previously accepted writefull, or are have been already assigned to a trial, user.writefull will be true,
@ -713,6 +731,7 @@ const _ProjectController = {
allowedFreeTrial, allowedFreeTrial,
featureSwitches: user.featureSwitches, featureSwitches: user.featureSwitches,
features: user.features, features: user.features,
featureUsage,
refProviders: _.mapValues(user.refProviders, Boolean), refProviders: _.mapValues(user.refProviders, Boolean),
writefull: { writefull: {
enabled: Boolean(user.writefull?.enabled && aiFeaturesAllowed), enabled: Boolean(user.writefull?.enabled && aiFeaturesAllowed),
@ -766,8 +785,7 @@ const _ProjectController = {
showSymbolPalette, showSymbolPalette,
symbolPaletteAvailable: Features.hasFeature('symbol-palette'), symbolPaletteAvailable: Features.hasFeature('symbol-palette'),
userRestrictions: Array.from(req.userRestrictions || []), userRestrictions: Array.from(req.userRestrictions || []),
showAiErrorAssistant: showAiErrorAssistant: aiFeaturesAllowed && canUseErrorAssistant,
aiFeaturesAllowed && user.features?.aiErrorAssistant,
detachRole, detachRole,
metadata: { viewport: false }, metadata: { viewport: false },
showUpgradePrompt, showUpgradePrompt,

View file

@ -152,6 +152,8 @@
"browser": "", "browser": "",
"bulk_accept_confirm": "", "bulk_accept_confirm": "",
"bulk_reject_confirm": "", "bulk_reject_confirm": "",
"buy_error_assistant_for": "",
"buy_now_no_exclamation_mark": "",
"buy_overleaf_assist": "", "buy_overleaf_assist": "",
"by_subscribing_you_agree_to_our_terms_of_service": "", "by_subscribing_you_agree_to_our_terms_of_service": "",
"can_edit": "", "can_edit": "",
@ -483,6 +485,7 @@
"first_name": "", "first_name": "",
"fit_to_height": "", "fit_to_height": "",
"fit_to_width": "", "fit_to_width": "",
"fix_latex_errors_fast_and_perfect_your_projects": "",
"fixed_width": "", "fixed_width": "",
"fixed_width_wrap_text": "", "fixed_width_wrap_text": "",
"fold_line": "", "fold_line": "",
@ -525,6 +528,7 @@
"get_discounted_plan": "", "get_discounted_plan": "",
"get_dropbox_sync": "", "get_dropbox_sync": "",
"get_early_access_to_ai": "", "get_early_access_to_ai": "",
"get_error_assistant": "",
"get_exclusive_access_to_labs": "", "get_exclusive_access_to_labs": "",
"get_full_project_history": "", "get_full_project_history": "",
"get_git_integration": "", "get_git_integration": "",
@ -763,6 +767,7 @@
"last_used": "", "last_used": "",
"latam_discount_modal_info": "", "latam_discount_modal_info": "",
"latam_discount_modal_title": "", "latam_discount_modal_title": "",
"latex_error_fixing_with_a_little_help": "",
"latex_in_thirty_minutes": "", "latex_in_thirty_minutes": "",
"latex_places_figures_according_to_a_special_algorithm": "", "latex_places_figures_according_to_a_special_algorithm": "",
"latex_places_tables_according_to_a_special_algorithm": "", "latex_places_tables_according_to_a_special_algorithm": "",
@ -1068,6 +1073,7 @@
"premium_feature": "", "premium_feature": "",
"premium_features": "", "premium_features": "",
"premium_plan_label": "", "premium_plan_label": "",
"premium_suggestion_available": "",
"presentation_mode": "", "presentation_mode": "",
"press_and_awards": "", "press_and_awards": "",
"previous_page": "", "previous_page": "",

View file

@ -31,11 +31,24 @@ export const useLogEvents = (setShowLogs: (show: boolean) => void) => {
}) })
if (suggestFix) { if (suggestFix) {
element // if they are paywalled, click that instead
.querySelector<HTMLButtonElement>( const paywall = document.querySelector<HTMLButtonElement>(
'button[data-action="suggest-fix"]' 'button[data-action="assistant-paywall-show"]'
) )
?.click()
if (paywall) {
paywall.scrollIntoView({
block: 'start',
inline: 'nearest',
})
paywall.click()
} else {
element
.querySelector<HTMLButtonElement>(
'button[data-action="suggest-fix"]'
)
?.click()
}
} }
} }
}) })

View file

@ -49,13 +49,17 @@ export const EditorContext = createContext<
currentPopup: string | null currentPopup: string | null
setCurrentPopup: Dispatch<SetStateAction<string | null>> setCurrentPopup: Dispatch<SetStateAction<string | null>>
setOutOfSync: (value: boolean) => void setOutOfSync: (value: boolean) => void
hasPremiumSuggestion: boolean
setHasPremiumSuggestion: (value: boolean) => void
setPremiumSuggestionResetDate: (date: Date) => void
premiumSuggestionResetDate: Date
} }
| undefined | undefined
>(undefined) >(undefined)
export const EditorProvider: FC = ({ children }) => { export const EditorProvider: FC = ({ children }) => {
const ide = useIdeContext() const ide = useIdeContext()
const { id: userId } = useUserContext() const { id: userId, featureUsage } = useUserContext()
const { role } = useDetachContext() const { role } = useDetachContext()
const { showGenericMessageModal } = useModalsContext() const { showGenericMessageModal } = useModalsContext()
@ -91,6 +95,20 @@ export const EditorProvider: FC = ({ children }) => {
) )
const [currentPopup, setCurrentPopup] = useState<string | null>(null) const [currentPopup, setCurrentPopup] = useState<string | null>(null)
const [hasPremiumSuggestion, setHasPremiumSuggestion] = useState<boolean>(
() => {
return Boolean(
featureUsage?.aiErrorAssistant &&
featureUsage?.aiErrorAssistant.remainingUsage > 0
)
}
)
const [premiumSuggestionResetDate, setPremiumSuggestionResetDate] =
useState<Date>(() => {
return featureUsage?.aiErrorAssistant.resetDate
? new Date(featureUsage.aiErrorAssistant.resetDate)
: new Date()
})
const isPendingEditor = useMemo( const isPendingEditor = useMemo(
() => () =>
@ -183,6 +201,10 @@ export const EditorProvider: FC = ({ children }) => {
currentPopup, currentPopup,
setCurrentPopup, setCurrentPopup,
setOutOfSync, setOutOfSync,
hasPremiumSuggestion,
setHasPremiumSuggestion,
premiumSuggestionResetDate,
setPremiumSuggestionResetDate,
}), }),
[ [
cobranding, cobranding,
@ -203,6 +225,10 @@ export const EditorProvider: FC = ({ children }) => {
setCurrentPopup, setCurrentPopup,
outOfSync, outOfSync,
setOutOfSync, setOutOfSync,
hasPremiumSuggestion,
setHasPremiumSuggestion,
premiumSuggestionResetDate,
setPremiumSuggestionResetDate,
] ]
) )

View file

@ -208,6 +208,7 @@
"built_in": "Built-In", "built_in": "Built-In",
"bulk_accept_confirm": "Are you sure you want to accept the selected __nChanges__ changes?", "bulk_accept_confirm": "Are you sure you want to accept the selected __nChanges__ changes?",
"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_error_assistant_for": "<strong>Buy Error Assist for $__aiAddonCost__ per year</strong>",
"buy_now_no_exclamation_mark": "Buy now", "buy_now_no_exclamation_mark": "Buy now",
"buy_overleaf_assist": "Buy Overleaf Assist", "buy_overleaf_assist": "Buy Overleaf Assist",
"by": "by", "by": "by",
@ -690,6 +691,7 @@
"first_name_sentence_case": "First name", "first_name_sentence_case": "First name",
"fit_to_height": "Fit to height", "fit_to_height": "Fit to height",
"fit_to_width": "Fit to width", "fit_to_width": "Fit to width",
"fix_latex_errors_fast_and_perfect_your_projects": "Fix LaTeX errors fast and perfect your projects with the AI-powered Error Assist.",
"fixed_width": "Fixed width", "fixed_width": "Fixed width",
"fixed_width_wrap_text": "Fixed width, wrap text", "fixed_width_wrap_text": "Fixed width, wrap text",
"flexible_plans_for_everyone": "Flexible plans for everyone—from individual students and researchers, to large businesses and universities.", "flexible_plans_for_everyone": "Flexible plans for everyone—from individual students and researchers, to large businesses and universities.",
@ -762,6 +764,7 @@
"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_early_access_to_ai": "Get early access to the new AI Error Assistant in Overleaf Labs", "get_early_access_to_ai": "Get early access to the new AI Error Assistant in Overleaf Labs",
"get_error_assistant": "Get Error Assistant",
"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_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",
@ -1089,6 +1092,7 @@
"latam_discount_offer_plans_page_banner": "__flag__ Weve applied a __discount__ discount to premium plans on this page for our users in __country__. Check out the new lower prices (in __currency__).", "latam_discount_offer_plans_page_banner": "__flag__ Weve applied a __discount__ discount to premium plans on this page for our users in __country__. Check out the new lower prices (in __currency__).",
"latex_articles_page_summary": "Papers, presentations, reports and more, written in LaTeX and published by our community. Search or browse below.", "latex_articles_page_summary": "Papers, presentations, reports and more, written in LaTeX and published by our community. Search or browse below.",
"latex_articles_page_title": "Articles - Papers, Presentations, Reports and more", "latex_articles_page_title": "Articles - Papers, Presentations, Reports and more",
"latex_error_fixing_with_a_little_help": "LaTeX error fixing with a little help from AI",
"latex_examples": "LaTeX examples", "latex_examples": "LaTeX examples",
"latex_examples_page_summary": "Examples of powerful LaTeX packages and techniques in use — a great way to learn LaTeX by example. Search or browse below.", "latex_examples_page_summary": "Examples of powerful LaTeX packages and techniques in use — a great way to learn LaTeX by example. Search or browse below.",
"latex_examples_page_title": "Examples - Equations, Formatting, TikZ, Packages and More", "latex_examples_page_title": "Examples - Equations, Formatting, TikZ, Packages and More",
@ -1549,6 +1553,7 @@
"premium_feature": "Premium feature", "premium_feature": "Premium feature",
"premium_features": "Premium features", "premium_features": "Premium features",
"premium_plan_label": "Youre using <b>Overleaf Premium</b>", "premium_plan_label": "Youre using <b>Overleaf Premium</b>",
"premium_suggestion_available": "Premium suggestion available",
"presentation": "Presentation", "presentation": "Presentation",
"presentation_mode": "Presentation mode", "presentation_mode": "Presentation mode",
"press_and_awards": "Press & awards", "press_and_awards": "Press & awards",

View file

@ -25,6 +25,13 @@ export type Features = {
zotero?: boolean zotero?: boolean
} }
export type FeatureUsage = {
[feature: string]: {
remainingUsage: number
resetDate: string // date string
}
}
export type User = { export type User = {
id: UserId | null id: UserId | null
isAdmin?: boolean isAdmin?: boolean
@ -44,6 +51,7 @@ export type User = {
autoCreatedAccount: boolean autoCreatedAccount: boolean
firstAutoLoad: boolean firstAutoLoad: boolean
} }
featureUsage?: FeatureUsage
} }
export type MongoUser = Pick<User, Exclude<keyof User, 'id'>> & { _id: string } export type MongoUser = Pick<User, Exclude<keyof User, 'id'>> & { _id: string }