[web] Minor fixes for AI Disabled state (#34504)

* [web] Minor fixes for AI Disabled state

- Applies opacity to disabled spark icons
- Forces AI controls to be hidden on View state

* Removed redundant check

GitOrigin-RevId: a1389bec3e80a396c77a22294b1d921b4116a75c
This commit is contained in:
Miguel Serrano
2026-09-17 11:17:38 +00:00
committed by Copybot
parent 69ccb3c316
commit 9394f64c54
4 changed files with 52 additions and 18 deletions
@@ -813,22 +813,25 @@ const _ProjectController = {
let aiFeaturesAllowedForUser = false
let aiFeaturesAllowedForProject = false
if (userId && Features.hasFeature('saas')) {
const canUserWriteOrReviewProjectContent =
privilegeLevel === PrivilegeLevels.READ_AND_WRITE ||
privilegeLevel === PrivilegeLevels.OWNER ||
privilegeLevel === PrivilegeLevels.REVIEW
if (
userId &&
canUserWriteOrReviewProjectContent &&
Features.hasFeature('saas')
) {
try {
aiFeaturesAllowedForUser = await checkUserPermissions(user, [
'use-ai',
])
const canUserWriteOrReviewProjectContent =
privilegeLevel === PrivilegeLevels.READ_AND_WRITE ||
privilegeLevel === PrivilegeLevels.OWNER ||
privilegeLevel === PrivilegeLevels.REVIEW
if (canUserWriteOrReviewProjectContent) {
aiFeaturesAllowedForProject = await checkUserPermissions(
project.owner_ref,
['use-ai']
)
}
aiFeaturesAllowedForProject = await checkUserPermissions(
project.owner_ref,
['use-ai']
)
} catch (err) {
// still allow users to access project if we cant get their permissions, but disable AI feature
aiFeaturesAllowedForUser = false
@@ -35,6 +35,7 @@ export const MathDropdown = memo(function MathDropdown() {
{t('toolbar_insert_math_lowercase')}
</DropdownHeader>
<OLListGroupItem
className={showAiFeaturesDisabled ? 'opacity-50' : ''}
aria-label={t('toolbar_generate_math')}
disabled={showAiFeaturesDisabled}
disabledReason={t('ai_features_unavailable_on_this_project')}
@@ -54,9 +55,7 @@ export const MathDropdown = memo(function MathDropdown() {
src={sparkleWhite}
aria-hidden="true"
/>
<span className={showAiFeaturesDisabled ? 'opacity-50' : ''}>
{t('generate_from_text_or_image')}
</span>
<span>{t('generate_from_text_or_image')}</span>
</OLListGroupItem>
</>
)}
@@ -47,6 +47,7 @@ export const TableDropdown = memo(function TableDropdown() {
{t('toolbar_table_insert_table_lowercase')}
</DropdownHeader>
<OLListGroupItem
className={showAiFeaturesDisabled ? 'opacity-50' : ''}
aria-label={t('toolbar_generate_table')}
disabled={showAiFeaturesDisabled}
disabledReason={t('ai_features_unavailable_on_this_project')}
@@ -66,9 +67,7 @@ export const TableDropdown = memo(function TableDropdown() {
src={sparkleWhite}
aria-hidden="true"
/>
<span className={showAiFeaturesDisabled ? 'opacity-50' : ''}>
{t('generate_from_text_or_image')}
</span>
<span>{t('generate_from_text_or_image')}</span>
</OLListGroupItem>
<div className="ol-cm-toolbar-dropdown-divider mx-2 my-0" />
<OLListGroupItem
@@ -1628,6 +1628,22 @@ describe('ProjectController', function () {
})
})
it('should set showAiFeatures to false when the user has read-only access', async function (ctx) {
ctx.PermissionsManager.promises.checkUserPermissions.resolves(true)
ctx.AuthorizationManager.promises.getPrivilegeLevelForProject.resolves(
'readOnly'
)
await new Promise((resolve, reject) => {
ctx.res.render = (pageName, opts) => {
expect(opts.showAiFeatures).to.equal(false)
resolve()
}
ctx.ProjectController.loadEditor(ctx.req, ctx.res, err => {
if (err) reject(err)
})
})
})
it('should set showAiFeatures to false when the user can use ai but the project disallows it', async function (ctx) {
ctx.PermissionsManager.promises.checkUserPermissions.callsFake(
subject => Promise.resolve(typeof subject !== 'string')
@@ -1760,6 +1776,23 @@ describe('ProjectController', function () {
})
})
})
it('should set showAiFeatures to false and showAiFeaturesDisabled to false when the user has read-only access', async function (ctx) {
ctx.PermissionsManager.promises.checkUserPermissions.resolves(true)
ctx.AuthorizationManager.promises.getPrivilegeLevelForProject.resolves(
'readOnly'
)
await new Promise((resolve, reject) => {
ctx.res.render = (pageName, opts) => {
expect(opts.showAiFeatures).to.equal(false)
expect(opts.showAiFeaturesDisabled).to.equal(false)
resolve()
}
ctx.ProjectController.loadEditor(ctx.req, ctx.res, err => {
if (err) reject(err)
})
})
})
})
})
})