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',
'default-visual-for-beginners',
'hotjar',
'spell-check-client',
'spell-check-no-server',
'ai-add-on',
].filter(Boolean)
const getUserValues = async userId =>
@ -646,6 +649,21 @@ const _ProjectController = {
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)
// 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,
featureSwitches: user.featureSwitches,
features: user.features,
featureUsage,
refProviders: _.mapValues(user.refProviders, Boolean),
writefull: {
enabled: Boolean(user.writefull?.enabled && aiFeaturesAllowed),
@ -766,8 +785,7 @@ const _ProjectController = {
showSymbolPalette,
symbolPaletteAvailable: Features.hasFeature('symbol-palette'),
userRestrictions: Array.from(req.userRestrictions || []),
showAiErrorAssistant:
aiFeaturesAllowed && user.features?.aiErrorAssistant,
showAiErrorAssistant: aiFeaturesAllowed && canUseErrorAssistant,
detachRole,
metadata: { viewport: false },
showUpgradePrompt,

View file

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

View file

@ -31,11 +31,24 @@ export const useLogEvents = (setShowLogs: (show: boolean) => void) => {
})
if (suggestFix) {
element
.querySelector<HTMLButtonElement>(
'button[data-action="suggest-fix"]'
)
?.click()
// if they are paywalled, click that instead
const paywall = document.querySelector<HTMLButtonElement>(
'button[data-action="assistant-paywall-show"]'
)
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
setCurrentPopup: Dispatch<SetStateAction<string | null>>
setOutOfSync: (value: boolean) => void
hasPremiumSuggestion: boolean
setHasPremiumSuggestion: (value: boolean) => void
setPremiumSuggestionResetDate: (date: Date) => void
premiumSuggestionResetDate: Date
}
| undefined
>(undefined)
export const EditorProvider: FC = ({ children }) => {
const ide = useIdeContext()
const { id: userId } = useUserContext()
const { id: userId, featureUsage } = useUserContext()
const { role } = useDetachContext()
const { showGenericMessageModal } = useModalsContext()
@ -91,6 +95,20 @@ export const EditorProvider: FC = ({ children }) => {
)
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(
() =>
@ -183,6 +201,10 @@ export const EditorProvider: FC = ({ children }) => {
currentPopup,
setCurrentPopup,
setOutOfSync,
hasPremiumSuggestion,
setHasPremiumSuggestion,
premiumSuggestionResetDate,
setPremiumSuggestionResetDate,
}),
[
cobranding,
@ -203,6 +225,10 @@ export const EditorProvider: FC = ({ children }) => {
setCurrentPopup,
outOfSync,
setOutOfSync,
hasPremiumSuggestion,
setHasPremiumSuggestion,
premiumSuggestionResetDate,
setPremiumSuggestionResetDate,
]
)

View file

@ -208,6 +208,7 @@
"built_in": "Built-In",
"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?",
"buy_error_assistant_for": "<strong>Buy Error Assist for $__aiAddonCost__ per year</strong>",
"buy_now_no_exclamation_mark": "Buy now",
"buy_overleaf_assist": "Buy Overleaf Assist",
"by": "by",
@ -690,6 +691,7 @@
"first_name_sentence_case": "First name",
"fit_to_height": "Fit to height",
"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_wrap_text": "Fixed width, wrap text",
"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_dropbox_sync": "Get Dropbox Sync",
"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_full_project_history": "Get full project history",
"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__).",
"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_error_fixing_with_a_little_help": "LaTeX error fixing with a little help from AI",
"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_title": "Examples - Equations, Formatting, TikZ, Packages and More",
@ -1549,6 +1553,7 @@
"premium_feature": "Premium feature",
"premium_features": "Premium features",
"premium_plan_label": "Youre using <b>Overleaf Premium</b>",
"premium_suggestion_available": "Premium suggestion available",
"presentation": "Presentation",
"presentation_mode": "Presentation mode",
"press_and_awards": "Press & awards",

View file

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