diff --git a/apps/web/src/config.ts b/apps/web/src/config.ts index 29d140b6..ee2ea2ea 100644 --- a/apps/web/src/config.ts +++ b/apps/web/src/config.ts @@ -985,6 +985,8 @@ export const wording = { model: (tier: string) => `${tier} model`, effort: (tier: string) => `${tier} effort`, pick: "Model profile", + replaceDefault: (name: string) => + `Do you want to make this your new default profile? It will replace ${name}.`, }, settings: { title: "Settings", diff --git a/apps/web/src/pages/profiles.tsx b/apps/web/src/pages/profiles.tsx index 1139688f..d5edfb28 100644 --- a/apps/web/src/pages/profiles.tsx +++ b/apps/web/src/pages/profiles.tsx @@ -8,7 +8,7 @@ * project keeps the bindings it started with, so a change here never alters a running project. */ import { wording } from "../config.ts"; -import { Input, Section } from "@corbits/react-ui"; +import { Input, Section, Switch } from "@corbits/react-ui"; import { Dialog, DialogBody, @@ -19,7 +19,7 @@ import { DialogHeader, DialogTitle, } from "@corbits/react-ui/ui/dialog"; -import { useEffect, useState } from "react"; +import { useEffect, useState, type FormEvent } from "react"; import { useQueryClient } from "@tanstack/react-query"; import type { CustomProfile, @@ -285,14 +285,21 @@ export function Profiles({ initialField }: { initialField?: Field } = {}) { )} editing={editing} onClose={() => setEditing(null)} - onSave={async (profile) => { + replacing={editing.mode === "new" ? (active?.label ?? null) : null} + onSave={async (profile, makeDefault) => { const list = editing.mode === "edit" ? custom.map((entry) => entry.id === profile.id ? profile : entry, ) : [...custom, profile]; - if (await save(choice(active?.id, list))) setEditing(null); + let profileId = field.value?.profile; + if (editing.mode === "new" && makeDefault) { + profileId = profile.id; + } else if (editing.mode === "new") { + profileId = active?.id ?? field.value?.profile; + } + if (await save(choice(profileId, list))) setEditing(null); }} /> ) : null} @@ -311,20 +318,24 @@ function ProfileEditor({ catalog, profiles, editing, + replacing, onClose, onSave, }: { catalog: ModelCatalog; profiles: readonly Profile[]; editing: Editing; + /** Label of the current default, when a new profile can replace it. */ + replacing: string | null; onClose: () => void; - onSave: (profile: CustomProfile) => Promise; + onSave: (profile: CustomProfile, makeDefault: boolean) => Promise; }) { const [draft, setDraft] = useState( editing.mode === "edit" ? { label: editing.profile.label, tiers: editing.profile.tiers } : { label: "", tiers: {} }, ); + const [makeDefault, setMakeDefault] = useState(false); const [busy, setBusy] = useState(false); const filled = complete( draft, @@ -340,6 +351,30 @@ function ProfileEditor({ tiers: { ...current.tiers, [tier]: binding }, })); + async function submitProfile(event: FormEvent) { + event.preventDefault(); + if (!ready || busy || filled === null) return; + setBusy(true); + try { + await onSave( + { + id: + editing.mode === "edit" + ? editing.profile.id + : `custom_${crypto.randomUUID().replaceAll("-", "").slice(0, 12)}`, + label: draft.label.trim(), + tiers: filled, + }, + replacing !== null && makeDefault, + ); + } catch (cause) { + if (cause instanceof Error) throw cause; + throw new Error(message(cause), { cause }); + } finally { + setBusy(false); + } + } + return ( {copy.editorLead} -
{ - event.preventDefault(); - if (!ready || busy) return; - setBusy(true); - void onSave({ - id: - editing.mode === "edit" - ? editing.profile.id - : `custom_${crypto.randomUUID().replaceAll("-", "").slice(0, 12)}`, - label: draft.label.trim(), - tiers: filled, - }).finally(() => setBusy(false)); - }} - > + {editing.mode === "new" ? (
@@ -498,6 +519,19 @@ function ProfileEditor({ ); })} + {replacing ? ( +
+ + {copy.replaceDefault(replacing)} + + +
+ ) : null}