diff --git a/packages/app/src/app/settings.tsx b/packages/app/src/app/settings.tsx index 9af600d3e..d0a3b48bb 100644 --- a/packages/app/src/app/settings.tsx +++ b/packages/app/src/app/settings.tsx @@ -1,55 +1,23 @@ import { useEffect, useMemo } from "react"; -import { ActivityIndicator, View } from "react-native"; import { useRouter } from "expo-router"; -import { useUnistyles } from "react-native-unistyles"; import { DraftAgentScreen } from "@/screens/agent/draft-agent-screen"; import { useHosts } from "@/runtime/host-runtime"; -import { useFormPreferences } from "@/hooks/use-form-preferences"; import { buildHostSettingsRoute } from "@/utils/host-routes"; export default function LegacySettingsRoute() { const router = useRouter(); - const { theme } = useUnistyles(); const daemons = useHosts(); - const { preferences, isLoading: preferencesLoading } = useFormPreferences(); const targetServerId = useMemo(() => { - if (daemons.length === 0) { - return null; - } - if (preferences.serverId) { - const match = daemons.find((daemon) => daemon.serverId === preferences.serverId); - if (match) { - return match.serverId; - } - } return daemons[0]?.serverId ?? null; - }, [daemons, preferences.serverId]); + }, [daemons]); useEffect(() => { - if (preferencesLoading) { - return; - } if (!targetServerId) { return; } router.replace(buildHostSettingsRoute(targetServerId) as any); - }, [preferencesLoading, router, targetServerId]); - - if (preferencesLoading) { - return ( - - - - ); - } + }, [router, targetServerId]); if (!targetServerId) { return ; diff --git a/packages/app/src/hooks/use-agent-form-state.ts b/packages/app/src/hooks/use-agent-form-state.ts index 3413794fe..8d8e70225 100644 --- a/packages/app/src/hooks/use-agent-form-state.ts +++ b/packages/app/src/hooks/use-agent-form-state.ts @@ -11,7 +11,11 @@ import type { } from "@server/server/agent/agent-sdk-types"; import { useHosts } from "@/runtime/host-runtime"; import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime"; -import { useFormPreferences, type FormPreferences } from "./use-form-preferences"; +import { + useFormPreferences, + type FormPreferences, + type ProviderPreferences, +} from "./use-form-preferences"; // Explicit overrides from URL params or "New Agent" button export interface FormInitialValues { @@ -197,6 +201,8 @@ function resolveFormState( } const providerDef = allowedProviderMap.get(result.provider); + const providerPrefs = preferences?.providerPreferences?.[result.provider]; + // 2. Resolve modeId (depends on provider) if (!userModified.modeId) { const validModeIds = providerDef?.modes.map((m) => m.id) ?? []; @@ -207,6 +213,8 @@ function resolveFormState( validModeIds.includes(initialValues.modeId) ) { result.modeId = initialValues.modeId; + } else if (providerPrefs?.mode && validModeIds.includes(providerPrefs.mode)) { + result.modeId = providerPrefs.mode; } else { result.modeId = providerDef?.defaultModeId ?? validModeIds[0] ?? ""; } @@ -216,16 +224,21 @@ function resolveFormState( if (!userModified.model) { const isValidModel = (m: string) => availableModels?.some((am) => am.id === m) ?? false; const initialModel = normalizeSelectedModelId(initialValues?.model); + const preferredModel = normalizeSelectedModelId(providerPrefs?.model); const defaultModelId = resolveDefaultModelId(availableModels); if (initialModel) { - // If models aren't loaded yet, trust the initial value - // It will be validated once models load if (!availableModels || isValidModel(initialModel)) { result.model = initialModel; } else { result.model = defaultModelId; } + } else if (preferredModel) { + if (!availableModels || isValidModel(preferredModel)) { + result.model = preferredModel; + } else { + result.model = defaultModelId; + } } else if (defaultModelId) { result.model = defaultModelId; } else { @@ -240,8 +253,15 @@ function resolveFormState( : ""; if (!userModified.thinkingOptionId) { + const effectiveModelId = result.model.trim(); + const preferredThinking = effectiveModelId + ? providerPrefs?.thinkingByModel?.[effectiveModelId]?.trim() ?? "" + : ""; + if (initialThinkingOptionId.length > 0) { result.thinkingOptionId = initialThinkingOptionId; + } else if (preferredThinking.length > 0) { + result.thinkingOptionId = preferredThinking; } else { result.thinkingOptionId = ""; } @@ -257,12 +277,9 @@ function resolveFormState( } // 5. Resolve serverId (independent) - // Only use stored serverId if the host still exists in the registry if (!userModified.serverId) { if (initialValues?.serverId !== undefined) { result.serverId = initialValues.serverId; - } else if (preferences?.serverId && validServerIds.has(preferences.serverId)) { - result.serverId = preferences.serverId; } // else keep current } @@ -271,8 +288,6 @@ function resolveFormState( if (!userModified.workingDir) { if (initialValues?.workingDir !== undefined) { result.workingDir = initialValues.workingDir; - } else if (preferences?.workingDir) { - result.workingDir = preferences.workingDir; } // else keep current (empty string) } @@ -540,44 +555,43 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg validServerIds, ]); - // Persist inferred serverId so reloads keep the selection (e.g. URL serverId or first-time load). - useEffect(() => { - if (!isVisible || !isCreateFlow) return; - if (isPreferencesLoading) return; - if (userModified.serverId) return; - const serverId = formState.serverId; - if (!serverId) return; - if (preferences?.serverId === serverId) return; - void updatePreferences({ serverId }); - }, [ - isVisible, - isCreateFlow, - isPreferencesLoading, - userModified.serverId, - formState.serverId, - preferences?.serverId, - updatePreferences, - ]); - // User setters - mark fields as modified and persist to preferences const setSelectedServerIdFromUser = useCallback( (value: string | null) => { setFormState((prev) => ({ ...prev, serverId: value })); setUserModified((prev) => ({ ...prev, serverId: true })); - void updatePreferences({ serverId: value ?? undefined }); }, - [updatePreferences], + [], ); const setProviderFromUser = useCallback( (provider: AgentProvider) => { const providerModels = allProviderModels.get(provider) ?? null; const providerDef = providerDefinitionMap.get(provider); + const providerPrefs = preferences?.providerPreferences?.[provider]; + + const isValidModel = (m: string) => + providerModels?.some((am) => am.id === m) ?? false; + const preferredModel = normalizeSelectedModelId(providerPrefs?.model); const defaultModelId = resolveDefaultModelId(providerModels); - const defaultThinkingOptionId = resolveThinkingOptionId({ + const nextModelId = + preferredModel && (!providerModels || isValidModel(preferredModel)) + ? preferredModel + : defaultModelId; + + const validModeIds = providerDef?.modes.map((m) => m.id) ?? []; + const nextModeId = + providerPrefs?.mode && validModeIds.includes(providerPrefs.mode) + ? providerPrefs.mode + : providerDef?.defaultModeId ?? ""; + + const preferredThinking = nextModelId + ? providerPrefs?.thinkingByModel?.[nextModelId]?.trim() ?? "" + : ""; + const nextThinkingOptionId = resolveThinkingOptionId({ availableModels: providerModels, - modelId: defaultModelId, - requestedThinkingOptionId: "", + modelId: nextModelId, + requestedThinkingOptionId: preferredThinking, }); setUserModified((prev) => ({ ...prev, provider: true })); @@ -586,12 +600,12 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg setFormState((prev) => ({ ...prev, provider, - modeId: providerDef?.defaultModeId ?? "", - model: defaultModelId, - thinkingOptionId: defaultThinkingOptionId, + modeId: nextModeId, + model: nextModelId, + thinkingOptionId: nextThinkingOptionId, })); }, - [allProviderModels, providerDefinitionMap, updatePreferences], + [allProviderModels, preferences?.providerPreferences, providerDefinitionMap, updatePreferences], ); const setProviderAndModelFromUser = useCallback( @@ -664,9 +678,8 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg (value: string) => { setFormState((prev) => ({ ...prev, workingDir: value })); setUserModified((prev) => ({ ...prev, workingDir: true })); - void updatePreferences({ workingDir: value }); }, - [updatePreferences], + [], ); const setSelectedServerId = useCallback((value: string | null) => { @@ -678,15 +691,35 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg }, [providerModelsQuery]); const persistFormPreferences = useCallback(async () => { + const resolvedModel = resolveEffectiveModel(availableModels, formState.model); + const modelId = resolvedModel?.id ?? formState.model; + const existingProviderPrefs = preferences?.providerPreferences?.[formState.provider]; + + const nextProviderPrefs: ProviderPreferences = { + model: modelId || undefined, + mode: formState.modeId || undefined, + thinkingByModel: { + ...existingProviderPrefs?.thinkingByModel, + ...(modelId && formState.thinkingOptionId + ? { [modelId]: formState.thinkingOptionId } + : {}), + }, + }; + await updatePreferences({ - workingDir: formState.workingDir, provider: formState.provider, - serverId: formState.serverId ?? undefined, + providerPreferences: { + ...preferences?.providerPreferences, + [formState.provider]: nextProviderPrefs, + }, }); }, [ + availableModels, + formState.model, + formState.modeId, formState.provider, - formState.serverId, - formState.workingDir, + formState.thinkingOptionId, + preferences?.providerPreferences, updatePreferences, ]); diff --git a/packages/app/src/hooks/use-form-preferences.ts b/packages/app/src/hooks/use-form-preferences.ts index 112f28ee7..5e5dc0363 100644 --- a/packages/app/src/hooks/use-form-preferences.ts +++ b/packages/app/src/hooks/use-form-preferences.ts @@ -2,15 +2,22 @@ import { useCallback } from "react"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { z } from "zod"; + const FORM_PREFERENCES_STORAGE_KEY = "@paseo:create-agent-preferences"; const FORM_PREFERENCES_QUERY_KEY = ["form-preferences"]; -const formPreferencesSchema = z.object({ - workingDir: z.string().optional(), - provider: z.string().optional(), - serverId: z.string().optional(), +const providerPreferencesSchema = z.object({ + model: z.string().optional(), + mode: z.string().optional(), + thinkingByModel: z.record(z.string()).optional(), }); +const formPreferencesSchema = z.object({ + provider: z.string().optional(), + providerPreferences: z.record(providerPreferencesSchema).optional(), +}); + +export type ProviderPreferences = z.infer; export type FormPreferences = z.infer; const DEFAULT_FORM_PREFERENCES: FormPreferences = {};