mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-05 12:16:46 +00:00
fix(ui/profiles): latch extraModels touch state for explicit-clear
The previous fix used react-hook-form's `dirtyFields.extraModels` to gate forwarding of the empty value. RHF compares each value to its `defaultValues` entry, so typing "x" and erasing back to "" reverts the dirty flag to false — making it impossible to clear a saved ANTHROPIC_EXTRA_MODELS through the dashboard. Track a local boolean that latches true on the first onChange and resets when the dialog closes. The latch survives a "type then delete" round-trip, so the empty-string delete signal still reaches the server, while a never-touched field still gets skipped to preserve the existing saved value.
This commit is contained in:
@@ -54,10 +54,17 @@ export function ProfileDialog({ open, onClose, profile }: ProfileDialogProps) {
|
|||||||
const updateMutation = useUpdateProfile();
|
const updateMutation = useUpdateProfile();
|
||||||
const [showModelMapping, setShowModelMapping] = useState(false);
|
const [showModelMapping, setShowModelMapping] = useState(false);
|
||||||
|
|
||||||
|
// Tracks whether the user has interacted with the Extra Models field in
|
||||||
|
// edit mode. RHF's `dirtyFields` compares the current value against
|
||||||
|
// `defaultValues`, so typing "x" and erasing back to "" reverts dirty to
|
||||||
|
// false. We need a latch so an explicit-clear (type then delete) still
|
||||||
|
// forwards the empty-string delete signal to the backend.
|
||||||
|
const [extraModelsTouched, setExtraModelsTouched] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors, dirtyFields },
|
formState: { errors },
|
||||||
reset,
|
reset,
|
||||||
control,
|
control,
|
||||||
} = useForm<FormData>({
|
} = useForm<FormData>({
|
||||||
@@ -91,6 +98,7 @@ export function ProfileDialog({ open, onClose, profile }: ProfileDialogProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setShowModelMapping(false);
|
setShowModelMapping(false);
|
||||||
|
setExtraModelsTouched(false);
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
@@ -99,18 +107,19 @@ export function ProfileDialog({ open, onClose, profile }: ProfileDialogProps) {
|
|||||||
if (profile) {
|
if (profile) {
|
||||||
// Update mode. The dialog cannot pre-populate `extraModels` from the
|
// Update mode. The dialog cannot pre-populate `extraModels` from the
|
||||||
// existing profile (Profile type carries no env data), so the field
|
// existing profile (Profile type carries no env data), so the field
|
||||||
// always starts blank in edit mode. Forwarding that blank value
|
// always starts blank. Forwarding that blank value unconditionally
|
||||||
// unconditionally would clobber any saved ANTHROPIC_EXTRA_MODELS via
|
// would clobber any saved ANTHROPIC_EXTRA_MODELS, since the PUT route
|
||||||
// the route's "empty string deletes" semantics. Only forward the
|
// treats an empty string as a delete signal. Use a latch flipped on
|
||||||
// field if the user actually edited it — typing then clearing still
|
// the first onChange so we can distinguish "user never touched it"
|
||||||
// marks it dirty, preserving the explicit-clear UX.
|
// (skip → preserve existing value) from "user typed then cleared"
|
||||||
|
// (forward "" → explicit delete).
|
||||||
await updateMutation.mutateAsync({
|
await updateMutation.mutateAsync({
|
||||||
name: profile.name,
|
name: profile.name,
|
||||||
data: {
|
data: {
|
||||||
baseUrl: data.baseUrl,
|
baseUrl: data.baseUrl,
|
||||||
apiKey: data.apiKey,
|
apiKey: data.apiKey,
|
||||||
model: data.model,
|
model: data.model,
|
||||||
...(dirtyFields.extraModels ? { extraModels: data.extraModels } : {}),
|
...(extraModelsTouched ? { extraModels: data.extraModels } : {}),
|
||||||
opusModel: data.opusModel,
|
opusModel: data.opusModel,
|
||||||
sonnetModel: data.sonnetModel,
|
sonnetModel: data.sonnetModel,
|
||||||
haikuModel: data.haikuModel,
|
haikuModel: data.haikuModel,
|
||||||
@@ -178,7 +187,9 @@ export function ProfileDialog({ open, onClose, profile }: ProfileDialogProps) {
|
|||||||
<Label htmlFor="extraModels">Extra Models</Label>
|
<Label htmlFor="extraModels">Extra Models</Label>
|
||||||
<Input
|
<Input
|
||||||
id="extraModels"
|
id="extraModels"
|
||||||
{...register('extraModels')}
|
{...register('extraModels', {
|
||||||
|
onChange: () => setExtraModelsTouched(true),
|
||||||
|
})}
|
||||||
placeholder="model-a,model-b,model-c"
|
placeholder="model-a,model-b,model-c"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user