mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
fix: load OpenRouter models on kanban board without visiting settings
Problem: - OpenRouter dynamic models only appeared after visiting settings page - PhaseModelSelector (used in Add/Edit Feature dialogs) only fetched Codex models - dynamicOpencodeModels remained empty until OpencodeSettingsTab mounted Solution: - Add fetchOpencodeModels() action to app-store mirroring fetchCodexModels pattern - Add state tracking: opencodeModelsLoading, opencodeModelsError, timestamps - Call fetchOpencodeModels() in PhaseModelSelector useEffect on mount - Use same caching strategy: 5min success cache, 30sec failure cooldown Files changed: - apps/ui/src/store/app-store.ts - Add OpenCode model loading state properties - Add fetchOpencodeModels action with error handling & caching - apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx - Add opencodeModelsLoading, fetchOpencodeModels to store hook - Add useEffect to fetch OpenCode models on mount Result: - OpenRouter models now appear in Add/Edit Feature dialogs immediately - No need to visit settings page first - Consistent with Codex model loading behavior
This commit is contained in:
@@ -166,6 +166,8 @@ export function PhaseModelSelector({
|
||||
codexModelsLoading,
|
||||
fetchCodexModels,
|
||||
dynamicOpencodeModels,
|
||||
opencodeModelsLoading,
|
||||
fetchOpencodeModels,
|
||||
} = useAppStore();
|
||||
|
||||
// Detect mobile devices to use inline expansion instead of nested popovers
|
||||
@@ -185,6 +187,15 @@ export function PhaseModelSelector({
|
||||
}
|
||||
}, [codexModels.length, codexModelsLoading, fetchCodexModels]);
|
||||
|
||||
// Fetch OpenCode models on mount
|
||||
useEffect(() => {
|
||||
if (dynamicOpencodeModels.length === 0 && !opencodeModelsLoading) {
|
||||
fetchOpencodeModels().catch(() => {
|
||||
// Silently fail - user will see only static OpenCode models
|
||||
});
|
||||
}
|
||||
}, [dynamicOpencodeModels.length, opencodeModelsLoading, fetchOpencodeModels]);
|
||||
|
||||
// Close expanded group when trigger scrolls out of view
|
||||
useEffect(() => {
|
||||
const triggerElement = expandedTriggerRef.current;
|
||||
|
||||
@@ -601,6 +601,10 @@ export interface AppState {
|
||||
authenticated: boolean;
|
||||
authMethod?: string;
|
||||
}>; // Cached providers
|
||||
opencodeModelsLoading: boolean; // Whether OpenCode models are being fetched
|
||||
opencodeModelsError: string | null; // Error message if fetch failed
|
||||
opencodeModelsLastFetched: number | null; // Timestamp of last successful fetch
|
||||
opencodeModelsLastFailedAt: number | null; // Timestamp of last failed fetch
|
||||
|
||||
// Claude Agent SDK Settings
|
||||
autoLoadClaudeMd: boolean; // Auto-load CLAUDE.md files using SDK's settingSources option
|
||||
@@ -1182,6 +1186,9 @@ export interface AppActions {
|
||||
}>
|
||||
) => void;
|
||||
|
||||
// OpenCode Models actions
|
||||
fetchOpencodeModels: (forceRefresh?: boolean) => Promise<void>;
|
||||
|
||||
// Init Script State actions (keyed by projectPath::branch to support concurrent scripts)
|
||||
setInitScriptState: (
|
||||
projectPath: string,
|
||||
@@ -1253,6 +1260,10 @@ const initialState: AppState = {
|
||||
dynamicOpencodeModels: [], // Empty until fetched from OpenCode CLI
|
||||
enabledDynamicModelIds: [], // Empty until user enables dynamic models
|
||||
cachedOpencodeProviders: [], // Empty until fetched from OpenCode CLI
|
||||
opencodeModelsLoading: false,
|
||||
opencodeModelsError: null,
|
||||
opencodeModelsLastFetched: null,
|
||||
opencodeModelsLastFailedAt: null,
|
||||
autoLoadClaudeMd: false, // Default to disabled (user must opt-in)
|
||||
skipSandboxWarning: false, // Default to disabled (show sandbox warning dialog)
|
||||
mcpServers: [], // No MCP servers configured by default
|
||||
@@ -3251,6 +3262,65 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
codexModelsLastFetched: Date.now(),
|
||||
}),
|
||||
|
||||
// OpenCode Models actions
|
||||
fetchOpencodeModels: async (forceRefresh = false) => {
|
||||
const FAILURE_COOLDOWN_MS = 30 * 1000; // 30 seconds
|
||||
const SUCCESS_CACHE_MS = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
const { opencodeModelsLastFetched, opencodeModelsLoading, opencodeModelsLastFailedAt } = get();
|
||||
|
||||
// Skip if already loading
|
||||
if (opencodeModelsLoading) return;
|
||||
|
||||
// Skip if recently failed and not forcing refresh
|
||||
if (
|
||||
!forceRefresh &&
|
||||
opencodeModelsLastFailedAt &&
|
||||
Date.now() - opencodeModelsLastFailedAt < FAILURE_COOLDOWN_MS
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if recently fetched successfully and not forcing refresh
|
||||
if (
|
||||
!forceRefresh &&
|
||||
opencodeModelsLastFetched &&
|
||||
Date.now() - opencodeModelsLastFetched < SUCCESS_CACHE_MS
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
set({ opencodeModelsLoading: true, opencodeModelsError: null });
|
||||
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
if (!api.setup) {
|
||||
throw new Error('Setup API not available');
|
||||
}
|
||||
|
||||
const result = await api.setup.getOpencodeModels(forceRefresh);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to fetch OpenCode models');
|
||||
}
|
||||
|
||||
set({
|
||||
dynamicOpencodeModels: result.models || [],
|
||||
opencodeModelsLastFetched: Date.now(),
|
||||
opencodeModelsLoading: false,
|
||||
opencodeModelsError: null,
|
||||
opencodeModelsLastFailedAt: null, // Clear failure on success
|
||||
});
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
set({
|
||||
opencodeModelsError: errorMessage,
|
||||
opencodeModelsLoading: false,
|
||||
opencodeModelsLastFailedAt: Date.now(), // Record failure time for cooldown
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Pipeline actions
|
||||
setPipelineConfig: (projectPath, config) => {
|
||||
set({
|
||||
|
||||
Reference in New Issue
Block a user