Persist create-agent mode preferences reliably
This commit is contained in:
@@ -84,7 +84,7 @@ interface UseDraftAgentCreateFlowOptions<TDraftAgent, TCreateResult> {
|
|||||||
initialAttempt?: CreateAttempt | null;
|
initialAttempt?: CreateAttempt | null;
|
||||||
allowEmptyText?: boolean;
|
allowEmptyText?: boolean;
|
||||||
validateBeforeSubmit?: (ctx: SubmitContext) => string | null;
|
validateBeforeSubmit?: (ctx: SubmitContext) => string | null;
|
||||||
onBeforeSubmit?: (ctx: CreateRequestContext) => void;
|
onBeforeSubmit?: (ctx: CreateRequestContext) => Promise<void> | void;
|
||||||
onCreateStart?: () => void;
|
onCreateStart?: () => void;
|
||||||
createRequest: (ctx: CreateRequestContext) => Promise<CreateRequestResult<TCreateResult>>;
|
createRequest: (ctx: CreateRequestContext) => Promise<CreateRequestResult<TCreateResult>>;
|
||||||
buildDraftAgent: (attempt: CreateAttempt) => TDraftAgent;
|
buildDraftAgent: (attempt: CreateAttempt) => TDraftAgent;
|
||||||
@@ -168,7 +168,7 @@ export function useDraftAgentCreateFlow<TDraftAgent, TCreateResult>({
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeSubmit?.({
|
await onBeforeSubmit?.({
|
||||||
attempt,
|
attempt,
|
||||||
text: attempt.text,
|
text: attempt.text,
|
||||||
images: attempt.images,
|
images: attempt.images,
|
||||||
|
|||||||
@@ -441,8 +441,8 @@ export function WorkspaceDraftAgentTab({
|
|||||||
workspaceDirectory: draftWorkingDirectory,
|
workspaceDirectory: draftWorkingDirectory,
|
||||||
hasClient: Boolean(client),
|
hasClient: Boolean(client),
|
||||||
}),
|
}),
|
||||||
onBeforeSubmit: () => {
|
onBeforeSubmit: async () => {
|
||||||
void composerState.persistFormPreferences();
|
await composerState.persistFormPreferences();
|
||||||
if (isWeb) {
|
if (isWeb) {
|
||||||
(document.activeElement as HTMLElement | null)?.blur?.();
|
(document.activeElement as HTMLElement | null)?.blur?.();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { CreateAgentPreferencesService } from "./service";
|
||||||
|
import {
|
||||||
|
mergeCreateAgentSelectionPreferences,
|
||||||
|
mergeProviderPreferences,
|
||||||
|
parseFormPreferences,
|
||||||
|
} from "./preferences";
|
||||||
|
import { FakeCreateAgentPreferenceStorage } from "./test-utils/fake-preference-storage";
|
||||||
|
|
||||||
|
describe("create agent preferences", () => {
|
||||||
|
it("keeps the selected mode after saving model and thinking", async () => {
|
||||||
|
const storage = new FakeCreateAgentPreferenceStorage();
|
||||||
|
const preferences = new CreateAgentPreferencesService(storage);
|
||||||
|
|
||||||
|
const modelWrite = preferences.update((current) =>
|
||||||
|
mergeProviderPreferences({
|
||||||
|
preferences: current,
|
||||||
|
provider: "codex",
|
||||||
|
updates: { model: "gpt-5.5", thinkingByModel: { "gpt-5.5": "high" } },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await storage.nextWrite();
|
||||||
|
|
||||||
|
const modeWrite = preferences.update((current) =>
|
||||||
|
mergeProviderPreferences({
|
||||||
|
preferences: current,
|
||||||
|
provider: "codex",
|
||||||
|
updates: { mode: "full-access" },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(storage.pendingWriteCount()).toBe(1);
|
||||||
|
storage.finishOldestWrite();
|
||||||
|
await modelWrite;
|
||||||
|
|
||||||
|
await storage.nextWrite();
|
||||||
|
storage.finishOldestWrite();
|
||||||
|
await modeWrite;
|
||||||
|
|
||||||
|
expect(storage.savedPreferences()).toEqual({
|
||||||
|
provider: "codex",
|
||||||
|
providerPreferences: {
|
||||||
|
codex: {
|
||||||
|
model: "gpt-5.5",
|
||||||
|
thinkingByModel: { "gpt-5.5": "high" },
|
||||||
|
mode: "full-access",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("flushes the full create-agent selection into provider preferences", async () => {
|
||||||
|
const storage = new FakeCreateAgentPreferenceStorage();
|
||||||
|
const preferences = new CreateAgentPreferencesService(storage);
|
||||||
|
|
||||||
|
const saveSelection = preferences.update((current) =>
|
||||||
|
mergeCreateAgentSelectionPreferences({
|
||||||
|
preferences: current,
|
||||||
|
provider: "codex",
|
||||||
|
modelId: "gpt-5.5",
|
||||||
|
modeId: "full-access",
|
||||||
|
thinkingOptionId: "high",
|
||||||
|
featureValues: { fast_mode: true },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await storage.nextWrite();
|
||||||
|
storage.finishOldestWrite();
|
||||||
|
await saveSelection;
|
||||||
|
|
||||||
|
expect(storage.savedPreferences()).toEqual({
|
||||||
|
provider: "codex",
|
||||||
|
providerPreferences: {
|
||||||
|
codex: {
|
||||||
|
model: "gpt-5.5",
|
||||||
|
mode: "full-access",
|
||||||
|
thinkingByModel: { "gpt-5.5": "high" },
|
||||||
|
featureValues: { fast_mode: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("loads invalid stored preferences as empty preferences", () => {
|
||||||
|
expect(parseFormPreferences({ providerPreferences: { codex: { mode: 42 } } })).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import type { AgentProvider } from "@getpaseo/protocol/agent-types";
|
||||||
|
|
||||||
|
export interface FavoriteModelPreference {
|
||||||
|
provider: string;
|
||||||
|
modelId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FavoriteModelRow {
|
||||||
|
favoriteKey: string;
|
||||||
|
provider: string;
|
||||||
|
providerLabel: string;
|
||||||
|
modelId: string;
|
||||||
|
modelLabel: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerPreferencesSchema = z.object({
|
||||||
|
model: z.string().optional(),
|
||||||
|
mode: z.string().optional(),
|
||||||
|
thinkingByModel: z.record(z.string()).optional(),
|
||||||
|
featureValues: z.record(z.unknown()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const formPreferencesSchema = z.object({
|
||||||
|
provider: z.string().optional(),
|
||||||
|
providerPreferences: z.record(providerPreferencesSchema).optional(),
|
||||||
|
favoriteModels: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
provider: z.string(),
|
||||||
|
modelId: z.string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ProviderPreferences = z.infer<typeof providerPreferencesSchema>;
|
||||||
|
export type FormPreferences = z.infer<typeof formPreferencesSchema>;
|
||||||
|
|
||||||
|
export const DEFAULT_FORM_PREFERENCES: FormPreferences = {};
|
||||||
|
|
||||||
|
export function parseFormPreferences(value: unknown): FormPreferences {
|
||||||
|
const result = formPreferencesSchema.safeParse(value);
|
||||||
|
return result.success ? result.data : DEFAULT_FORM_PREFERENCES;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
const nextFeatureValues =
|
||||||
|
updates.featureValues === undefined
|
||||||
|
? existing.featureValues
|
||||||
|
: {
|
||||||
|
...existing.featureValues,
|
||||||
|
...updates.featureValues,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...preferences,
|
||||||
|
provider,
|
||||||
|
providerPreferences: {
|
||||||
|
...existingProviderPreferences,
|
||||||
|
[provider]: {
|
||||||
|
...existing,
|
||||||
|
...updates,
|
||||||
|
...(nextThinkingByModel ? { thinkingByModel: nextThinkingByModel } : {}),
|
||||||
|
...(nextFeatureValues ? { featureValues: nextFeatureValues } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeCreateAgentSelectionPreferences(args: {
|
||||||
|
preferences: FormPreferences;
|
||||||
|
provider: AgentProvider | null;
|
||||||
|
modelId?: string | null;
|
||||||
|
modeId?: string | null;
|
||||||
|
thinkingOptionId?: string | null;
|
||||||
|
featureValues?: Record<string, unknown>;
|
||||||
|
}): FormPreferences {
|
||||||
|
if (!args.provider) {
|
||||||
|
return args.preferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelId = args.modelId?.trim() ?? "";
|
||||||
|
const modeId = args.modeId?.trim() ?? "";
|
||||||
|
const thinkingOptionId = args.thinkingOptionId?.trim() ?? "";
|
||||||
|
|
||||||
|
return mergeProviderPreferences({
|
||||||
|
preferences: args.preferences,
|
||||||
|
provider: args.provider,
|
||||||
|
updates: {
|
||||||
|
model: modelId || undefined,
|
||||||
|
mode: modeId || undefined,
|
||||||
|
...(modelId && thinkingOptionId ? { thinkingByModel: { [modelId]: thinkingOptionId } } : {}),
|
||||||
|
...(args.featureValues ? { featureValues: args.featureValues } : {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildFavoriteModelKey(input: FavoriteModelPreference): string {
|
||||||
|
return `${input.provider}:${input.modelId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFavoriteModel(args: {
|
||||||
|
preferences: FormPreferences;
|
||||||
|
provider: string;
|
||||||
|
modelId: string;
|
||||||
|
}): boolean {
|
||||||
|
const favoriteKey = buildFavoriteModelKey({ provider: args.provider, modelId: args.modelId });
|
||||||
|
return (args.preferences.favoriteModels ?? []).some(
|
||||||
|
(favorite) => buildFavoriteModelKey(favorite) === favoriteKey,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggleFavoriteModel(args: {
|
||||||
|
preferences: FormPreferences;
|
||||||
|
provider: string;
|
||||||
|
modelId: string;
|
||||||
|
}): FormPreferences {
|
||||||
|
const favorite = { provider: args.provider, modelId: args.modelId };
|
||||||
|
const favoriteKey = buildFavoriteModelKey(favorite);
|
||||||
|
const existingFavorites = args.preferences.favoriteModels ?? [];
|
||||||
|
const hasFavorite = existingFavorites.some(
|
||||||
|
(entry) => buildFavoriteModelKey(entry) === favoriteKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...args.preferences,
|
||||||
|
favoriteModels: hasFavorite
|
||||||
|
? existingFavorites.filter((entry) => buildFavoriteModelKey(entry) !== favoriteKey)
|
||||||
|
: [...existingFavorites, favorite],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { AsyncStorageCreateAgentPreferenceStorage } from "./storage";
|
||||||
|
import {
|
||||||
|
DEFAULT_FORM_PREFERENCES,
|
||||||
|
parseFormPreferences,
|
||||||
|
type FormPreferences,
|
||||||
|
} from "./preferences";
|
||||||
|
import type { CreateAgentPreferenceStorage } from "./storage";
|
||||||
|
|
||||||
|
export type FormPreferenceUpdate =
|
||||||
|
| Partial<FormPreferences>
|
||||||
|
| ((current: FormPreferences) => FormPreferences);
|
||||||
|
|
||||||
|
export class CreateAgentPreferencesService {
|
||||||
|
private preferences: FormPreferences = DEFAULT_FORM_PREFERENCES;
|
||||||
|
private isLoaded = false;
|
||||||
|
private loadPromise: Promise<FormPreferences> | null = null;
|
||||||
|
private writeQueue: Promise<void> = Promise.resolve();
|
||||||
|
|
||||||
|
constructor(private readonly storage: CreateAgentPreferenceStorage) {}
|
||||||
|
|
||||||
|
async load(): Promise<FormPreferences> {
|
||||||
|
if (this.isLoaded) {
|
||||||
|
return this.preferences;
|
||||||
|
}
|
||||||
|
if (!this.loadPromise) {
|
||||||
|
this.loadPromise = this.storage.read().then((stored) => {
|
||||||
|
this.preferences = parseFormPreferences(stored);
|
||||||
|
this.isLoaded = true;
|
||||||
|
return this.preferences;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this.loadPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(update: FormPreferenceUpdate): Promise<FormPreferences> {
|
||||||
|
const previousWrite = this.writeQueue;
|
||||||
|
const operation = this.applyQueuedUpdate(previousWrite, update);
|
||||||
|
|
||||||
|
this.writeQueue = operation.then(
|
||||||
|
() => undefined,
|
||||||
|
() => undefined,
|
||||||
|
);
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async applyQueuedUpdate(
|
||||||
|
previousWrite: Promise<void>,
|
||||||
|
update: FormPreferenceUpdate,
|
||||||
|
): Promise<FormPreferences> {
|
||||||
|
await previousWrite;
|
||||||
|
const current = await this.load();
|
||||||
|
const next = typeof update === "function" ? update(current) : { ...current, ...update };
|
||||||
|
this.preferences = parseFormPreferences(next);
|
||||||
|
this.isLoaded = true;
|
||||||
|
await this.storage.write(this.preferences);
|
||||||
|
return this.preferences;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createAgentPreferencesService = new CreateAgentPreferencesService(
|
||||||
|
new AsyncStorageCreateAgentPreferenceStorage(),
|
||||||
|
);
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
|
import type { FormPreferences } from "./preferences";
|
||||||
|
|
||||||
|
export const CREATE_AGENT_PREFERENCES_STORAGE_KEY = "@paseo:create-agent-preferences";
|
||||||
|
|
||||||
|
export interface CreateAgentPreferenceStorage {
|
||||||
|
read(): Promise<unknown>;
|
||||||
|
write(preferences: FormPreferences): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AsyncStorageCreateAgentPreferenceStorage implements CreateAgentPreferenceStorage {
|
||||||
|
async read(): Promise<unknown> {
|
||||||
|
const stored = await AsyncStorage.getItem(CREATE_AGENT_PREFERENCES_STORAGE_KEY);
|
||||||
|
if (!stored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(stored);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async write(preferences: FormPreferences): Promise<void> {
|
||||||
|
await AsyncStorage.setItem(CREATE_AGENT_PREFERENCES_STORAGE_KEY, JSON.stringify(preferences));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import type { FormPreferences } from "../preferences";
|
||||||
|
import type { CreateAgentPreferenceStorage } from "../storage";
|
||||||
|
|
||||||
|
interface PendingWrite {
|
||||||
|
preferences: FormPreferences;
|
||||||
|
finish: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FakeCreateAgentPreferenceStorage implements CreateAgentPreferenceStorage {
|
||||||
|
private stored: unknown;
|
||||||
|
private readonly pendingWrites: PendingWrite[] = [];
|
||||||
|
private readonly pendingWriteWaiters: Array<(write: PendingWrite) => void> = [];
|
||||||
|
|
||||||
|
constructor(input: { stored?: unknown } = {}) {
|
||||||
|
this.stored = input.stored ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async read(): Promise<unknown> {
|
||||||
|
return this.stored;
|
||||||
|
}
|
||||||
|
|
||||||
|
write(preferences: FormPreferences): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const write = {
|
||||||
|
preferences,
|
||||||
|
finish: () => {
|
||||||
|
this.stored = clone(preferences);
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.pendingWrites.push(write);
|
||||||
|
this.pendingWriteWaiters.shift()?.(write);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nextWrite(): Promise<PendingWrite> {
|
||||||
|
const next = this.pendingWrites[0];
|
||||||
|
if (next) {
|
||||||
|
return Promise.resolve(next);
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.pendingWriteWaiters.push(resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingWriteCount(): number {
|
||||||
|
return this.pendingWrites.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
finishOldestWrite(): void {
|
||||||
|
const write = this.pendingWrites.shift();
|
||||||
|
if (!write) {
|
||||||
|
throw new Error("No pending create-agent preference write");
|
||||||
|
}
|
||||||
|
write.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
savedPreferences(): unknown {
|
||||||
|
return this.stored;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clone<T>(value: T): T {
|
||||||
|
return JSON.parse(JSON.stringify(value)) as T;
|
||||||
|
}
|
||||||
@@ -1,137 +1,35 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
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 {
|
||||||
import type { AgentProvider } from "@getpaseo/protocol/agent-types";
|
buildFavoriteModelKey,
|
||||||
|
DEFAULT_FORM_PREFERENCES,
|
||||||
|
isFavoriteModel,
|
||||||
|
mergeProviderPreferences,
|
||||||
|
toggleFavoriteModel,
|
||||||
|
type FavoriteModelPreference,
|
||||||
|
type FavoriteModelRow,
|
||||||
|
type FormPreferences,
|
||||||
|
type ProviderPreferences,
|
||||||
|
} from "@/create-agent-preferences/preferences";
|
||||||
|
import {
|
||||||
|
createAgentPreferencesService,
|
||||||
|
type FormPreferenceUpdate,
|
||||||
|
} from "@/create-agent-preferences/service";
|
||||||
|
|
||||||
const FORM_PREFERENCES_STORAGE_KEY = "@paseo:create-agent-preferences";
|
|
||||||
const FORM_PREFERENCES_QUERY_KEY = ["form-preferences"];
|
const FORM_PREFERENCES_QUERY_KEY = ["form-preferences"];
|
||||||
|
|
||||||
export interface FavoriteModelPreference {
|
export type { FavoriteModelPreference, FavoriteModelRow, FormPreferences, ProviderPreferences };
|
||||||
provider: string;
|
|
||||||
modelId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FavoriteModelRow {
|
export { buildFavoriteModelKey, isFavoriteModel, mergeProviderPreferences, toggleFavoriteModel };
|
||||||
favoriteKey: string;
|
|
||||||
provider: string;
|
|
||||||
providerLabel: string;
|
|
||||||
modelId: string;
|
|
||||||
modelLabel: string;
|
|
||||||
description?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const providerPreferencesSchema = z.object({
|
|
||||||
model: z.string().optional(),
|
|
||||||
mode: z.string().optional(),
|
|
||||||
thinkingByModel: z.record(z.string()).optional(),
|
|
||||||
featureValues: z.record(z.unknown()).optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const formPreferencesSchema = z.object({
|
|
||||||
provider: z.string().optional(),
|
|
||||||
providerPreferences: z.record(providerPreferencesSchema).optional(),
|
|
||||||
favoriteModels: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
provider: z.string(),
|
|
||||||
modelId: z.string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type ProviderPreferences = z.infer<typeof providerPreferencesSchema>;
|
|
||||||
export type FormPreferences = z.infer<typeof formPreferencesSchema>;
|
|
||||||
|
|
||||||
const DEFAULT_FORM_PREFERENCES: FormPreferences = {};
|
|
||||||
|
|
||||||
async function loadFormPreferences(): Promise<FormPreferences> {
|
async function loadFormPreferences(): Promise<FormPreferences> {
|
||||||
const stored = await AsyncStorage.getItem(FORM_PREFERENCES_STORAGE_KEY);
|
return createAgentPreferencesService.load();
|
||||||
if (!stored) return DEFAULT_FORM_PREFERENCES;
|
|
||||||
const result = formPreferencesSchema.safeParse(JSON.parse(stored));
|
|
||||||
return result.success ? result.data : DEFAULT_FORM_PREFERENCES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UseFormPreferencesReturn {
|
export interface UseFormPreferencesReturn {
|
||||||
preferences: FormPreferences;
|
preferences: FormPreferences;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
updatePreferences: (
|
updatePreferences: (updates: FormPreferenceUpdate) => Promise<void>;
|
||||||
updates: Partial<FormPreferences> | ((current: FormPreferences) => 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,
|
|
||||||
};
|
|
||||||
const nextFeatureValues =
|
|
||||||
updates.featureValues === undefined
|
|
||||||
? existing.featureValues
|
|
||||||
: {
|
|
||||||
...existing.featureValues,
|
|
||||||
...updates.featureValues,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...preferences,
|
|
||||||
provider,
|
|
||||||
providerPreferences: {
|
|
||||||
...existingProviderPreferences,
|
|
||||||
[provider]: {
|
|
||||||
...existing,
|
|
||||||
...updates,
|
|
||||||
...(nextThinkingByModel ? { thinkingByModel: nextThinkingByModel } : {}),
|
|
||||||
...(nextFeatureValues ? { featureValues: nextFeatureValues } : {}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildFavoriteModelKey(input: FavoriteModelPreference): string {
|
|
||||||
return `${input.provider}:${input.modelId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isFavoriteModel(args: {
|
|
||||||
preferences: FormPreferences;
|
|
||||||
provider: string;
|
|
||||||
modelId: string;
|
|
||||||
}): boolean {
|
|
||||||
const favoriteKey = buildFavoriteModelKey({ provider: args.provider, modelId: args.modelId });
|
|
||||||
return (args.preferences.favoriteModels ?? []).some(
|
|
||||||
(favorite) => buildFavoriteModelKey(favorite) === favoriteKey,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleFavoriteModel(args: {
|
|
||||||
preferences: FormPreferences;
|
|
||||||
provider: string;
|
|
||||||
modelId: string;
|
|
||||||
}): FormPreferences {
|
|
||||||
const favorite = { provider: args.provider, modelId: args.modelId };
|
|
||||||
const favoriteKey = buildFavoriteModelKey(favorite);
|
|
||||||
const existingFavorites = args.preferences.favoriteModels ?? [];
|
|
||||||
const hasFavorite = existingFavorites.some(
|
|
||||||
(entry) => buildFavoriteModelKey(entry) === favoriteKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...args.preferences,
|
|
||||||
favoriteModels: hasFavorite
|
|
||||||
? existingFavorites.filter((entry) => buildFavoriteModelKey(entry) !== favoriteKey)
|
|
||||||
: [...existingFavorites, favorite],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFormPreferences(): UseFormPreferencesReturn {
|
export function useFormPreferences(): UseFormPreferencesReturn {
|
||||||
@@ -146,13 +44,9 @@ export function useFormPreferences(): UseFormPreferencesReturn {
|
|||||||
const preferences = data ?? DEFAULT_FORM_PREFERENCES;
|
const preferences = data ?? DEFAULT_FORM_PREFERENCES;
|
||||||
|
|
||||||
const updatePreferences = useCallback(
|
const updatePreferences = useCallback(
|
||||||
async (updates: Partial<FormPreferences> | ((current: FormPreferences) => FormPreferences)) => {
|
async (updates: FormPreferenceUpdate) => {
|
||||||
const prev =
|
const next = await createAgentPreferencesService.update(updates);
|
||||||
queryClient.getQueryData<FormPreferences>(FORM_PREFERENCES_QUERY_KEY) ??
|
|
||||||
DEFAULT_FORM_PREFERENCES;
|
|
||||||
const next = typeof updates === "function" ? updates(prev) : { ...prev, ...updates };
|
|
||||||
queryClient.setQueryData<FormPreferences>(FORM_PREFERENCES_QUERY_KEY, next);
|
queryClient.setQueryData<FormPreferences>(FORM_PREFERENCES_QUERY_KEY, next);
|
||||||
await AsyncStorage.setItem(FORM_PREFERENCES_STORAGE_KEY, JSON.stringify(next));
|
|
||||||
},
|
},
|
||||||
[queryClient],
|
[queryClient],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -747,9 +747,7 @@ function submitWorkspaceDraft(input: SubmitDraftInput): void {
|
|||||||
provider,
|
provider,
|
||||||
clientMessageId,
|
clientMessageId,
|
||||||
timestamp,
|
timestamp,
|
||||||
...(composerState.modeOptions.length > 0 && composerState.selectedMode !== ""
|
...(composerState.selectedMode !== "" ? { modeId: composerState.selectedMode } : {}),
|
||||||
? { modeId: composerState.selectedMode }
|
|
||||||
: {}),
|
|
||||||
...(composerState.effectiveModelId ? { model: composerState.effectiveModelId } : {}),
|
...(composerState.effectiveModelId ? { model: composerState.effectiveModelId } : {}),
|
||||||
...(composerState.effectiveThinkingOptionId
|
...(composerState.effectiveThinkingOptionId
|
||||||
? { thinkingOptionId: composerState.effectiveThinkingOptionId }
|
? { thinkingOptionId: composerState.effectiveThinkingOptionId }
|
||||||
@@ -1063,6 +1061,7 @@ export function NewWorkspaceScreen({
|
|||||||
async (payload: MessagePayload) => {
|
async (payload: MessagePayload) => {
|
||||||
try {
|
try {
|
||||||
setErrorMessage(null);
|
setErrorMessage(null);
|
||||||
|
await composerState?.persistFormPreferences();
|
||||||
if (isEmptyWorkspaceSubmission(payload)) {
|
if (isEmptyWorkspaceSubmission(payload)) {
|
||||||
setPendingAction("empty");
|
setPendingAction("empty");
|
||||||
await runCreateEmptyWorkspace({
|
await runCreateEmptyWorkspace({
|
||||||
|
|||||||
Reference in New Issue
Block a user