diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx index 3c354709cc..d34fe1768d 100644 --- a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx +++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx @@ -121,12 +121,15 @@ const ModelHarnessSectionBody = ({ section, ...params }: { - section: "model-harness" | "advanced" + section: "model-harness" | "advanced" | "connect-key" } & Parameters[0]) => { const mh = useModelHarness(params) if (section === "advanced") { return <>{mh.advancedDrawerBody} } + if (section === "connect-key") { + return <>{mh.providerCredentialsSection} + } return <>{mh.modelHarnessDrawerBody} } @@ -490,6 +493,15 @@ export const AgentTemplateControl = memo(function AgentTemplateControl({ }), [sectionChanges.draft, onChange, config, committed], ) + // Whether this edit touched the provider/connection specifically, not just any model-harness field. + const credentialsPathChanged = useMemo( + () => + (sectionChanges.draft.sectionsByKey.get("model-harness")?.scalarChanges ?? []).some( + (c) => c.key === "llm.provider" || c.key.startsWith("llm.connection"), + ), + [sectionChanges.draft], + ) + // The inline body for a drawer-backed section: its own controls, narrowed to what changed — the // same affordance as the Connect-key field, with a different filter (see SectionChangeBody). // The body is a COMPONENT rendered inside the providers, never `mh`'s pre-built output: the hook @@ -817,7 +829,8 @@ export const AgentTemplateControl = memo(function AgentTemplateControl({ // What the section surfaces inline, in precedence order. Dropping `onOpen` is what makes // a section expand inline instead of routing to the drawer. // 1. Required info missing (no provider key) — BLOCKING, so it wins: the same key field - // the drawer uses, right here. + // the drawer uses, right here, swapped for the changed-aware variant when this edit + // touched the provider/connection (`credentialsPathChanged`) so that diff isn't lost. // 2. Uncommitted changes — informational: what changed (see `changeBodyFor`). // 3. Neither — the plain drawer row it has always been. ...(showKeyPane @@ -832,7 +845,22 @@ export const AgentTemplateControl = memo(function AgentTemplateControl({ onOpenDetails={() => openSectionDrawer("model-harness")} disabled={disabled} > - {mh.providerCredentialsInline} + {credentialsPathChanged ? ( + + + + ) : ( + mh.providerCredentialsInline + )} diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx index 545b00ac6b..69254115e8 100644 --- a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx +++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx @@ -454,10 +454,24 @@ export function useModelHarness({ // `harness.kind` (NOT all of `harness`) so a permissions edit doesn't light up the Harness header. const harnessKindChanged = useHasChangedUnder("harness.kind") const modelChanged = useHasChangedUnder("llm.model") - const credentialsChanged = useHasChangedUnder("llm.connection") + // Provider credentials cover both the connection (mode/slug) and the bare `llm.provider` field a + // catalog model switch can move on its own — track both, or a provider-only change gets a "Connect + // key" badge with no changed indicator or Restore to go with it. + const connectionChanged = useHasChangedUnder("llm.connection") + const providerFieldChanged = useHasChangedUnder("llm.provider") + const credentialsChanged = connectionChanged || providerFieldChanged const revertHarnessKind = useRevertUnder("harness.kind") const revertModel = useRevertUnder("llm.model") - const revertCredentials = useRevertUnder("llm.connection") + const revertConnection = useRevertUnder("llm.connection") + const revertProviderField = useRevertUnder("llm.provider") + // `writeModel` can move `llm.model` atomically with `llm.provider`/`llm.connection.slug` (a + // catalog pick clears the old custom slug) — revert all three together or the group's Restore + // reattaches a stale slug to the new model, a combination the backend rejects. + const revertCredentials = useCallback(() => { + revertConnection?.() + revertProviderField?.() + revertModel?.() + }, [revertConnection, revertProviderField, revertModel]) // Confirmed, because unlike the per-row undo (which is reached THROUGH the popover showing the // exact value it restores — see `RailField`) this one discards every change in the group at once // and names none of them. @@ -722,7 +736,7 @@ export function useModelHarness({ disabled, revisionId: revisionId ?? null, indicator: changedIndicator(credentialsChanged), - revertControl: revertAction(revertCredentials), + revertControl: credentialsChanged ? revertAction(revertCredentials) : undefined, } : null // The full pane (own header + rail) for the drawer body; the `bare` variant (toggle + key form / @@ -1094,6 +1108,10 @@ export function useModelHarness({ // self-managed card; no nested header/badge, no rail) for the inline "Connect key" // quick-action under the section header — aligned with the drawer without duplicating it. providerCredentialsInline, + // The full accordion variant (header, "Connect key" badge, changed-path indicator, group + // revert) — used instead of the bare pane when the connect-key state coincides with an + // uncommitted change, so the change stays visible rather than being silently dropped. + providerCredentialsSection, // A model is selected but the chosen harness can't run it — a *model* problem (the harness // itself stays valid), so the config panel flags the Model & harness section as invalid. modelUnsupported: !!modelId && !selectedKeepsModel,