feat: add two-layer AI model availability filtering (#18170)
## Summary - **Admin-level filtering**: New AI tab in admin panel with server-wide model availability controls (whitelist/blacklist via `AI_AUTO_ENABLE_NEW_MODELS`, `AI_DISABLED_MODEL_IDS`, `AI_ENABLED_MODEL_IDS` config variables). Dedicated `setAdminAiModelEnabled` mutation replaces frontend config-variable manipulation. Filter dropdown to show/hide unconfigured and deprecated models. - **Workspace-level filtering**: Per-workspace controls with "Use best models only" mode (curated list backed by `isRecommended` flag), or custom whitelist/blacklist. Separate Smart/Fast model selectors with "Best (...)" virtual options. - **Security enforcement**: Both layers enforced at every backend execution point — workspace update, agent create/update, chat execution. Model ID validated against known models before config mutation. All admin endpoints protected by `AdminPanelGuard`. ## Changes ### Backend (`twenty-server`) - New config variables for admin-level model filtering - `AiModelRegistryService`: `getAllModelsWithStatus()`, `setModelAdminEnabled()` with model ID validation, `isModelAdminAllowed()` - `AdminPanelResolver`: `getAdminAiModels` query, `setAdminAiModelEnabled` mutation - `WorkspaceEntity`: new fields (`autoEnableNewAiModels`, `disabledAiModelIds`, `enabledAiModelIds`, `useRecommendedModels`) - `WorkspaceService`: model validation on `smartModel`/`fastModel` updates - `AgentResolver`: model availability checks on create/update - `isModelAllowedByWorkspace` centralized utility - `isRecommended` flag on model definitions - Two TypeORM migrations ### Frontend (`twenty-front`) - New `SettingsAdminAI` component with search, filter dropdown (unconfigured/deprecated), and model toggle cards - AI tab added to admin panel navigation - `useWorkspaceAiModelAvailability` hook for workspace-level filtering - `SettingsAIModelsTab` redesigned: merged sections, "Use best models only" toggle, conditional available models list - `getModelIcon`/`getModelProviderLabel` shared utilities with GraphQL enum casing normalization - Updated generated GraphQL types and mock data ## Test plan - [ ] Toggle models on/off in admin panel AI tab and verify they appear/disappear in workspace settings - [ ] Enable "Use best models only" in workspace settings and verify only recommended models are selectable - [ ] Disable recommended mode and verify whitelist/blacklist toggles work correctly - [ ] Verify deprecated models hidden by default, shown greyed out when filter enabled - [ ] Verify unconfigured models hidden by default, shown disabled when filter enabled - [ ] Try setting a disabled model as Smart/Fast model — should be rejected - [ ] Try creating an agent with a disabled model — should be rejected - [ ] Verify admin panel AI tab requires admin access Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+14
-3
@@ -4,7 +4,12 @@ import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAd
|
||||
import { SETTINGS_ADMIN_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminTabsId';
|
||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconHeart, IconSettings2, IconVariable } from 'twenty-ui/display';
|
||||
import {
|
||||
IconHeart,
|
||||
IconSettings2,
|
||||
IconSparkles,
|
||||
IconVariable,
|
||||
} from 'twenty-ui/display';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
|
||||
export const SettingsAdminContent = () => {
|
||||
@@ -19,15 +24,21 @@ export const SettingsAdminContent = () => {
|
||||
Icon: IconSettings2,
|
||||
disabled: !canAccessFullAdminPanel && !canImpersonate,
|
||||
},
|
||||
{
|
||||
id: SETTINGS_ADMIN_TABS.AI,
|
||||
title: t`AI`,
|
||||
Icon: IconSparkles,
|
||||
disabled: !canAccessFullAdminPanel,
|
||||
},
|
||||
{
|
||||
id: SETTINGS_ADMIN_TABS.CONFIG_VARIABLES,
|
||||
title: t`Config Variables`,
|
||||
title: t`Config`,
|
||||
Icon: IconVariable,
|
||||
disabled: !canAccessFullAdminPanel,
|
||||
},
|
||||
{
|
||||
id: SETTINGS_ADMIN_TABS.HEALTH_STATUS,
|
||||
title: t`Health Status`,
|
||||
title: t`Health`,
|
||||
Icon: IconHeart,
|
||||
disabled: !canAccessFullAdminPanel,
|
||||
},
|
||||
|
||||
+3
@@ -1,3 +1,4 @@
|
||||
import { SettingsAdminAI } from '@/settings/admin-panel/ai/components/SettingsAdminAI';
|
||||
import { SettingsAdminGeneral } from '@/settings/admin-panel/components/SettingsAdminGeneral';
|
||||
import { SettingsAdminConfigVariables } from '@/settings/admin-panel/config-variables/components/SettingsAdminConfigVariables';
|
||||
import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAdminTabs';
|
||||
@@ -15,6 +16,8 @@ export const SettingsAdminTabContent = () => {
|
||||
switch (activeTabId) {
|
||||
case SETTINGS_ADMIN_TABS.GENERAL:
|
||||
return <SettingsAdminGeneral />;
|
||||
case SETTINGS_ADMIN_TABS.AI:
|
||||
return <SettingsAdminAI />;
|
||||
case SETTINGS_ADMIN_TABS.CONFIG_VARIABLES:
|
||||
return <SettingsAdminConfigVariables />;
|
||||
case SETTINGS_ADMIN_TABS.HEALTH_STATUS:
|
||||
|
||||
Reference in New Issue
Block a user