@camstack/ui-library 1.1.42 → 1.1.44
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/camera-stream-player.d.ts +2 -2
- package/dist/composites/device-controls/control-registry.d.ts +14 -5
- package/dist/composites/device-step-matrix.d.ts +33 -0
- package/dist/composites/index.d.ts +3 -0
- package/dist/composites/pipeline-matrix-shared.d.ts +69 -0
- package/dist/composites/pipeline-tree-matrix.d.ts +2 -14
- package/dist/generated/system-hooks.d.ts +0 -2
- package/dist/hooks/use-device-webrtc.d.ts +2 -1
- package/dist/index.cjs +764 -270
- package/dist/index.js +764 -271
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -8249,6 +8249,30 @@ var ArrowUp = createLucideIcon("arrow-up", [["path", {
|
|
|
8249
8249
|
* This source code is licensed under the ISC license.
|
|
8250
8250
|
* See the LICENSE file in the root directory of this source tree.
|
|
8251
8251
|
*/
|
|
8252
|
+
var Baby = createLucideIcon("baby", [
|
|
8253
|
+
["path", {
|
|
8254
|
+
d: "M10 16c.5.3 1.2.5 2 .5s1.5-.2 2-.5",
|
|
8255
|
+
key: "1u7htd"
|
|
8256
|
+
}],
|
|
8257
|
+
["path", {
|
|
8258
|
+
d: "M15 12h.01",
|
|
8259
|
+
key: "1k8ypt"
|
|
8260
|
+
}],
|
|
8261
|
+
["path", {
|
|
8262
|
+
d: "M19.38 6.813A9 9 0 0 1 20.8 10.2a2 2 0 0 1 0 3.6 9 9 0 0 1-17.6 0 2 2 0 0 1 0-3.6A9 9 0 0 1 12 3c2 0 3.5 1.1 3.5 2.5s-.9 2.5-2 2.5c-.8 0-1.5-.4-1.5-1",
|
|
8263
|
+
key: "11xh7x"
|
|
8264
|
+
}],
|
|
8265
|
+
["path", {
|
|
8266
|
+
d: "M9 12h.01",
|
|
8267
|
+
key: "157uk2"
|
|
8268
|
+
}]
|
|
8269
|
+
]);
|
|
8270
|
+
/**
|
|
8271
|
+
* @license lucide-react v0.576.0 - ISC
|
|
8272
|
+
*
|
|
8273
|
+
* This source code is licensed under the ISC license.
|
|
8274
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
8275
|
+
*/
|
|
8252
8276
|
var Ban = createLucideIcon("ban", [["circle", {
|
|
8253
8277
|
cx: "12",
|
|
8254
8278
|
cy: "12",
|
|
@@ -9203,6 +9227,74 @@ var Cloud = createLucideIcon("cloud", [["path", {
|
|
|
9203
9227
|
* This source code is licensed under the ISC license.
|
|
9204
9228
|
* See the LICENSE file in the root directory of this source tree.
|
|
9205
9229
|
*/
|
|
9230
|
+
var Cog = createLucideIcon("cog", [
|
|
9231
|
+
["path", {
|
|
9232
|
+
d: "M11 10.27 7 3.34",
|
|
9233
|
+
key: "16pf9h"
|
|
9234
|
+
}],
|
|
9235
|
+
["path", {
|
|
9236
|
+
d: "m11 13.73-4 6.93",
|
|
9237
|
+
key: "794ttg"
|
|
9238
|
+
}],
|
|
9239
|
+
["path", {
|
|
9240
|
+
d: "M12 22v-2",
|
|
9241
|
+
key: "1osdcq"
|
|
9242
|
+
}],
|
|
9243
|
+
["path", {
|
|
9244
|
+
d: "M12 2v2",
|
|
9245
|
+
key: "tus03m"
|
|
9246
|
+
}],
|
|
9247
|
+
["path", {
|
|
9248
|
+
d: "M14 12h8",
|
|
9249
|
+
key: "4f43i9"
|
|
9250
|
+
}],
|
|
9251
|
+
["path", {
|
|
9252
|
+
d: "m17 20.66-1-1.73",
|
|
9253
|
+
key: "eq3orb"
|
|
9254
|
+
}],
|
|
9255
|
+
["path", {
|
|
9256
|
+
d: "m17 3.34-1 1.73",
|
|
9257
|
+
key: "2wel8s"
|
|
9258
|
+
}],
|
|
9259
|
+
["path", {
|
|
9260
|
+
d: "M2 12h2",
|
|
9261
|
+
key: "1t8f8n"
|
|
9262
|
+
}],
|
|
9263
|
+
["path", {
|
|
9264
|
+
d: "m20.66 17-1.73-1",
|
|
9265
|
+
key: "sg0v6f"
|
|
9266
|
+
}],
|
|
9267
|
+
["path", {
|
|
9268
|
+
d: "m20.66 7-1.73 1",
|
|
9269
|
+
key: "1ow05n"
|
|
9270
|
+
}],
|
|
9271
|
+
["path", {
|
|
9272
|
+
d: "m3.34 17 1.73-1",
|
|
9273
|
+
key: "nuk764"
|
|
9274
|
+
}],
|
|
9275
|
+
["path", {
|
|
9276
|
+
d: "m3.34 7 1.73 1",
|
|
9277
|
+
key: "1ulond"
|
|
9278
|
+
}],
|
|
9279
|
+
["circle", {
|
|
9280
|
+
cx: "12",
|
|
9281
|
+
cy: "12",
|
|
9282
|
+
r: "2",
|
|
9283
|
+
key: "1c9p78"
|
|
9284
|
+
}],
|
|
9285
|
+
["circle", {
|
|
9286
|
+
cx: "12",
|
|
9287
|
+
cy: "12",
|
|
9288
|
+
r: "8",
|
|
9289
|
+
key: "46899m"
|
|
9290
|
+
}]
|
|
9291
|
+
]);
|
|
9292
|
+
/**
|
|
9293
|
+
* @license lucide-react v0.576.0 - ISC
|
|
9294
|
+
*
|
|
9295
|
+
* This source code is licensed under the ISC license.
|
|
9296
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
9297
|
+
*/
|
|
9206
9298
|
var CopyPlus = createLucideIcon("copy-plus", [
|
|
9207
9299
|
["line", {
|
|
9208
9300
|
x1: "15",
|
|
@@ -9855,6 +9947,26 @@ var GripHorizontal = createLucideIcon("grip-horizontal", [
|
|
|
9855
9947
|
* This source code is licensed under the ISC license.
|
|
9856
9948
|
* See the LICENSE file in the root directory of this source tree.
|
|
9857
9949
|
*/
|
|
9950
|
+
var Hammer = createLucideIcon("hammer", [
|
|
9951
|
+
["path", {
|
|
9952
|
+
d: "m15 12-9.373 9.373a1 1 0 0 1-3.001-3L12 9",
|
|
9953
|
+
key: "1hayfq"
|
|
9954
|
+
}],
|
|
9955
|
+
["path", {
|
|
9956
|
+
d: "m18 15 4-4",
|
|
9957
|
+
key: "16gjal"
|
|
9958
|
+
}],
|
|
9959
|
+
["path", {
|
|
9960
|
+
d: "m21.5 11.5-1.914-1.914A2 2 0 0 1 19 8.172v-.344a2 2 0 0 0-.586-1.414l-1.657-1.657A6 6 0 0 0 12.516 3H9l1.243 1.243A6 6 0 0 1 12 8.485V10l2 2h1.172a2 2 0 0 1 1.414.586L18.5 14.5",
|
|
9961
|
+
key: "15ts47"
|
|
9962
|
+
}]
|
|
9963
|
+
]);
|
|
9964
|
+
/**
|
|
9965
|
+
* @license lucide-react v0.576.0 - ISC
|
|
9966
|
+
*
|
|
9967
|
+
* This source code is licensed under the ISC license.
|
|
9968
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
9969
|
+
*/
|
|
9858
9970
|
var HardDrive = createLucideIcon("hard-drive", [
|
|
9859
9971
|
["path", {
|
|
9860
9972
|
d: "M10 16h.01",
|
|
@@ -10031,6 +10143,38 @@ var Info = createLucideIcon("info", [
|
|
|
10031
10143
|
* This source code is licensed under the ISC license.
|
|
10032
10144
|
* See the LICENSE file in the root directory of this source tree.
|
|
10033
10145
|
*/
|
|
10146
|
+
var Laugh = createLucideIcon("laugh", [
|
|
10147
|
+
["circle", {
|
|
10148
|
+
cx: "12",
|
|
10149
|
+
cy: "12",
|
|
10150
|
+
r: "10",
|
|
10151
|
+
key: "1mglay"
|
|
10152
|
+
}],
|
|
10153
|
+
["path", {
|
|
10154
|
+
d: "M18 13a6 6 0 0 1-6 5 6 6 0 0 1-6-5h12Z",
|
|
10155
|
+
key: "b2q4dd"
|
|
10156
|
+
}],
|
|
10157
|
+
["line", {
|
|
10158
|
+
x1: "9",
|
|
10159
|
+
x2: "9.01",
|
|
10160
|
+
y1: "9",
|
|
10161
|
+
y2: "9",
|
|
10162
|
+
key: "yxxnd0"
|
|
10163
|
+
}],
|
|
10164
|
+
["line", {
|
|
10165
|
+
x1: "15",
|
|
10166
|
+
x2: "15.01",
|
|
10167
|
+
y1: "9",
|
|
10168
|
+
y2: "9",
|
|
10169
|
+
key: "1p4y9e"
|
|
10170
|
+
}]
|
|
10171
|
+
]);
|
|
10172
|
+
/**
|
|
10173
|
+
* @license lucide-react v0.576.0 - ISC
|
|
10174
|
+
*
|
|
10175
|
+
* This source code is licensed under the ISC license.
|
|
10176
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
10177
|
+
*/
|
|
10034
10178
|
var LayoutGrid = createLucideIcon("layout-grid", [
|
|
10035
10179
|
["rect", {
|
|
10036
10180
|
width: "7",
|
|
@@ -10722,6 +10866,16 @@ var Pencil = createLucideIcon("pencil", [["path", {
|
|
|
10722
10866
|
* This source code is licensed under the ISC license.
|
|
10723
10867
|
* See the LICENSE file in the root directory of this source tree.
|
|
10724
10868
|
*/
|
|
10869
|
+
var Phone = createLucideIcon("phone", [["path", {
|
|
10870
|
+
d: "M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384",
|
|
10871
|
+
key: "9njp5v"
|
|
10872
|
+
}]]);
|
|
10873
|
+
/**
|
|
10874
|
+
* @license lucide-react v0.576.0 - ISC
|
|
10875
|
+
*
|
|
10876
|
+
* This source code is licensed under the ISC license.
|
|
10877
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
10878
|
+
*/
|
|
10725
10879
|
var Pipette = createLucideIcon("pipette", [
|
|
10726
10880
|
["path", {
|
|
10727
10881
|
d: "m12 9-8.414 8.414A2 2 0 0 0 3 18.828v1.344a2 2 0 0 1-.586 1.414A2 2 0 0 1 3.828 21h1.344a2 2 0 0 0 1.414-.586L15 12",
|
|
@@ -12074,6 +12228,32 @@ var User = createLucideIcon("user", [["path", {
|
|
|
12074
12228
|
* This source code is licensed under the ISC license.
|
|
12075
12229
|
* See the LICENSE file in the root directory of this source tree.
|
|
12076
12230
|
*/
|
|
12231
|
+
var Users = createLucideIcon("users", [
|
|
12232
|
+
["path", {
|
|
12233
|
+
d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",
|
|
12234
|
+
key: "1yyitq"
|
|
12235
|
+
}],
|
|
12236
|
+
["path", {
|
|
12237
|
+
d: "M16 3.128a4 4 0 0 1 0 7.744",
|
|
12238
|
+
key: "16gr8j"
|
|
12239
|
+
}],
|
|
12240
|
+
["path", {
|
|
12241
|
+
d: "M22 21v-2a4 4 0 0 0-3-3.87",
|
|
12242
|
+
key: "kshegd"
|
|
12243
|
+
}],
|
|
12244
|
+
["circle", {
|
|
12245
|
+
cx: "9",
|
|
12246
|
+
cy: "7",
|
|
12247
|
+
r: "4",
|
|
12248
|
+
key: "nufk8"
|
|
12249
|
+
}]
|
|
12250
|
+
]);
|
|
12251
|
+
/**
|
|
12252
|
+
* @license lucide-react v0.576.0 - ISC
|
|
12253
|
+
*
|
|
12254
|
+
* This source code is licensed under the ISC license.
|
|
12255
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
12256
|
+
*/
|
|
12077
12257
|
var UtensilsCrossed = createLucideIcon("utensils-crossed", [
|
|
12078
12258
|
["path", {
|
|
12079
12259
|
d: "m16 2-2.3 2.3a3 3 0 0 0 0 4.2l1.8 1.8a3 3 0 0 0 4.2 0L22 8",
|
|
@@ -14246,7 +14426,7 @@ function buildDisplayStep(addon, cfg) {
|
|
|
14246
14426
|
function computeEffective(mode, agentDefault, patch) {
|
|
14247
14427
|
if (mode !== "device" || !patch) return agentDefault;
|
|
14248
14428
|
return {
|
|
14249
|
-
enabled:
|
|
14429
|
+
enabled: agentDefault.enabled,
|
|
14250
14430
|
modelId: patch.modelId ?? agentDefault.modelId,
|
|
14251
14431
|
settings: patch.settings ?? agentDefault.settings
|
|
14252
14432
|
};
|
|
@@ -14260,97 +14440,62 @@ function AgentModeEditor({ addon, agentDefault, modelsForFormat, onChangeAgentCo
|
|
|
14260
14440
|
};
|
|
14261
14441
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14262
14442
|
className: "space-y-4 text-xs",
|
|
14263
|
-
children: [
|
|
14264
|
-
|
|
14265
|
-
|
|
14266
|
-
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
|
|
14270
|
-
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
|
|
14274
|
-
|
|
14275
|
-
}),
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
|
|
14280
|
-
|
|
14281
|
-
|
|
14282
|
-
|
|
14283
|
-
})] }),
|
|
14284
|
-
(addon.configSchema?.length ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14285
|
-
className: "pt-2 border-t border-border/50 space-y-3",
|
|
14286
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
14287
|
-
className: "text-[10px] uppercase tracking-widest text-foreground-subtle",
|
|
14288
|
-
children: "Settings"
|
|
14289
|
-
}), addon.configSchema?.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigSchemaField, {
|
|
14290
|
-
field,
|
|
14291
|
-
allFields: addon.configSchema ?? [],
|
|
14292
|
-
step: buildDisplayStep(addon, agentDefault),
|
|
14293
|
-
disabled: false,
|
|
14294
|
-
onChange: (next) => update({ settings: { ...next.settings } })
|
|
14295
|
-
}, field.key))]
|
|
14296
|
-
})
|
|
14297
|
-
]
|
|
14443
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
14444
|
+
className: "text-[10px] uppercase tracking-widest text-foreground-subtle mb-1",
|
|
14445
|
+
children: "Model"
|
|
14446
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelPicker, {
|
|
14447
|
+
models: modelsForFormat,
|
|
14448
|
+
value: agentDefault.modelId ?? "",
|
|
14449
|
+
onChange: (modelId) => update({ modelId })
|
|
14450
|
+
})] }), (addon.configSchema?.length ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14451
|
+
className: "pt-2 border-t border-border/50 space-y-3",
|
|
14452
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
14453
|
+
className: "text-[10px] uppercase tracking-widest text-foreground-subtle",
|
|
14454
|
+
children: "Settings"
|
|
14455
|
+
}), addon.configSchema?.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigSchemaField, {
|
|
14456
|
+
field,
|
|
14457
|
+
allFields: addon.configSchema ?? [],
|
|
14458
|
+
step: buildDisplayStep(addon, agentDefault),
|
|
14459
|
+
disabled: false,
|
|
14460
|
+
onChange: (next) => update({ settings: { ...next.settings } })
|
|
14461
|
+
}, field.key))]
|
|
14462
|
+
})]
|
|
14298
14463
|
});
|
|
14299
14464
|
}
|
|
14300
14465
|
function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, modelsForFormat, onChangePatch }) {
|
|
14301
14466
|
const effective = computeEffective("device", agentDefault, currentPatch);
|
|
14302
14467
|
const commitPatch = (next) => {
|
|
14303
|
-
const hasAnyField = next.
|
|
14468
|
+
const hasAnyField = next.modelId !== void 0 || next.settings !== void 0;
|
|
14304
14469
|
onChangePatch?.(hasAnyField ? next : null);
|
|
14305
14470
|
};
|
|
14306
|
-
const setEnabled = (state) => {
|
|
14307
|
-
const { enabled: _prev, ...rest } = currentPatch ?? {};
|
|
14308
|
-
commitPatch(state === "inherit" ? rest : {
|
|
14309
|
-
...rest,
|
|
14310
|
-
enabled: state === "on"
|
|
14311
|
-
});
|
|
14312
|
-
};
|
|
14313
14471
|
const setModel = (modelId) => {
|
|
14314
|
-
const { modelId: _prev, ...rest } = currentPatch ?? {};
|
|
14472
|
+
const { enabled: _enabled, modelId: _prev, ...rest } = currentPatch ?? {};
|
|
14315
14473
|
commitPatch(modelId === void 0 ? rest : {
|
|
14316
14474
|
...rest,
|
|
14317
14475
|
modelId
|
|
14318
14476
|
});
|
|
14319
14477
|
};
|
|
14320
14478
|
const setSettings = (settings) => {
|
|
14321
|
-
const { settings: _prev, ...rest } = currentPatch ?? {};
|
|
14479
|
+
const { enabled: _enabled, settings: _prev, ...rest } = currentPatch ?? {};
|
|
14322
14480
|
commitPatch(settings === void 0 ? rest : {
|
|
14323
14481
|
...rest,
|
|
14324
14482
|
settings
|
|
14325
14483
|
});
|
|
14326
14484
|
};
|
|
14327
|
-
const enabledTri = currentPatch?.enabled === void 0 ? "inherit" : currentPatch.enabled ? "on" : "off";
|
|
14328
14485
|
const modelOverridden = currentPatch?.modelId !== void 0;
|
|
14329
14486
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14330
14487
|
className: "space-y-4 text-xs",
|
|
14331
14488
|
children: [
|
|
14332
14489
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14333
|
-
className: "
|
|
14334
|
-
children: [
|
|
14335
|
-
"
|
|
14336
|
-
"
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
|
|
14341
|
-
|
|
14342
|
-
children: s
|
|
14343
|
-
}, s)), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
14344
|
-
className: "ml-2 text-[10px] text-foreground-subtle",
|
|
14345
|
-
children: [
|
|
14346
|
-
"agent default:",
|
|
14347
|
-
" ",
|
|
14348
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14349
|
-
className: "font-semibold",
|
|
14350
|
-
children: agentDefault.enabled ? "on" : "off"
|
|
14351
|
-
})
|
|
14352
|
-
]
|
|
14353
|
-
})]
|
|
14490
|
+
className: "rounded bg-muted/40 px-3 py-2 text-[11px] text-foreground-subtle",
|
|
14491
|
+
children: [
|
|
14492
|
+
"Provisioning for ",
|
|
14493
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14494
|
+
className: "font-semibold text-foreground",
|
|
14495
|
+
children: agentNodeId
|
|
14496
|
+
}),
|
|
14497
|
+
" — model + settings only. Whether this camera runs the step is set by its per-camera gate above."
|
|
14498
|
+
]
|
|
14354
14499
|
}),
|
|
14355
14500
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
14356
14501
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -14421,15 +14566,13 @@ function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, mode
|
|
|
14421
14566
|
className: "rounded bg-muted/40 px-3 py-2 text-[11px] text-foreground-subtle",
|
|
14422
14567
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
14423
14568
|
className: "font-semibold text-foreground mb-0.5",
|
|
14424
|
-
children: "Effective
|
|
14569
|
+
children: "Effective provisioning preview"
|
|
14425
14570
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14426
14571
|
className: "font-mono truncate",
|
|
14427
14572
|
children: [
|
|
14428
14573
|
addon.id,
|
|
14429
14574
|
" · model: ",
|
|
14430
|
-
effective.modelId
|
|
14431
|
-
" · ",
|
|
14432
|
-
effective.enabled ? "on" : "off"
|
|
14575
|
+
effective.modelId ?? "Auto"
|
|
14433
14576
|
]
|
|
14434
14577
|
})]
|
|
14435
14578
|
})
|
|
@@ -14454,19 +14597,6 @@ function AgentStepEditor(props) {
|
|
|
14454
14597
|
onChangePatch
|
|
14455
14598
|
});
|
|
14456
14599
|
}
|
|
14457
|
-
//#endregion
|
|
14458
|
-
//#region src/composites/pipeline-tree-matrix.tsx
|
|
14459
|
-
/**
|
|
14460
|
-
* Fixed column geometry (rem). The matrix uses a CSS grid with STATIC track
|
|
14461
|
-
* widths so a cell's content (a status chip, a long model id) can never resize
|
|
14462
|
-
* a column or shift its neighbours — cell content is truncated WITHIN the fixed
|
|
14463
|
-
* track instead. This kills the "table dances when the last column's status
|
|
14464
|
-
* changes" jitter. See `min-w-0` on every grid child, which lets the child
|
|
14465
|
-
* shrink to the track (a grid item's automatic minimum is its content size, so
|
|
14466
|
-
* without this a non-wrapping model id would blow the track wider than its max).
|
|
14467
|
-
*/
|
|
14468
|
-
var STEP_COL_REM = 18;
|
|
14469
|
-
var AGENT_COL_REM = 13;
|
|
14470
14600
|
function flattenTree(nodes) {
|
|
14471
14601
|
const out = [];
|
|
14472
14602
|
const walk = (list, depth) => {
|
|
@@ -14481,6 +14611,20 @@ function flattenTree(nodes) {
|
|
|
14481
14611
|
walk(nodes, 0);
|
|
14482
14612
|
return out;
|
|
14483
14613
|
}
|
|
14614
|
+
/**
|
|
14615
|
+
* Layout decision for the responsive A↔B switch. Pure so it can be unit-tested
|
|
14616
|
+
* in isolation from the DOM.
|
|
14617
|
+
*
|
|
14618
|
+
* Returns `true` (single-node fallback) when there is MORE than one node but
|
|
14619
|
+
* the container is too narrow to show the step column plus at least TWO node
|
|
14620
|
+
* columns side by side. An unmeasured container (`width <= 0`) defaults to the
|
|
14621
|
+
* wide matrix so there is no first-paint flash into the fallback.
|
|
14622
|
+
*/
|
|
14623
|
+
function shouldUseSingleNode(width, nodeCount, stepColPx, agentColPx = 208) {
|
|
14624
|
+
if (nodeCount <= 1) return false;
|
|
14625
|
+
if (width <= 0) return false;
|
|
14626
|
+
return width < stepColPx + agentColPx * 2;
|
|
14627
|
+
}
|
|
14484
14628
|
var SLOT_LABEL = {
|
|
14485
14629
|
detector: {
|
|
14486
14630
|
label: "detector",
|
|
@@ -14531,25 +14675,26 @@ function ClassChips({ inputs, outputs }) {
|
|
|
14531
14675
|
]
|
|
14532
14676
|
});
|
|
14533
14677
|
}
|
|
14534
|
-
|
|
14535
|
-
|
|
14536
|
-
|
|
14537
|
-
|
|
14538
|
-
"
|
|
14539
|
-
|
|
14540
|
-
|
|
14541
|
-
|
|
14542
|
-
|
|
14543
|
-
|
|
14544
|
-
|
|
14545
|
-
|
|
14546
|
-
|
|
14678
|
+
/** Step label (slot heading + addon name + class chips), shared by both views. */
|
|
14679
|
+
function StepLabel({ node }) {
|
|
14680
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14681
|
+
className: "flex flex-col gap-1 min-w-0 flex-1",
|
|
14682
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SlotHeading, { slot: node.slot }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14683
|
+
className: "flex items-center gap-2 flex-wrap",
|
|
14684
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14685
|
+
className: "font-semibold truncate",
|
|
14686
|
+
children: node.addonName
|
|
14687
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClassChips, {
|
|
14688
|
+
inputs: node.inputClasses,
|
|
14689
|
+
outputs: node.outputClasses
|
|
14690
|
+
})]
|
|
14691
|
+
})]
|
|
14547
14692
|
});
|
|
14548
14693
|
}
|
|
14549
14694
|
/**
|
|
14550
|
-
* Cell content.
|
|
14551
|
-
*
|
|
14552
|
-
*
|
|
14695
|
+
* Cell content. Width-stable: the status dot is `shrink-0` (reserved space) and
|
|
14696
|
+
* the model id truncates inside a `min-w-0` flex row, so a longer model id never
|
|
14697
|
+
* widens the cell — it ellipsises instead.
|
|
14553
14698
|
*/
|
|
14554
14699
|
function renderCellContent(state) {
|
|
14555
14700
|
if (state.kind === "enabled") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -14582,31 +14727,41 @@ function renderCellContent(state) {
|
|
|
14582
14727
|
})
|
|
14583
14728
|
]
|
|
14584
14729
|
});
|
|
14730
|
+
if (state.kind === "skip") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
14731
|
+
className: "flex min-w-0 items-center gap-1 text-amber-500",
|
|
14732
|
+
title: "Gated on for this camera, but no compatible model on this node — click to provision",
|
|
14733
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "h-1.5 w-1.5 rounded-full bg-amber-500 shrink-0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14734
|
+
className: "truncate font-medium",
|
|
14735
|
+
children: "⚠ skip"
|
|
14736
|
+
})]
|
|
14737
|
+
});
|
|
14585
14738
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
14586
14739
|
className: "flex min-w-0 items-center gap-1 text-foreground-subtle/70",
|
|
14587
14740
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "h-1.5 w-1.5 rounded-full bg-muted shrink-0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14588
14741
|
className: "truncate",
|
|
14589
|
-
children: "
|
|
14742
|
+
children: "—"
|
|
14590
14743
|
})]
|
|
14591
14744
|
});
|
|
14592
14745
|
}
|
|
14593
14746
|
function cellButtonClass(state, isSelected, extra) {
|
|
14594
|
-
return cn("text-left text-xs hover:bg-muted/40", state.kind === "enabled" && "bg-emerald-500/5", state.kind === "na" && "bg-muted/30", isSelected && "ring-2 ring-primary ring-inset", extra);
|
|
14747
|
+
return cn("text-left text-xs hover:bg-muted/40", state.kind === "enabled" && "bg-emerald-500/5", state.kind === "skip" && "bg-amber-500/5", state.kind === "na" && "bg-muted/30", isSelected && "ring-2 ring-primary ring-inset", extra);
|
|
14595
14748
|
}
|
|
14596
|
-
|
|
14597
|
-
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
|
|
14605
|
-
|
|
14606
|
-
|
|
14607
|
-
|
|
14608
|
-
|
|
14609
|
-
|
|
14749
|
+
//#endregion
|
|
14750
|
+
//#region src/composites/pipeline-tree-matrix.tsx
|
|
14751
|
+
var STEP_COL_REM$1 = 18;
|
|
14752
|
+
function QuickToggle({ enabled, onChange, disabled }) {
|
|
14753
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
14754
|
+
type: "button",
|
|
14755
|
+
role: "switch",
|
|
14756
|
+
"aria-checked": enabled,
|
|
14757
|
+
disabled,
|
|
14758
|
+
onClick: (e) => {
|
|
14759
|
+
e.stopPropagation();
|
|
14760
|
+
onChange(!enabled);
|
|
14761
|
+
},
|
|
14762
|
+
className: cn("relative inline-flex h-3.5 w-7 shrink-0 items-center rounded-full transition-colors", enabled ? "bg-emerald-500" : "bg-foreground-subtle/30", disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"),
|
|
14763
|
+
title: enabled ? "Click to disable" : "Click to enable",
|
|
14764
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cn("inline-block h-3 w-3 rounded-full bg-white shadow transition-transform", enabled ? "translate-x-3.5" : "translate-x-0.5") })
|
|
14610
14765
|
});
|
|
14611
14766
|
}
|
|
14612
14767
|
function GridRow({ node, depth, agents, getCellState, onCellClick, selectedCell, toggleProps }) {
|
|
@@ -14634,11 +14789,11 @@ function GridRow({ node, depth, agents, getCellState, onCellClick, selectedCell,
|
|
|
14634
14789
|
/**
|
|
14635
14790
|
* Wide layout: horizontally self-scrolling matrix. The scroll lives INSIDE
|
|
14636
14791
|
* this `overflow-auto` box (sticky first column + sticky header row) so the
|
|
14637
|
-
* page body never scrolls sideways. Column tracks are fixed px widths
|
|
14638
|
-
*
|
|
14792
|
+
* page body never scrolls sideways. Column tracks are fixed px widths so the
|
|
14793
|
+
* grid is static regardless of content.
|
|
14639
14794
|
*/
|
|
14640
|
-
function MatrixGrid({ rows, agents, getCellState, onCellClick, selectedCell, onToggleEnabled }) {
|
|
14641
|
-
const gridTemplate = `${STEP_COL_REM}rem repeat(${agents.length},
|
|
14795
|
+
function MatrixGrid$1({ rows, agents, getCellState, onCellClick, selectedCell, onToggleEnabled }) {
|
|
14796
|
+
const gridTemplate = `${STEP_COL_REM$1}rem repeat(${agents.length}, 13rem)`;
|
|
14642
14797
|
const showToggle = onToggleEnabled !== void 0 && agents.length === 1;
|
|
14643
14798
|
const onlyAgent = agents[0]?.agentNodeId;
|
|
14644
14799
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -14683,9 +14838,7 @@ function MatrixGrid({ rows, agents, getCellState, onCellClick, selectedCell, onT
|
|
|
14683
14838
|
}
|
|
14684
14839
|
/**
|
|
14685
14840
|
* Narrow layout: one card per agent column, each listing every step stacked
|
|
14686
|
-
* vertically.
|
|
14687
|
-
* (click-to-edit, status, toggle) reflow into full-width rows so nothing scrolls
|
|
14688
|
-
* horizontally. Feature-parity with the matrix — same `onCellClick`, same
|
|
14841
|
+
* vertically. Feature-parity with the matrix — same `onCellClick`, same
|
|
14689
14842
|
* `renderCellContent`, same single-agent `onToggleEnabled`.
|
|
14690
14843
|
*/
|
|
14691
14844
|
function StackedCards({ rows, agents, getCellState, onCellClick, selectedCell, onToggleEnabled }) {
|
|
@@ -14754,7 +14907,7 @@ function PipelineTreeMatrix({ tree, agents, getCellState, onCellClick, selectedC
|
|
|
14754
14907
|
})
|
|
14755
14908
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
14756
14909
|
className: "hidden @2xl/matrix:block",
|
|
14757
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MatrixGrid, {
|
|
14910
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MatrixGrid$1, {
|
|
14758
14911
|
rows,
|
|
14759
14912
|
agents,
|
|
14760
14913
|
getCellState,
|
|
@@ -14766,6 +14919,226 @@ function PipelineTreeMatrix({ tree, agents, getCellState, onCellClick, selectedC
|
|
|
14766
14919
|
});
|
|
14767
14920
|
}
|
|
14768
14921
|
//#endregion
|
|
14922
|
+
//#region src/composites/device-step-matrix.tsx
|
|
14923
|
+
/**
|
|
14924
|
+
* DeviceStepMatrix — the per-camera "Detection pipeline → Steps" surface (#33)
|
|
14925
|
+
* rendered as ONE hierarchical steps×nodes matrix.
|
|
14926
|
+
*
|
|
14927
|
+
* Column 1 (STICKY): the indented step tree. Each step carries its slot +
|
|
14928
|
+
* name + class-flow chips PLUS a per-camera, node-independent GATE control
|
|
14929
|
+
* with THREE states — On / Off / Default (inherit). `package-detection` is
|
|
14930
|
+
* zone-driven, so it shows a "Zones" badge instead of a gate.
|
|
14931
|
+
* Columns 2..N (one per online node): the provisioned model chip (click →
|
|
14932
|
+
* the model/config picker), a clickable "⚠ skip" when the step is gated ON
|
|
14933
|
+
* but the node has no compatible model, or "—" when N/A.
|
|
14934
|
+
*
|
|
14935
|
+
* Responsive, container-measured (never a global media query, so it also works
|
|
14936
|
+
* embedded):
|
|
14937
|
+
* A) wide — sticky step column + node columns in an `overflow-x:auto` box, so
|
|
14938
|
+
* narrow viewports scroll the NODES sideways while col-1 stays pinned and
|
|
14939
|
+
* the page body never scrolls horizontally.
|
|
14940
|
+
* B) narrow — below the width needed for ≥2 node columns, collapse to a
|
|
14941
|
+
* single-node view: a node selector picks ONE node and the matrix renders
|
|
14942
|
+
* `step-tree × that-one-node`.
|
|
14943
|
+
*/
|
|
14944
|
+
/** Wider than the cluster matrix's 18rem: col-1 also hosts the gate control. */
|
|
14945
|
+
var STEP_COL_REM = 21;
|
|
14946
|
+
var STEP_COL_PX = STEP_COL_REM * 16;
|
|
14947
|
+
function activeChoice(gate) {
|
|
14948
|
+
if (!gate.explicit) return "default";
|
|
14949
|
+
return gate.effectiveOn ? "on" : "off";
|
|
14950
|
+
}
|
|
14951
|
+
/**
|
|
14952
|
+
* Tri-state segmented control. `Default` inherits the catalog value (shown in
|
|
14953
|
+
* the caption); `On`/`Off` pin an explicit per-camera override. Touch-friendly
|
|
14954
|
+
* 28px segments.
|
|
14955
|
+
*/
|
|
14956
|
+
function GateControl({ gate, busy, onSet }) {
|
|
14957
|
+
const active = activeChoice(gate);
|
|
14958
|
+
const seg = (choice, label, value, activeClass) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
14959
|
+
type: "button",
|
|
14960
|
+
"aria-pressed": active === choice,
|
|
14961
|
+
disabled: busy,
|
|
14962
|
+
onClick: () => onSet(value),
|
|
14963
|
+
className: cn("flex-1 px-2 py-1 text-[11px] font-medium leading-none transition-colors", "min-h-[26px]", active === choice ? activeClass : "text-foreground-subtle hover:bg-muted/60", busy && "opacity-50 cursor-not-allowed"),
|
|
14964
|
+
children: label
|
|
14965
|
+
});
|
|
14966
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14967
|
+
className: "flex flex-col gap-1",
|
|
14968
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14969
|
+
className: "inline-flex w-fit max-w-full self-start overflow-hidden rounded-md border border-border",
|
|
14970
|
+
children: [
|
|
14971
|
+
seg("default", "Auto", null, "bg-primary/15 text-primary"),
|
|
14972
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "w-px self-stretch bg-border" }),
|
|
14973
|
+
seg("on", "On", true, "bg-emerald-500 text-white"),
|
|
14974
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "w-px self-stretch bg-border" }),
|
|
14975
|
+
seg("off", "Off", false, "bg-foreground-subtle/70 text-white")
|
|
14976
|
+
]
|
|
14977
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14978
|
+
className: "text-[10px] leading-none text-foreground-subtle",
|
|
14979
|
+
children: active === "default" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
14980
|
+
"= default (",
|
|
14981
|
+
gate.defaultOn ? "on" : "off",
|
|
14982
|
+
")"
|
|
14983
|
+
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: ["overridden · default ", gate.defaultOn ? "on" : "off"] })
|
|
14984
|
+
})]
|
|
14985
|
+
});
|
|
14986
|
+
}
|
|
14987
|
+
function ZoneBadge() {
|
|
14988
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14989
|
+
className: "flex flex-col gap-1",
|
|
14990
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14991
|
+
className: "inline-flex w-fit items-center gap-1 rounded-md border border-violet-500/40 bg-violet-500/10 px-2 py-1 text-[11px] font-medium text-violet-400",
|
|
14992
|
+
children: "Zones"
|
|
14993
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14994
|
+
className: "text-[10px] leading-none text-foreground-subtle",
|
|
14995
|
+
children: "driven by package zones"
|
|
14996
|
+
})]
|
|
14997
|
+
});
|
|
14998
|
+
}
|
|
14999
|
+
function StepGateCell({ node, depth, gate, busy, onSet }) {
|
|
15000
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
15001
|
+
className: "sticky left-0 z-10 flex flex-col gap-2 border-b border-border bg-surface px-3 py-2.5 text-xs min-w-0",
|
|
15002
|
+
style: { paddingLeft: `${12 + depth * 14}px` },
|
|
15003
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepLabel, { node }), gate.kind === "zone-driven" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoneBadge, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GateControl, {
|
|
15004
|
+
gate,
|
|
15005
|
+
busy,
|
|
15006
|
+
onSet
|
|
15007
|
+
})]
|
|
15008
|
+
});
|
|
15009
|
+
}
|
|
15010
|
+
/**
|
|
15011
|
+
* Agents-scoped node selector for the B fallback. Styled like the shared
|
|
15012
|
+
* `NodePicker` but driven by THIS matrix's `agents` set — the cluster-wide
|
|
15013
|
+
* NodePicker can list nodes (e.g. the hub) that own no pipeline column here,
|
|
15014
|
+
* which would let the operator select a node the matrix can't show.
|
|
15015
|
+
*/
|
|
15016
|
+
function NodeSelectorBar({ agents, selected, onSelect }) {
|
|
15017
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
15018
|
+
className: "flex flex-wrap items-center gap-1.5",
|
|
15019
|
+
role: "tablist",
|
|
15020
|
+
"aria-label": "Node",
|
|
15021
|
+
children: [
|
|
15022
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Server, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
|
|
15023
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
15024
|
+
className: "text-[10px] uppercase tracking-wider text-foreground-subtle",
|
|
15025
|
+
children: "Node"
|
|
15026
|
+
}),
|
|
15027
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
15028
|
+
className: "flex overflow-hidden rounded-md border border-border text-[11px] font-medium",
|
|
15029
|
+
children: agents.map((a, idx) => {
|
|
15030
|
+
const isSelected = a.agentNodeId === selected;
|
|
15031
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
15032
|
+
type: "button",
|
|
15033
|
+
role: "tab",
|
|
15034
|
+
"aria-selected": isSelected,
|
|
15035
|
+
onClick: () => onSelect(a.agentNodeId),
|
|
15036
|
+
title: `${a.agentNodeId} · ${a.engineLabel}`,
|
|
15037
|
+
className: cn("px-2.5 py-1 transition-colors", isSelected ? "bg-primary/15 text-primary" : "bg-transparent text-foreground-subtle hover:bg-surface-hover hover:text-foreground", idx > 0 && "border-l border-border"),
|
|
15038
|
+
children: a.agentNodeId
|
|
15039
|
+
}, a.agentNodeId);
|
|
15040
|
+
})
|
|
15041
|
+
})
|
|
15042
|
+
]
|
|
15043
|
+
});
|
|
15044
|
+
}
|
|
15045
|
+
function MatrixGrid({ tree, agents, gateFor, getCellState, onCellClick, selectedCell, onSetGate, gateBusy, single }) {
|
|
15046
|
+
const rows = (0, react$1.useMemo)(() => flattenTree(tree), [tree]);
|
|
15047
|
+
const gridTemplate = single ? `minmax(0, 1.35fr) repeat(${agents.length}, minmax(0, 1fr))` : `${STEP_COL_REM}rem repeat(${agents.length}, 13rem)`;
|
|
15048
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
15049
|
+
className: cn("rounded border border-border", single ? "overflow-hidden" : "overflow-auto"),
|
|
15050
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
15051
|
+
className: cn("grid", single ? "w-full" : "w-max"),
|
|
15052
|
+
style: { gridTemplateColumns: gridTemplate },
|
|
15053
|
+
children: [
|
|
15054
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
15055
|
+
className: "sticky left-0 top-0 z-20 border-b border-border bg-muted/60 px-3 py-2 text-[10px] uppercase tracking-widest text-foreground-subtle",
|
|
15056
|
+
children: "Step · gate"
|
|
15057
|
+
}),
|
|
15058
|
+
agents.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
15059
|
+
className: "sticky top-0 z-10 min-w-0 border-b border-l border-border bg-muted/60 px-3 py-2",
|
|
15060
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
15061
|
+
className: "truncate text-xs font-semibold text-foreground",
|
|
15062
|
+
children: a.agentNodeId
|
|
15063
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
15064
|
+
className: "truncate text-[10px] text-foreground-subtle",
|
|
15065
|
+
children: a.engineLabel
|
|
15066
|
+
})]
|
|
15067
|
+
}, a.agentNodeId)),
|
|
15068
|
+
rows.map(({ node, depth }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MatrixRow, {
|
|
15069
|
+
node,
|
|
15070
|
+
depth,
|
|
15071
|
+
agents,
|
|
15072
|
+
gate: gateFor(node.addonId),
|
|
15073
|
+
getCellState,
|
|
15074
|
+
onCellClick,
|
|
15075
|
+
selectedCell,
|
|
15076
|
+
onSetGate,
|
|
15077
|
+
gateBusy
|
|
15078
|
+
}, node.addonId))
|
|
15079
|
+
]
|
|
15080
|
+
})
|
|
15081
|
+
});
|
|
15082
|
+
}
|
|
15083
|
+
function MatrixRow({ node, depth, agents, gate, getCellState, onCellClick, selectedCell, onSetGate, gateBusy }) {
|
|
15084
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepGateCell, {
|
|
15085
|
+
node,
|
|
15086
|
+
depth,
|
|
15087
|
+
gate,
|
|
15088
|
+
busy: gateBusy,
|
|
15089
|
+
onSet: (next) => onSetGate(node.addonId, next)
|
|
15090
|
+
}), agents.map((a) => {
|
|
15091
|
+
const state = getCellState(node.addonId, a.agentNodeId);
|
|
15092
|
+
const isSelected = selectedCell?.addonId === node.addonId && selectedCell.agentNodeId === a.agentNodeId;
|
|
15093
|
+
const actionable = state.kind === "enabled" || state.kind === "skip";
|
|
15094
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
15095
|
+
type: "button",
|
|
15096
|
+
disabled: !actionable,
|
|
15097
|
+
onClick: () => onCellClick(node.addonId, a.agentNodeId),
|
|
15098
|
+
className: cellButtonClass(state, isSelected, cn("min-w-0 overflow-hidden border-b border-l border-border px-3 py-2", !actionable && "cursor-default hover:bg-transparent")),
|
|
15099
|
+
children: renderCellContent(state)
|
|
15100
|
+
}, a.agentNodeId);
|
|
15101
|
+
})] });
|
|
15102
|
+
}
|
|
15103
|
+
function DeviceStepMatrix({ tree, agents, gateFor, getCellState, onCellClick, selectedCell, onSetGate, gateBusy, fallbackNodeId, onFallbackNodeChange }) {
|
|
15104
|
+
const containerRef = (0, react$1.useRef)(null);
|
|
15105
|
+
const [width, setWidth] = (0, react$1.useState)(0);
|
|
15106
|
+
(0, react$1.useLayoutEffect)(() => {
|
|
15107
|
+
const el = containerRef.current;
|
|
15108
|
+
if (!el) return;
|
|
15109
|
+
const measure = () => setWidth(el.getBoundingClientRect().width);
|
|
15110
|
+
measure();
|
|
15111
|
+
const RO = typeof ResizeObserver !== "undefined" ? new ResizeObserver(measure) : null;
|
|
15112
|
+
RO?.observe(el);
|
|
15113
|
+
return () => RO?.disconnect();
|
|
15114
|
+
}, []);
|
|
15115
|
+
const single = shouldUseSingleNode(width, agents.length, STEP_COL_PX);
|
|
15116
|
+
const selectedNode = (0, react$1.useMemo)(() => {
|
|
15117
|
+
if (agents.length === 0) return null;
|
|
15118
|
+
return agents.find((a) => a.agentNodeId === fallbackNodeId) ?? agents[0];
|
|
15119
|
+
}, [agents, fallbackNodeId]);
|
|
15120
|
+
const shownAgents = single && selectedNode ? [selectedNode] : agents;
|
|
15121
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
15122
|
+
ref: containerRef,
|
|
15123
|
+
className: "w-full space-y-2",
|
|
15124
|
+
children: [single && selectedNode && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeSelectorBar, {
|
|
15125
|
+
agents,
|
|
15126
|
+
selected: selectedNode.agentNodeId,
|
|
15127
|
+
onSelect: onFallbackNodeChange
|
|
15128
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MatrixGrid, {
|
|
15129
|
+
tree,
|
|
15130
|
+
agents: shownAgents,
|
|
15131
|
+
gateFor,
|
|
15132
|
+
getCellState,
|
|
15133
|
+
onCellClick,
|
|
15134
|
+
selectedCell,
|
|
15135
|
+
onSetGate,
|
|
15136
|
+
gateBusy,
|
|
15137
|
+
single: single && selectedNode !== null
|
|
15138
|
+
})]
|
|
15139
|
+
});
|
|
15140
|
+
}
|
|
15141
|
+
//#endregion
|
|
14769
15142
|
//#region src/trpc-react.ts
|
|
14770
15143
|
/**
|
|
14771
15144
|
* Shared typed React Query proxy for the backend AppRouter.
|
|
@@ -17507,8 +17880,6 @@ var useAddonsListFrameworkPackages = trpc.addons.listFrameworkPackages.useQuery;
|
|
|
17507
17880
|
var useAddonsListCapabilityProviders = trpc.addons.listCapabilityProviders.useQuery;
|
|
17508
17881
|
/** Generated alias around `trpc.addons.setCapabilityProviderEnabled.useMutation`. */
|
|
17509
17882
|
var useAddonsSetCapabilityProviderEnabled = trpc.addons.setCapabilityProviderEnabled.useMutation;
|
|
17510
|
-
/** Generated alias around `trpc.addons.updateFrameworkPackage.useMutation`. */
|
|
17511
|
-
var useAddonsUpdateFrameworkPackage = trpc.addons.updateFrameworkPackage.useMutation;
|
|
17512
17883
|
/** Generated alias around `trpc.addons.getVersions.useQuery`. */
|
|
17513
17884
|
var useAddonsGetVersions = trpc.addons.getVersions.useQuery;
|
|
17514
17885
|
/** Generated alias around `trpc.addons.restartAddon.useMutation`. */
|
|
@@ -26355,131 +26726,117 @@ function ControlHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
26355
26726
|
}
|
|
26356
26727
|
//#endregion
|
|
26357
26728
|
//#region src/composites/device-controls/control-registry.tsx
|
|
26358
|
-
var COVER_CONTROL = {
|
|
26359
|
-
InlineControl: CoverInlineControl,
|
|
26360
|
-
HeroCard: CoverHeroCard
|
|
26361
|
-
};
|
|
26362
|
-
var DUMMY_CONTROL = {
|
|
26363
|
-
InlineControl: DummyInline,
|
|
26364
|
-
HeroCard: DummyHeroCard
|
|
26365
|
-
};
|
|
26366
|
-
var SENSOR_CONTROL = {
|
|
26367
|
-
InlineControl: SensorInlineControl,
|
|
26368
|
-
HeroCard: SensorHeroCard
|
|
26369
|
-
};
|
|
26370
|
-
var UPDATE_CONTROL = {
|
|
26371
|
-
InlineControl: UpdateInlineControl,
|
|
26372
|
-
HeroCard: UpdateHeroCard
|
|
26373
|
-
};
|
|
26374
|
-
var SWITCH_CONTROL = {
|
|
26375
|
-
InlineControl: SwitchInlineControl,
|
|
26376
|
-
HeroCard: SwitchHeroCard
|
|
26377
|
-
};
|
|
26378
|
-
var LOCK_CONTROL = {
|
|
26379
|
-
InlineControl: LockInlineControl,
|
|
26380
|
-
HeroCard: LockHeroCard
|
|
26381
|
-
};
|
|
26382
|
-
var LIGHT_CONTROL = {
|
|
26383
|
-
InlineControl: LightInlineControl,
|
|
26384
|
-
HeroCard: LightHeroCard
|
|
26385
|
-
};
|
|
26386
|
-
var FAN_CONTROL = {
|
|
26387
|
-
InlineControl: FanInlineControl,
|
|
26388
|
-
HeroCard: FanHeroCard
|
|
26389
|
-
};
|
|
26390
|
-
var THERMOSTAT_CONTROL = {
|
|
26391
|
-
InlineControl: ThermostatInlineControl,
|
|
26392
|
-
HeroCard: ThermostatHeroCard
|
|
26393
|
-
};
|
|
26394
|
-
var CLIMATE_CONTROL = {
|
|
26395
|
-
InlineControl: AcInlineControl,
|
|
26396
|
-
HeroCard: AcHeroCard
|
|
26397
|
-
};
|
|
26398
|
-
var MEDIA_PLAYER_CONTROL = {
|
|
26399
|
-
InlineControl: MediaPlayerInlineControl,
|
|
26400
|
-
HeroCard: MediaPlayerHeroCard
|
|
26401
|
-
};
|
|
26402
|
-
var ALARM_PANEL_CONTROL = {
|
|
26403
|
-
InlineControl: AlarmInlineControl,
|
|
26404
|
-
HeroCard: AlarmHeroCard
|
|
26405
|
-
};
|
|
26406
|
-
var CONTROL_CONTROL = {
|
|
26407
|
-
InlineControl: ControlInlineControl,
|
|
26408
|
-
HeroCard: ControlHeroCard
|
|
26409
|
-
};
|
|
26410
|
-
var VACUUM_CONTROL = {
|
|
26411
|
-
InlineControl: VacuumInlineControl,
|
|
26412
|
-
HeroCard: VacuumHeroCard
|
|
26413
|
-
};
|
|
26414
|
-
var LAWN_MOWER_CONTROL = {
|
|
26415
|
-
InlineControl: LawnMowerInlineControl,
|
|
26416
|
-
HeroCard: LawnMowerHeroCard
|
|
26417
|
-
};
|
|
26418
|
-
var VALVE_CONTROL = {
|
|
26419
|
-
InlineControl: ValveInlineControl,
|
|
26420
|
-
HeroCard: ValveHeroCard
|
|
26421
|
-
};
|
|
26422
|
-
var HUMIDIFIER_CONTROL = {
|
|
26423
|
-
InlineControl: HumidifierInlineControl,
|
|
26424
|
-
HeroCard: HumidifierHeroCard
|
|
26425
|
-
};
|
|
26426
|
-
var WATER_HEATER_CONTROL = {
|
|
26427
|
-
InlineControl: WaterHeaterInlineControl,
|
|
26428
|
-
HeroCard: WaterHeaterHeroCard
|
|
26429
|
-
};
|
|
26430
|
-
var WEATHER_CONTROL = {
|
|
26431
|
-
InlineControl: WeatherInlineControl,
|
|
26432
|
-
HeroCard: WeatherHeroCard
|
|
26433
|
-
};
|
|
26434
|
-
var IMAGE_CONTROL = {
|
|
26435
|
-
InlineControl: ImageInlineControl,
|
|
26436
|
-
HeroCard: ImageHeroCard
|
|
26437
|
-
};
|
|
26438
|
-
var PET_FEEDER_CONTROL = {
|
|
26439
|
-
InlineControl: PetFeederInlineControl,
|
|
26440
|
-
HeroCard: PetFeederHeroCard
|
|
26441
|
-
};
|
|
26442
26729
|
/**
|
|
26443
|
-
*
|
|
26444
|
-
*
|
|
26445
|
-
|
|
26446
|
-
|
|
26447
|
-
|
|
26448
|
-
|
|
26449
|
-
|
|
26450
|
-
|
|
26451
|
-
|
|
26452
|
-
|
|
26453
|
-
|
|
26454
|
-
|
|
26455
|
-
|
|
26456
|
-
|
|
26457
|
-
|
|
26458
|
-
|
|
26459
|
-
|
|
26460
|
-
|
|
26730
|
+
* Presentation binding: the shared `DeviceControlKind` (decided in
|
|
26731
|
+
* `@camstack/types` `resolveDeviceControlKind`) → this platform's web
|
|
26732
|
+
* components. Exhaustive over `DeviceControlKind` — omitting a kind fails the
|
|
26733
|
+
* build here. The type→kind DECISION is single-sourced in `@camstack/types`;
|
|
26734
|
+
* this map only binds a kind to its web widgets.
|
|
26735
|
+
*/
|
|
26736
|
+
var CONTROL_KIND_COMPONENTS = {
|
|
26737
|
+
cover: {
|
|
26738
|
+
InlineControl: CoverInlineControl,
|
|
26739
|
+
HeroCard: CoverHeroCard
|
|
26740
|
+
},
|
|
26741
|
+
valve: {
|
|
26742
|
+
InlineControl: ValveInlineControl,
|
|
26743
|
+
HeroCard: ValveHeroCard
|
|
26744
|
+
},
|
|
26745
|
+
humidifier: {
|
|
26746
|
+
InlineControl: HumidifierInlineControl,
|
|
26747
|
+
HeroCard: HumidifierHeroCard
|
|
26748
|
+
},
|
|
26749
|
+
"water-heater": {
|
|
26750
|
+
InlineControl: WaterHeaterInlineControl,
|
|
26751
|
+
HeroCard: WaterHeaterHeroCard
|
|
26752
|
+
},
|
|
26753
|
+
switch: {
|
|
26754
|
+
InlineControl: SwitchInlineControl,
|
|
26755
|
+
HeroCard: SwitchHeroCard
|
|
26756
|
+
},
|
|
26757
|
+
light: {
|
|
26758
|
+
InlineControl: LightInlineControl,
|
|
26759
|
+
HeroCard: LightHeroCard
|
|
26760
|
+
},
|
|
26761
|
+
fan: {
|
|
26762
|
+
InlineControl: FanInlineControl,
|
|
26763
|
+
HeroCard: FanHeroCard
|
|
26764
|
+
},
|
|
26765
|
+
sensor: {
|
|
26766
|
+
InlineControl: SensorInlineControl,
|
|
26767
|
+
HeroCard: SensorHeroCard
|
|
26768
|
+
},
|
|
26769
|
+
thermostat: {
|
|
26770
|
+
InlineControl: ThermostatInlineControl,
|
|
26771
|
+
HeroCard: ThermostatHeroCard
|
|
26772
|
+
},
|
|
26773
|
+
climate: {
|
|
26774
|
+
InlineControl: AcInlineControl,
|
|
26775
|
+
HeroCard: AcHeroCard
|
|
26776
|
+
},
|
|
26777
|
+
button: {
|
|
26461
26778
|
InlineControl: ButtonControl,
|
|
26462
26779
|
HeroCard: ButtonHeroCard
|
|
26463
26780
|
},
|
|
26464
|
-
|
|
26465
|
-
|
|
26466
|
-
|
|
26467
|
-
|
|
26468
|
-
|
|
26469
|
-
|
|
26470
|
-
|
|
26471
|
-
|
|
26472
|
-
|
|
26473
|
-
|
|
26474
|
-
|
|
26475
|
-
|
|
26476
|
-
|
|
26477
|
-
|
|
26478
|
-
|
|
26479
|
-
|
|
26480
|
-
|
|
26781
|
+
update: {
|
|
26782
|
+
InlineControl: UpdateInlineControl,
|
|
26783
|
+
HeroCard: UpdateHeroCard
|
|
26784
|
+
},
|
|
26785
|
+
lock: {
|
|
26786
|
+
InlineControl: LockInlineControl,
|
|
26787
|
+
HeroCard: LockHeroCard
|
|
26788
|
+
},
|
|
26789
|
+
"media-player": {
|
|
26790
|
+
InlineControl: MediaPlayerInlineControl,
|
|
26791
|
+
HeroCard: MediaPlayerHeroCard
|
|
26792
|
+
},
|
|
26793
|
+
alarm: {
|
|
26794
|
+
InlineControl: AlarmInlineControl,
|
|
26795
|
+
HeroCard: AlarmHeroCard
|
|
26796
|
+
},
|
|
26797
|
+
control: {
|
|
26798
|
+
InlineControl: ControlInlineControl,
|
|
26799
|
+
HeroCard: ControlHeroCard
|
|
26800
|
+
},
|
|
26801
|
+
weather: {
|
|
26802
|
+
InlineControl: WeatherInlineControl,
|
|
26803
|
+
HeroCard: WeatherHeroCard
|
|
26804
|
+
},
|
|
26805
|
+
vacuum: {
|
|
26806
|
+
InlineControl: VacuumInlineControl,
|
|
26807
|
+
HeroCard: VacuumHeroCard
|
|
26808
|
+
},
|
|
26809
|
+
"lawn-mower": {
|
|
26810
|
+
InlineControl: LawnMowerInlineControl,
|
|
26811
|
+
HeroCard: LawnMowerHeroCard
|
|
26812
|
+
},
|
|
26813
|
+
image: {
|
|
26814
|
+
InlineControl: ImageInlineControl,
|
|
26815
|
+
HeroCard: ImageHeroCard
|
|
26816
|
+
},
|
|
26817
|
+
"pet-feeder": {
|
|
26818
|
+
InlineControl: PetFeederInlineControl,
|
|
26819
|
+
HeroCard: PetFeederHeroCard
|
|
26820
|
+
},
|
|
26821
|
+
dummy: {
|
|
26822
|
+
InlineControl: DummyInline,
|
|
26823
|
+
HeroCard: DummyHeroCard
|
|
26824
|
+
}
|
|
26481
26825
|
};
|
|
26482
26826
|
/**
|
|
26827
|
+
* Exhaustive record over DeviceType, DERIVED from the shared decision
|
|
26828
|
+
* (`DEVICE_TYPE_CONTROL_KIND`) composed with the web `CONTROL_KIND_COMPONENTS`
|
|
26829
|
+
* binding — so the type→control mapping can never diverge from the single
|
|
26830
|
+
* source in `@camstack/types`. Preserved as a public export for consumers/tests
|
|
26831
|
+
* that index it by `DeviceType`.
|
|
26832
|
+
*
|
|
26833
|
+
* The `Object.fromEntries` result is a `{ [k: string]: DeviceTypeControl }`;
|
|
26834
|
+
* `Object.values(DeviceType)` (a string enum) yields exactly the enum's members,
|
|
26835
|
+
* so the record is fully populated — the assertion narrows that documented
|
|
26836
|
+
* enum-record projection boundary.
|
|
26837
|
+
*/
|
|
26838
|
+
var DEVICE_TYPE_CONTROL = Object.fromEntries(Object.values(_camstack_types.DeviceType).map((type) => [type, CONTROL_KIND_COMPONENTS[_camstack_types.DEVICE_TYPE_CONTROL_KIND[type]]]));
|
|
26839
|
+
/**
|
|
26483
26840
|
* Per-type FILL flag.
|
|
26484
26841
|
*
|
|
26485
26842
|
* true — range / choice / button controls (cover ▲■▼ triad, thermostat
|
|
@@ -26533,12 +26890,14 @@ var CONTROL_FILLS = {
|
|
|
26533
26890
|
};
|
|
26534
26891
|
var DEVICE_TYPE_VALUES$1 = new Set(Object.values(_camstack_types.DeviceType));
|
|
26535
26892
|
/**
|
|
26536
|
-
* Runtime-string-safe lookup: a device row's `type` crosses the wire
|
|
26537
|
-
*
|
|
26538
|
-
*
|
|
26893
|
+
* Runtime-string-safe lookup: a device row's `type` crosses the wire as a plain
|
|
26894
|
+
* string. The type→control-kind DECISION is single-sourced in `@camstack/types`
|
|
26895
|
+
* (`resolveDeviceControlKind`, null for an unrecognised type); this maps the
|
|
26896
|
+
* resolved kind onto the web components.
|
|
26539
26897
|
*/
|
|
26540
26898
|
function resolveDeviceControl(type) {
|
|
26541
|
-
|
|
26899
|
+
const kind = (0, _camstack_types.resolveDeviceControlKind)(type);
|
|
26900
|
+
return kind ? CONTROL_KIND_COMPONENTS[kind] : null;
|
|
26542
26901
|
}
|
|
26543
26902
|
/**
|
|
26544
26903
|
* Runtime-string-safe alignment lookup for the fixed-width control column.
|
|
@@ -31310,12 +31669,30 @@ var EVENT_KIND_ICONS = {
|
|
|
31310
31669
|
doorbell: BellRing,
|
|
31311
31670
|
button: CircleDot,
|
|
31312
31671
|
"audio-speech": MessageSquare,
|
|
31672
|
+
"audio-scream": Megaphone,
|
|
31673
|
+
"audio-crying": Baby,
|
|
31674
|
+
"audio-laughter": Laugh,
|
|
31313
31675
|
"audio-music": Music,
|
|
31314
31676
|
"audio-dog": Dog,
|
|
31677
|
+
"audio-cat": Cat,
|
|
31678
|
+
"audio-bird": Bird,
|
|
31679
|
+
"audio-animal": PawPrint,
|
|
31680
|
+
"audio-alarm": BellRing,
|
|
31315
31681
|
"audio-doorbell": BellRing,
|
|
31316
|
-
"audio-siren": Siren,
|
|
31317
31682
|
"audio-glass_breaking": TriangleAlert,
|
|
31318
|
-
"audio-
|
|
31683
|
+
"audio-gunshot": Crosshair,
|
|
31684
|
+
"audio-vehicle": Car,
|
|
31685
|
+
"audio-siren": Siren,
|
|
31686
|
+
"audio-fire": Flame,
|
|
31687
|
+
"audio-water": Droplets,
|
|
31688
|
+
"audio-wind": Wind,
|
|
31689
|
+
"audio-door": DoorOpen,
|
|
31690
|
+
"audio-footsteps": Footprints,
|
|
31691
|
+
"audio-crowd": Users,
|
|
31692
|
+
"audio-telephone": Phone,
|
|
31693
|
+
"audio-engine": Cog,
|
|
31694
|
+
"audio-tools": Hammer,
|
|
31695
|
+
"audio-silence": VolumeX
|
|
31319
31696
|
};
|
|
31320
31697
|
/** Resolve an `iconId` to a lucide component (falls back to `Tag`). */
|
|
31321
31698
|
function resolveEventKindIcon(iconId) {
|
|
@@ -39783,6 +40160,7 @@ function computeClientHints(container) {
|
|
|
39783
40160
|
var RECONNECT_BASE_DELAY_MS = 1500;
|
|
39784
40161
|
var RECONNECT_MAX_DELAY_MS = 15e3;
|
|
39785
40162
|
var MAX_RECONNECT_ATTEMPTS = 40;
|
|
40163
|
+
var FIRST_FRAME_TIMEOUT_MS = 8e3;
|
|
39786
40164
|
function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, muted: initialMuted = true, showControls = true, showStats = false, onPlaybackStats, onClientNetworkSample, onConnectTiming, className = "", onStateChange, onError, overlay, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, reoffer, posterUrl, hintsOverride, reconnectSignal, onControlChannel }) {
|
|
39787
40165
|
const videoRef = (0, react$1.useRef)(null);
|
|
39788
40166
|
const containerRef = (0, react$1.useRef)(null);
|
|
@@ -40159,14 +40537,26 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40159
40537
|
});
|
|
40160
40538
|
onConnectTiming?.({ ...timing });
|
|
40161
40539
|
const v = videoRef.current;
|
|
40162
|
-
if (v && "requestVideoFrameCallback" in v)
|
|
40163
|
-
|
|
40164
|
-
|
|
40165
|
-
|
|
40166
|
-
|
|
40540
|
+
if (v && "requestVideoFrameCallback" in v) {
|
|
40541
|
+
v.requestVideoFrameCallback(() => {
|
|
40542
|
+
timing.firstFrameMs = ms();
|
|
40543
|
+
console.info("[WebRTC timing] first decoded frame", {
|
|
40544
|
+
streamKey,
|
|
40545
|
+
firstFrameMs: timing.firstFrameMs
|
|
40546
|
+
});
|
|
40547
|
+
onConnectTiming?.({ ...timing });
|
|
40167
40548
|
});
|
|
40168
|
-
|
|
40169
|
-
|
|
40549
|
+
setTimeout(() => {
|
|
40550
|
+
if (!mountedRef.current) return;
|
|
40551
|
+
if (timing.firstFrameMs !== void 0) return;
|
|
40552
|
+
console.warn("[WebRTC timing] NO decoded frame (stalled)", {
|
|
40553
|
+
streamKey,
|
|
40554
|
+
sinceIceConnectedMs: ms() - (timing.iceConnectedMs ?? 0),
|
|
40555
|
+
iceConnectedMs: timing.iceConnectedMs,
|
|
40556
|
+
firstTrackMs: timing.firstTrackMs
|
|
40557
|
+
});
|
|
40558
|
+
}, FIRST_FRAME_TIMEOUT_MS);
|
|
40559
|
+
}
|
|
40170
40560
|
}
|
|
40171
40561
|
reconnectAttemptsRef.current = 0;
|
|
40172
40562
|
if (seamless) swapInOnce();
|
|
@@ -40199,8 +40589,12 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40199
40589
|
await pc.setLocalDescription(offer);
|
|
40200
40590
|
timing.localSetMs = ms();
|
|
40201
40591
|
if (!mountedRef.current || abort.signal.aborted) return;
|
|
40592
|
+
const clientOfferHints = {
|
|
40593
|
+
...computeClientHints(containerRef.current),
|
|
40594
|
+
...hintsOverride
|
|
40595
|
+
};
|
|
40202
40596
|
const signalingStart = ms();
|
|
40203
|
-
const { sessionId: sid, sdpAnswer } = await handleOffer(pc.localDescription?.sdp ?? offer.sdp ?? "");
|
|
40597
|
+
const { sessionId: sid, sdpAnswer } = await handleOffer(pc.localDescription?.sdp ?? offer.sdp ?? "", void 0, clientOfferHints);
|
|
40204
40598
|
timing.signalingRttMs = ms() - signalingStart;
|
|
40205
40599
|
sessionId = sid;
|
|
40206
40600
|
sessionIdsRef.current.add(sid);
|
|
@@ -40261,7 +40655,8 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40261
40655
|
partialReconnectReset,
|
|
40262
40656
|
updateState,
|
|
40263
40657
|
onError,
|
|
40264
|
-
startSessionStatePoll
|
|
40658
|
+
startSessionStatePoll,
|
|
40659
|
+
hintsOverride
|
|
40265
40660
|
]);
|
|
40266
40661
|
renegotiateRef.current = (0, react$1.useCallback)(async (sessionId, target) => {
|
|
40267
40662
|
const reofferFn = reoffer;
|
|
@@ -40274,8 +40669,30 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40274
40669
|
const pc = new RTCPeerConnection({ iceServers: [...iceServers ?? [], { urls: "stun:stun.l.google.com:19302" }] });
|
|
40275
40670
|
const newStream = new MediaStream();
|
|
40276
40671
|
let swappedIn = false;
|
|
40277
|
-
|
|
40278
|
-
|
|
40672
|
+
let warmupStarted = false;
|
|
40673
|
+
let warmupVideo = null;
|
|
40674
|
+
let warmupRvfc;
|
|
40675
|
+
let warmupSafety;
|
|
40676
|
+
const teardownWarmup = () => {
|
|
40677
|
+
if (warmupSafety !== void 0) {
|
|
40678
|
+
clearTimeout(warmupSafety);
|
|
40679
|
+
warmupSafety = void 0;
|
|
40680
|
+
}
|
|
40681
|
+
const v = warmupVideo;
|
|
40682
|
+
warmupVideo = null;
|
|
40683
|
+
if (!v) return;
|
|
40684
|
+
if (warmupRvfc !== void 0 && typeof v.cancelVideoFrameCallback === "function") v.cancelVideoFrameCallback(warmupRvfc);
|
|
40685
|
+
v.removeEventListener("loadeddata", commitSwap);
|
|
40686
|
+
try {
|
|
40687
|
+
v.srcObject = null;
|
|
40688
|
+
} catch {}
|
|
40689
|
+
v.remove();
|
|
40690
|
+
};
|
|
40691
|
+
function commitSwap() {
|
|
40692
|
+
if (swappedIn || !mountedRef.current) {
|
|
40693
|
+
teardownWarmup();
|
|
40694
|
+
return;
|
|
40695
|
+
}
|
|
40279
40696
|
swappedIn = true;
|
|
40280
40697
|
pcRef.current = pc;
|
|
40281
40698
|
if (videoRef.current) {
|
|
@@ -40283,11 +40700,32 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40283
40700
|
videoRef.current.play().catch(() => {});
|
|
40284
40701
|
}
|
|
40285
40702
|
if (oldPc && oldPc !== pc) oldPc.close();
|
|
40703
|
+
teardownWarmup();
|
|
40704
|
+
}
|
|
40705
|
+
const armSwap = () => {
|
|
40706
|
+
if (warmupStarted || swappedIn || !mountedRef.current) return;
|
|
40707
|
+
warmupStarted = true;
|
|
40708
|
+
if (typeof document === "undefined") {
|
|
40709
|
+
commitSwap();
|
|
40710
|
+
return;
|
|
40711
|
+
}
|
|
40712
|
+
const v = document.createElement("video");
|
|
40713
|
+
v.muted = true;
|
|
40714
|
+
v.playsInline = true;
|
|
40715
|
+
v.autoplay = true;
|
|
40716
|
+
v.style.cssText = "position:absolute;left:-9999px;top:0;width:1px;height:1px;opacity:0;pointer-events:none;";
|
|
40717
|
+
v.srcObject = newStream;
|
|
40718
|
+
warmupVideo = v;
|
|
40719
|
+
(containerRef.current ?? document.body).appendChild(v);
|
|
40720
|
+
v.play().catch(() => {});
|
|
40721
|
+
if (typeof v.requestVideoFrameCallback === "function") warmupRvfc = v.requestVideoFrameCallback(() => commitSwap());
|
|
40722
|
+
v.addEventListener("loadeddata", commitSwap, { once: true });
|
|
40723
|
+
warmupSafety = setTimeout(() => commitSwap(), 1800);
|
|
40286
40724
|
};
|
|
40287
40725
|
pc.ontrack = (event) => {
|
|
40288
40726
|
console.debug("[WebRTC] ontrack (reoffer)", event.track.kind, event.track.readyState);
|
|
40289
40727
|
newStream.addTrack(event.track);
|
|
40290
|
-
|
|
40728
|
+
armSwap();
|
|
40291
40729
|
};
|
|
40292
40730
|
pc.oniceconnectionstatechange = () => {
|
|
40293
40731
|
if (!mountedRef.current) return;
|
|
@@ -40295,10 +40733,11 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40295
40733
|
console.debug("[WebRTC] ICE state (reoffer):", iceState);
|
|
40296
40734
|
if (iceState === "connected" || iceState === "completed") {
|
|
40297
40735
|
reconnectAttemptsRef.current = 0;
|
|
40298
|
-
|
|
40736
|
+
armSwap();
|
|
40299
40737
|
updateState("playing");
|
|
40300
40738
|
if (videoRef.current?.paused) videoRef.current.play().catch(() => {});
|
|
40301
40739
|
} else if (iceState === "failed" || iceState === "closed") {
|
|
40740
|
+
teardownWarmup();
|
|
40302
40741
|
if (!swappedIn && iceState === "failed") pc.close();
|
|
40303
40742
|
updateState("disconnected");
|
|
40304
40743
|
scheduleReconnect();
|
|
@@ -40324,7 +40763,11 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40324
40763
|
pc.close();
|
|
40325
40764
|
return;
|
|
40326
40765
|
}
|
|
40327
|
-
const
|
|
40766
|
+
const reofferHints = {
|
|
40767
|
+
...computeClientHints(containerRef.current),
|
|
40768
|
+
...hintsOverride
|
|
40769
|
+
};
|
|
40770
|
+
const { sessionId: sid, sdpAnswer } = await reofferFn(sessionId, pc.localDescription?.sdp ?? offer.sdp ?? "", target, reofferHints);
|
|
40328
40771
|
if (!mountedRef.current) {
|
|
40329
40772
|
pc.close();
|
|
40330
40773
|
return;
|
|
@@ -40375,7 +40818,8 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
|
|
|
40375
40818
|
addIceCandidate,
|
|
40376
40819
|
getIceCandidates,
|
|
40377
40820
|
updateState,
|
|
40378
|
-
startSessionStatePoll
|
|
40821
|
+
startSessionStatePoll,
|
|
40822
|
+
hintsOverride
|
|
40379
40823
|
]);
|
|
40380
40824
|
const connect = useClientOffer ? connectClientOffer : useServerOffer ? connectServerOffer : connectWhep;
|
|
40381
40825
|
const connectRef = (0, react$1.useRef)(() => {});
|
|
@@ -44371,6 +44815,33 @@ function buildRawTree(data, fetchedAt) {
|
|
|
44371
44815
|
* State). Computed once at module load.
|
|
44372
44816
|
*/
|
|
44373
44817
|
var ALL_DEVICE_CAP_NAMES = Object.freeze([...new Set(_camstack_types.ALL_CAPABILITY_DEFINITIONS.filter((c) => c.scope === "device").map((c) => c.name))].toSorted());
|
|
44818
|
+
/**
|
|
44819
|
+
* The keys of a cap's `runtimeState` object schema, or `[]` when the cap
|
|
44820
|
+
* declares none / it is not an object schema. Read structurally (no Zod
|
|
44821
|
+
* import, no cast) so a self-contained addon's schema instance still works
|
|
44822
|
+
* across the bundle boundary — `ZodObject.shape` is a public getter.
|
|
44823
|
+
*/
|
|
44824
|
+
function runtimeStateKeys(capName) {
|
|
44825
|
+
const rs = _camstack_types.ALL_CAPABILITY_DEFINITIONS.find((c) => c.name === capName)?.runtimeState;
|
|
44826
|
+
if (!rs || typeof rs !== "object") return [];
|
|
44827
|
+
const shape = Reflect.get(rs, "shape");
|
|
44828
|
+
if (!shape || typeof shape !== "object") return [];
|
|
44829
|
+
return Object.keys(shape);
|
|
44830
|
+
}
|
|
44831
|
+
/**
|
|
44832
|
+
* Cold placeholder slice for a bound cap that declares `runtimeState` but
|
|
44833
|
+
* has never written it (e.g. a virtual-doorbell camera that has never rung).
|
|
44834
|
+
* Every declared field is seeded `null` so the tree surfaces the cap's
|
|
44835
|
+
* presence and reads as "never" instead of the cap being OMITTED entirely.
|
|
44836
|
+
* Returns `null` when the cap has no runtime-state schema (nothing to seed).
|
|
44837
|
+
*/
|
|
44838
|
+
function coldRuntimeStateSlice(capName) {
|
|
44839
|
+
const keys = runtimeStateKeys(capName);
|
|
44840
|
+
if (keys.length === 0) return null;
|
|
44841
|
+
const slice = {};
|
|
44842
|
+
for (const key of keys) slice[key] = null;
|
|
44843
|
+
return slice;
|
|
44844
|
+
}
|
|
44374
44845
|
/** Depth at which tree nodes start collapsed. Cap nodes (depth 0) and
|
|
44375
44846
|
* their direct slice keys (depth 1) render expanded; anything nested
|
|
44376
44847
|
* deeper opens on demand. */
|
|
@@ -44417,6 +44888,27 @@ function StateValuesStream({ deviceId, defaultCaps, maxHeight = "max-h-96", onCl
|
|
|
44417
44888
|
(0, react$1.useEffect)(() => {
|
|
44418
44889
|
seededRef.current = false;
|
|
44419
44890
|
}, [deviceId]);
|
|
44891
|
+
const { data: bindings } = trpc.deviceManager.getBindings.useQuery({ deviceId }, { staleTime: 3e4 });
|
|
44892
|
+
(0, react$1.useEffect)(() => {
|
|
44893
|
+
const entries = bindings?.entries;
|
|
44894
|
+
if (!entries || entries.length === 0) return;
|
|
44895
|
+
const now = Date.now();
|
|
44896
|
+
setCapStates((prev) => {
|
|
44897
|
+
let mutated = false;
|
|
44898
|
+
const next = new Map(prev);
|
|
44899
|
+
for (const entry of entries) {
|
|
44900
|
+
if (next.has(entry.capName)) continue;
|
|
44901
|
+
const slice = coldRuntimeStateSlice(entry.capName);
|
|
44902
|
+
if (!slice) continue;
|
|
44903
|
+
next.set(entry.capName, {
|
|
44904
|
+
slice,
|
|
44905
|
+
updatedAt: now
|
|
44906
|
+
});
|
|
44907
|
+
mutated = true;
|
|
44908
|
+
}
|
|
44909
|
+
return mutated ? next : prev;
|
|
44910
|
+
});
|
|
44911
|
+
}, [bindings, deviceId]);
|
|
44420
44912
|
const patchCapState = (0, react$1.useCallback)((capName, slice) => {
|
|
44421
44913
|
setCapStates((prev) => {
|
|
44422
44914
|
const next = new Map(prev);
|
|
@@ -46088,13 +46580,14 @@ function useDeviceWebrtc(trpc, deviceId, pollIntervalMs = 5e3) {
|
|
|
46088
46580
|
deviceId,
|
|
46089
46581
|
getIceServers
|
|
46090
46582
|
]);
|
|
46091
|
-
const handleOffer = (0, react$1.useCallback)(async (target, sdpOffer, sessionId) => {
|
|
46583
|
+
const handleOffer = (0, react$1.useCallback)(async (target, sdpOffer, sessionId, hints) => {
|
|
46092
46584
|
if (deviceId === null) throw new Error("No device selected");
|
|
46093
46585
|
return trpc.webrtcSession.handleOffer.mutate({
|
|
46094
46586
|
deviceId,
|
|
46095
46587
|
target,
|
|
46096
46588
|
sdpOffer,
|
|
46097
|
-
...sessionId !== void 0 ? { sessionId } : {}
|
|
46589
|
+
...sessionId !== void 0 ? { sessionId } : {},
|
|
46590
|
+
...hints !== void 0 ? { hints } : {}
|
|
46098
46591
|
});
|
|
46099
46592
|
}, [trpc, deviceId]);
|
|
46100
46593
|
const sendAnswer = (0, react$1.useCallback)(async (sessionId, sdpAnswer) => {
|
|
@@ -46518,6 +47011,7 @@ exports.DeviceItem = DeviceItem;
|
|
|
46518
47011
|
exports.DeviceList = DeviceList;
|
|
46519
47012
|
exports.DeviceMultiSelectField = DeviceMultiSelectField;
|
|
46520
47013
|
exports.DeviceSelectField = DeviceSelectField;
|
|
47014
|
+
exports.DeviceStepMatrix = DeviceStepMatrix;
|
|
46521
47015
|
exports.Dialog = Dialog;
|
|
46522
47016
|
exports.DialogContent = DialogContent;
|
|
46523
47017
|
exports.DialogDescription = DialogDescription;
|
|
@@ -46751,6 +47245,7 @@ exports.selectedDeviceOptions = selectedDeviceOptions;
|
|
|
46751
47245
|
exports.serializeRecordedCommand = serializeRecordedCommand;
|
|
46752
47246
|
exports.shouldCommit = shouldCommit;
|
|
46753
47247
|
exports.shouldEmit = shouldEmit;
|
|
47248
|
+
exports.shouldUseSingleNode = shouldUseSingleNode;
|
|
46754
47249
|
exports.sortRows = sortRows;
|
|
46755
47250
|
exports.statusIcons = statusIcons;
|
|
46756
47251
|
exports.stripParentNamePrefix = stripParentNamePrefix;
|
|
@@ -46797,7 +47292,6 @@ exports.useAddonsSetAutoUpdateSettings = useAddonsSetAutoUpdateSettings;
|
|
|
46797
47292
|
exports.useAddonsSetCapabilityProviderEnabled = useAddonsSetCapabilityProviderEnabled;
|
|
46798
47293
|
exports.useAddonsStartJob = useAddonsStartJob;
|
|
46799
47294
|
exports.useAddonsUninstallPackage = useAddonsUninstallPackage;
|
|
46800
|
-
exports.useAddonsUpdateFrameworkPackage = useAddonsUpdateFrameworkPackage;
|
|
46801
47295
|
exports.useAddonsUpdatePackage = useAddonsUpdatePackage;
|
|
46802
47296
|
exports.useAirQualitySensorGetStatus = useAirQualitySensorGetStatus;
|
|
46803
47297
|
exports.useAlarmPanelArm = useAlarmPanelArm;
|