mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-11 15:10:48 +02:00
This reverts commit 17763447965aae5777053b783d2601517bfe6b12. GitOrigin-RevId: f6589bdbf0ac7e71313739e3e3f4fb5bedd48c22
This commit is contained in:
committed by
Copybot
parent
92463fb3e2
commit
892047fcf6
@@ -15,14 +15,7 @@ async function _getInstitutionsAddons(userId) {
|
||||
const hasAssistBundle = affiliates.some(
|
||||
affiliate => affiliate?.institution?.writefullCommonsAccount === true
|
||||
)
|
||||
|
||||
// todo: seperate quota value depending on source of entitlement if needed
|
||||
// todo: quota clean-up: remove aiErrorAssistant once migration finishes
|
||||
const bundleFeatures = {
|
||||
aiUsageQuota: Settings.writefull.quotaTierGranted,
|
||||
aiErrorAssistant: true,
|
||||
}
|
||||
return hasAssistBundle ? bundleFeatures : {}
|
||||
return hasAssistBundle ? { aiErrorAssistant: true } : {}
|
||||
}
|
||||
|
||||
async function getInstitutionsFeatures(userId) {
|
||||
|
||||
@@ -952,8 +952,6 @@ const _ProjectController = {
|
||||
symbolPaletteAvailable: Features.hasFeature('symbol-palette'),
|
||||
userRestrictions: Array.from(req.userRestrictions || []),
|
||||
showAiFeatures: aiFeaturesAllowed && !aiFeaturesDisabled,
|
||||
onAiFreeTrial:
|
||||
user.features?.aiUsageQuota === Settings.aiFeatures?.freeTrialQuota,
|
||||
detachRole,
|
||||
metadata: { viewport: false },
|
||||
showUpgradePrompt,
|
||||
|
||||
@@ -510,10 +510,8 @@ async function projectListPage(req, res, next) {
|
||||
logger.error({ err: error }, 'Failed to get individual subscription')
|
||||
}
|
||||
|
||||
const aiBlocked =
|
||||
Features.hasFeature('saas') && !(await _canUseAIAssist(user))
|
||||
const hasAiAssist =
|
||||
Features.hasFeature('saas') && (await _userHasAIAssist(user))
|
||||
const aiBlocked = !(await _canUseAIAssist(user))
|
||||
const hasAiAssist = await _userHasAIAssist(user)
|
||||
|
||||
await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
@@ -901,13 +899,11 @@ function _hasActiveFilter(filters) {
|
||||
)
|
||||
}
|
||||
|
||||
// todo: quota clean-up: rename function and vars
|
||||
async function _userHasAIAssist(user) {
|
||||
// Check if the user has a non free trial version of our AI features
|
||||
if (user.features?.aiUsageQuota === Settings.aiFeatures.unlimitedQuota) {
|
||||
// Check if the user has AI Assist enabled via Overleaf
|
||||
if (user.features?.aiErrorAssistant) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if the user has AI Assist enabled via Writefull
|
||||
const { isPremium: hasAiAssistViaWritefull } =
|
||||
await UserGetter.promises.getWritefullData(user._id)
|
||||
@@ -922,7 +918,6 @@ async function _userHasAIAssist(user) {
|
||||
// It does NOT determine if the user has AI Assist enabled
|
||||
async function _canUseAIAssist(user) {
|
||||
// Check if the assistant has been manually disabled by the user
|
||||
// post https://github.com/overleaf/internal/pull/31273 we can rely on user.aiFeatures being populated
|
||||
if (user.aiFeatures?.enabled === false) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -306,20 +306,6 @@ async function getOneTimeAssignment(splitTestName) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a feature flag is enabled for a specific user
|
||||
*
|
||||
* Retrieves the feature flag assignment for a user and determines if the assigned variant is 'enabled'
|
||||
*
|
||||
* @param {string} userId - The ID of the user to check the feature flag for
|
||||
* @param {string} splitTestName - The unique name of the feature flag
|
||||
* @returns {Promise<boolean>} True if the user's assigned variant is 'enabled', false otherwise
|
||||
*/
|
||||
async function featureFlagEnabledForUser(userId, splitTestName) {
|
||||
const { variant } = await getAssignmentForUser(userId, splitTestName)
|
||||
return variant === 'enabled'
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of valid variant names for the given split test, including default
|
||||
*
|
||||
@@ -816,7 +802,6 @@ export default {
|
||||
getPercentile,
|
||||
getAssignment: callbackify(getAssignment),
|
||||
getAssignmentForUser: callbackify(getAssignmentForUser),
|
||||
featureFlagEnabledForUser: callbackify(featureFlagEnabledForUser),
|
||||
getOneTimeAssignment: callbackify(getOneTimeAssignment),
|
||||
getActiveAssignmentsForUser: callbackify(getActiveAssignmentsForUser),
|
||||
hasUserBeenAssignedToVariant: callbackify(hasUserBeenAssignedToVariant),
|
||||
@@ -825,7 +810,6 @@ export default {
|
||||
promises: {
|
||||
getAssignment,
|
||||
getAssignmentForUser,
|
||||
featureFlagEnabledForUser,
|
||||
getOneTimeAssignment,
|
||||
getActiveAssignmentsForUser,
|
||||
hasUserBeenAssignedToVariant,
|
||||
|
||||
@@ -38,15 +38,6 @@ function mergeFeatures(featuresA, featuresB) {
|
||||
features.compileTimeout || 0,
|
||||
featuresB.compileTimeout || 0
|
||||
)
|
||||
} else if (key === 'aiUsageQuota') {
|
||||
if (
|
||||
features.aiUsageQuota === Settings.aiFeatures.unlimitedQuota ||
|
||||
featuresB.aiUsageQuota === Settings.aiFeatures.unlimitedQuota
|
||||
) {
|
||||
features.aiUsageQuota = Settings.aiFeatures.unlimitedQuota
|
||||
} else {
|
||||
features.aiUsageQuota = Settings.aiFeatures.freeTrialQuota
|
||||
}
|
||||
} else {
|
||||
// Boolean keys, true is better
|
||||
features[key] = features[key] || featuresB[key]
|
||||
|
||||
@@ -100,10 +100,7 @@ async function refreshFeatures(userId, reason) {
|
||||
},
|
||||
json: {
|
||||
userOverleafId: userId,
|
||||
// todo: quota clean-up: collab with writefull to rename this, and check if still needed
|
||||
hasAiAssist:
|
||||
newFeatures.aiErrorAssistant ||
|
||||
newFeatures.aiUsageQuota === Settings.aiFeatures.unlimitedQuota,
|
||||
hasAiAssist: newFeatures.aiErrorAssistant,
|
||||
},
|
||||
method: 'POST',
|
||||
}
|
||||
@@ -195,9 +192,6 @@ async function _getIndividualFeatures(userId) {
|
||||
featureSets.push(_subscriptionToFeatures(subscription))
|
||||
}
|
||||
|
||||
// todo: quota clean-up - remove
|
||||
// if they are in the quota split test, we no longer look at the add-on, since every plan will now have the same quota
|
||||
// standalone plan will receive correct state since their plan will provide the correct quota
|
||||
featureSets.push(_aiAddOnFeatures(subscription))
|
||||
return _.reduce(featureSets, FeaturesHelper.mergeFeatures, {})
|
||||
}
|
||||
@@ -243,14 +237,9 @@ function _subscriptionToFeatures(subscription) {
|
||||
}
|
||||
}
|
||||
|
||||
// todo: quota clean-up: remove post split test
|
||||
function _aiAddOnFeatures(subscription) {
|
||||
if (subscription?.addOns?.some(addOn => addOn.addOnCode === AI_ADD_ON_CODE)) {
|
||||
return {
|
||||
// allow both naming systems to work
|
||||
aiErrorAssistant: true,
|
||||
aiUsageQuota: Settings.aiFeatures.unlimitedQuota,
|
||||
}
|
||||
return { aiErrorAssistant: true }
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ export default class FeatureUsageRateLimiter {
|
||||
upsert: true,
|
||||
}
|
||||
).exec()
|
||||
|
||||
const featureUsage = featureUsages.features?.[this.featureName] ?? {}
|
||||
setRateLimitHeaders(res, featureUsage, allowance)
|
||||
this._checkRateLimit(featureUsage, allowance)
|
||||
@@ -184,6 +185,7 @@ function setRateLimitHeaders(res, featureUsage, allowance) {
|
||||
const usage = featureUsage.usage ?? 0
|
||||
const refreshEpoch = periodStart.getTime() + PERIOD_IN_MILLISECONDS
|
||||
const secondsTillReset = Math.ceil((refreshEpoch - Date.now()) / 1000)
|
||||
|
||||
if (!res.headersSent) {
|
||||
res.set('RateLimit-Limit', String(allowance))
|
||||
res.set('RateLimit-Remaining', String(Math.max(0, allowance - usage)))
|
||||
|
||||
@@ -143,7 +143,6 @@ export const UserSchema = new Schema(
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
aiUsageQuota: { type: String, default: 'basic' },
|
||||
},
|
||||
featuresOverrides: [
|
||||
{
|
||||
@@ -156,9 +155,7 @@ export const UserSchema = new Schema(
|
||||
expiresAt: { type: Date },
|
||||
note: { type: String },
|
||||
features: {
|
||||
// todo: quota clean-up: remove aiErrorAssistant
|
||||
aiErrorAssistant: { type: Boolean },
|
||||
aiUsageQuota: { type: String },
|
||||
collaborators: { type: Number },
|
||||
versioning: { type: Boolean },
|
||||
dropbox: { type: Boolean },
|
||||
|
||||
@@ -25,7 +25,6 @@ meta(name="ol-debugPdfDetach" data-type="boolean" content=debugPdfDetach)
|
||||
meta(name="ol-showSymbolPalette" data-type="boolean" content=showSymbolPalette)
|
||||
meta(name="ol-symbolPaletteAvailable" data-type="boolean" content=symbolPaletteAvailable)
|
||||
meta(name="ol-showAiFeatures" data-type="boolean" content=showAiFeatures)
|
||||
meta(name="ol-onAiFreeTrial" data-type="boolean" content=onAiFreeTrial)
|
||||
meta(name="ol-detachRole" data-type="string" content=detachRole)
|
||||
meta(name="ol-imageNames" data-type="json" content=imageNames)
|
||||
meta(name="ol-languages" data-type="json" content=languages)
|
||||
|
||||
@@ -419,11 +419,6 @@ module.exports = {
|
||||
personal: defaultFeatures,
|
||||
},
|
||||
|
||||
aiFeatures: {
|
||||
freeTrialQuota: 'basic',
|
||||
unlimitedQuota: 'unlimited',
|
||||
},
|
||||
|
||||
groupPlanModalOptions: {
|
||||
plan_codes: [],
|
||||
currencies: [],
|
||||
|
||||
@@ -11,12 +11,9 @@ import { useUserContext } from './user-context'
|
||||
import { useReceiveUser } from '../hooks/user-channel/use-receive-user'
|
||||
import { getJSON } from '@/infrastructure/fetch-json'
|
||||
import { useEditorContext } from './editor-context'
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
export const UserFeaturesContext = createContext<User['features']>(undefined)
|
||||
|
||||
const onAiFreeTrial = getMeta('ol-onAiFreeTrial')
|
||||
|
||||
export const UserFeaturesProvider: FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
@@ -34,12 +31,7 @@ export const UserFeaturesProvider: FC<React.PropsWithChildren> = ({
|
||||
|
||||
useEffect(() => {
|
||||
const listener = async ({ isPremium }: { isPremium: boolean }) => {
|
||||
// todo: quota clean-up: remove once we are transitioned off aiErrorAssistant naming
|
||||
const hasPremiumQuota = !onAiFreeTrial
|
||||
const alreadyPremium =
|
||||
features?.aiErrorAssistant === isPremium ||
|
||||
hasPremiumQuota === isPremium
|
||||
if (alreadyPremium) {
|
||||
if (features?.aiErrorAssistant === isPremium) {
|
||||
// the user is premium on writefull and has the AI assist, no need to refresh the features
|
||||
return
|
||||
}
|
||||
|
||||
@@ -213,7 +213,6 @@ export interface Meta {
|
||||
'ol-notificationsInstitution': InstitutionType[]
|
||||
'ol-oauthProviders': OAuthProviders
|
||||
'ol-odcData': OnboardingFormData
|
||||
'ol-onAiFreeTrial': boolean
|
||||
'ol-otMigrationStage': number
|
||||
'ol-overallThemes': OverallThemeMeta[]
|
||||
'ol-ownerIsManaged': boolean
|
||||
|
||||
@@ -115,7 +115,6 @@ module.exports = {
|
||||
compileGroup: 'standard',
|
||||
trackChanges: false,
|
||||
symbolPalette: false,
|
||||
aiUsageQuota: 'basic',
|
||||
aiErrorAssistant: false,
|
||||
},
|
||||
personal: {
|
||||
@@ -133,7 +132,6 @@ module.exports = {
|
||||
compileGroup: 'standard',
|
||||
trackChanges: false,
|
||||
symbolPalette: false,
|
||||
aiUsageQuota: 'basic',
|
||||
aiErrorAssistant: false,
|
||||
},
|
||||
collaborator: {
|
||||
@@ -151,7 +149,6 @@ module.exports = {
|
||||
compileGroup: 'priority',
|
||||
trackChanges: true,
|
||||
symbolPalette: true,
|
||||
aiUsageQuota: 'basic',
|
||||
aiErrorAssistant: false,
|
||||
},
|
||||
professional: {
|
||||
@@ -169,7 +166,6 @@ module.exports = {
|
||||
compileGroup: 'priority',
|
||||
trackChanges: true,
|
||||
symbolPalette: true,
|
||||
aiUsageQuota: 'basic',
|
||||
aiErrorAssistant: false,
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -30,13 +30,6 @@ describe('InstitutionsFeatures', function () {
|
||||
default: {
|
||||
institutionPlanCode: ctx.institutionPlanCode,
|
||||
overleaf: {},
|
||||
writefull: {
|
||||
quotaTierGranted: 'unlimited',
|
||||
},
|
||||
aiFeatures: {
|
||||
freeTrialQuota: 'basic',
|
||||
unlimitedQuota: 'unlimited',
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -59,18 +52,10 @@ describe('InstitutionsFeatures', function () {
|
||||
}
|
||||
ctx.testFeatures = { features: { institution: 'all' } }
|
||||
ctx.testFeaturesWithAiAddon = {
|
||||
features: {
|
||||
institution: 'all',
|
||||
aiUsageQuota: 'unlimited',
|
||||
aiErrorAssistant: true,
|
||||
},
|
||||
features: { institution: 'all', aiErrorAssistant: true },
|
||||
}
|
||||
ctx.testFeaturesWithNoAddon = {
|
||||
features: {
|
||||
institution: 'all',
|
||||
aiUsageQuota: 'basic',
|
||||
aiErrorAssistant: false,
|
||||
},
|
||||
features: { institution: 'all', aiErrorAssistant: false },
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ describe('ProjectListController', function () {
|
||||
theme: 'textmate',
|
||||
mode: 'none',
|
||||
},
|
||||
aiFeatures: { enabled: false },
|
||||
}
|
||||
ctx.users = {
|
||||
'user-1': {
|
||||
|
||||
@@ -74,11 +74,6 @@ describe('FeaturesUpdater', function () {
|
||||
},
|
||||
writefull: {
|
||||
overleafApiUrl: 'https://www.writefull.com',
|
||||
quotaTierGranted: 'unlimited',
|
||||
},
|
||||
aiFeatures: {
|
||||
freeTrialQuota: 'basic',
|
||||
unlimitedQuota: 'unlimited',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -328,7 +323,6 @@ describe('FeaturesUpdater', function () {
|
||||
default: 'features',
|
||||
individual: 'features',
|
||||
aiErrorAssistant: true,
|
||||
aiUsageQuota: 'unlimited',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -347,7 +341,6 @@ describe('FeaturesUpdater', function () {
|
||||
expect(features).to.deep.equal({
|
||||
default: 'features',
|
||||
aiErrorAssistant: true,
|
||||
aiUsageQuota: 'unlimited',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -66,10 +66,6 @@ describe('UserGetter', function () {
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: (ctx.settings = {
|
||||
reconfirmNotificationDays: 14,
|
||||
aiFeatures: {
|
||||
freeTrialQuota: 'basic',
|
||||
unlimitedQuota: 'unlimited',
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
@@ -1316,8 +1312,8 @@ describe('UserGetter', function () {
|
||||
|
||||
it('should take into account features overrides from modules', async function (ctx) {
|
||||
// this case occurs when the user has bought the ai bundle on WF, which should include our error assistant
|
||||
const bundleFeatures = { aiUsageQuota: 'unlimited' }
|
||||
ctx.fakeUser.features = { aiUsageQuota: 'basic' }
|
||||
const bundleFeatures = { aiErrorAssistant: true }
|
||||
ctx.fakeUser.features = { aiErrorAssistant: false }
|
||||
ctx.Modules.promises.hooks.fire = sinon.stub().resolves([bundleFeatures])
|
||||
const features = await ctx.UserGetter.promises.getUserFeatures(
|
||||
ctx.fakeUser._id
|
||||
|
||||
@@ -10,7 +10,6 @@ export type UserId = Brand<string, 'UserId'>
|
||||
|
||||
export type Features = {
|
||||
aiErrorAssistant?: boolean
|
||||
aiUsageQuota?: string
|
||||
collaborators?: number
|
||||
compileGroup?: 'standard' | 'priority'
|
||||
compileTimeout?: number
|
||||
|
||||
Reference in New Issue
Block a user