@camstack/ui-library 1.2.237 → 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.
- package/dist/composites/model-bound-slider.d.ts +28 -0
- package/dist/index.cjs +176 -24
- package/dist/index.js +177 -25
- package/package.json +1 -1
|
@@ -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
|
@@ -28752,18 +28752,31 @@ function GroupedModelSelector({ catalog, value, onChange, disabled }) {
|
|
|
28752
28752
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
28753
28753
|
className: "space-y-3",
|
|
28754
28754
|
children: [
|
|
28755
|
-
families.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
28756
|
-
|
|
28757
|
-
|
|
28758
|
-
|
|
28759
|
-
|
|
28760
|
-
|
|
28761
|
-
|
|
28762
|
-
|
|
28763
|
-
|
|
28764
|
-
|
|
28765
|
-
|
|
28766
|
-
|
|
28755
|
+
families.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
28756
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
28757
|
+
className: "text-[10px] uppercase tracking-wide text-foreground-subtle mb-1.5",
|
|
28758
|
+
children: "Family"
|
|
28759
|
+
}),
|
|
28760
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
28761
|
+
className: "flex flex-wrap gap-1.5",
|
|
28762
|
+
children: families.map((f) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
|
|
28763
|
+
active: f.family === family.family,
|
|
28764
|
+
disabled,
|
|
28765
|
+
...f.hint !== void 0 ? { title: f.hint } : {},
|
|
28766
|
+
onClick: () => onChange(f.tiers[0]?.baseModelId ?? ""),
|
|
28767
|
+
children: f.label
|
|
28768
|
+
}, f.family))
|
|
28769
|
+
}),
|
|
28770
|
+
family.hint !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
28771
|
+
className: "mt-1 text-[10px] text-foreground-subtle",
|
|
28772
|
+
"data-testid": "model-family-hint",
|
|
28773
|
+
children: [
|
|
28774
|
+
family.label,
|
|
28775
|
+
": ",
|
|
28776
|
+
family.hint
|
|
28777
|
+
]
|
|
28778
|
+
})
|
|
28779
|
+
] }),
|
|
28767
28780
|
family.tiers.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
28768
28781
|
className: "text-[10px] uppercase tracking-wide text-foreground-subtle mb-1.5",
|
|
28769
28782
|
children: [family.label, " — size"]
|
|
@@ -28957,6 +28970,106 @@ function ModelPicker({ models, value, onChange, disabled, defaultModelId }) {
|
|
|
28957
28970
|
});
|
|
28958
28971
|
}
|
|
28959
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
|
|
28960
29073
|
//#region src/composites/pipeline-step.tsx
|
|
28961
29074
|
/**
|
|
28962
29075
|
* PipelineStep — single step card in the pipeline builder.
|
|
@@ -29214,6 +29327,16 @@ function ConfigSchemaFieldControl({ field, allFields, step, disabled, modelScope
|
|
|
29214
29327
|
onChange: handleChange
|
|
29215
29328
|
});
|
|
29216
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
|
+
});
|
|
29217
29340
|
if (field.type === "slider" || field.type === "number") {
|
|
29218
29341
|
const raw = step.settings?.[field.key];
|
|
29219
29342
|
const fallback = typeof field.default === "number" ? field.default : field.min ?? 0;
|
|
@@ -29400,6 +29523,7 @@ function supplyNotes(supply) {
|
|
|
29400
29523
|
if (supply.stepId === "package-detection") return [...notes, "The root model's own package boxes become the package result at this step's threshold; this step's zone-crop view is not used, so small parcels the full-frame model misses stay missed."];
|
|
29401
29524
|
return [...notes, "This step's class list decides which of the root's labels become the fine class; a switched-off class stays at its macro."];
|
|
29402
29525
|
}
|
|
29526
|
+
if (supply.kind === "mask") return ["Masks come from the root model: each detection carries the root's own instance mask, so this step's model does not run. A root mask follows the model's mask grid (a quarter of its input), coarser than this step's own per-crop mask."];
|
|
29403
29527
|
if (supply.stepId === "face-detection") return ["Face boxes come from the root model. Each is re-found by this step's face detector in a square around it before recognition, so only a face it finds is recognised; a box with no face in it is dropped."];
|
|
29404
29528
|
return ["Plate boxes come from the root model and the plate reader reads them. The crop margin around a root plate box has not been measured for this model yet."];
|
|
29405
29529
|
}
|
|
@@ -29471,7 +29595,19 @@ function ClusterSettingsHint({ addonId }) {
|
|
|
29471
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."
|
|
29472
29596
|
});
|
|
29473
29597
|
}
|
|
29474
|
-
|
|
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) {
|
|
29475
29611
|
return {
|
|
29476
29612
|
addonId: addon.id,
|
|
29477
29613
|
addonName: addon.name,
|
|
@@ -29479,7 +29615,7 @@ function buildDisplayStep(addon, cfg) {
|
|
|
29479
29615
|
inputClasses: [...addon.inputClasses],
|
|
29480
29616
|
outputClasses: [...addon.outputClasses],
|
|
29481
29617
|
enabled: cfg.enabled,
|
|
29482
|
-
modelId
|
|
29618
|
+
modelId,
|
|
29483
29619
|
children: [],
|
|
29484
29620
|
settings: { ...cfg.settings }
|
|
29485
29621
|
};
|
|
@@ -29544,13 +29680,21 @@ function AgentModeEditor({ addon, agentDefault, modelsForFormat, engineFormat, e
|
|
|
29544
29680
|
})] }),
|
|
29545
29681
|
localSchema.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
29546
29682
|
className: "pt-2 border-t border-border/50 space-y-3",
|
|
29547
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
29548
|
-
className: "
|
|
29549
|
-
children: "
|
|
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
|
+
})]
|
|
29550
29694
|
}), localSchema.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigSchemaField, {
|
|
29551
29695
|
field,
|
|
29552
29696
|
allFields: localSchema,
|
|
29553
|
-
step: buildDisplayStep(addon, agentDefault),
|
|
29697
|
+
step: buildDisplayStep(addon, agentDefault, settingsModelIdFor(addon, agentDefault, effectiveModelId, rootModel)),
|
|
29554
29698
|
disabled: false,
|
|
29555
29699
|
onChange: (next) => update({ settings: { ...next.settings } })
|
|
29556
29700
|
}, field.key))]
|
|
@@ -29646,11 +29790,19 @@ function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, mode
|
|
|
29646
29790
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
29647
29791
|
className: "text-[10px] uppercase tracking-widest text-foreground-subtle",
|
|
29648
29792
|
children: "Settings"
|
|
29649
|
-
}), settingsOverridden ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
29650
|
-
|
|
29651
|
-
|
|
29652
|
-
|
|
29653
|
-
|
|
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
|
+
})]
|
|
29654
29806
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
29655
29807
|
type: "button",
|
|
29656
29808
|
className: "text-[10px] text-primary hover:underline",
|
|
@@ -29661,7 +29813,7 @@ function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, mode
|
|
|
29661
29813
|
localSchema.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigSchemaField, {
|
|
29662
29814
|
field,
|
|
29663
29815
|
allFields: localSchema,
|
|
29664
|
-
step: buildDisplayStep(addon, effective),
|
|
29816
|
+
step: buildDisplayStep(addon, effective, settingsModelIdFor(addon, effective, inheritedModelId, rootModel)),
|
|
29665
29817
|
disabled: !settingsOverridden,
|
|
29666
29818
|
onChange: (next) => setSettings({ ...next.settings })
|
|
29667
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
|
|
@@ -28728,18 +28728,31 @@ function GroupedModelSelector({ catalog, value, onChange, disabled }) {
|
|
|
28728
28728
|
return /* @__PURE__ */ jsxs("div", {
|
|
28729
28729
|
className: "space-y-3",
|
|
28730
28730
|
children: [
|
|
28731
|
-
families.length > 1 && /* @__PURE__ */ jsxs("div", { children: [
|
|
28732
|
-
|
|
28733
|
-
|
|
28734
|
-
|
|
28735
|
-
|
|
28736
|
-
|
|
28737
|
-
|
|
28738
|
-
|
|
28739
|
-
|
|
28740
|
-
|
|
28741
|
-
|
|
28742
|
-
|
|
28731
|
+
families.length > 1 && /* @__PURE__ */ jsxs("div", { children: [
|
|
28732
|
+
/* @__PURE__ */ jsx("p", {
|
|
28733
|
+
className: "text-[10px] uppercase tracking-wide text-foreground-subtle mb-1.5",
|
|
28734
|
+
children: "Family"
|
|
28735
|
+
}),
|
|
28736
|
+
/* @__PURE__ */ jsx("div", {
|
|
28737
|
+
className: "flex flex-wrap gap-1.5",
|
|
28738
|
+
children: families.map((f) => /* @__PURE__ */ jsx(Chip, {
|
|
28739
|
+
active: f.family === family.family,
|
|
28740
|
+
disabled,
|
|
28741
|
+
...f.hint !== void 0 ? { title: f.hint } : {},
|
|
28742
|
+
onClick: () => onChange(f.tiers[0]?.baseModelId ?? ""),
|
|
28743
|
+
children: f.label
|
|
28744
|
+
}, f.family))
|
|
28745
|
+
}),
|
|
28746
|
+
family.hint !== void 0 && /* @__PURE__ */ jsxs("p", {
|
|
28747
|
+
className: "mt-1 text-[10px] text-foreground-subtle",
|
|
28748
|
+
"data-testid": "model-family-hint",
|
|
28749
|
+
children: [
|
|
28750
|
+
family.label,
|
|
28751
|
+
": ",
|
|
28752
|
+
family.hint
|
|
28753
|
+
]
|
|
28754
|
+
})
|
|
28755
|
+
] }),
|
|
28743
28756
|
family.tiers.length > 1 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("p", {
|
|
28744
28757
|
className: "text-[10px] uppercase tracking-wide text-foreground-subtle mb-1.5",
|
|
28745
28758
|
children: [family.label, " — size"]
|
|
@@ -28933,6 +28946,106 @@ function ModelPicker({ models, value, onChange, disabled, defaultModelId }) {
|
|
|
28933
28946
|
});
|
|
28934
28947
|
}
|
|
28935
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
|
|
28936
29049
|
//#region src/composites/pipeline-step.tsx
|
|
28937
29050
|
/**
|
|
28938
29051
|
* PipelineStep — single step card in the pipeline builder.
|
|
@@ -29190,6 +29303,16 @@ function ConfigSchemaFieldControl({ field, allFields, step, disabled, modelScope
|
|
|
29190
29303
|
onChange: handleChange
|
|
29191
29304
|
});
|
|
29192
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
|
+
});
|
|
29193
29316
|
if (field.type === "slider" || field.type === "number") {
|
|
29194
29317
|
const raw = step.settings?.[field.key];
|
|
29195
29318
|
const fallback = typeof field.default === "number" ? field.default : field.min ?? 0;
|
|
@@ -29376,6 +29499,7 @@ function supplyNotes(supply) {
|
|
|
29376
29499
|
if (supply.stepId === "package-detection") return [...notes, "The root model's own package boxes become the package result at this step's threshold; this step's zone-crop view is not used, so small parcels the full-frame model misses stay missed."];
|
|
29377
29500
|
return [...notes, "This step's class list decides which of the root's labels become the fine class; a switched-off class stays at its macro."];
|
|
29378
29501
|
}
|
|
29502
|
+
if (supply.kind === "mask") return ["Masks come from the root model: each detection carries the root's own instance mask, so this step's model does not run. A root mask follows the model's mask grid (a quarter of its input), coarser than this step's own per-crop mask."];
|
|
29379
29503
|
if (supply.stepId === "face-detection") return ["Face boxes come from the root model. Each is re-found by this step's face detector in a square around it before recognition, so only a face it finds is recognised; a box with no face in it is dropped."];
|
|
29380
29504
|
return ["Plate boxes come from the root model and the plate reader reads them. The crop margin around a root plate box has not been measured for this model yet."];
|
|
29381
29505
|
}
|
|
@@ -29447,7 +29571,19 @@ function ClusterSettingsHint({ addonId }) {
|
|
|
29447
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."
|
|
29448
29572
|
});
|
|
29449
29573
|
}
|
|
29450
|
-
|
|
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) {
|
|
29451
29587
|
return {
|
|
29452
29588
|
addonId: addon.id,
|
|
29453
29589
|
addonName: addon.name,
|
|
@@ -29455,7 +29591,7 @@ function buildDisplayStep(addon, cfg) {
|
|
|
29455
29591
|
inputClasses: [...addon.inputClasses],
|
|
29456
29592
|
outputClasses: [...addon.outputClasses],
|
|
29457
29593
|
enabled: cfg.enabled,
|
|
29458
|
-
modelId
|
|
29594
|
+
modelId,
|
|
29459
29595
|
children: [],
|
|
29460
29596
|
settings: { ...cfg.settings }
|
|
29461
29597
|
};
|
|
@@ -29520,13 +29656,21 @@ function AgentModeEditor({ addon, agentDefault, modelsForFormat, engineFormat, e
|
|
|
29520
29656
|
})] }),
|
|
29521
29657
|
localSchema.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
29522
29658
|
className: "pt-2 border-t border-border/50 space-y-3",
|
|
29523
|
-
children: [/* @__PURE__ */
|
|
29524
|
-
className: "
|
|
29525
|
-
children: "
|
|
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
|
+
})]
|
|
29526
29670
|
}), localSchema.map((field) => /* @__PURE__ */ jsx(ConfigSchemaField, {
|
|
29527
29671
|
field,
|
|
29528
29672
|
allFields: localSchema,
|
|
29529
|
-
step: buildDisplayStep(addon, agentDefault),
|
|
29673
|
+
step: buildDisplayStep(addon, agentDefault, settingsModelIdFor(addon, agentDefault, effectiveModelId, rootModel)),
|
|
29530
29674
|
disabled: false,
|
|
29531
29675
|
onChange: (next) => update({ settings: { ...next.settings } })
|
|
29532
29676
|
}, field.key))]
|
|
@@ -29622,11 +29766,19 @@ function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, mode
|
|
|
29622
29766
|
children: [/* @__PURE__ */ jsx("div", {
|
|
29623
29767
|
className: "text-[10px] uppercase tracking-widest text-foreground-subtle",
|
|
29624
29768
|
children: "Settings"
|
|
29625
|
-
}), settingsOverridden ? /* @__PURE__ */
|
|
29626
|
-
|
|
29627
|
-
|
|
29628
|
-
|
|
29629
|
-
|
|
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
|
+
})]
|
|
29630
29782
|
}) : /* @__PURE__ */ jsx("button", {
|
|
29631
29783
|
type: "button",
|
|
29632
29784
|
className: "text-[10px] text-primary hover:underline",
|
|
@@ -29637,7 +29789,7 @@ function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, mode
|
|
|
29637
29789
|
localSchema.map((field) => /* @__PURE__ */ jsx(ConfigSchemaField, {
|
|
29638
29790
|
field,
|
|
29639
29791
|
allFields: localSchema,
|
|
29640
|
-
step: buildDisplayStep(addon, effective),
|
|
29792
|
+
step: buildDisplayStep(addon, effective, settingsModelIdFor(addon, effective, inheritedModelId, rootModel)),
|
|
29641
29793
|
disabled: !settingsOverridden,
|
|
29642
29794
|
onChange: (next) => setSettings({ ...next.settings })
|
|
29643
29795
|
}, field.key)),
|