Fix mobile dropdown close and draft thinking persistence
Closes #150 Closes #151
This commit is contained in:
@@ -8,6 +8,7 @@ import { getProviderIcon } from "@/components/provider-icons";
|
|||||||
import { CombinedModelSelector } from "@/components/combined-model-selector";
|
import { CombinedModelSelector } from "@/components/combined-model-selector";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
|
import { mergeProviderPreferences, useFormPreferences } from "@/hooks/use-form-preferences";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -609,6 +610,7 @@ function ControlledStatusBar({
|
|||||||
const EMPTY_MODES: AgentMode[] = [];
|
const EMPTY_MODES: AgentMode[] = [];
|
||||||
|
|
||||||
export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
||||||
|
const { preferences, updatePreferences } = useFormPreferences();
|
||||||
const agent = useSessionStore(
|
const agent = useSessionStore(
|
||||||
useShallow((state) => {
|
useShallow((state) => {
|
||||||
const currentAgent = state.sessions[serverId]?.agents?.get(agentId) ?? null;
|
const currentAgent = state.sessions[serverId]?.agents?.get(agentId) ?? null;
|
||||||
@@ -709,6 +711,17 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
if (!client) {
|
if (!client) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
void updatePreferences(
|
||||||
|
mergeProviderPreferences({
|
||||||
|
preferences,
|
||||||
|
provider: agent.provider,
|
||||||
|
updates: {
|
||||||
|
model: modelId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).catch((error) => {
|
||||||
|
console.warn("[AgentStatusBar] persist model preference failed", error);
|
||||||
|
});
|
||||||
void client.setAgentModel(agentId, modelId).catch((error) => {
|
void client.setAgentModel(agentId, modelId).catch((error) => {
|
||||||
console.warn("[AgentStatusBar] setAgentModel failed", error);
|
console.warn("[AgentStatusBar] setAgentModel failed", error);
|
||||||
});
|
});
|
||||||
@@ -719,6 +732,23 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) {
|
|||||||
if (!client) {
|
if (!client) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const activeModelId = modelSelection.activeModelId;
|
||||||
|
if (activeModelId) {
|
||||||
|
void updatePreferences(
|
||||||
|
mergeProviderPreferences({
|
||||||
|
preferences,
|
||||||
|
provider: agent.provider,
|
||||||
|
updates: {
|
||||||
|
model: activeModelId,
|
||||||
|
thinkingByModel: {
|
||||||
|
[activeModelId]: thinkingOptionId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).catch((error) => {
|
||||||
|
console.warn("[AgentStatusBar] persist thinking preference failed", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
void client.setAgentThinkingOption(agentId, thinkingOptionId).catch((error) => {
|
void client.setAgentThinkingOption(agentId, thinkingOptionId).catch((error) => {
|
||||||
console.warn("[AgentStatusBar] setAgentThinkingOption failed", error);
|
console.warn("[AgentStatusBar] setAgentThinkingOption failed", error);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -278,7 +278,10 @@ export function DropdownMenuContent({
|
|||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
setClosing(false);
|
setClosing(false);
|
||||||
} else if (modalVisible) {
|
} else if (modalVisible) {
|
||||||
setClosing(true);
|
// Avoid leaving an invisible full-screen Modal mounted on native when
|
||||||
|
// the exit animation callback does not fire.
|
||||||
|
setClosing(false);
|
||||||
|
setModalVisible(false);
|
||||||
}
|
}
|
||||||
}, [open, modalVisible]);
|
}, [open, modalVisible]);
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { useHosts } from "@/runtime/host-runtime";
|
|||||||
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
||||||
import {
|
import {
|
||||||
useFormPreferences,
|
useFormPreferences,
|
||||||
|
mergeProviderPreferences,
|
||||||
type FormPreferences,
|
type FormPreferences,
|
||||||
type ProviderPreferences,
|
type ProviderPreferences,
|
||||||
} from "./use-form-preferences";
|
} from "./use-form-preferences";
|
||||||
@@ -693,26 +694,23 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
|||||||
const persistFormPreferences = useCallback(async () => {
|
const persistFormPreferences = useCallback(async () => {
|
||||||
const resolvedModel = resolveEffectiveModel(availableModels, formState.model);
|
const resolvedModel = resolveEffectiveModel(availableModels, formState.model);
|
||||||
const modelId = resolvedModel?.id ?? formState.model;
|
const modelId = resolvedModel?.id ?? formState.model;
|
||||||
const existingProviderPrefs = preferences?.providerPreferences?.[formState.provider];
|
const nextPreferences = mergeProviderPreferences({
|
||||||
|
preferences: preferences ?? {},
|
||||||
const nextProviderPrefs: ProviderPreferences = {
|
|
||||||
model: modelId || undefined,
|
|
||||||
mode: formState.modeId || undefined,
|
|
||||||
thinkingByModel: {
|
|
||||||
...existingProviderPrefs?.thinkingByModel,
|
|
||||||
...(modelId && formState.thinkingOptionId
|
|
||||||
? { [modelId]: formState.thinkingOptionId }
|
|
||||||
: {}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
await updatePreferences({
|
|
||||||
provider: formState.provider,
|
provider: formState.provider,
|
||||||
providerPreferences: {
|
updates: {
|
||||||
...preferences?.providerPreferences,
|
model: modelId || undefined,
|
||||||
[formState.provider]: nextProviderPrefs,
|
mode: formState.modeId || undefined,
|
||||||
},
|
...(modelId && formState.thinkingOptionId
|
||||||
|
? {
|
||||||
|
thinkingByModel: {
|
||||||
|
[modelId]: formState.thinkingOptionId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
} satisfies Partial<ProviderPreferences>,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await updatePreferences(nextPreferences);
|
||||||
}, [
|
}, [
|
||||||
availableModels,
|
availableModels,
|
||||||
formState.model,
|
formState.model,
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { mergeProviderPreferences } from "./use-form-preferences";
|
||||||
|
|
||||||
|
describe("mergeProviderPreferences", () => {
|
||||||
|
it("stores the selected model for a provider", () => {
|
||||||
|
expect(
|
||||||
|
mergeProviderPreferences({
|
||||||
|
preferences: {},
|
||||||
|
provider: "claude",
|
||||||
|
updates: { model: "claude-opus-4-6" },
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
provider: "claude",
|
||||||
|
providerPreferences: {
|
||||||
|
claude: {
|
||||||
|
model: "claude-opus-4-6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("merges thinking preferences by model without dropping existing entries", () => {
|
||||||
|
expect(
|
||||||
|
mergeProviderPreferences({
|
||||||
|
preferences: {
|
||||||
|
provider: "claude",
|
||||||
|
providerPreferences: {
|
||||||
|
claude: {
|
||||||
|
model: "claude-sonnet-4-6",
|
||||||
|
thinkingByModel: {
|
||||||
|
"claude-sonnet-4-6": "medium",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
provider: "claude",
|
||||||
|
updates: {
|
||||||
|
thinkingByModel: {
|
||||||
|
"claude-opus-4-6": "high",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
provider: "claude",
|
||||||
|
providerPreferences: {
|
||||||
|
claude: {
|
||||||
|
model: "claude-sonnet-4-6",
|
||||||
|
thinkingByModel: {
|
||||||
|
"claude-sonnet-4-6": "medium",
|
||||||
|
"claude-opus-4-6": "high",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,6 +2,7 @@ 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";
|
||||||
|
import type { AgentProvider } from "@server/server/agent/agent-sdk-types";
|
||||||
|
|
||||||
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"];
|
||||||
@@ -35,6 +36,36 @@ export interface UseFormPreferencesReturn {
|
|||||||
updatePreferences: (updates: Partial<FormPreferences>) => Promise<void>;
|
updatePreferences: (updates: Partial<FormPreferences>) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mergeProviderPreferences(args: {
|
||||||
|
preferences: FormPreferences;
|
||||||
|
provider: AgentProvider;
|
||||||
|
updates: Partial<ProviderPreferences>;
|
||||||
|
}): FormPreferences {
|
||||||
|
const { preferences, provider, updates } = args;
|
||||||
|
const existingProviderPreferences = preferences.providerPreferences ?? {};
|
||||||
|
const existing = existingProviderPreferences[provider] ?? {};
|
||||||
|
const nextThinkingByModel =
|
||||||
|
updates.thinkingByModel === undefined
|
||||||
|
? existing.thinkingByModel
|
||||||
|
: {
|
||||||
|
...existing.thinkingByModel,
|
||||||
|
...updates.thinkingByModel,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...preferences,
|
||||||
|
provider,
|
||||||
|
providerPreferences: {
|
||||||
|
...existingProviderPreferences,
|
||||||
|
[provider]: {
|
||||||
|
...existing,
|
||||||
|
...updates,
|
||||||
|
...(nextThinkingByModel ? { thinkingByModel: nextThinkingByModel } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function useFormPreferences(): UseFormPreferencesReturn {
|
export function useFormPreferences(): UseFormPreferencesReturn {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { data, isPending } = useQuery({
|
const { data, isPending } = useQuery({
|
||||||
|
|||||||
Reference in New Issue
Block a user