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:
@@ -1,6 +1,6 @@
|
||||
import type { KeyboardEvent } from 'react';
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useVisiblePiModels } from 'state/useModels';
|
||||
import { useUserVisibleModels } from 'state/useModels';
|
||||
import { usePiChat, type UsePiChatType } from '../../../hooks/usePiChat';
|
||||
import { useAttachments } from '../useAttachments';
|
||||
import { useSlashCommands } from '../useSlashCommands';
|
||||
@@ -47,7 +47,7 @@ export function useEmbeddableChat(params: UseEmbeddableChatParams, onMessageComp
|
||||
stopGeneration,
|
||||
} = chat;
|
||||
|
||||
const availableModels = useVisiblePiModels();
|
||||
const availableModels = useUserVisibleModels();
|
||||
const attachmentManager = useAttachments({ sessionId });
|
||||
const slashCommands = useSlashCommands({ sessionId });
|
||||
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import { cardStyle } from '@/components/Card';
|
||||
import type { TaskInfo, ChatMessage } from '../../../Chat';
|
||||
import { usePiChat, MessageBubble, StreamingBubble, ModelSelector } from '../../../Chat';
|
||||
import { useSettings } from 'state/useSettings';
|
||||
import { useVisiblePiModels } from 'state/useModels';
|
||||
import { useUserVisibleModels } from 'state/useModels';
|
||||
import type { TaskSummary } from '../../useTasks';
|
||||
|
||||
const playDing = () => {
|
||||
@@ -46,7 +46,7 @@ type PiMonoInnerProps = {
|
||||
const PiMonoInner = ({ defaultInput, cwd, initialModel, taskInfo, sandboxed }: PiMonoInnerProps) => {
|
||||
const [phase, setPhase] = useState<Phase>('ready');
|
||||
const chat = usePiChat(undefined, initialModel, { replaceUrl: false, taskInfo });
|
||||
const availableModels = useVisiblePiModels();
|
||||
const availableModels = useUserVisibleModels();
|
||||
|
||||
// --- Independent message accumulator (never loses messages) ---
|
||||
const accRef = useRef<ChatMessage[]>([]);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -55,6 +55,7 @@ export type UserSettings = {
|
||||
systemPrompt: string;
|
||||
temperature: number;
|
||||
defaultPwd: string;
|
||||
hiddenModels?: string[];
|
||||
};
|
||||
ai: {
|
||||
enabledModels: string[];
|
||||
|
||||
Reference in New Issue
Block a user