import { useState, useCallback, useMemo, useEffect } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { useAppStore } from '@/store/app-store'; import { OpencodeCliStatus, OpencodeCliStatusSkeleton } from '../cli-status/opencode-cli-status'; import { OpencodeModelConfiguration } from './opencode-model-configuration'; import { ProviderToggle } from './provider-toggle'; import { useOpencodeCliStatus, useOpencodeProviders, useOpencodeModels } from '@/hooks/queries'; import { queryKeys } from '@/lib/query-keys'; import type { CliStatus as SharedCliStatus } from '../shared/types'; import type { OpencodeModelId } from '@automaker/types'; import type { OpencodeAuthStatus, OpenCodeProviderInfo } from '../cli-status/opencode-cli-status'; export function OpencodeSettingsTab() { const queryClient = useQueryClient(); const { enabledOpencodeModels, opencodeDefaultModel, setOpencodeDefaultModel, toggleOpencodeModel, enabledDynamicModelIds, toggleDynamicModel, setDynamicOpencodeModels, } = useAppStore(); const [isSaving, setIsSaving] = useState(false); // React Query hooks for data fetching const { data: cliStatusData, isLoading: isCheckingOpencodeCli, refetch: refetchCliStatus, } = useOpencodeCliStatus(); const isCliInstalled = cliStatusData?.installed ?? false; const { data: providersData = [], isFetching: isFetchingProviders } = useOpencodeProviders(); const { data: modelsData = [], isFetching: isFetchingModels } = useOpencodeModels(); // Sync React Query opencode models data to Zustand store so that the model // selector dropdown (PhaseModelSelector) reflects newly enabled models without // requiring a page refresh. The selector reads from the Zustand store while // this settings tab fetches via React Query — keeping them in sync bridges that gap. useEffect(() => { if (modelsData.length > 0) { setDynamicOpencodeModels(modelsData); } }, [modelsData, setDynamicOpencodeModels]); // Transform CLI status to the expected format const cliStatus = useMemo((): SharedCliStatus | null => { if (!cliStatusData) return null; return { success: cliStatusData.success ?? false, status: cliStatusData.installed ? 'installed' : 'not_installed', method: cliStatusData.auth?.method, version: cliStatusData.version, path: cliStatusData.path, recommendation: cliStatusData.recommendation, installCommands: cliStatusData.installCommands, }; }, [cliStatusData]); // Transform auth status to the expected format const authStatus = useMemo((): OpencodeAuthStatus | null => { if (!cliStatusData?.auth) return null; // Cast auth to include optional error field for type compatibility const auth = cliStatusData.auth as typeof cliStatusData.auth & { error?: string }; return { authenticated: auth.authenticated, method: (auth.method as OpencodeAuthStatus['method']) || 'none', hasApiKey: auth.hasApiKey, hasEnvApiKey: auth.hasEnvApiKey, hasOAuthToken: auth.hasOAuthToken, error: auth.error, }; }, [cliStatusData]); // Refresh all opencode-related queries const handleRefreshOpencodeCli = useCallback(async () => { await Promise.all([ queryClient.invalidateQueries({ queryKey: queryKeys.cli.opencode() }), queryClient.invalidateQueries({ queryKey: queryKeys.models.opencodeProviders() }), queryClient.invalidateQueries({ queryKey: queryKeys.models.opencode() }), ]); await refetchCliStatus(); toast.success('OpenCode CLI refreshed'); }, [queryClient, refetchCliStatus]); const handleDefaultModelChange = useCallback( (model: OpencodeModelId) => { setIsSaving(true); try { setOpencodeDefaultModel(model); toast.success('Default model updated'); } catch { toast.error('Failed to update default model'); } finally { setIsSaving(false); } }, [setOpencodeDefaultModel] ); const handleModelToggle = useCallback( (model: OpencodeModelId, enabled: boolean) => { setIsSaving(true); try { toggleOpencodeModel(model, enabled); } catch { toast.error('Failed to update models'); } finally { setIsSaving(false); } }, [toggleOpencodeModel] ); const handleDynamicModelToggle = useCallback( (modelId: string, enabled: boolean) => { setIsSaving(true); try { toggleDynamicModel(modelId, enabled); } catch { toast.error('Failed to update dynamic model'); } finally { setIsSaving(false); } }, [toggleDynamicModel] ); // Show skeleton only while checking CLI status initially if (!cliStatus && isCheckingOpencodeCli) { return (
); } const isLoadingDynamicModels = isFetchingProviders || isFetchingModels; return (
{/* Provider Visibility Toggle */} {/* Model Configuration - Only show when CLI is installed */} {isCliInstalled && ( )}
); } export default OpencodeSettingsTab;