Offer Use default on a node field only where there is one

The editor a node opens for a single field offered Use default on every
optional field that was not a checkbox: a storage's connection, its
operation, its scope, and any other choice between alternatives, where
there is nothing to go back to. The settings dialog already followed the
schema's x-ui-defaults-when-empty; this editor now does too, so the
button is on the model's sampling knobs and nowhere else.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucio Lelii 2026-09-28 15:39:20 +02:00
parent 1aa32efa80
commit 672f53d9ab
2 changed files with 36 additions and 3 deletions

View File

@ -857,6 +857,35 @@ describe('GenericNodeComponent', () => {
expect(component.localEditorGlobalName).toBe('');
});
it('offers Use default only on a field whose schema says it falls back to one', async () => {
// Every optional field used to get it - a choice between a catalog storage and your own
// connection, an operation, a scope - where there is nothing to go back to.
const component = fixture.componentInstance as any;
const choice = {
...bindableFieldDefinition(),
path: 'source',
label: 'Connection',
enumOptions: ['CATALOG', 'PERSONAL'],
ui: { visibleWhen: [], enabledWhen: [] }
};
const knob = {
...bindableFieldDefinition(),
path: 'llmDescriptor.parameters.temperature',
label: 'Temperature',
type: 'number' as const,
ui: { visibleWhen: [], enabledWhen: [], defaultsWhenEmpty: true }
};
component.editableFieldDefinitions = [choice, knob];
component.ensureBlockConfiguration()['llmDescriptor'] = { provider: 'InternalOllama' };
await component.openParameterEditor('source');
expect(component.showLocalEditorDefault()).toBe(false);
component.closeLocalEditor?.();
await component.openParameterEditor('llmDescriptor.parameters.temperature');
expect(component.showLocalEditorDefault()).toBe(true);
});
it('renders the field description, which the modal never used to show', async () => {
// The other half of why none of this was discoverable: the modal held no tip at all, so a
// @UiDescription explaining that the field takes a placeholder was invisible.

View File

@ -213,7 +213,7 @@ export class GenericNodeComponent implements OnDestroy {
localEditorPattern: string | null = null;
/** The field's own description. The modal never rendered one, so every tip was invisible. */
localEditorTip: string | null = null;
/** Empty means unset on this field, and the modal says so. Every optional field, not just numbers. */
/** Empty hands the field to a default, and the modal says so - only where the schema says it does. */
localEditorDefaultsWhenEmpty = false;
localEditorDefaultValue: string | null = null;
localEditorMin: number | null = null;
@ -449,8 +449,12 @@ export class GenericNodeComponent implements OnDestroy {
this.localEditorMinLength = definition.ui.minLength ?? null;
this.localEditorPattern = definition.ui.pattern ?? null;
this.localEditorTip = definition.ui.tip ?? null;
// A boolean has no empty state: false is a value, not an absence.
this.localEditorDefaultsWhenEmpty = definition.type !== 'boolean' && !this.isPathRequired(definition.path);
// Only where the server says emptying the field hands it to a default - the model's sampling
// knobs. An optional field is not one: most have no default to go back to, and a choice
// between alternatives (a source, a scope, an operation) is picked, not reset. A boolean has
// no empty state at all: false is a value, not an absence.
this.localEditorDefaultsWhenEmpty = definition.ui.defaultsWhenEmpty === true
&& definition.type !== 'boolean' && !this.isPathRequired(definition.path);
this.localEditorDefaultValue = this.schemaDeclaredDefault(definition.path);
this.localEditorMin = definition.ui.minimum ?? null;
this.localEditorMax = definition.ui.maximum ?? null;