feat: live model list in provider state, validate saved model, expose model options

This commit is contained in:
Zac Gaetano 2026-05-30 10:00:33 -04:00
parent 3e9c9a6672
commit 5a73ec83fc

View file

@ -4,6 +4,8 @@ import { CLAUDE_MODELS, CODEX_MODELS, CURSOR_MODELS, GEMINI_MODELS } from '../..
import type { PendingPermissionRequest, PermissionMode } from '../types/types'; import type { PendingPermissionRequest, PermissionMode } from '../types/types';
import type { ProjectSession, LLMProvider } from '../../../types/app'; import type { ProjectSession, LLMProvider } from '../../../types/app';
type ModelOption = { value: string; label: string };
const getPermissionModesForProvider = (provider: LLMProvider): PermissionMode[] => { const getPermissionModesForProvider = (provider: LLMProvider): PermissionMode[] => {
if (provider === 'codex') { if (provider === 'codex') {
return ['default', 'acceptEdits', 'bypassPermissions']; return ['default', 'acceptEdits', 'bypassPermissions'];
@ -37,6 +39,38 @@ export function useChatProviderState({ selectedSession }: UseChatProviderStateAr
return localStorage.getItem('gemini-model') || GEMINI_MODELS.DEFAULT; return localStorage.getItem('gemini-model') || GEMINI_MODELS.DEFAULT;
}); });
// Live model lists — fall back to static constants until API responds
const [claudeModelOptions, setClaudeModelOptions] = useState<ModelOption[]>(CLAUDE_MODELS.OPTIONS);
const [codexModelOptions] = useState<ModelOption[]>(CODEX_MODELS.OPTIONS);
const [geminiModelOptions] = useState<ModelOption[]>(GEMINI_MODELS.OPTIONS);
const [cursorModelOptions] = useState<ModelOption[]>(CURSOR_MODELS.OPTIONS);
// Fetch live model list and validate the saved claude model
useEffect(() => {
authenticatedFetch('/api/models')
.then((res) => {
if (!res.ok) return;
return res.json();
})
.then((data) => {
if (!Array.isArray(data?.claude) || data.claude.length === 0) return;
const options: ModelOption[] = data.claude;
setClaudeModelOptions(options);
// Validate saved model — if it's no longer in the list, reset to default
setClaudeModel((current) => {
const valid = options.some((o) => o.value === current);
if (valid) return current;
const fallback = options[0]?.value ?? CLAUDE_MODELS.DEFAULT;
localStorage.setItem('claude-model', fallback);
return fallback;
});
})
.catch(() => {
// Static fallback already in place — nothing to do
});
}, []);
const lastProviderRef = useRef(provider); const lastProviderRef = useRef(provider);
useEffect(() => { useEffect(() => {
@ -118,6 +152,10 @@ export function useChatProviderState({ selectedSession }: UseChatProviderStateAr
setCodexModel, setCodexModel,
geminiModel, geminiModel,
setGeminiModel, setGeminiModel,
claudeModelOptions,
codexModelOptions,
geminiModelOptions,
cursorModelOptions,
permissionMode, permissionMode,
setPermissionMode, setPermissionMode,
pendingPermissionRequests, pendingPermissionRequests,