add /settings/ai route with role-aware model management

Two-tier model policy: system policy (admin, allowedModels) controls
member access, per-user hiddenModels controls personal visibility.
All model selectors now use useUserVisibleModels. Moved providers
and model visibility out of system settings, AI models out of profile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 21:36:58 +00:00
co-authored by Claude Opus 4.6
parent d5fd3f58b1
commit 188e91920a
13 changed files with 606 additions and 413 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ export { useSettings, DEFAULT_SETTINGS } from './useSettings';
export type { UseSettingsType, UserSettings, UserState } from './useSettings';
export { useUserState } from './useUserState';
export { useDashboardState } from './useDashboardState';
export { usePiModels, useVisiblePiModels, useEnabledPiModels, modelKey } from './useModels';
export { usePiModels, useVisiblePiModels, useUserVisibleModels, useEnabledPiModels, modelKey } from './useModels';
export type { ModelOption } from './useModels';
export { useAccessPolicy } from './useAccessPolicy';
export { useRecentModels } from './useRecentModels';
+20 -3
View File
@@ -2,6 +2,7 @@ import { useQuery } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import { useAuth } from 'hooks/useAuth';
import { useAccessPolicy } from './useAccessPolicy';
import { useSettings } from './useSettings';
import type { ModelOption } from 'officerdev';
export type { ModelOption };
@@ -52,14 +53,13 @@ export function usePiModels() {
return models;
}
/** Filter models by system-wide access policy. Super Admin sees all. New providers pass through. */
/** Filter models by system-wide access policy. New providers pass through. */
export function useVisiblePiModels() {
const models = usePiModels();
const { user } = useAuth();
const { policy } = useAccessPolicy();
const allowed = policy.allowedModels;
if (user?.role === 'Super Admin' || allowed.length === 0) return models;
if (allowed.length === 0) return models;
const allowedSet = new Set(allowed);
const allowedProviderSet = new Set(allowed.map((key) => key.split(':')[0]));
@@ -72,6 +72,23 @@ export function useVisiblePiModels() {
});
}
/** Models visible to the current user: system policy (members) or all (admins), minus per-user hidden. */
export function useUserVisibleModels() {
const allModels = usePiModels();
const policyModels = useVisiblePiModels();
const { user } = useAuth();
const { settings } = useSettings();
const isAdmin = user?.role !== 'Member';
const base = isAdmin ? allModels : policyModels;
const hidden = settings.chat.hiddenModels;
if (!hidden || hidden.length === 0) return base;
const hiddenSet = new Set(hidden);
return base.filter((m) => !hiddenSet.has(modelKey(m)));
}
/** Strict filtering — only explicitly allowed models, no "new provider" passthrough. */
export function useEnabledPiModels() {
const models = usePiModels();
+1
View File
@@ -55,6 +55,7 @@ export type UserSettings = {
systemPrompt: string;
temperature: number;
defaultPwd: string;
hiddenModels?: string[];
};
ai: {
enabledModels: string[];