mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 06:33:55 +01:00
Cleanup AiErrorAssistant todos post plans changes (#35067)
* feat: cleaning up ai quota leftovers fix: removing more straggler invos * fix: featureUpdater still need analyticsId in the user projection * feat: updating cypress tests to also check for fair usage text GitOrigin-RevId: 09c1167106c144084c111beea56867f6aa4a0906
This commit is contained in:
committed by
Copybot
parent
275d381656
commit
ba76fda37b
@@ -18,10 +18,8 @@ async function _getInstitutionsAddons(userId) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// todo: seperate quota value depending on source of entitlement if needed
|
// todo: seperate quota value depending on source of entitlement if needed
|
||||||
// todo: quota clean-up: remove aiErrorAssistant once migration finishes
|
|
||||||
const bundleFeatures = {
|
const bundleFeatures = {
|
||||||
aiUsageQuota: Settings.writefull.quotaTierGranted,
|
aiUsageQuota: Settings.writefull.quotaTierGranted,
|
||||||
aiErrorAssistant: true,
|
|
||||||
}
|
}
|
||||||
return hasAssistBundle ? bundleFeatures : {}
|
return hasAssistBundle ? bundleFeatures : {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -492,7 +492,6 @@ const _ProjectController = {
|
|||||||
'editor-popup-ux-survey-03-2026',
|
'editor-popup-ux-survey-03-2026',
|
||||||
'chat-edit-delete',
|
'chat-edit-delete',
|
||||||
'comment-mentions',
|
'comment-mentions',
|
||||||
'ai-workbench-release',
|
|
||||||
'compile-timeout-target-plans',
|
'compile-timeout-target-plans',
|
||||||
'writefull-figure-generator',
|
'writefull-figure-generator',
|
||||||
'writefull-toolbar-migration',
|
'writefull-toolbar-migration',
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ async function projectListPage(req, res, next) {
|
|||||||
userId,
|
userId,
|
||||||
`email isAdmin emails features alphaProgram betaProgram lastPrimaryEmailCheck lastActive signUpDate ace refProviders${
|
`email isAdmin emails features alphaProgram betaProgram lastPrimaryEmailCheck lastActive signUpDate ace refProviders${
|
||||||
isSaas
|
isSaas
|
||||||
? ' enrollment writefull completedTutorials aiFeatures aiErrorAssistant labsProgram'
|
? ' enrollment writefull completedTutorials aiFeatures labsProgram'
|
||||||
: ''
|
: ''
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
@@ -538,8 +538,8 @@ async function projectListPage(req, res, next) {
|
|||||||
|
|
||||||
const aiBlocked =
|
const aiBlocked =
|
||||||
Features.hasFeature('saas') && !(await _canUseAIAssist(user))
|
Features.hasFeature('saas') && !(await _canUseAIAssist(user))
|
||||||
const hasAiAssist =
|
const hasUnlimitedAi =
|
||||||
Features.hasFeature('saas') && (await _userHasAIAssist(user))
|
Features.hasFeature('saas') && (await _userHasUnlimitedAiTier(user))
|
||||||
|
|
||||||
const splitTests = [
|
const splitTests = [
|
||||||
// Split tests that will be made available to the frontend
|
// Split tests that will be made available to the frontend
|
||||||
@@ -591,7 +591,7 @@ async function projectListPage(req, res, next) {
|
|||||||
best_subscription_type: usersBestSubscription.type,
|
best_subscription_type: usersBestSubscription.type,
|
||||||
}),
|
}),
|
||||||
ai_blocked: aiBlocked,
|
ai_blocked: aiBlocked,
|
||||||
has_ai_assist: hasAiAssist,
|
has_ai_assist: hasUnlimitedAi,
|
||||||
...(subjectArea && { subject_area: subjectArea }),
|
...(subjectArea && { subject_area: subjectArea }),
|
||||||
...(role && { role }),
|
...(role && { role }),
|
||||||
...(primaryOccupation && { primary_occupation: primaryOccupation }),
|
...(primaryOccupation && { primary_occupation: primaryOccupation }),
|
||||||
@@ -951,18 +951,19 @@ function _hasActiveFilter(filters) {
|
|||||||
/**
|
/**
|
||||||
* @param {any} user
|
* @param {any} user
|
||||||
*/
|
*/
|
||||||
async function _userHasAIAssist(user) {
|
async function _userHasUnlimitedAiTier(user) {
|
||||||
const hasPremiumAiFeatures =
|
const hasPremiumAiFeatures =
|
||||||
user.features?.aiUsageQuota === Settings.aiFeatures.unlimitedQuota
|
user.features?.aiUsageQuota === Settings.aiFeatures.unlimitedQuota
|
||||||
// Check if the user has a non free trial version of our AI features
|
|
||||||
|
// Check if the user has highest tier version of our AI features
|
||||||
if (hasPremiumAiFeatures) {
|
if (hasPremiumAiFeatures) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user has AI Assist enabled via Writefull
|
// Check if the user has Unlimited AI quota via Writefull
|
||||||
const { isPremium: hasAiAssistViaWritefull } =
|
const { isPremium: hasUnlimitedAiViaWritefull } =
|
||||||
await UserGetter.promises.getWritefullData(user._id)
|
await UserGetter.promises.getWritefullData(user._id)
|
||||||
if (hasAiAssistViaWritefull) {
|
if (hasUnlimitedAiViaWritefull) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -365,10 +365,7 @@ function hasPlanAiEnabled(plan) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return plan.features.aiUsageQuota === Settings.aiFeatures.unlimitedQuota
|
||||||
plan.features.aiUsageQuota === Settings.aiFeatures.unlimitedQuota ||
|
|
||||||
plan.features.aiErrorAssistant === true
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import CustomerIoPlanHelpers from './CustomerIoPlanHelpers.mjs'
|
|||||||
import { GroupPolicy } from '../../models/GroupPolicy.mjs'
|
import { GroupPolicy } from '../../models/GroupPolicy.mjs'
|
||||||
import { AI_ADD_ON_CODE } from './AiHelper.mjs'
|
import { AI_ADD_ON_CODE } from './AiHelper.mjs'
|
||||||
import { fetchNothing } from '@overleaf/fetch-utils'
|
import { fetchNothing } from '@overleaf/fetch-utils'
|
||||||
|
import SplitTestUserGetter from '../SplitTests/SplitTestUserGetter.mjs'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enqueue a job for refreshing features for the given user
|
* Enqueue a job for refreshing features for the given user
|
||||||
@@ -46,8 +47,7 @@ async function refreshFeatures(userId, reason) {
|
|||||||
_id: 1,
|
_id: 1,
|
||||||
features: 1,
|
features: 1,
|
||||||
email: 1,
|
email: 1,
|
||||||
analyticsId: 1,
|
...SplitTestUserGetter.getProjection(),
|
||||||
labsProgram: 1,
|
|
||||||
})
|
})
|
||||||
const oldFeatures = _.clone(user.features)
|
const oldFeatures = _.clone(user.features)
|
||||||
const features = await computeFeatures(userId)
|
const features = await computeFeatures(userId)
|
||||||
@@ -104,6 +104,7 @@ async function refreshFeatures(userId, reason) {
|
|||||||
json: {
|
json: {
|
||||||
userOverleafId: userId,
|
userOverleafId: userId,
|
||||||
// todo: quota clean-up: collab with writefull to rename this, and check if still needed
|
// todo: quota clean-up: collab with writefull to rename this, and check if still needed
|
||||||
|
// AiAssist is legacy naming for our old one tier AI subscription, which is now our "Unlimited" quota tier
|
||||||
hasAiAssist: hasPremiumAiFeatures,
|
hasAiAssist: hasPremiumAiFeatures,
|
||||||
aiUsageQuota: newFeatures.aiUsageQuota,
|
aiUsageQuota: newFeatures.aiUsageQuota,
|
||||||
},
|
},
|
||||||
@@ -271,7 +272,7 @@ async function _getIndividualFeatures(userId) {
|
|||||||
featureSets.push(_subscriptionToFeatures(subscription))
|
featureSets.push(_subscriptionToFeatures(subscription))
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: quota clean-up - remove
|
// todo: quota clean-up - remove once we finish transitioning all users to other plans
|
||||||
// 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
|
// 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
|
// standalone plan will receive correct state since their plan will provide the correct quota
|
||||||
featureSets.push(_aiAddOnFeatures(subscription))
|
featureSets.push(_aiAddOnFeatures(subscription))
|
||||||
@@ -323,8 +324,6 @@ function _subscriptionToFeatures(subscription) {
|
|||||||
function _aiAddOnFeatures(subscription) {
|
function _aiAddOnFeatures(subscription) {
|
||||||
if (subscription?.addOns?.some(addOn => addOn.addOnCode === AI_ADD_ON_CODE)) {
|
if (subscription?.addOns?.some(addOn => addOn.addOnCode === AI_ADD_ON_CODE)) {
|
||||||
return {
|
return {
|
||||||
// allow both naming systems to work
|
|
||||||
aiErrorAssistant: true,
|
|
||||||
aiUsageQuota: Settings.aiFeatures.unlimitedQuota,
|
aiUsageQuota: Settings.aiFeatures.unlimitedQuota,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import UserAuditLogHandler from '../../Features/User/UserAuditLogHandler.mjs'
|
|||||||
const PERIOD = 24 // hours
|
const PERIOD = 24 // hours
|
||||||
const PERIOD_IN_MILLISECONDS = PERIOD * 60 * 60 * 1000
|
const PERIOD_IN_MILLISECONDS = PERIOD * 60 * 60 * 1000
|
||||||
|
|
||||||
// todo: quota clean-up: extend this off base RateLimitController and unify behaviour where possible.
|
|
||||||
|
|
||||||
export default class TokenUsageRateLimiter {
|
export default class TokenUsageRateLimiter {
|
||||||
/**
|
/**
|
||||||
* @param {string} featureName
|
* @param {string} featureName
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
import SplitTestHandler from '../../Features/SplitTests/SplitTestHandler.mjs'
|
|
||||||
import UserGetter from '../../Features/User/UserGetter.mjs'
|
|
||||||
import TokenUsageRateLimiter from './TokenUsageRateLimiter.mjs'
|
import TokenUsageRateLimiter from './TokenUsageRateLimiter.mjs'
|
||||||
/** @typedef {{usage?: number | null, periodStart?: Date | null}} FeatureUsage */
|
/** @typedef {{usage?: number | null, periodStart?: Date | null}} FeatureUsage */
|
||||||
|
|
||||||
const DEFAULT_USER_TOKEN_ALLOWANCE = 8_000_000
|
const DEFAULT_USER_TOKEN_ALLOWANCE = 8_000_000
|
||||||
const ALPHA_USER_TOKEN_ALLOWANCE = 8_000_000
|
|
||||||
|
|
||||||
class WorkbenchRateLimiter extends TokenUsageRateLimiter {
|
class WorkbenchRateLimiter extends TokenUsageRateLimiter {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('aiWorkbench')
|
super('aiWorkbench')
|
||||||
@@ -17,23 +13,6 @@ class WorkbenchRateLimiter extends TokenUsageRateLimiter {
|
|||||||
* @returns {Promise<number>}
|
* @returns {Promise<number>}
|
||||||
*/
|
*/
|
||||||
async _getAllowance(userId) {
|
async _getAllowance(userId) {
|
||||||
const splitTestAssignment =
|
|
||||||
await SplitTestHandler.promises.getAssignmentForUser(
|
|
||||||
userId,
|
|
||||||
'ai-workbench-release'
|
|
||||||
)
|
|
||||||
const inSplitTest = splitTestAssignment.variant === 'enabled'
|
|
||||||
if (!inSplitTest) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
const user = await UserGetter.promises.getUser(userId, {
|
|
||||||
alphaProgram: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (user?.alphaProgram) {
|
|
||||||
return ALPHA_USER_TOKEN_ALLOWANCE
|
|
||||||
}
|
|
||||||
|
|
||||||
// all users have the same token limit (fair usage)
|
// all users have the same token limit (fair usage)
|
||||||
return DEFAULT_USER_TOKEN_ALLOWANCE
|
return DEFAULT_USER_TOKEN_ALLOWANCE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,10 +153,6 @@ export const UserSchema = new Schema(
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: Settings.defaultFeatures.symbolPalette,
|
default: Settings.defaultFeatures.symbolPalette,
|
||||||
},
|
},
|
||||||
aiErrorAssistant: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
aiUsageQuota: { type: String, default: 'basic' },
|
aiUsageQuota: { type: String, default: 'basic' },
|
||||||
},
|
},
|
||||||
featuresOverrides: [
|
featuresOverrides: [
|
||||||
@@ -170,8 +166,6 @@ export const UserSchema = new Schema(
|
|||||||
expiresAt: { type: Date },
|
expiresAt: { type: Date },
|
||||||
note: { type: String },
|
note: { type: String },
|
||||||
features: {
|
features: {
|
||||||
// todo: quota clean-up: remove aiErrorAssistant
|
|
||||||
aiErrorAssistant: { type: Boolean },
|
|
||||||
aiUsageQuota: { type: String },
|
aiUsageQuota: { type: String },
|
||||||
collaborators: { type: Number },
|
collaborators: { type: Number },
|
||||||
versioning: { type: Boolean },
|
versioning: { type: Boolean },
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { useEffect } from 'react'
|
|||||||
import Notification from '@/shared/components/notification'
|
import Notification from '@/shared/components/notification'
|
||||||
import PaywallUpgradeButton from '@/shared/components/paywall-upgrade-button'
|
import PaywallUpgradeButton from '@/shared/components/paywall-upgrade-button'
|
||||||
import { useEditorContext } from '@/shared/context/editor-context'
|
import { useEditorContext } from '@/shared/context/editor-context'
|
||||||
import { useUserFeaturesContext } from '@/shared/context/user-features-context'
|
|
||||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { formatSecondsToHoursAndMinutes } from '@/shared/utils/time'
|
import { formatSecondsToHoursAndMinutes } from '@/shared/utils/time'
|
||||||
@@ -10,8 +9,6 @@ import getMeta from '@/utils/meta'
|
|||||||
import { AiFeatureLocations } from './types/ai'
|
import { AiFeatureLocations } from './types/ai'
|
||||||
import { paywallTypeByLocation } from '../utils/ai-features'
|
import { paywallTypeByLocation } from '../utils/ai-features'
|
||||||
|
|
||||||
const hasUnlimitedAi = getMeta('ol-hasUnlimitedAi')
|
|
||||||
|
|
||||||
function AiPaywallNotification({
|
function AiPaywallNotification({
|
||||||
isActionBelowContent = false,
|
isActionBelowContent = false,
|
||||||
featureLocation,
|
featureLocation,
|
||||||
@@ -26,7 +23,6 @@ function AiPaywallNotification({
|
|||||||
premiumSuggestionResetDate,
|
premiumSuggestionResetDate,
|
||||||
} = useEditorContext()
|
} = useEditorContext()
|
||||||
|
|
||||||
const features = useUserFeaturesContext()
|
|
||||||
const user = getMeta('ol-user')
|
const user = getMeta('ol-user')
|
||||||
|
|
||||||
const isCommons = user.hasInstitutionLicence
|
const isCommons = user.hasInstitutionLicence
|
||||||
@@ -36,8 +32,7 @@ function AiPaywallNotification({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: quota clean-up: remove once we are transitioned off aiErrorAssistant naming and replace with just hasUnlimitedAi, also remove null FF check
|
const hasAddOn = getMeta('ol-hasUnlimitedAi')
|
||||||
const hasAddOn = hasUnlimitedAi || Boolean(features?.aiErrorAssistant)
|
|
||||||
|
|
||||||
// error assist only needs usage quota
|
// error assist only needs usage quota
|
||||||
const canUseErrorAssist = hasSuggestionsLeft
|
const canUseErrorAssist = hasSuggestionsLeft
|
||||||
|
|||||||
@@ -34,12 +34,7 @@ export const UserFeaturesProvider: FC<React.PropsWithChildren> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = async ({ isPremium }: { isPremium: boolean }) => {
|
const listener = async ({ isPremium }: { isPremium: boolean }) => {
|
||||||
// todo: quota clean-up: remove once we are transitioned off aiErrorAssistant naming
|
if (hasUnlimitedAi === isPremium) {
|
||||||
const hasPremiumQuota = hasUnlimitedAi
|
|
||||||
const alreadyPremium =
|
|
||||||
features?.aiErrorAssistant === isPremium ||
|
|
||||||
hasPremiumQuota === isPremium
|
|
||||||
if (alreadyPremium) {
|
|
||||||
// the user is premium on writefull and has the AI assist, no need to refresh the features
|
// the user is premium on writefull and has the AI assist, no need to refresh the features
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -55,7 +50,7 @@ export const UserFeaturesProvider: FC<React.PropsWithChildren> = ({
|
|||||||
listener
|
listener
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, [features?.aiErrorAssistant, writefullInstance])
|
}, [writefullInstance])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserFeaturesContext.Provider value={features}>
|
<UserFeaturesContext.Provider value={features}>
|
||||||
|
|||||||
@@ -116,7 +116,6 @@ module.exports = {
|
|||||||
trackChanges: false,
|
trackChanges: false,
|
||||||
symbolPalette: false,
|
symbolPalette: false,
|
||||||
aiUsageQuota: 'basic',
|
aiUsageQuota: 'basic',
|
||||||
aiErrorAssistant: false,
|
|
||||||
},
|
},
|
||||||
personal: {
|
personal: {
|
||||||
collaborators: 1,
|
collaborators: 1,
|
||||||
@@ -134,7 +133,6 @@ module.exports = {
|
|||||||
trackChanges: false,
|
trackChanges: false,
|
||||||
symbolPalette: false,
|
symbolPalette: false,
|
||||||
aiUsageQuota: 'basic',
|
aiUsageQuota: 'basic',
|
||||||
aiErrorAssistant: false,
|
|
||||||
},
|
},
|
||||||
collaborator: {
|
collaborator: {
|
||||||
collaborators: 10,
|
collaborators: 10,
|
||||||
@@ -152,7 +150,6 @@ module.exports = {
|
|||||||
trackChanges: true,
|
trackChanges: true,
|
||||||
symbolPalette: true,
|
symbolPalette: true,
|
||||||
aiUsageQuota: 'basic',
|
aiUsageQuota: 'basic',
|
||||||
aiErrorAssistant: false,
|
|
||||||
},
|
},
|
||||||
professional: {
|
professional: {
|
||||||
collaborators: -1,
|
collaborators: -1,
|
||||||
@@ -170,7 +167,6 @@ module.exports = {
|
|||||||
trackChanges: true,
|
trackChanges: true,
|
||||||
symbolPalette: true,
|
symbolPalette: true,
|
||||||
aiUsageQuota: 'basic',
|
aiUsageQuota: 'basic',
|
||||||
aiErrorAssistant: false,
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
+14
-3
@@ -6,7 +6,8 @@ import {
|
|||||||
makeEditorProvider,
|
makeEditorProvider,
|
||||||
} from '../../helpers/editor-providers'
|
} from '../../helpers/editor-providers'
|
||||||
|
|
||||||
const PAYWALL_TEXT = 'You’ve reached the fair usage limit on your plan'
|
const PAYWALL_TEXT = 'You’ve hit your daily AI limit'
|
||||||
|
const FAIR_USAGE_TEXT = 'You’ve reached the fair usage limit on your plan'
|
||||||
|
|
||||||
const futureDate = () => new Date(Date.now() + 60 * 60 * 1000)
|
const futureDate = () => new Date(Date.now() + 60 * 60 * 1000)
|
||||||
|
|
||||||
@@ -39,14 +40,17 @@ function makeShowLogsCompileProvider(initialShowLogs: boolean) {
|
|||||||
return Provider
|
return Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
function mountSuggestFixPaywall(initialShowLogs = true) {
|
function mountSuggestFixPaywall(
|
||||||
|
initialShowLogs = true,
|
||||||
|
{ hasUnlimitedAi = false }: { hasUnlimitedAi?: boolean } = {}
|
||||||
|
) {
|
||||||
cy.window().then(win => {
|
cy.window().then(win => {
|
||||||
win.metaAttributesCache.set('ol-showAiFeatures', true)
|
win.metaAttributesCache.set('ol-showAiFeatures', true)
|
||||||
|
win.metaAttributesCache.set('ol-hasUnlimitedAi', hasUnlimitedAi)
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.mount(
|
cy.mount(
|
||||||
<EditorProviders
|
<EditorProviders
|
||||||
features={{ aiErrorAssistant: true }}
|
|
||||||
providers={{
|
providers={{
|
||||||
EditorProvider: makeEditorProvider({
|
EditorProvider: makeEditorProvider({
|
||||||
hasSuggestionsLeft: false,
|
hasSuggestionsLeft: false,
|
||||||
@@ -72,6 +76,13 @@ describe('<ErrorAssistantAiPaywallNotification />', function () {
|
|||||||
cy.contains(PAYWALL_TEXT).should('be.visible')
|
cy.contains(PAYWALL_TEXT).should('be.visible')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('renders the fair usage limit paywall for users with unlimited AI', function () {
|
||||||
|
mountSuggestFixPaywall(true, { hasUnlimitedAi: true })
|
||||||
|
dispatchSuggestFixPaywall()
|
||||||
|
cy.contains(FAIR_USAGE_TEXT).should('be.visible')
|
||||||
|
cy.contains(PAYWALL_TEXT).should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
it('ignores paywall events from other origins', function () {
|
it('ignores paywall events from other origins', function () {
|
||||||
mountSuggestFixPaywall()
|
mountSuggestFixPaywall()
|
||||||
cy.window().then(win => {
|
cy.window().then(win => {
|
||||||
|
|||||||
@@ -10,9 +10,6 @@ describe('Workbench', { scrollBehavior: false }, function () {
|
|||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
cy.window().then(win => {
|
cy.window().then(win => {
|
||||||
win.metaAttributesCache.set('ol-showAiFeatures', true)
|
win.metaAttributesCache.set('ol-showAiFeatures', true)
|
||||||
win.metaAttributesCache.set('ol-splitTestVariants', {
|
|
||||||
'ai-workbench-release': 'enabled',
|
|
||||||
})
|
|
||||||
win.metaAttributesCache.set('ol-inactiveTutorials', [
|
win.metaAttributesCache.set('ol-inactiveTutorials', [
|
||||||
AI_CONSENT_TUTORIAL_KEY,
|
AI_CONSENT_TUTORIAL_KEY,
|
||||||
])
|
])
|
||||||
@@ -30,13 +27,13 @@ describe('Workbench', { scrollBehavior: false }, function () {
|
|||||||
</EditorViewContext.Provider>
|
</EditorViewContext.Provider>
|
||||||
)
|
)
|
||||||
|
|
||||||
const Providers: FC<PropsWithChildren<{ aiAssistEnabled?: boolean }>> = ({
|
const Providers: FC<PropsWithChildren<{ aiUsageQuota?: string }>> = ({
|
||||||
children,
|
children,
|
||||||
aiAssistEnabled = true,
|
aiUsageQuota = 'unlimited',
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<EditorProviders
|
<EditorProviders
|
||||||
features={{ aiErrorAssistant: aiAssistEnabled }}
|
features={{ aiUsageQuota: aiUsageQuota }}
|
||||||
providers={{ EditorViewProvider, TutorialProvider }}
|
providers={{ EditorViewProvider, TutorialProvider }}
|
||||||
>
|
>
|
||||||
<div style={{ backgroundColor: '#1b222c' }}>{children}</div>
|
<div style={{ backgroundColor: '#1b222c' }}>{children}</div>
|
||||||
@@ -47,7 +44,7 @@ describe('Workbench', { scrollBehavior: false }, function () {
|
|||||||
describe('when AI assist is enabled and consent is given', function () {
|
describe('when AI assist is enabled and consent is given', function () {
|
||||||
it('should show the chat interface', function () {
|
it('should show the chat interface', function () {
|
||||||
cy.mount(
|
cy.mount(
|
||||||
<Providers aiAssistEnabled>
|
<Providers aiUsageQuota="unlimited">
|
||||||
<Workbench />
|
<Workbench />
|
||||||
</Providers>
|
</Providers>
|
||||||
)
|
)
|
||||||
@@ -70,7 +67,7 @@ describe('Workbench', { scrollBehavior: false }, function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
cy.mount(
|
cy.mount(
|
||||||
<Providers aiAssistEnabled>
|
<Providers aiUsageQuota="unlimited">
|
||||||
<Workbench />
|
<Workbench />
|
||||||
</Providers>
|
</Providers>
|
||||||
)
|
)
|
||||||
@@ -111,7 +108,7 @@ describe('Workbench', { scrollBehavior: false }, function () {
|
|||||||
win.metaAttributesCache.set('ol-showAiFeatures', false)
|
win.metaAttributesCache.set('ol-showAiFeatures', false)
|
||||||
})
|
})
|
||||||
cy.mount(
|
cy.mount(
|
||||||
<Providers aiAssistEnabled={false}>
|
<Providers aiUsageQuota="free">
|
||||||
<Workbench />
|
<Workbench />
|
||||||
</Providers>
|
</Providers>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ export type EditorProvidersProps = {
|
|||||||
socket?: Socket
|
socket?: Socket
|
||||||
isRestrictedTokenMember?: boolean
|
isRestrictedTokenMember?: boolean
|
||||||
scope?: Record<string, any>
|
scope?: Record<string, any>
|
||||||
features?: Record<string, boolean>
|
features?: Record<string, boolean | string>
|
||||||
projectFeatures?: Record<string, boolean>
|
projectFeatures?: Record<string, boolean | string>
|
||||||
permissionsLevel?: PermissionsLevel
|
permissionsLevel?: PermissionsLevel
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
rootFolder?: Folder[]
|
rootFolder?: Folder[]
|
||||||
|
|||||||
@@ -72,14 +72,12 @@ describe('InstitutionsFeatures', function () {
|
|||||||
features: {
|
features: {
|
||||||
institution: 'all',
|
institution: 'all',
|
||||||
aiUsageQuota: 'unlimited',
|
aiUsageQuota: 'unlimited',
|
||||||
aiErrorAssistant: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx.testFeaturesWithNoAddon = {
|
ctx.testFeaturesWithNoAddon = {
|
||||||
features: {
|
features: {
|
||||||
institution: 'all',
|
institution: 'all',
|
||||||
aiUsageQuota: 'basic',
|
aiUsageQuota: 'basic',
|
||||||
aiErrorAssistant: false,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -388,7 +388,6 @@ describe('FeaturesUpdater', function () {
|
|||||||
expect(features).to.deep.equal({
|
expect(features).to.deep.equal({
|
||||||
default: 'features',
|
default: 'features',
|
||||||
individual: 'features',
|
individual: 'features',
|
||||||
aiErrorAssistant: true,
|
|
||||||
aiUsageQuota: 'unlimited',
|
aiUsageQuota: 'unlimited',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -407,7 +406,6 @@ describe('FeaturesUpdater', function () {
|
|||||||
)
|
)
|
||||||
expect(features).to.deep.equal({
|
expect(features).to.deep.equal({
|
||||||
default: 'features',
|
default: 'features',
|
||||||
aiErrorAssistant: true,
|
|
||||||
aiUsageQuota: 'unlimited',
|
aiUsageQuota: 'unlimited',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -65,9 +65,6 @@ describe('WorkbenchRateLimiter', function () {
|
|||||||
featureFlagEnabledForMongoUser: sinon.stub().resolves(true),
|
featureFlagEnabledForMongoUser: sinon.stub().resolves(true),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx.SplitTestHandler.promises.getAssignmentForUser
|
|
||||||
.withArgs(ctx.alphaUserId, 'ai-workbench-release')
|
|
||||||
.resolves({ variant: 'enabled' })
|
|
||||||
|
|
||||||
vi.doMock('../../../../app/src/infrastructure/mongodb', () => ({
|
vi.doMock('../../../../app/src/infrastructure/mongodb', () => ({
|
||||||
ObjectId,
|
ObjectId,
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { StripeCurrencyCode } from './currency'
|
|||||||
|
|
||||||
export type Features = {
|
export type Features = {
|
||||||
aiUsageQuota: 'free' | 'basic' | 'standard' | 'unlimited'
|
aiUsageQuota: 'free' | 'basic' | 'standard' | 'unlimited'
|
||||||
// todo: quota clean-up: remove aiErrorAssistant once migration finishes
|
|
||||||
aiErrorAssistant?: boolean
|
|
||||||
collaborators: number
|
collaborators: number
|
||||||
compileGroup: string
|
compileGroup: string
|
||||||
compileTimeout: number
|
compileTimeout: number
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export type RefProviders = {
|
|||||||
export type UserId = Brand<string, 'UserId'>
|
export type UserId = Brand<string, 'UserId'>
|
||||||
|
|
||||||
export type Features = {
|
export type Features = {
|
||||||
aiErrorAssistant?: boolean
|
|
||||||
aiUsageQuota?: string
|
aiUsageQuota?: string
|
||||||
collaborators?: number
|
collaborators?: number
|
||||||
compileGroup?: 'standard' | 'priority'
|
compileGroup?: 'standard' | 'priority'
|
||||||
|
|||||||
Reference in New Issue
Block a user