Restore per-provider form preferences and remove Auto model fallback
- Add per-provider preference storage: model, mode, thinkingByModel - resolveFormState reads stored prefs as fallback between initial values and provider defaults - setProviderFromUser seeds model/mode/thinking from stored prefs when switching providers - persistFormPreferences writes full per-provider state on agent create - Remove workingDir and serverId from stored preferences (redundant with workspace tab context) - Simplify settings.tsx by removing preference-based server resolution
This commit is contained in:
@@ -1,55 +1,23 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { ActivityIndicator, View } from "react-native";
|
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import { useUnistyles } from "react-native-unistyles";
|
|
||||||
import { DraftAgentScreen } from "@/screens/agent/draft-agent-screen";
|
import { DraftAgentScreen } from "@/screens/agent/draft-agent-screen";
|
||||||
import { useHosts } from "@/runtime/host-runtime";
|
import { useHosts } from "@/runtime/host-runtime";
|
||||||
import { useFormPreferences } from "@/hooks/use-form-preferences";
|
|
||||||
import { buildHostSettingsRoute } from "@/utils/host-routes";
|
import { buildHostSettingsRoute } from "@/utils/host-routes";
|
||||||
|
|
||||||
export default function LegacySettingsRoute() {
|
export default function LegacySettingsRoute() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { theme } = useUnistyles();
|
|
||||||
const daemons = useHosts();
|
const daemons = useHosts();
|
||||||
const { preferences, isLoading: preferencesLoading } = useFormPreferences();
|
|
||||||
|
|
||||||
const targetServerId = useMemo(() => {
|
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;
|
return daemons[0]?.serverId ?? null;
|
||||||
}, [daemons, preferences.serverId]);
|
}, [daemons]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (preferencesLoading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!targetServerId) {
|
if (!targetServerId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.replace(buildHostSettingsRoute(targetServerId) as any);
|
router.replace(buildHostSettingsRoute(targetServerId) as any);
|
||||||
}, [preferencesLoading, router, targetServerId]);
|
}, [router, targetServerId]);
|
||||||
|
|
||||||
if (preferencesLoading) {
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
backgroundColor: theme.colors.surface0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetServerId) {
|
if (!targetServerId) {
|
||||||
return <DraftAgentScreen />;
|
return <DraftAgentScreen />;
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ import type {
|
|||||||
} from "@server/server/agent/agent-sdk-types";
|
} from "@server/server/agent/agent-sdk-types";
|
||||||
import { useHosts } from "@/runtime/host-runtime";
|
import { useHosts } from "@/runtime/host-runtime";
|
||||||
import { useHostRuntimeClient, useHostRuntimeIsConnected } 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
|
// Explicit overrides from URL params or "New Agent" button
|
||||||
export interface FormInitialValues {
|
export interface FormInitialValues {
|
||||||
@@ -197,6 +201,8 @@ function resolveFormState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const providerDef = allowedProviderMap.get(result.provider);
|
const providerDef = allowedProviderMap.get(result.provider);
|
||||||
|
const providerPrefs = preferences?.providerPreferences?.[result.provider];
|
||||||
|
|
||||||
// 2. Resolve modeId (depends on provider)
|
// 2. Resolve modeId (depends on provider)
|
||||||
if (!userModified.modeId) {
|
if (!userModified.modeId) {
|
||||||
const validModeIds = providerDef?.modes.map((m) => m.id) ?? [];
|
const validModeIds = providerDef?.modes.map((m) => m.id) ?? [];
|
||||||
@@ -207,6 +213,8 @@ function resolveFormState(
|
|||||||
validModeIds.includes(initialValues.modeId)
|
validModeIds.includes(initialValues.modeId)
|
||||||
) {
|
) {
|
||||||
result.modeId = initialValues.modeId;
|
result.modeId = initialValues.modeId;
|
||||||
|
} else if (providerPrefs?.mode && validModeIds.includes(providerPrefs.mode)) {
|
||||||
|
result.modeId = providerPrefs.mode;
|
||||||
} else {
|
} else {
|
||||||
result.modeId = providerDef?.defaultModeId ?? validModeIds[0] ?? "";
|
result.modeId = providerDef?.defaultModeId ?? validModeIds[0] ?? "";
|
||||||
}
|
}
|
||||||
@@ -216,16 +224,21 @@ function resolveFormState(
|
|||||||
if (!userModified.model) {
|
if (!userModified.model) {
|
||||||
const isValidModel = (m: string) => availableModels?.some((am) => am.id === m) ?? false;
|
const isValidModel = (m: string) => availableModels?.some((am) => am.id === m) ?? false;
|
||||||
const initialModel = normalizeSelectedModelId(initialValues?.model);
|
const initialModel = normalizeSelectedModelId(initialValues?.model);
|
||||||
|
const preferredModel = normalizeSelectedModelId(providerPrefs?.model);
|
||||||
const defaultModelId = resolveDefaultModelId(availableModels);
|
const defaultModelId = resolveDefaultModelId(availableModels);
|
||||||
|
|
||||||
if (initialModel) {
|
if (initialModel) {
|
||||||
// If models aren't loaded yet, trust the initial value
|
|
||||||
// It will be validated once models load
|
|
||||||
if (!availableModels || isValidModel(initialModel)) {
|
if (!availableModels || isValidModel(initialModel)) {
|
||||||
result.model = initialModel;
|
result.model = initialModel;
|
||||||
} else {
|
} else {
|
||||||
result.model = defaultModelId;
|
result.model = defaultModelId;
|
||||||
}
|
}
|
||||||
|
} else if (preferredModel) {
|
||||||
|
if (!availableModels || isValidModel(preferredModel)) {
|
||||||
|
result.model = preferredModel;
|
||||||
|
} else {
|
||||||
|
result.model = defaultModelId;
|
||||||
|
}
|
||||||
} else if (defaultModelId) {
|
} else if (defaultModelId) {
|
||||||
result.model = defaultModelId;
|
result.model = defaultModelId;
|
||||||
} else {
|
} else {
|
||||||
@@ -240,8 +253,15 @@ function resolveFormState(
|
|||||||
: "";
|
: "";
|
||||||
|
|
||||||
if (!userModified.thinkingOptionId) {
|
if (!userModified.thinkingOptionId) {
|
||||||
|
const effectiveModelId = result.model.trim();
|
||||||
|
const preferredThinking = effectiveModelId
|
||||||
|
? providerPrefs?.thinkingByModel?.[effectiveModelId]?.trim() ?? ""
|
||||||
|
: "";
|
||||||
|
|
||||||
if (initialThinkingOptionId.length > 0) {
|
if (initialThinkingOptionId.length > 0) {
|
||||||
result.thinkingOptionId = initialThinkingOptionId;
|
result.thinkingOptionId = initialThinkingOptionId;
|
||||||
|
} else if (preferredThinking.length > 0) {
|
||||||
|
result.thinkingOptionId = preferredThinking;
|
||||||
} else {
|
} else {
|
||||||
result.thinkingOptionId = "";
|
result.thinkingOptionId = "";
|
||||||
}
|
}
|
||||||
@@ -257,12 +277,9 @@ function resolveFormState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 5. Resolve serverId (independent)
|
// 5. Resolve serverId (independent)
|
||||||
// Only use stored serverId if the host still exists in the registry
|
|
||||||
if (!userModified.serverId) {
|
if (!userModified.serverId) {
|
||||||
if (initialValues?.serverId !== undefined) {
|
if (initialValues?.serverId !== undefined) {
|
||||||
result.serverId = initialValues.serverId;
|
result.serverId = initialValues.serverId;
|
||||||
} else if (preferences?.serverId && validServerIds.has(preferences.serverId)) {
|
|
||||||
result.serverId = preferences.serverId;
|
|
||||||
}
|
}
|
||||||
// else keep current
|
// else keep current
|
||||||
}
|
}
|
||||||
@@ -271,8 +288,6 @@ function resolveFormState(
|
|||||||
if (!userModified.workingDir) {
|
if (!userModified.workingDir) {
|
||||||
if (initialValues?.workingDir !== undefined) {
|
if (initialValues?.workingDir !== undefined) {
|
||||||
result.workingDir = initialValues.workingDir;
|
result.workingDir = initialValues.workingDir;
|
||||||
} else if (preferences?.workingDir) {
|
|
||||||
result.workingDir = preferences.workingDir;
|
|
||||||
}
|
}
|
||||||
// else keep current (empty string)
|
// else keep current (empty string)
|
||||||
}
|
}
|
||||||
@@ -540,44 +555,43 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
|||||||
validServerIds,
|
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
|
// User setters - mark fields as modified and persist to preferences
|
||||||
const setSelectedServerIdFromUser = useCallback(
|
const setSelectedServerIdFromUser = useCallback(
|
||||||
(value: string | null) => {
|
(value: string | null) => {
|
||||||
setFormState((prev) => ({ ...prev, serverId: value }));
|
setFormState((prev) => ({ ...prev, serverId: value }));
|
||||||
setUserModified((prev) => ({ ...prev, serverId: true }));
|
setUserModified((prev) => ({ ...prev, serverId: true }));
|
||||||
void updatePreferences({ serverId: value ?? undefined });
|
|
||||||
},
|
},
|
||||||
[updatePreferences],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const setProviderFromUser = useCallback(
|
const setProviderFromUser = useCallback(
|
||||||
(provider: AgentProvider) => {
|
(provider: AgentProvider) => {
|
||||||
const providerModels = allProviderModels.get(provider) ?? null;
|
const providerModels = allProviderModels.get(provider) ?? null;
|
||||||
const providerDef = providerDefinitionMap.get(provider);
|
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 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,
|
availableModels: providerModels,
|
||||||
modelId: defaultModelId,
|
modelId: nextModelId,
|
||||||
requestedThinkingOptionId: "",
|
requestedThinkingOptionId: preferredThinking,
|
||||||
});
|
});
|
||||||
|
|
||||||
setUserModified((prev) => ({ ...prev, provider: true }));
|
setUserModified((prev) => ({ ...prev, provider: true }));
|
||||||
@@ -586,12 +600,12 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
|||||||
setFormState((prev) => ({
|
setFormState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
provider,
|
provider,
|
||||||
modeId: providerDef?.defaultModeId ?? "",
|
modeId: nextModeId,
|
||||||
model: defaultModelId,
|
model: nextModelId,
|
||||||
thinkingOptionId: defaultThinkingOptionId,
|
thinkingOptionId: nextThinkingOptionId,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
[allProviderModels, providerDefinitionMap, updatePreferences],
|
[allProviderModels, preferences?.providerPreferences, providerDefinitionMap, updatePreferences],
|
||||||
);
|
);
|
||||||
|
|
||||||
const setProviderAndModelFromUser = useCallback(
|
const setProviderAndModelFromUser = useCallback(
|
||||||
@@ -664,9 +678,8 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
|||||||
(value: string) => {
|
(value: string) => {
|
||||||
setFormState((prev) => ({ ...prev, workingDir: value }));
|
setFormState((prev) => ({ ...prev, workingDir: value }));
|
||||||
setUserModified((prev) => ({ ...prev, workingDir: true }));
|
setUserModified((prev) => ({ ...prev, workingDir: true }));
|
||||||
void updatePreferences({ workingDir: value });
|
|
||||||
},
|
},
|
||||||
[updatePreferences],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const setSelectedServerId = useCallback((value: string | null) => {
|
const setSelectedServerId = useCallback((value: string | null) => {
|
||||||
@@ -678,15 +691,35 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
|||||||
}, [providerModelsQuery]);
|
}, [providerModelsQuery]);
|
||||||
|
|
||||||
const persistFormPreferences = useCallback(async () => {
|
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({
|
await updatePreferences({
|
||||||
workingDir: formState.workingDir,
|
|
||||||
provider: formState.provider,
|
provider: formState.provider,
|
||||||
serverId: formState.serverId ?? undefined,
|
providerPreferences: {
|
||||||
|
...preferences?.providerPreferences,
|
||||||
|
[formState.provider]: nextProviderPrefs,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
|
availableModels,
|
||||||
|
formState.model,
|
||||||
|
formState.modeId,
|
||||||
formState.provider,
|
formState.provider,
|
||||||
formState.serverId,
|
formState.thinkingOptionId,
|
||||||
formState.workingDir,
|
preferences?.providerPreferences,
|
||||||
updatePreferences,
|
updatePreferences,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,22 @@ import { useCallback } from "react";
|
|||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const FORM_PREFERENCES_STORAGE_KEY = "@paseo:create-agent-preferences";
|
const FORM_PREFERENCES_STORAGE_KEY = "@paseo:create-agent-preferences";
|
||||||
const FORM_PREFERENCES_QUERY_KEY = ["form-preferences"];
|
const FORM_PREFERENCES_QUERY_KEY = ["form-preferences"];
|
||||||
|
|
||||||
const formPreferencesSchema = z.object({
|
const providerPreferencesSchema = z.object({
|
||||||
workingDir: z.string().optional(),
|
model: z.string().optional(),
|
||||||
provider: z.string().optional(),
|
mode: z.string().optional(),
|
||||||
serverId: 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<typeof providerPreferencesSchema>;
|
||||||
export type FormPreferences = z.infer<typeof formPreferencesSchema>;
|
export type FormPreferences = z.infer<typeof formPreferencesSchema>;
|
||||||
|
|
||||||
const DEFAULT_FORM_PREFERENCES: FormPreferences = {};
|
const DEFAULT_FORM_PREFERENCES: FormPreferences = {};
|
||||||
|
|||||||
Reference in New Issue
Block a user