@camstack/ui-library 1.2.63 → 1.2.65

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.
@@ -6,10 +6,14 @@ export type ModelOption = PipelineAddonSchema['models'][number];
6
6
  * Renders the grouped Family→Tier→Resolution→Variant selector
7
7
  * ({@link GroupedModelSelector}) when the step's models declare variant groups
8
8
  * (yolo26 / yolov9 …), else a flat `<select>` (face / plate / classifier
9
- * catalogs and operator-registered custom models). Custom / ungrouped ids stay
10
- * selectable via an "Other models" flat select alongside the grouped picker, and
11
- * a legacy/removed pin is surfaced with an explicit banner rather than a
12
- * silently mis-highlighted chip.
9
+ * catalogs and operator-registered custom models).
10
+ *
11
+ * D214 the catalog and the custom/ungrouped list are MUTUALLY EXCLUSIVE
12
+ * views. A pinned custom model hides the grouped catalog entirely (both used
13
+ * to render, and the grouped picker highlighted a family that was NOT the
14
+ * selection); while a catalog model is pinned the custom list stays behind an
15
+ * explicit affordance. A legacy/removed pin is surfaced with an explicit
16
+ * banner rather than a silently mis-highlighted chip.
13
17
  *
14
18
  * Used by the pipeline stepper (`PipelineStep`), the agent/device step editor
15
19
  * (`AgentStepEditor`), and the benchmark — so the picker looks identical
@@ -76,6 +76,17 @@ export type CellState = {
76
76
  * "→ label" badge on the cell.
77
77
  */
78
78
  readonly jumpTo?: string;
79
+ /**
80
+ * This model is a CLUSTER-wide choice, not a per-(node,device) one — the
81
+ * step writes into a shared vector index, where two encoders make
82
+ * similarity scores meaningless.
83
+ *
84
+ * The cell must SAY so. Rendering a cluster value in a per-node matrix
85
+ * with no marking invites the operator to change it expecting a per-node
86
+ * effect, and to read a neighbouring node's identical value as a
87
+ * coincidence rather than as the same single row.
88
+ */
89
+ readonly clusterWide?: boolean;
79
90
  } | {
80
91
  readonly kind: 'disabled';
81
92
  readonly overridden?: boolean;
@@ -72,4 +72,4 @@ export declare function ConfigSchemaField({ field, allFields, step, disabled, on
72
72
  step: PipelineStepDisplayConfig;
73
73
  disabled: boolean;
74
74
  onChange: (updated: PipelineStepDisplayConfig) => void;
75
- }): import("react").JSX.Element | null;
75
+ }): import("react").JSX.Element;
package/dist/index.cjs CHANGED
@@ -21855,10 +21855,14 @@ function GroupedModelSelector({ catalog, value, onChange, disabled }) {
21855
21855
  * Renders the grouped Family→Tier→Resolution→Variant selector
21856
21856
  * ({@link GroupedModelSelector}) when the step's models declare variant groups
21857
21857
  * (yolo26 / yolov9 …), else a flat `<select>` (face / plate / classifier
21858
- * catalogs and operator-registered custom models). Custom / ungrouped ids stay
21859
- * selectable via an "Other models" flat select alongside the grouped picker, and
21860
- * a legacy/removed pin is surfaced with an explicit banner rather than a
21861
- * silently mis-highlighted chip.
21858
+ * catalogs and operator-registered custom models).
21859
+ *
21860
+ * D214 the catalog and the custom/ungrouped list are MUTUALLY EXCLUSIVE
21861
+ * views. A pinned custom model hides the grouped catalog entirely (both used
21862
+ * to render, and the grouped picker highlighted a family that was NOT the
21863
+ * selection); while a catalog model is pinned the custom list stays behind an
21864
+ * explicit affordance. A legacy/removed pin is surfaced with an explicit
21865
+ * banner rather than a silently mis-highlighted chip.
21862
21866
  *
21863
21867
  * Used by the pipeline stepper (`PipelineStep`), the agent/device step editor
21864
21868
  * (`AgentStepEditor`), and the benchmark — so the picker looks identical
@@ -21869,6 +21873,11 @@ function GroupedModelSelector({ catalog, value, onChange, disabled }) {
21869
21873
  function ModelPicker({ models, value, onChange, disabled }) {
21870
21874
  const hasGroups = (0, react$1.useMemo)(() => (0, _camstack_types.buildModelVariantGroups)(models).length > 0, [models]);
21871
21875
  const ungrouped = (0, react$1.useMemo)(() => models.filter((m) => m.group === void 0), [models]);
21876
+ const customSelected = ungrouped.some((m) => m.id === value);
21877
+ const [view, setView] = (0, react$1.useState)(customSelected ? "custom" : "catalog");
21878
+ (0, react$1.useEffect)(() => {
21879
+ setView(customSelected ? "custom" : "catalog");
21880
+ }, [customSelected, value]);
21872
21881
  const unresolvedBanner = value !== "" && !models.some((m) => m.id === value) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21873
21882
  className: "rounded border border-amber-500/40 bg-amber-500/10 px-2 py-1.5 text-[10px] text-amber-300",
21874
21883
  children: [
@@ -21894,10 +21903,35 @@ function ModelPicker({ models, value, onChange, disabled }) {
21894
21903
  children: m.name
21895
21904
  }, m.id))]
21896
21905
  });
21906
+ const switchButton = (label, target) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
21907
+ type: "button",
21908
+ disabled,
21909
+ onClick: () => setView(target),
21910
+ className: "text-[10px] text-primary hover:underline disabled:opacity-50",
21911
+ children: label
21912
+ });
21897
21913
  if (!hasGroups) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21898
21914
  className: "space-y-2",
21899
21915
  children: [unresolvedBanner, flatSelect(models, "— select a model —")]
21900
21916
  });
21917
+ if (view === "custom" && ungrouped.length > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21918
+ className: "space-y-2",
21919
+ children: [
21920
+ unresolvedBanner,
21921
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21922
+ className: "flex items-center justify-between",
21923
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
21924
+ className: "text-[10px] uppercase tracking-widest text-foreground-subtle",
21925
+ children: "Custom / ungrouped model"
21926
+ }), switchButton("Choose from catalog instead", "catalog")]
21927
+ }),
21928
+ flatSelect(ungrouped, "— custom / ungrouped model —"),
21929
+ customSelected && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
21930
+ className: "text-[10px] text-foreground-subtle",
21931
+ children: "A custom model is pinned — the catalog selection is hidden while it is active."
21932
+ })
21933
+ ]
21934
+ });
21901
21935
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
21902
21936
  className: "space-y-3",
21903
21937
  children: [
@@ -21908,10 +21942,10 @@ function ModelPicker({ models, value, onChange, disabled }) {
21908
21942
  onChange,
21909
21943
  disabled
21910
21944
  }),
21911
- ungrouped.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
21912
- className: "text-[10px] uppercase tracking-widest text-foreground-subtle mb-1",
21913
- children: "Other models"
21914
- }), flatSelect(ungrouped, "— custom / ungrouped model —")] })
21945
+ ungrouped.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
21946
+ className: "flex justify-end",
21947
+ children: switchButton("Use a custom model…", "custom")
21948
+ })
21915
21949
  ]
21916
21950
  });
21917
21951
  }
@@ -22112,6 +22146,26 @@ function resolveSettingValue(key, step, allFields) {
22112
22146
  return sibling && "default" in sibling ? sibling.default : void 0;
22113
22147
  }
22114
22148
  function ConfigSchemaField({ field, allFields, step, disabled, onChange }) {
22149
+ const applicability = "modelApplicability" in field ? field.modelApplicability : void 0;
22150
+ const inapplicableReason = applicability?.inapplicable?.[step.modelId];
22151
+ const control = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigSchemaFieldControl, {
22152
+ field,
22153
+ allFields,
22154
+ step,
22155
+ disabled: disabled || inapplicableReason !== void 0,
22156
+ modelScopedOptions: applicability?.optionsByModel?.[step.modelId],
22157
+ onChange
22158
+ });
22159
+ if (inapplicableReason === void 0) return control;
22160
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22161
+ className: "space-y-1",
22162
+ children: [control, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22163
+ className: "text-[10px] text-amber-400/90",
22164
+ children: ["No effect with the selected model — ", inapplicableReason]
22165
+ })]
22166
+ });
22167
+ }
22168
+ function ConfigSchemaFieldControl({ field, allFields, step, disabled, modelScopedOptions, onChange }) {
22115
22169
  const showWhen = field.type !== "separator" && field.type !== "info" ? field.showWhen : void 0;
22116
22170
  if (showWhen) {
22117
22171
  const { field: condField, equals, notEquals, in: inList, notIn } = showWhen;
@@ -22137,7 +22191,7 @@ function ConfigSchemaField({ field, allFields, step, disabled, onChange }) {
22137
22191
  label: field.label,
22138
22192
  value: current,
22139
22193
  disabled: disabled || field.disabled,
22140
- options: field.options,
22194
+ options: modelScopedOptions ?? field.options,
22141
22195
  onChange: handleChange
22142
22196
  });
22143
22197
  }
@@ -22148,7 +22202,7 @@ function ConfigSchemaField({ field, allFields, step, disabled, onChange }) {
22148
22202
  label: field.label,
22149
22203
  description: field.description,
22150
22204
  value: current,
22151
- options: field.options,
22205
+ options: modelScopedOptions ?? field.options,
22152
22206
  disabled: disabled || field.disabled,
22153
22207
  onChange: handleChange
22154
22208
  });
@@ -44198,6 +44252,11 @@ function renderCellContent(state) {
44198
44252
  className: "font-mono text-foreground truncate",
44199
44253
  children: state.modelId
44200
44254
  }),
44255
+ state.clusterWide === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
44256
+ className: "ml-auto shrink-0 text-[8px] font-semibold uppercase text-primary",
44257
+ title: "One value for the whole cluster — this step writes into a shared vector index",
44258
+ children: "cluster"
44259
+ }),
44201
44260
  state.jumpTo !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
44202
44261
  className: "ml-auto shrink-0 text-[8px] font-semibold text-sky-500",
44203
44262
  title: `runs on ${state.jumpTo}`,
package/dist/index.js CHANGED
@@ -21831,10 +21831,14 @@ function GroupedModelSelector({ catalog, value, onChange, disabled }) {
21831
21831
  * Renders the grouped Family→Tier→Resolution→Variant selector
21832
21832
  * ({@link GroupedModelSelector}) when the step's models declare variant groups
21833
21833
  * (yolo26 / yolov9 …), else a flat `<select>` (face / plate / classifier
21834
- * catalogs and operator-registered custom models). Custom / ungrouped ids stay
21835
- * selectable via an "Other models" flat select alongside the grouped picker, and
21836
- * a legacy/removed pin is surfaced with an explicit banner rather than a
21837
- * silently mis-highlighted chip.
21834
+ * catalogs and operator-registered custom models).
21835
+ *
21836
+ * D214 the catalog and the custom/ungrouped list are MUTUALLY EXCLUSIVE
21837
+ * views. A pinned custom model hides the grouped catalog entirely (both used
21838
+ * to render, and the grouped picker highlighted a family that was NOT the
21839
+ * selection); while a catalog model is pinned the custom list stays behind an
21840
+ * explicit affordance. A legacy/removed pin is surfaced with an explicit
21841
+ * banner rather than a silently mis-highlighted chip.
21838
21842
  *
21839
21843
  * Used by the pipeline stepper (`PipelineStep`), the agent/device step editor
21840
21844
  * (`AgentStepEditor`), and the benchmark — so the picker looks identical
@@ -21845,6 +21849,11 @@ function GroupedModelSelector({ catalog, value, onChange, disabled }) {
21845
21849
  function ModelPicker({ models, value, onChange, disabled }) {
21846
21850
  const hasGroups = useMemo(() => buildModelVariantGroups(models).length > 0, [models]);
21847
21851
  const ungrouped = useMemo(() => models.filter((m) => m.group === void 0), [models]);
21852
+ const customSelected = ungrouped.some((m) => m.id === value);
21853
+ const [view, setView] = useState(customSelected ? "custom" : "catalog");
21854
+ useEffect(() => {
21855
+ setView(customSelected ? "custom" : "catalog");
21856
+ }, [customSelected, value]);
21848
21857
  const unresolvedBanner = value !== "" && !models.some((m) => m.id === value) && /* @__PURE__ */ jsxs("div", {
21849
21858
  className: "rounded border border-amber-500/40 bg-amber-500/10 px-2 py-1.5 text-[10px] text-amber-300",
21850
21859
  children: [
@@ -21870,10 +21879,35 @@ function ModelPicker({ models, value, onChange, disabled }) {
21870
21879
  children: m.name
21871
21880
  }, m.id))]
21872
21881
  });
21882
+ const switchButton = (label, target) => /* @__PURE__ */ jsx("button", {
21883
+ type: "button",
21884
+ disabled,
21885
+ onClick: () => setView(target),
21886
+ className: "text-[10px] text-primary hover:underline disabled:opacity-50",
21887
+ children: label
21888
+ });
21873
21889
  if (!hasGroups) return /* @__PURE__ */ jsxs("div", {
21874
21890
  className: "space-y-2",
21875
21891
  children: [unresolvedBanner, flatSelect(models, "— select a model —")]
21876
21892
  });
21893
+ if (view === "custom" && ungrouped.length > 0) return /* @__PURE__ */ jsxs("div", {
21894
+ className: "space-y-2",
21895
+ children: [
21896
+ unresolvedBanner,
21897
+ /* @__PURE__ */ jsxs("div", {
21898
+ className: "flex items-center justify-between",
21899
+ children: [/* @__PURE__ */ jsx("div", {
21900
+ className: "text-[10px] uppercase tracking-widest text-foreground-subtle",
21901
+ children: "Custom / ungrouped model"
21902
+ }), switchButton("Choose from catalog instead", "catalog")]
21903
+ }),
21904
+ flatSelect(ungrouped, "— custom / ungrouped model —"),
21905
+ customSelected && /* @__PURE__ */ jsx("div", {
21906
+ className: "text-[10px] text-foreground-subtle",
21907
+ children: "A custom model is pinned — the catalog selection is hidden while it is active."
21908
+ })
21909
+ ]
21910
+ });
21877
21911
  return /* @__PURE__ */ jsxs("div", {
21878
21912
  className: "space-y-3",
21879
21913
  children: [
@@ -21884,10 +21918,10 @@ function ModelPicker({ models, value, onChange, disabled }) {
21884
21918
  onChange,
21885
21919
  disabled
21886
21920
  }),
21887
- ungrouped.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
21888
- className: "text-[10px] uppercase tracking-widest text-foreground-subtle mb-1",
21889
- children: "Other models"
21890
- }), flatSelect(ungrouped, "— custom / ungrouped model —")] })
21921
+ ungrouped.length > 0 && /* @__PURE__ */ jsx("div", {
21922
+ className: "flex justify-end",
21923
+ children: switchButton("Use a custom model…", "custom")
21924
+ })
21891
21925
  ]
21892
21926
  });
21893
21927
  }
@@ -22088,6 +22122,26 @@ function resolveSettingValue(key, step, allFields) {
22088
22122
  return sibling && "default" in sibling ? sibling.default : void 0;
22089
22123
  }
22090
22124
  function ConfigSchemaField({ field, allFields, step, disabled, onChange }) {
22125
+ const applicability = "modelApplicability" in field ? field.modelApplicability : void 0;
22126
+ const inapplicableReason = applicability?.inapplicable?.[step.modelId];
22127
+ const control = /* @__PURE__ */ jsx(ConfigSchemaFieldControl, {
22128
+ field,
22129
+ allFields,
22130
+ step,
22131
+ disabled: disabled || inapplicableReason !== void 0,
22132
+ modelScopedOptions: applicability?.optionsByModel?.[step.modelId],
22133
+ onChange
22134
+ });
22135
+ if (inapplicableReason === void 0) return control;
22136
+ return /* @__PURE__ */ jsxs("div", {
22137
+ className: "space-y-1",
22138
+ children: [control, /* @__PURE__ */ jsxs("div", {
22139
+ className: "text-[10px] text-amber-400/90",
22140
+ children: ["No effect with the selected model — ", inapplicableReason]
22141
+ })]
22142
+ });
22143
+ }
22144
+ function ConfigSchemaFieldControl({ field, allFields, step, disabled, modelScopedOptions, onChange }) {
22091
22145
  const showWhen = field.type !== "separator" && field.type !== "info" ? field.showWhen : void 0;
22092
22146
  if (showWhen) {
22093
22147
  const { field: condField, equals, notEquals, in: inList, notIn } = showWhen;
@@ -22113,7 +22167,7 @@ function ConfigSchemaField({ field, allFields, step, disabled, onChange }) {
22113
22167
  label: field.label,
22114
22168
  value: current,
22115
22169
  disabled: disabled || field.disabled,
22116
- options: field.options,
22170
+ options: modelScopedOptions ?? field.options,
22117
22171
  onChange: handleChange
22118
22172
  });
22119
22173
  }
@@ -22124,7 +22178,7 @@ function ConfigSchemaField({ field, allFields, step, disabled, onChange }) {
22124
22178
  label: field.label,
22125
22179
  description: field.description,
22126
22180
  value: current,
22127
- options: field.options,
22181
+ options: modelScopedOptions ?? field.options,
22128
22182
  disabled: disabled || field.disabled,
22129
22183
  onChange: handleChange
22130
22184
  });
@@ -44174,6 +44228,11 @@ function renderCellContent(state) {
44174
44228
  className: "font-mono text-foreground truncate",
44175
44229
  children: state.modelId
44176
44230
  }),
44231
+ state.clusterWide === true && /* @__PURE__ */ jsx("span", {
44232
+ className: "ml-auto shrink-0 text-[8px] font-semibold uppercase text-primary",
44233
+ title: "One value for the whole cluster — this step writes into a shared vector index",
44234
+ children: "cluster"
44235
+ }),
44177
44236
  state.jumpTo !== void 0 && /* @__PURE__ */ jsxs("span", {
44178
44237
  className: "ml-auto shrink-0 text-[8px] font-semibold text-sky-500",
44179
44238
  title: `runs on ${state.jumpTo}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/ui-library",
3
- "version": "1.2.63",
3
+ "version": "1.2.65",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",