@camstack/ui-library 1.2.238 → 1.2.239

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ import { ConfigField, ConfigNumberField, ConfigSliderField } from '@camstack/types';
2
+ /** A numeric field this control can render. */
3
+ export type ModelBoundNumericField = ConfigSliderField | ConfigNumberField;
4
+ export interface ModelBoundSliderProps {
5
+ readonly field: ModelBoundNumericField;
6
+ /** The model the edited row runs — pinned, or its effective default. */
7
+ readonly modelId: string;
8
+ readonly settings: Readonly<Record<string, unknown>> | undefined;
9
+ readonly disabled: boolean;
10
+ readonly onChangeSettings: (next: Record<string, unknown>) => void;
11
+ }
12
+ export declare function ModelBoundSlider({ field, modelId, settings, disabled, onChangeSettings, }: ModelBoundSliderProps): import("react").JSX.Element;
13
+ /** The keys of the model-bound fields of a schema, groups flattened. */
14
+ export declare function modelBoundKeysOf(fields: readonly ConfigField[]): string[];
15
+ export interface ResetModelDefaultsButtonProps {
16
+ readonly fields: readonly ConfigField[];
17
+ /** The model the edited row runs — pinned, or its effective default. */
18
+ readonly modelId: string;
19
+ readonly settings: Readonly<Record<string, unknown>> | undefined;
20
+ readonly disabled?: boolean;
21
+ readonly onReset: (next: Record<string, unknown>) => void;
22
+ }
23
+ /**
24
+ * "Reset thresholds to model defaults" — every model-bound override in force
25
+ * for `modelId`, cleared in ONE settings change (one save). Rendered only when
26
+ * there is something to reset. An override stored for another model is kept.
27
+ */
28
+ export declare function ResetModelDefaultsButton({ fields, modelId, settings, disabled, onReset, }: ResetModelDefaultsButtonProps): import("react").JSX.Element | null;
package/dist/index.cjs CHANGED
@@ -28970,6 +28970,106 @@ function ModelPicker({ models, value, onChange, disabled, defaultModelId }) {
28970
28970
  });
28971
28971
  }
28972
28972
  //#endregion
28973
+ //#region src/composites/model-bound-slider.tsx
28974
+ var SOURCE_LABEL = {
28975
+ override: "Override for this model",
28976
+ "model-default": "Model default",
28977
+ "global-default": "Default — not tuned for this model"
28978
+ };
28979
+ function formatNumber(value, scale) {
28980
+ return (scale !== void 0 ? value / scale : value).toFixed(2).replace(/\.?0+$/, "");
28981
+ }
28982
+ function ModelBoundSlider({ field, modelId, settings, disabled, onChangeSettings }) {
28983
+ const modelDefault = field.modelApplicability?.defaultByModel?.[modelId];
28984
+ const state = (0, _camstack_types.resolveModelBoundSetting)(settings, field.key, modelId.length > 0 ? modelId : void 0, modelDefault, field.default);
28985
+ const fallback = typeof field.default === "number" ? field.default : field.min ?? 0;
28986
+ const current = typeof state.value === "number" ? state.value : fallback;
28987
+ const scale = field.type === "slider" ? field.displayScale : void 0;
28988
+ const unit = "unit" in field ? field.unit : void 0;
28989
+ const stale = state.inapplicableOverride;
28990
+ const hasStoredValue = settings?.[field.key] !== void 0;
28991
+ const handleChange = (value) => {
28992
+ onChangeSettings(modelId.length > 0 ? (0, _camstack_types.writeModelBoundSetting)(settings, field.key, value, modelId) : {
28993
+ ...settings,
28994
+ [field.key]: value
28995
+ });
28996
+ };
28997
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
28998
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
28999
+ className: "flex items-center justify-between mb-1",
29000
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
29001
+ className: "text-[10px] font-medium text-foreground-subtle uppercase tracking-wide",
29002
+ children: field.label
29003
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
29004
+ className: "text-xs font-medium text-foreground tabular-nums",
29005
+ children: [formatNumber(current, scale), unit ? ` ${unit}` : ""]
29006
+ })]
29007
+ }),
29008
+ field.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
29009
+ className: "text-[10px] text-foreground-subtle/60 mb-1.5",
29010
+ children: field.description
29011
+ }),
29012
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
29013
+ type: "range",
29014
+ min: field.min ?? 0,
29015
+ max: field.max ?? 1,
29016
+ step: field.step ?? .01,
29017
+ value: current,
29018
+ disabled: disabled || field.disabled,
29019
+ onChange: (e) => handleChange(Number(e.target.value)),
29020
+ className: "w-full accent-primary h-1.5"
29021
+ }),
29022
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
29023
+ className: "mt-1 flex items-center justify-between gap-2 text-[10px]",
29024
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
29025
+ "data-testid": "model-bound-source",
29026
+ className: state.source === "override" ? "text-primary font-medium" : "text-foreground-subtle",
29027
+ children: SOURCE_LABEL[state.source]
29028
+ }), hasStoredValue && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
29029
+ type: "button",
29030
+ disabled,
29031
+ className: "text-foreground-subtle hover:text-foreground disabled:opacity-50",
29032
+ onClick: () => onChangeSettings((0, _camstack_types.clearModelBoundSetting)(settings, field.key)),
29033
+ children: "Reset to model default"
29034
+ })]
29035
+ }),
29036
+ stale !== void 0 && typeof stale.value === "number" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
29037
+ className: "mt-1 text-[10px] text-amber-400/90",
29038
+ children: [
29039
+ "The override ",
29040
+ formatNumber(stale.value, scale),
29041
+ " was set for ",
29042
+ stale.modelId,
29043
+ " and does not apply to this model."
29044
+ ]
29045
+ })
29046
+ ] });
29047
+ }
29048
+ /** The keys of the model-bound fields of a schema, groups flattened. */
29049
+ function modelBoundKeysOf(fields) {
29050
+ return fields.flatMap((field) => {
29051
+ if (field.type === "group") return modelBoundKeysOf(field.fields);
29052
+ return "key" in field && "bindsToModel" in field && field.bindsToModel === true ? [field.key] : [];
29053
+ });
29054
+ }
29055
+ /**
29056
+ * "Reset thresholds to model defaults" — every model-bound override in force
29057
+ * for `modelId`, cleared in ONE settings change (one save). Rendered only when
29058
+ * there is something to reset. An override stored for another model is kept.
29059
+ */
29060
+ function ResetModelDefaultsButton({ fields, modelId, settings, disabled, onReset }) {
29061
+ const keys = modelBoundKeysOf(fields);
29062
+ const model = modelId.length > 0 ? modelId : void 0;
29063
+ if (keys.length === 0 || !(0, _camstack_types.hasModelBoundOverrides)(settings, keys, model)) return null;
29064
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
29065
+ type: "button",
29066
+ disabled,
29067
+ className: "text-[10px] text-primary hover:underline disabled:opacity-50",
29068
+ onClick: () => onReset((0, _camstack_types.clearModelBoundOverrides)(settings, keys, model)),
29069
+ children: "Reset thresholds to model defaults"
29070
+ });
29071
+ }
29072
+ //#endregion
28973
29073
  //#region src/composites/pipeline-step.tsx
28974
29074
  /**
28975
29075
  * PipelineStep — single step card in the pipeline builder.
@@ -29227,6 +29327,16 @@ function ConfigSchemaFieldControl({ field, allFields, step, disabled, modelScope
29227
29327
  onChange: handleChange
29228
29328
  });
29229
29329
  }
29330
+ if ((field.type === "slider" || field.type === "number") && field.bindsToModel === true) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelBoundSlider, {
29331
+ field,
29332
+ modelId: step.modelId,
29333
+ settings: step.settings,
29334
+ disabled,
29335
+ onChangeSettings: (settings) => onChange({
29336
+ ...step,
29337
+ settings
29338
+ })
29339
+ });
29230
29340
  if (field.type === "slider" || field.type === "number") {
29231
29341
  const raw = step.settings?.[field.key];
29232
29342
  const fallback = typeof field.default === "number" ? field.default : field.min ?? 0;
@@ -29485,7 +29595,19 @@ function ClusterSettingsHint({ addonId }) {
29485
29595
  children: "Admission knobs for this step live in Pipeline → Cluster → Settings → Enrichment models. They apply to every camera — a per-device copy would fill the shared index from two policies."
29486
29596
  });
29487
29597
  }
29488
- function buildDisplayStep(addon, cfg) {
29598
+ /**
29599
+ * The model the step's settings are read and written for — what a model-bound
29600
+ * field (D738) binds to. While the ROOT supplies the step's fine label (D741),
29601
+ * that is the root model: the step's threshold then gates the root's label, on
29602
+ * the root's score scale. Otherwise the step's own model: its pin, else the
29603
+ * model the row really runs (`effectiveModelId` — the device's hardware-aware
29604
+ * default when unpinned), else the step's flat catalog default.
29605
+ */
29606
+ function settingsModelIdFor(addon, cfg, effectiveModelId, rootModel) {
29607
+ if (rootModel !== void 0 && rootSourceState(addon.id, cfg.settings, rootModel)?.effective) return rootModel.modelId;
29608
+ return cfg.modelId ?? effectiveModelId ?? addon.defaultModelId;
29609
+ }
29610
+ function buildDisplayStep(addon, cfg, modelId) {
29489
29611
  return {
29490
29612
  addonId: addon.id,
29491
29613
  addonName: addon.name,
@@ -29493,7 +29615,7 @@ function buildDisplayStep(addon, cfg) {
29493
29615
  inputClasses: [...addon.inputClasses],
29494
29616
  outputClasses: [...addon.outputClasses],
29495
29617
  enabled: cfg.enabled,
29496
- modelId: cfg.modelId ?? addon.defaultModelId,
29618
+ modelId,
29497
29619
  children: [],
29498
29620
  settings: { ...cfg.settings }
29499
29621
  };
@@ -29558,13 +29680,21 @@ function AgentModeEditor({ addon, agentDefault, modelsForFormat, engineFormat, e
29558
29680
  })] }),
29559
29681
  localSchema.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
29560
29682
  className: "pt-2 border-t border-border/50 space-y-3",
29561
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
29562
- className: "text-[10px] uppercase tracking-widest text-foreground-subtle",
29563
- children: "Settings"
29683
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
29684
+ className: "flex items-center justify-between",
29685
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
29686
+ className: "text-[10px] uppercase tracking-widest text-foreground-subtle",
29687
+ children: "Settings"
29688
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetModelDefaultsButton, {
29689
+ fields: localSchema,
29690
+ modelId: settingsModelIdFor(addon, agentDefault, effectiveModelId, rootModel),
29691
+ settings: agentDefault.settings,
29692
+ onReset: (settings) => update({ settings })
29693
+ })]
29564
29694
  }), localSchema.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigSchemaField, {
29565
29695
  field,
29566
29696
  allFields: localSchema,
29567
- step: buildDisplayStep(addon, agentDefault),
29697
+ step: buildDisplayStep(addon, agentDefault, settingsModelIdFor(addon, agentDefault, effectiveModelId, rootModel)),
29568
29698
  disabled: false,
29569
29699
  onChange: (next) => update({ settings: { ...next.settings } })
29570
29700
  }, field.key))]
@@ -29660,11 +29790,19 @@ function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, mode
29660
29790
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
29661
29791
  className: "text-[10px] uppercase tracking-widest text-foreground-subtle",
29662
29792
  children: "Settings"
29663
- }), settingsOverridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
29664
- type: "button",
29665
- className: "text-[10px] text-foreground-subtle hover:text-foreground",
29666
- onClick: () => setSettings(void 0),
29667
- children: "Clear"
29793
+ }), settingsOverridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
29794
+ className: "flex items-center gap-3",
29795
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetModelDefaultsButton, {
29796
+ fields: localSchema,
29797
+ modelId: settingsModelIdFor(addon, effective, inheritedModelId, rootModel),
29798
+ settings: currentPatch?.settings,
29799
+ onReset: (settings) => setSettings(settings)
29800
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
29801
+ type: "button",
29802
+ className: "text-[10px] text-foreground-subtle hover:text-foreground",
29803
+ onClick: () => setSettings(void 0),
29804
+ children: "Clear"
29805
+ })]
29668
29806
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
29669
29807
  type: "button",
29670
29808
  className: "text-[10px] text-primary hover:underline",
@@ -29675,7 +29813,7 @@ function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, mode
29675
29813
  localSchema.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigSchemaField, {
29676
29814
  field,
29677
29815
  allFields: localSchema,
29678
- step: buildDisplayStep(addon, effective),
29816
+ step: buildDisplayStep(addon, effective, settingsModelIdFor(addon, effective, inheritedModelId, rootModel)),
29679
29817
  disabled: !settingsOverridden,
29680
29818
  onChange: (next) => setSettings({ ...next.settings })
29681
29819
  }, field.key)),
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import * as __mfHostReactDOMClient from "react-dom/client";
13
13
  import { createRoot } from "react-dom/client";
14
14
  import * as __mfHostTrpcClient from "@trpc/client";
15
15
  import { createTRPCClient, createWSClient, httpLink, splitLink, wsLink } from "@trpc/client";
16
- import { ALL_CAPABILITY_DEFINITIONS, CAM_PROFILE_ORDER, CLIP_STILL_DISPOSITION_HEADER, CLIP_STILL_REASON_CODE_HEADER, CLIP_STILL_REASON_HEADER, CONTAINER_CHILD_PRIORITY, DEFAULT_EVENTS_BAND_BUFFER_SEC, DEFAULT_EVENT_COLOR, DEVICE_TYPE_CONTROL_KIND, DISPLAY_ICON_GROUPS, DeviceExportStatusSchema, DeviceFeature, DeviceRole, DeviceType, EVENT_TAXONOMY, EXTENSION_SLOTS, EXTENSION_SLOT_IDS, EventCategory, ExposedDeviceSchema, FIRST_LEVEL_MACRO_CLASSES, KNOWN_CAP_NAMES, MAX_SENSOR_TRIGGER_DEVICES, MODEL_PROVIDER_IDS, NAVIGATION_LIGHT_LEVEL_MAX, NAVIGATION_LIGHT_LEVEL_MIN, PET_FEEDER_MANUAL_FEED_MAX, PET_FEEDER_MANUAL_FEED_MIN, RECORDING_AUDIO_TRIGGER_OFFERED_DBFS, RECORDING_SOURCE_CAMSTACK_ADDON, RecordingConfigSchema, RecordingStatusSchema, SOURCE_CAP_ACTIVE_FIELD, STATIONARY_OVERLAY_STYLE, TIMEZONES, USE_ROOT_MODEL_SETTING_KEY, buildModelVariantGroups, canConvertUnit, clusterStepSettingFieldsFor, createDeviceProxy, createLazyTrpcSource, createSliceHandle, deriveRecordingMode, describeModelVariant, findTimezone, formatParkedFor, isRootSuppliableStepId, normalizeUnit, publicEventCategories, resolveContainerPrimaryChild, resolveDeviceControlKind, resolveRecordingProfiles, resolveRootSupply, resolveVariantModelId, resolveViewableDeviceIds, searchDisplayIcons, selectAssignedProfileSlots, tryConvertUnit, unitsForDimension } from "@camstack/types";
16
+ import { ALL_CAPABILITY_DEFINITIONS, CAM_PROFILE_ORDER, CLIP_STILL_DISPOSITION_HEADER, CLIP_STILL_REASON_CODE_HEADER, CLIP_STILL_REASON_HEADER, CONTAINER_CHILD_PRIORITY, DEFAULT_EVENTS_BAND_BUFFER_SEC, DEFAULT_EVENT_COLOR, DEVICE_TYPE_CONTROL_KIND, DISPLAY_ICON_GROUPS, DeviceExportStatusSchema, DeviceFeature, DeviceRole, DeviceType, EVENT_TAXONOMY, EXTENSION_SLOTS, EXTENSION_SLOT_IDS, EventCategory, ExposedDeviceSchema, FIRST_LEVEL_MACRO_CLASSES, KNOWN_CAP_NAMES, MAX_SENSOR_TRIGGER_DEVICES, MODEL_PROVIDER_IDS, NAVIGATION_LIGHT_LEVEL_MAX, NAVIGATION_LIGHT_LEVEL_MIN, PET_FEEDER_MANUAL_FEED_MAX, PET_FEEDER_MANUAL_FEED_MIN, RECORDING_AUDIO_TRIGGER_OFFERED_DBFS, RECORDING_SOURCE_CAMSTACK_ADDON, RecordingConfigSchema, RecordingStatusSchema, SOURCE_CAP_ACTIVE_FIELD, STATIONARY_OVERLAY_STYLE, TIMEZONES, USE_ROOT_MODEL_SETTING_KEY, buildModelVariantGroups, canConvertUnit, clearModelBoundOverrides, clearModelBoundSetting, clusterStepSettingFieldsFor, createDeviceProxy, createLazyTrpcSource, createSliceHandle, deriveRecordingMode, describeModelVariant, findTimezone, formatParkedFor, hasModelBoundOverrides, isRootSuppliableStepId, normalizeUnit, publicEventCategories, resolveContainerPrimaryChild, resolveDeviceControlKind, resolveModelBoundSetting, resolveRecordingProfiles, resolveRootSupply, resolveVariantModelId, resolveViewableDeviceIds, searchDisplayIcons, selectAssignedProfileSlots, tryConvertUnit, unitsForDimension, writeModelBoundSetting } from "@camstack/types";
17
17
  import { createSystem } from "@camstack/sdk";
18
18
  import { z } from "zod";
19
19
  //#region ../../node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.js
@@ -28946,6 +28946,106 @@ function ModelPicker({ models, value, onChange, disabled, defaultModelId }) {
28946
28946
  });
28947
28947
  }
28948
28948
  //#endregion
28949
+ //#region src/composites/model-bound-slider.tsx
28950
+ var SOURCE_LABEL = {
28951
+ override: "Override for this model",
28952
+ "model-default": "Model default",
28953
+ "global-default": "Default — not tuned for this model"
28954
+ };
28955
+ function formatNumber(value, scale) {
28956
+ return (scale !== void 0 ? value / scale : value).toFixed(2).replace(/\.?0+$/, "");
28957
+ }
28958
+ function ModelBoundSlider({ field, modelId, settings, disabled, onChangeSettings }) {
28959
+ const modelDefault = field.modelApplicability?.defaultByModel?.[modelId];
28960
+ const state = resolveModelBoundSetting(settings, field.key, modelId.length > 0 ? modelId : void 0, modelDefault, field.default);
28961
+ const fallback = typeof field.default === "number" ? field.default : field.min ?? 0;
28962
+ const current = typeof state.value === "number" ? state.value : fallback;
28963
+ const scale = field.type === "slider" ? field.displayScale : void 0;
28964
+ const unit = "unit" in field ? field.unit : void 0;
28965
+ const stale = state.inapplicableOverride;
28966
+ const hasStoredValue = settings?.[field.key] !== void 0;
28967
+ const handleChange = (value) => {
28968
+ onChangeSettings(modelId.length > 0 ? writeModelBoundSetting(settings, field.key, value, modelId) : {
28969
+ ...settings,
28970
+ [field.key]: value
28971
+ });
28972
+ };
28973
+ return /* @__PURE__ */ jsxs("div", { children: [
28974
+ /* @__PURE__ */ jsxs("div", {
28975
+ className: "flex items-center justify-between mb-1",
28976
+ children: [/* @__PURE__ */ jsx("span", {
28977
+ className: "text-[10px] font-medium text-foreground-subtle uppercase tracking-wide",
28978
+ children: field.label
28979
+ }), /* @__PURE__ */ jsxs("span", {
28980
+ className: "text-xs font-medium text-foreground tabular-nums",
28981
+ children: [formatNumber(current, scale), unit ? ` ${unit}` : ""]
28982
+ })]
28983
+ }),
28984
+ field.description && /* @__PURE__ */ jsx("p", {
28985
+ className: "text-[10px] text-foreground-subtle/60 mb-1.5",
28986
+ children: field.description
28987
+ }),
28988
+ /* @__PURE__ */ jsx("input", {
28989
+ type: "range",
28990
+ min: field.min ?? 0,
28991
+ max: field.max ?? 1,
28992
+ step: field.step ?? .01,
28993
+ value: current,
28994
+ disabled: disabled || field.disabled,
28995
+ onChange: (e) => handleChange(Number(e.target.value)),
28996
+ className: "w-full accent-primary h-1.5"
28997
+ }),
28998
+ /* @__PURE__ */ jsxs("div", {
28999
+ className: "mt-1 flex items-center justify-between gap-2 text-[10px]",
29000
+ children: [/* @__PURE__ */ jsx("span", {
29001
+ "data-testid": "model-bound-source",
29002
+ className: state.source === "override" ? "text-primary font-medium" : "text-foreground-subtle",
29003
+ children: SOURCE_LABEL[state.source]
29004
+ }), hasStoredValue && /* @__PURE__ */ jsx("button", {
29005
+ type: "button",
29006
+ disabled,
29007
+ className: "text-foreground-subtle hover:text-foreground disabled:opacity-50",
29008
+ onClick: () => onChangeSettings(clearModelBoundSetting(settings, field.key)),
29009
+ children: "Reset to model default"
29010
+ })]
29011
+ }),
29012
+ stale !== void 0 && typeof stale.value === "number" && /* @__PURE__ */ jsxs("p", {
29013
+ className: "mt-1 text-[10px] text-amber-400/90",
29014
+ children: [
29015
+ "The override ",
29016
+ formatNumber(stale.value, scale),
29017
+ " was set for ",
29018
+ stale.modelId,
29019
+ " and does not apply to this model."
29020
+ ]
29021
+ })
29022
+ ] });
29023
+ }
29024
+ /** The keys of the model-bound fields of a schema, groups flattened. */
29025
+ function modelBoundKeysOf(fields) {
29026
+ return fields.flatMap((field) => {
29027
+ if (field.type === "group") return modelBoundKeysOf(field.fields);
29028
+ return "key" in field && "bindsToModel" in field && field.bindsToModel === true ? [field.key] : [];
29029
+ });
29030
+ }
29031
+ /**
29032
+ * "Reset thresholds to model defaults" — every model-bound override in force
29033
+ * for `modelId`, cleared in ONE settings change (one save). Rendered only when
29034
+ * there is something to reset. An override stored for another model is kept.
29035
+ */
29036
+ function ResetModelDefaultsButton({ fields, modelId, settings, disabled, onReset }) {
29037
+ const keys = modelBoundKeysOf(fields);
29038
+ const model = modelId.length > 0 ? modelId : void 0;
29039
+ if (keys.length === 0 || !hasModelBoundOverrides(settings, keys, model)) return null;
29040
+ return /* @__PURE__ */ jsx("button", {
29041
+ type: "button",
29042
+ disabled,
29043
+ className: "text-[10px] text-primary hover:underline disabled:opacity-50",
29044
+ onClick: () => onReset(clearModelBoundOverrides(settings, keys, model)),
29045
+ children: "Reset thresholds to model defaults"
29046
+ });
29047
+ }
29048
+ //#endregion
28949
29049
  //#region src/composites/pipeline-step.tsx
28950
29050
  /**
28951
29051
  * PipelineStep — single step card in the pipeline builder.
@@ -29203,6 +29303,16 @@ function ConfigSchemaFieldControl({ field, allFields, step, disabled, modelScope
29203
29303
  onChange: handleChange
29204
29304
  });
29205
29305
  }
29306
+ if ((field.type === "slider" || field.type === "number") && field.bindsToModel === true) return /* @__PURE__ */ jsx(ModelBoundSlider, {
29307
+ field,
29308
+ modelId: step.modelId,
29309
+ settings: step.settings,
29310
+ disabled,
29311
+ onChangeSettings: (settings) => onChange({
29312
+ ...step,
29313
+ settings
29314
+ })
29315
+ });
29206
29316
  if (field.type === "slider" || field.type === "number") {
29207
29317
  const raw = step.settings?.[field.key];
29208
29318
  const fallback = typeof field.default === "number" ? field.default : field.min ?? 0;
@@ -29461,7 +29571,19 @@ function ClusterSettingsHint({ addonId }) {
29461
29571
  children: "Admission knobs for this step live in Pipeline → Cluster → Settings → Enrichment models. They apply to every camera — a per-device copy would fill the shared index from two policies."
29462
29572
  });
29463
29573
  }
29464
- function buildDisplayStep(addon, cfg) {
29574
+ /**
29575
+ * The model the step's settings are read and written for — what a model-bound
29576
+ * field (D738) binds to. While the ROOT supplies the step's fine label (D741),
29577
+ * that is the root model: the step's threshold then gates the root's label, on
29578
+ * the root's score scale. Otherwise the step's own model: its pin, else the
29579
+ * model the row really runs (`effectiveModelId` — the device's hardware-aware
29580
+ * default when unpinned), else the step's flat catalog default.
29581
+ */
29582
+ function settingsModelIdFor(addon, cfg, effectiveModelId, rootModel) {
29583
+ if (rootModel !== void 0 && rootSourceState(addon.id, cfg.settings, rootModel)?.effective) return rootModel.modelId;
29584
+ return cfg.modelId ?? effectiveModelId ?? addon.defaultModelId;
29585
+ }
29586
+ function buildDisplayStep(addon, cfg, modelId) {
29465
29587
  return {
29466
29588
  addonId: addon.id,
29467
29589
  addonName: addon.name,
@@ -29469,7 +29591,7 @@ function buildDisplayStep(addon, cfg) {
29469
29591
  inputClasses: [...addon.inputClasses],
29470
29592
  outputClasses: [...addon.outputClasses],
29471
29593
  enabled: cfg.enabled,
29472
- modelId: cfg.modelId ?? addon.defaultModelId,
29594
+ modelId,
29473
29595
  children: [],
29474
29596
  settings: { ...cfg.settings }
29475
29597
  };
@@ -29534,13 +29656,21 @@ function AgentModeEditor({ addon, agentDefault, modelsForFormat, engineFormat, e
29534
29656
  })] }),
29535
29657
  localSchema.length > 0 && /* @__PURE__ */ jsxs("div", {
29536
29658
  className: "pt-2 border-t border-border/50 space-y-3",
29537
- children: [/* @__PURE__ */ jsx("div", {
29538
- className: "text-[10px] uppercase tracking-widest text-foreground-subtle",
29539
- children: "Settings"
29659
+ children: [/* @__PURE__ */ jsxs("div", {
29660
+ className: "flex items-center justify-between",
29661
+ children: [/* @__PURE__ */ jsx("div", {
29662
+ className: "text-[10px] uppercase tracking-widest text-foreground-subtle",
29663
+ children: "Settings"
29664
+ }), /* @__PURE__ */ jsx(ResetModelDefaultsButton, {
29665
+ fields: localSchema,
29666
+ modelId: settingsModelIdFor(addon, agentDefault, effectiveModelId, rootModel),
29667
+ settings: agentDefault.settings,
29668
+ onReset: (settings) => update({ settings })
29669
+ })]
29540
29670
  }), localSchema.map((field) => /* @__PURE__ */ jsx(ConfigSchemaField, {
29541
29671
  field,
29542
29672
  allFields: localSchema,
29543
- step: buildDisplayStep(addon, agentDefault),
29673
+ step: buildDisplayStep(addon, agentDefault, settingsModelIdFor(addon, agentDefault, effectiveModelId, rootModel)),
29544
29674
  disabled: false,
29545
29675
  onChange: (next) => update({ settings: { ...next.settings } })
29546
29676
  }, field.key))]
@@ -29636,11 +29766,19 @@ function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, mode
29636
29766
  children: [/* @__PURE__ */ jsx("div", {
29637
29767
  className: "text-[10px] uppercase tracking-widest text-foreground-subtle",
29638
29768
  children: "Settings"
29639
- }), settingsOverridden ? /* @__PURE__ */ jsx("button", {
29640
- type: "button",
29641
- className: "text-[10px] text-foreground-subtle hover:text-foreground",
29642
- onClick: () => setSettings(void 0),
29643
- children: "Clear"
29769
+ }), settingsOverridden ? /* @__PURE__ */ jsxs("div", {
29770
+ className: "flex items-center gap-3",
29771
+ children: [/* @__PURE__ */ jsx(ResetModelDefaultsButton, {
29772
+ fields: localSchema,
29773
+ modelId: settingsModelIdFor(addon, effective, inheritedModelId, rootModel),
29774
+ settings: currentPatch?.settings,
29775
+ onReset: (settings) => setSettings(settings)
29776
+ }), /* @__PURE__ */ jsx("button", {
29777
+ type: "button",
29778
+ className: "text-[10px] text-foreground-subtle hover:text-foreground",
29779
+ onClick: () => setSettings(void 0),
29780
+ children: "Clear"
29781
+ })]
29644
29782
  }) : /* @__PURE__ */ jsx("button", {
29645
29783
  type: "button",
29646
29784
  className: "text-[10px] text-primary hover:underline",
@@ -29651,7 +29789,7 @@ function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, mode
29651
29789
  localSchema.map((field) => /* @__PURE__ */ jsx(ConfigSchemaField, {
29652
29790
  field,
29653
29791
  allFields: localSchema,
29654
- step: buildDisplayStep(addon, effective),
29792
+ step: buildDisplayStep(addon, effective, settingsModelIdFor(addon, effective, inheritedModelId, rootModel)),
29655
29793
  disabled: !settingsOverridden,
29656
29794
  onChange: (next) => setSettings({ ...next.settings })
29657
29795
  }, field.key)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/ui-library",
3
- "version": "1.2.238",
3
+ "version": "1.2.239",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",