From 7298ee72aa8c13c5b554aaa4ea055d111d883fbc Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Mon, 23 Feb 2026 08:23:40 +0000 Subject: [PATCH] only enabled ai models for all users --- .../Settings/ProfileSettings/AIModels.tsx | 2 - .../Dashboard/Settings/SystemSettings.tsx | 13 +++-- src/workspaces/state/src/index.ts | 3 +- src/workspaces/state/src/useAccessPolicy.ts | 33 +++++++++++ src/workspaces/state/src/useModels.ts | 55 ++++++------------- 5 files changed, 59 insertions(+), 47 deletions(-) create mode 100644 src/workspaces/state/src/useAccessPolicy.ts diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/AIModels.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/AIModels.tsx index d89fa1f9..5a4eb5e6 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/AIModels.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/AIModels.tsx @@ -33,8 +33,6 @@ export const AIModels = () => { const piModels = useEnabledPiModels(); const [isSaving, setIsSaving] = useState(false); - console.log('[AIModels] enabledModels:', settings.ai?.enabledModels?.length, 'filtered:', piModels.length); - const [chatModel, setChatModel] = useState(settings.chat.defaultModel); const [projectModel, setProjectModel] = useState(settings.chat.defaultProjectModel); const [taskModel, setTaskModel] = useState(settings.tasks.defaultModel); diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/SystemSettings.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/SystemSettings.tsx index 94a5228f..eca86f53 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/SystemSettings.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/SystemSettings.tsx @@ -16,6 +16,7 @@ import { createSettingsPanelComponents, type SettingsSectionGroup } from './Sett import { useSettings } from 'state/useSettings'; import { useUserState } from 'state/useUserState'; import { usePiModels, useVisiblePiModels, modelKey, getProviderDisplayName, type ModelOption } from 'state/useModels'; +import { useAccessPolicy } from 'state/useAccessPolicy'; import type { UserSettings } from 'state/useSettings'; import { AIHarnessesSection } from './ServerSettings/AIHarnessesSection'; import { RUN_COMMAND_CHANNEL, type RunCommandState } from './ServerSettings/run-command-channel'; @@ -337,11 +338,11 @@ const DropZone = ({ label, children, onDrop }: DropZoneProps) => { }; function ModelVisibilitySection() { - const { settings, saveSettings } = useSettings(); + const { policy, savePolicy } = useAccessPolicy(); const piModels = usePiModels(); const [activeProvider, setActiveProvider] = useUserState('model-visibility-provider', ''); - const enabledModels = settings.ai?.enabledModels ?? []; + const enabledModels = policy.allowedModels; const providerGroups = useMemo(() => { const groups: Record = {}; @@ -383,9 +384,9 @@ function ModelVisibilitySection() { const enableModel = useCallback( async (key: string) => { if (enabledModels.includes(key)) return; - await saveSettings({ ...settings, ai: { ...settings.ai, enabledModels: [...enabledModels, key] } }); + await savePolicy({ allowedModels: [...enabledModels, key] }); }, - [settings, enabledModels, saveSettings], + [enabledModels, savePolicy], ); const disableModel = useCallback( @@ -401,9 +402,9 @@ function ModelVisibilitySection() { base = [...base, ...newProviderKeys]; } } - await saveSettings({ ...settings, ai: { ...settings.ai, enabledModels: base.filter((id) => id !== key) } }); + await savePolicy({ allowedModels: base.filter((id) => id !== key) }); }, - [settings, enabledModels, piModels, saveSettings], + [enabledModels, piModels, savePolicy], ); const onDragStart = useCallback((ev: DragEvent, key: string) => { diff --git a/src/workspaces/state/src/index.ts b/src/workspaces/state/src/index.ts index ab09f451..9bdda252 100644 --- a/src/workspaces/state/src/index.ts +++ b/src/workspaces/state/src/index.ts @@ -2,8 +2,9 @@ export { useSettings, DEFAULT_SETTINGS } from './useSettings'; export type { UseSettingsType, UserSettings, UserState } from './useSettings'; export { useUserState } from './useUserState'; export { useWorkspacesState } from './useWorkspacesState'; -export { usePiModels, useVisiblePiModels, modelKey } from './useModels'; +export { usePiModels, useVisiblePiModels, useEnabledPiModels, modelKey } from './useModels'; export type { ModelOption } from './useModels'; +export { useAccessPolicy } from './useAccessPolicy'; export { useRecentModels } from './useRecentModels'; export { usePlans } from './usePlans'; export { useLandingPage } from './useLandingPage'; diff --git a/src/workspaces/state/src/useAccessPolicy.ts b/src/workspaces/state/src/useAccessPolicy.ts new file mode 100644 index 00000000..95134d6f --- /dev/null +++ b/src/workspaces/state/src/useAccessPolicy.ts @@ -0,0 +1,33 @@ +import { useCallback } from 'react'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useClient } from 'hooks/useClient'; +import { useAuth } from 'hooks/useAuth'; + +type AccessPolicy = { + allowedModels: string[]; +}; + +const QUERY_KEY = ['ACCESS_POLICY']; + +export function useAccessPolicy() { + const client = useClient(); + const { isAuthenticated } = useAuth(); + const queryClient = useQueryClient(); + + const { data: policy = { allowedModels: [] } } = useQuery({ + queryKey: QUERY_KEY, + enabled: isAuthenticated, + queryFn: () => client.get('/server-settings/pi-mono/access-policy'), + staleTime: 5 * 60 * 1000, + }); + + const savePolicy = useCallback( + async (newPolicy: AccessPolicy) => { + queryClient.setQueryData(QUERY_KEY, newPolicy); + await client.put('/server-settings/pi-mono/access-policy', newPolicy); + }, + [client, queryClient], + ); + + return { policy, savePolicy }; +} diff --git a/src/workspaces/state/src/useModels.ts b/src/workspaces/state/src/useModels.ts index 74068713..7d21fef0 100644 --- a/src/workspaces/state/src/useModels.ts +++ b/src/workspaces/state/src/useModels.ts @@ -1,7 +1,7 @@ import { useQuery } from '@tanstack/react-query'; import { useClient } from 'hooks/useClient'; import { useAuth } from 'hooks/useAuth'; -import { useSettings } from './useSettings'; +import { useAccessPolicy } from './useAccessPolicy'; import type { ModelOption } from 'officerdev'; export type { ModelOption }; @@ -48,53 +48,32 @@ export function usePiModels() { return models; } +/** Filter models by system-wide access policy. New providers pass through. */ export function useVisiblePiModels() { const models = usePiModels(); - const { settings } = useSettings(); - const enabled = settings.ai?.enabledModels ?? []; - const disabledProviders = new Set(settings.ai?.disabledProviders ?? []); + const { policy } = useAccessPolicy(); + const allowed = policy.allowedModels; - // First filter out disabled providers - const providerFiltered = disabledProviders.size > 0 - ? models.filter((m) => !disabledProviders.has(m.provider)) - : models; + if (allowed.length === 0) return models; - // If no enabled models list, show all (after provider filtering) - if (enabled.length === 0) { - return providerFiltered; - } + const allowedSet = new Set(allowed); + const allowedProviderSet = new Set(allowed.map((key) => key.split(':')[0])); - // Get all providers from enabled models - const enabledProviderSet = new Set( - enabled.map(key => key.split(':')[0]), - ); - - // Filter to include: - // 1. Models that are explicitly enabled - // 2. Models from providers that aren't in the enabled list at all (new providers) - return providerFiltered.filter((m) => { - const isExplicitlyEnabled = enabled.includes(modelKey(m)); - const isFromNewProvider = !enabledProviderSet.has(m.provider); - return isExplicitlyEnabled || isFromNewProvider; + return models.filter((m) => { + const isExplicitlyAllowed = allowedSet.has(modelKey(m)); + const isFromNewProvider = !allowedProviderSet.has(m.provider); + return isExplicitlyAllowed || isFromNewProvider; }); } -/** Strict filtering — only explicitly enabled models, no "new provider" passthrough. */ +/** Strict filtering — only explicitly allowed models, no "new provider" passthrough. */ export function useEnabledPiModels() { const models = usePiModels(); - const { settings } = useSettings(); - const enabled = settings.ai?.enabledModels ?? []; - const disabledProviders = new Set(settings.ai?.disabledProviders ?? []); + const { policy } = useAccessPolicy(); + const allowed = policy.allowedModels; - const providerFiltered = disabledProviders.size > 0 - ? models.filter((m) => !disabledProviders.has(m.provider)) - : models; + if (allowed.length === 0) return models; - if (enabled.length === 0) return providerFiltered; - - // Match by modelKey (new format), model id, or model name (legacy formats) - const enabledSet = new Set(enabled); - return providerFiltered.filter((m) => - enabledSet.has(modelKey(m)) || enabledSet.has(m.id) || enabledSet.has(m.name), - ); + const allowedSet = new Set(allowed); + return models.filter((m) => allowedSet.has(modelKey(m))); }