@camstack/ui-library 1.2.234 → 1.2.236

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/index.cjs CHANGED
@@ -7388,6 +7388,16 @@ var useLocalNetworkSetNotificationEndpoint = trpc.localNetwork.setNotificationEn
7388
7388
  var useLocalNetworkGetViewerEndpoints = trpc.localNetwork.getViewerEndpoints.useQuery;
7389
7389
  /** Generated alias around `trpc.localNetwork.setViewerEndpoints.useMutation`. */
7390
7390
  var useLocalNetworkSetViewerEndpoints = trpc.localNetwork.setViewerEndpoints.useMutation;
7391
+ /** Generated alias around `trpc.localNetwork.getAddresses.useQuery`. */
7392
+ var useLocalNetworkGetAddresses = trpc.localNetwork.getAddresses.useQuery;
7393
+ /** Generated alias around `trpc.localNetwork.addCustomAddress.useMutation`. */
7394
+ var useLocalNetworkAddCustomAddress = trpc.localNetwork.addCustomAddress.useMutation;
7395
+ /** Generated alias around `trpc.localNetwork.removeCustomAddress.useMutation`. */
7396
+ var useLocalNetworkRemoveCustomAddress = trpc.localNetwork.removeCustomAddress.useMutation;
7397
+ /** Generated alias around `trpc.localNetwork.setAddressRole.useMutation`. */
7398
+ var useLocalNetworkSetAddressRole = trpc.localNetwork.setAddressRole.useMutation;
7399
+ /** Generated alias around `trpc.localNetwork.getHubAddresses.useQuery`. */
7400
+ var useLocalNetworkGetHubAddresses = trpc.localNetwork.getHubAddresses.useQuery;
7391
7401
  /** Generated alias around `trpc.localNetwork.getAllowedAddresses.useQuery`. */
7392
7402
  var useLocalNetworkGetAllowedAddresses = trpc.localNetwork.getAllowedAddresses.useQuery;
7393
7403
  /** Generated alias around `trpc.localNetwork.setAllowedAddresses.useMutation`. */
@@ -8716,6 +8726,8 @@ var useWebrtcSessionCloseSession = trpc.webrtcSession.closeSession.useMutation;
8716
8726
  var useWebrtcSessionHasAdaptiveBitrate = trpc.webrtcSession.hasAdaptiveBitrate.useQuery;
8717
8727
  /** Generated alias around `trpc.webrtcSession.getSessionState.useQuery`. */
8718
8728
  var useWebrtcSessionGetSessionState = trpc.webrtcSession.getSessionState.useQuery;
8729
+ /** Generated alias around `trpc.webrtcSession.applyDownlinkProbe.useMutation`. */
8730
+ var useWebrtcSessionApplyDownlinkProbe = trpc.webrtcSession.applyDownlinkProbe.useMutation;
8719
8731
  /** Generated alias around `trpc.webrtcSession.getSessionDebug.useQuery`. */
8720
8732
  var useWebrtcSessionGetSessionDebug = trpc.webrtcSession.getSessionDebug.useQuery;
8721
8733
  /** Generated alias around `trpc.zoneAnalytics.getCurrentSnapshot.useQuery`. */
@@ -29358,6 +29370,89 @@ function resolveEffectiveStepModel(input) {
29358
29370
  return input.override ?? input.base ?? resolveEffectiveDefaultModel(input.catalog, input.device);
29359
29371
  }
29360
29372
  //#endregion
29373
+ //#region src/composites/root-model-source.tsx
29374
+ /**
29375
+ * "Use the root model" — the step editor's control for a step the camera's
29376
+ * ROOT detection model can supply (D730).
29377
+ *
29378
+ * The flag (`useRootModel`, in the step's own settings) is an operator INTENT
29379
+ * kept beside the step's model: checking it never changes the model, and a
29380
+ * root that cannot supply the step leaves the step running its own model while
29381
+ * the flag waits, inactive, for a root that can. Whether it is in force is
29382
+ * `resolveRootSupply` — the same function the runner decides with.
29383
+ */
29384
+ /** The editor's view of the flag for one step, or `null` for a step no root can supply. */
29385
+ function rootSourceState(stepId, settings, rootModel) {
29386
+ if (!(0, _camstack_types.isRootSuppliableStepId)(stepId)) return null;
29387
+ return (0, _camstack_types.resolveRootSupply)(stepId, settings, rootModel?.supplies);
29388
+ }
29389
+ /** The settings bag with the flag set — every other key untouched. */
29390
+ function withUseRootModel(settings, value) {
29391
+ return {
29392
+ ...settings,
29393
+ [_camstack_types.USE_ROOT_MODEL_SETTING_KEY]: value
29394
+ };
29395
+ }
29396
+ /** What the operator must know about a supply, beyond "it is on". */
29397
+ function supplyNotes(supply) {
29398
+ if (supply.kind === "label") {
29399
+ const notes = supply.uncovered.length > 0 ? [`Not produced by this model: ${supply.uncovered.join(", ")}.`] : [];
29400
+ 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
+ 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
+ }
29403
+ 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
+ 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
+ }
29406
+ /** The reason an intended supply is not in force, in the operator's words. */
29407
+ function inactiveText(state, rootModel) {
29408
+ switch (state.inactiveReason) {
29409
+ case "root-input-too-small": return `the current root model (${rootModel.name}) sees the frame too small to find faces reliably (it needs an input of 640 px or more).`;
29410
+ case "root-does-not-declare":
29411
+ case void 0: return `the current root model (${rootModel.name}) cannot supply this.`;
29412
+ }
29413
+ }
29414
+ function RootModelSourceControl({ stepId, settings, rootModel, disabled, onChange }) {
29415
+ if (rootModel === void 0) return null;
29416
+ const state = rootSourceState(stepId, settings, rootModel);
29417
+ if (state === null) return null;
29418
+ const inactiveNote = state.inactive ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
29419
+ className: "rounded bg-muted/40 px-3 py-2 text-[11px] text-foreground-subtle",
29420
+ "data-testid": "root-model-inactive",
29421
+ children: [
29422
+ "Use the root model: inactive — ",
29423
+ inactiveText(state, rootModel),
29424
+ " The step runs its own model."
29425
+ ]
29426
+ }) : null;
29427
+ if (state.supply === void 0) return inactiveNote;
29428
+ const checked = state.intent;
29429
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
29430
+ className: "space-y-1",
29431
+ "data-testid": "root-model-source",
29432
+ children: [
29433
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
29434
+ className: "flex items-center gap-2 text-xs text-foreground",
29435
+ children: [
29436
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
29437
+ type: "checkbox",
29438
+ checked,
29439
+ disabled,
29440
+ onChange: (e) => onChange(e.target.checked)
29441
+ }),
29442
+ "Use the root model (",
29443
+ rootModel.name,
29444
+ ")"
29445
+ ]
29446
+ }),
29447
+ inactiveNote,
29448
+ state.effective && supplyNotes(state.supply).map((note) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
29449
+ className: "text-[10px] leading-snug text-foreground-subtle",
29450
+ children: note
29451
+ }, note))
29452
+ ]
29453
+ });
29454
+ }
29455
+ //#endregion
29361
29456
  //#region src/composites/agent-step-editor.tsx
29362
29457
  /**
29363
29458
  * Per-(node,device) and per-camera editors must not surface knobs that the
@@ -29389,6 +29484,25 @@ function buildDisplayStep(addon, cfg) {
29389
29484
  settings: { ...cfg.settings }
29390
29485
  };
29391
29486
  }
29487
+ /**
29488
+ * Shown in place of the model picker while the root model supplies the step:
29489
+ * the step's own model is kept, not changed, for when the root cannot.
29490
+ */
29491
+ function RootSuppliedModelNote({ rootModel, stepModelId }) {
29492
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
29493
+ className: "rounded bg-primary/10 px-3 py-2 text-[11px] text-foreground-subtle",
29494
+ "data-testid": "root-supplied-model",
29495
+ children: [
29496
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
29497
+ className: "font-semibold text-foreground",
29498
+ children: ["Model: ", rootModel.name]
29499
+ }),
29500
+ " (the root model). This step's own model",
29501
+ stepModelId !== void 0 ? ` (${stepModelId})` : "",
29502
+ " is kept and runs again whenever the root model cannot supply the step."
29503
+ ]
29504
+ });
29505
+ }
29392
29506
  function computeEffective(mode, agentDefault, patch) {
29393
29507
  if (mode !== "device" || !patch) return agentDefault;
29394
29508
  return {
@@ -29397,7 +29511,7 @@ function computeEffective(mode, agentDefault, patch) {
29397
29511
  settings: patch.settings ?? agentDefault.settings
29398
29512
  };
29399
29513
  }
29400
- function AgentModeEditor({ addon, agentDefault, modelsForFormat, engineFormat, effectiveModelId, onChangeAgentConfig }) {
29514
+ function AgentModeEditor({ addon, agentDefault, modelsForFormat, engineFormat, effectiveModelId, rootModel, onChangeAgentConfig }) {
29401
29515
  const update = (patch) => {
29402
29516
  onChangeAgentConfig?.({
29403
29517
  ...agentDefault,
@@ -29406,10 +29520,20 @@ function AgentModeEditor({ addon, agentDefault, modelsForFormat, engineFormat, e
29406
29520
  };
29407
29521
  const localSchema = nodeLocalConfigSchema(addon);
29408
29522
  const formatDefault = addon.defaultModelIdByFormat?.[engineFormat] ?? addon.defaultModelId;
29523
+ const rootSupplied = rootSourceState(addon.id, agentDefault.settings, rootModel)?.effective === true;
29409
29524
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
29410
29525
  className: "space-y-4 text-xs",
29411
29526
  children: [
29412
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
29527
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RootModelSourceControl, {
29528
+ stepId: addon.id,
29529
+ settings: agentDefault.settings,
29530
+ rootModel,
29531
+ onChange: (v) => update({ settings: withUseRootModel(agentDefault.settings, v) })
29532
+ }),
29533
+ rootSupplied && rootModel !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RootSuppliedModelNote, {
29534
+ rootModel,
29535
+ stepModelId: agentDefault.modelId ?? effectiveModelId
29536
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
29413
29537
  className: "text-[10px] uppercase tracking-widest text-foreground-subtle mb-1",
29414
29538
  children: "Model"
29415
29539
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelPicker, {
@@ -29435,7 +29559,7 @@ function AgentModeEditor({ addon, agentDefault, modelsForFormat, engineFormat, e
29435
29559
  ]
29436
29560
  });
29437
29561
  }
29438
- function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, modelsForFormat, engineFormat, effectiveModelId, onChangePatch }) {
29562
+ function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, modelsForFormat, engineFormat, effectiveModelId, rootModel, onChangePatch }) {
29439
29563
  const effective = computeEffective("device", agentDefault, currentPatch);
29440
29564
  const inheritedModelId = agentDefault.modelId ?? effectiveModelId;
29441
29565
  const formatDefault = addon.defaultModelIdByFormat?.[engineFormat] ?? addon.defaultModelId;
@@ -29473,7 +29597,16 @@ function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, mode
29473
29597
  " — model + settings only. Whether this camera runs the step is set by its per-camera gate above."
29474
29598
  ]
29475
29599
  }),
29476
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
29600
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RootModelSourceControl, {
29601
+ stepId: addon.id,
29602
+ settings: effective.settings,
29603
+ rootModel,
29604
+ onChange: (v) => setSettings(withUseRootModel(effective.settings, v))
29605
+ }),
29606
+ rootSourceState(addon.id, effective.settings, rootModel)?.effective === true && rootModel !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RootSuppliedModelNote, {
29607
+ rootModel,
29608
+ stepModelId: effective.modelId ?? inheritedModelId
29609
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
29477
29610
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
29478
29611
  className: "flex items-center justify-between mb-1",
29479
29612
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -29558,7 +29691,7 @@ function DeviceModeEditor({ addon, agentDefault, agentNodeId, currentPatch, mode
29558
29691
  });
29559
29692
  }
29560
29693
  function AgentStepEditor(props) {
29561
- const { mode, addon, agentDefault, agentNodeId, currentPatch, onChangeAgentConfig, onChangePatch, engineFormat, effectiveModelId } = props;
29694
+ const { mode, addon, agentDefault, agentNodeId, currentPatch, onChangeAgentConfig, onChangePatch, engineFormat, effectiveModelId, rootModel } = props;
29562
29695
  const modelsForFormat = (0, react$1.useMemo)(() => stepModelOptions(addon.models, engineFormat), [addon.models, engineFormat]);
29563
29696
  if (mode === "agent") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentModeEditor, {
29564
29697
  addon,
@@ -29566,6 +29699,7 @@ function AgentStepEditor(props) {
29566
29699
  modelsForFormat,
29567
29700
  engineFormat,
29568
29701
  effectiveModelId,
29702
+ ...rootModel !== void 0 ? { rootModel } : {},
29569
29703
  onChangeAgentConfig
29570
29704
  });
29571
29705
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceModeEditor, {
@@ -29576,6 +29710,7 @@ function AgentStepEditor(props) {
29576
29710
  modelsForFormat,
29577
29711
  engineFormat,
29578
29712
  effectiveModelId,
29713
+ ...rootModel !== void 0 ? { rootModel } : {},
29579
29714
  onChangePatch
29580
29715
  });
29581
29716
  }
@@ -31088,7 +31223,8 @@ function adaptiveHintsKey(report) {
31088
31223
  rung,
31089
31224
  report.prefersTier ?? "-",
31090
31225
  report.probeOrigin ?? "unreachable",
31091
- report.probeStatus ?? "-"
31226
+ report.probeStatus ?? "-",
31227
+ report.probeAnswer ?? "-"
31092
31228
  ].join("|");
31093
31229
  }
31094
31230
  function mbpsOf(bytes, elapsedMs) {
@@ -31203,6 +31339,38 @@ function createDownlinkProbe(deps) {
31203
31339
  }
31204
31340
  };
31205
31341
  }
31342
+ /**
31343
+ * Hand a probe to the session it was measured on (D733).
31344
+ *
31345
+ * The probe climb used to be a SECOND session: a make-before-break reconnect
31346
+ * carrying `downlinkMbps`, so every adaptive live open cost two sessions. The
31347
+ * hub now moves the open session to the probed rung itself. `answered` means
31348
+ * the hub owns the climb, WHATEVER it answered — `held` included: a reconnect
31349
+ * after a `held` would buy the second session for a rung the hub just refused.
31350
+ * Only `unsupported` (no host wiring, no session id yet, or a hub that does not
31351
+ * know the method) leaves the caller its legacy reconnect climb.
31352
+ */
31353
+ async function handProbeToSession(apply, sessionId, downlinkMbps) {
31354
+ if (apply === void 0) return {
31355
+ kind: "unsupported",
31356
+ why: "no-wiring"
31357
+ };
31358
+ if (sessionId === null) return {
31359
+ kind: "unsupported",
31360
+ why: "no-session"
31361
+ };
31362
+ try {
31363
+ return {
31364
+ kind: "answered",
31365
+ answer: await apply(sessionId, downlinkMbps)
31366
+ };
31367
+ } catch {
31368
+ return {
31369
+ kind: "unsupported",
31370
+ why: "call-failed"
31371
+ };
31372
+ }
31373
+ }
31206
31374
  //#endregion
31207
31375
  //#region src/composites/client-network-sample.ts
31208
31376
  /** Schema bound of `estimatedBandwidthKbps` / `packetLossPercent`. */
@@ -31958,7 +32126,7 @@ function mergePlayerHints(autoHints, override, downlinkMbps, coldStart) {
31958
32126
  return coldStart ? stripDownlinkForColdStart(merged) : merged;
31959
32127
  }
31960
32128
  var FIRST_FRAME_TIMEOUT_MS = 8e3;
31961
- function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, muted: initialMuted = true, showControls = true, showStats = false, onDebugStats, onPlaybackStats, onClientNetworkSample, probeAuthToken, onConnectTiming, className = "", onStateChange, onError, onReconnectAttempt, overlay, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, getSessionDebug, reoffer, posterUrl, hintsOverride, onAdaptiveHints, reconnectSignal, onControlChannel, onVideoElement }) {
32129
+ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, muted: initialMuted = true, showControls = true, showStats = false, onDebugStats, onPlaybackStats, onClientNetworkSample, probeAuthToken, onConnectTiming, className = "", onStateChange, onError, onReconnectAttempt, overlay, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, getSessionDebug, applyDownlinkProbe, reoffer, posterUrl, hintsOverride, onAdaptiveHints, reconnectSignal, onControlChannel, onVideoElement }) {
31962
32130
  const videoRef = (0, react$1.useRef)(null);
31963
32131
  const containerRef = (0, react$1.useRef)(null);
31964
32132
  const pcRef = (0, react$1.useRef)(null);
@@ -32012,6 +32180,29 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
32012
32180
  * — once per origin app-wide (`firstRefusal`), so a missing `probeAuthToken`
32013
32181
  * is visible instead of reading like a failed probe (review N3).
32014
32182
  */
32183
+ /** A ref so wiring it never rebuilds the connect callbacks (same as `onAdaptiveHints`). */
32184
+ const applyDownlinkProbeRef = (0, react$1.useRef)(applyDownlinkProbe);
32185
+ applyDownlinkProbeRef.current = applyDownlinkProbe;
32186
+ /**
32187
+ * After a cold start's probe justifies a higher rung: hand it to the session
32188
+ * it was measured on (D733). Only when the hub cannot take it does the
32189
+ * legacy climb run — `reconnect`, a second make-before-break session.
32190
+ */
32191
+ const climbAfterProbe = (0, react$1.useCallback)(async (sessionId, mbps, reconnect) => {
32192
+ const handoff = await handProbeToSession(applyDownlinkProbeRef.current, sessionId, mbps);
32193
+ if (!mountedRef.current) return;
32194
+ if (handoff.kind === "unsupported") {
32195
+ reconnect();
32196
+ return;
32197
+ }
32198
+ const { outcome, tier, reason } = handoff.answer;
32199
+ onAdaptiveHintsRef.current?.({
32200
+ phase: "probe-in-session",
32201
+ probeOrigin: probeOriginFor(serverUrl),
32202
+ downlinkMbps: mbps,
32203
+ probeAnswer: `${outcome}:${tier ?? "-"}:${reason}`
32204
+ });
32205
+ }, [serverUrl]);
32015
32206
  const reportProbeRefused = (0, react$1.useCallback)((origin, status) => {
32016
32207
  onAdaptiveHintsRef.current?.({
32017
32208
  phase: "probe-refused",
@@ -32175,7 +32366,10 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
32175
32366
  if (!mountedRef.current || abort.signal.aborted) return;
32176
32367
  if (probeUpgradeRef.current) return;
32177
32368
  probeUpgradeRef.current = true;
32178
- connectServerOffer(true, true, mbps);
32369
+ climbAfterProbe(sessionId, mbps, () => {
32370
+ if (!mountedRef.current || abort.signal.aborted) return;
32371
+ connectServerOffer(true, true, mbps);
32372
+ });
32179
32373
  });
32180
32374
  let swappedIn = false;
32181
32375
  const swapInOnce = () => {
@@ -32278,7 +32472,8 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
32278
32472
  hintsOverride,
32279
32473
  reportAdaptiveHints,
32280
32474
  probeAuthToken,
32281
- reportProbeRefused
32475
+ reportProbeRefused,
32476
+ climbAfterProbe
32282
32477
  ]);
32283
32478
  const connectWhep = (0, react$1.useCallback)(async () => {
32284
32479
  if (!mountedRef.current) return;
@@ -32360,6 +32555,9 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
32360
32555
  const pc = new RTCPeerConnection({ iceServers: [...iceServers ?? [], { urls: "stun:stun.l.google.com:19302" }] });
32361
32556
  if (!seamless) pcRef.current = pc;
32362
32557
  const combinedStream = new MediaStream();
32558
+ /** This connect's server session — known once `handleOffer` answers,
32559
+ * which is before the first frame the probe waits for. */
32560
+ let probeSessionId = null;
32363
32561
  if (!withDownlink) probeAfterThisStreamsFirstFrame({
32364
32562
  video: () => videoRef.current,
32365
32563
  stream: combinedStream,
@@ -32372,7 +32570,10 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
32372
32570
  if (!mountedRef.current || abort.signal.aborted) return;
32373
32571
  if (probeUpgradeRef.current) return;
32374
32572
  probeUpgradeRef.current = true;
32375
- connectClientOffer(true, true, mbps);
32573
+ climbAfterProbe(probeSessionId, mbps, () => {
32574
+ if (!mountedRef.current || abort.signal.aborted) return;
32575
+ connectClientOffer(true, true, mbps);
32576
+ });
32376
32577
  });
32377
32578
  let swappedIn = false;
32378
32579
  const swapInOnce = () => {
@@ -32482,6 +32683,7 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
32482
32683
  const { sessionId: sid, sdpAnswer } = await handleOffer(pc.localDescription?.sdp ?? offer.sdp ?? "", void 0, clientOfferHints);
32483
32684
  timing.signalingRttMs = ms() - signalingStart;
32484
32685
  sessionId = sid;
32686
+ probeSessionId = sid;
32485
32687
  sessionIdsRef.current.add(sid);
32486
32688
  activeSessionIdRef.current = sid;
32487
32689
  if (!mountedRef.current || abort.signal.aborted) {
@@ -32545,7 +32747,8 @@ function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay = true, mute
32545
32747
  hintsOverride,
32546
32748
  reportAdaptiveHints,
32547
32749
  probeAuthToken,
32548
- reportProbeRefused
32750
+ reportProbeRefused,
32751
+ climbAfterProbe
32549
32752
  ]);
32550
32753
  renegotiateRef.current = (0, react$1.useCallback)(async (sessionId, target) => {
32551
32754
  const reofferFn = reoffer;
@@ -53136,10 +53339,10 @@ function renderCellContent(state) {
53136
53339
  className: "font-mono text-foreground truncate",
53137
53340
  children: state.modelId
53138
53341
  }),
53139
- state.filledBy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
53140
- className: "ml-auto shrink-0 truncate rounded bg-primary/10 px-1 text-[8px] font-semibold text-primary",
53141
- title: `The root model ${state.filledBy} already names this step's classes — the step is skipped for those subjects`,
53142
- children: ["filled by ", state.filledBy]
53342
+ state.suppliedByRoot !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53343
+ className: "ml-auto shrink-0 truncate rounded bg-primary/10 px-1 text-[8px] font-semibold uppercase text-primary",
53344
+ title: `Uses the root model ${state.suppliedByRoot} — this step's own model does not run here`,
53345
+ children: "root"
53143
53346
  }),
53144
53347
  state.clusterWide === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53145
53348
  className: "ml-auto shrink-0 text-[8px] font-semibold uppercase text-primary",
@@ -56181,7 +56384,7 @@ function ImageOffIcon({ className }) {
56181
56384
  ]
56182
56385
  });
56183
56386
  }
56184
- function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, getSessionDebug, playbackUrl, playbackFormat, playbackAuthToken, onExitPlayback, posterUrl, streams, activeStreamId: controlledStreamId, onStreamChange, deviceName, phase, pipelineMetrics, detections, stationaryObjects, defaultShowDetections = true, defaultShowMotion = false, streamStats, autoPlay = false, showPlayStop = false, snapshotSrc, snapshotLoading = false, onRefreshSnapshot, showStreamStats = false, children, extraOverlay, ptzAvailable = false, ptzShown = false, ptzOverlay, onPtzToggle, intercomAvailable = false, intercomShown = false, onIntercomToggle, chromeless = false, className }) {
56387
+ function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, getSessionDebug, applyDownlinkProbe, playbackUrl, playbackFormat, playbackAuthToken, onExitPlayback, posterUrl, streams, activeStreamId: controlledStreamId, onStreamChange, deviceName, phase, pipelineMetrics, detections, stationaryObjects, defaultShowDetections = true, defaultShowMotion = false, streamStats, autoPlay = false, showPlayStop = false, snapshotSrc, snapshotLoading = false, onRefreshSnapshot, showStreamStats = false, children, extraOverlay, ptzAvailable = false, ptzShown = false, ptzOverlay, onPtzToggle, intercomAvailable = false, intercomShown = false, onIntercomToggle, chromeless = false, className }) {
56185
56388
  const registeredButtons = usePlayerToolbarButtons();
56186
56389
  const recordedPlayback = useRecordedPlayback();
56187
56390
  const [isPlaying, setIsPlaying] = (0, react$1.useState)(autoPlay);
@@ -56501,6 +56704,7 @@ function StreamPanel({ serverUrl, createSession, sendAnswer, handleOffer, getIce
56501
56704
  reoffer: playerReoffer,
56502
56705
  getSessionState,
56503
56706
  ...getSessionDebug ? { getSessionDebug } : {},
56707
+ ...applyDownlinkProbe ? { applyDownlinkProbe } : {},
56504
56708
  getIceServers,
56505
56709
  addIceCandidate,
56506
56710
  getIceCandidates,
@@ -58723,6 +58927,15 @@ function useDeviceWebrtc(trpc, deviceId, pollIntervalMs = 5e3) {
58723
58927
  deviceId,
58724
58928
  sessionId
58725
58929
  });
58930
+ }, [trpc, deviceId]),
58931
+ applyDownlinkProbe: (0, react$1.useCallback)(async (sessionId, downlinkMbps) => {
58932
+ const mutate = trpc.webrtcSession.applyDownlinkProbe;
58933
+ if (deviceId === null || mutate === void 0) throw new Error("applyDownlinkProbe: not available for this player");
58934
+ return mutate.mutate({
58935
+ deviceId,
58936
+ sessionId,
58937
+ downlinkMbps
58938
+ });
58726
58939
  }, [trpc, deviceId])
58727
58940
  };
58728
58941
  }
@@ -59452,6 +59665,7 @@ exports.resolveStepDefaultModel = resolveStepDefaultModel;
59452
59665
  exports.resolveTableLayout = resolveTableLayout;
59453
59666
  exports.roiTooLarge = roiTooLarge;
59454
59667
  exports.roiTooLarge$1 = roiTooLarge;
59668
+ exports.rootSourceState = rootSourceState;
59455
59669
  exports.routerBasename = routerBasename;
59456
59670
  exports.scrubReducer = scrubReducer;
59457
59671
  exports.selectPickerGroup = selectPickerGroup;
@@ -59948,17 +60162,22 @@ exports.useLlmStartRuntime = useLlmStartRuntime;
59948
60162
  exports.useLlmStopRuntime = useLlmStopRuntime;
59949
60163
  exports.useLlmTestProfile = useLlmTestProfile;
59950
60164
  exports.useLlmUpsertProfile = useLlmUpsertProfile;
60165
+ exports.useLocalNetworkAddCustomAddress = useLocalNetworkAddCustomAddress;
59951
60166
  exports.useLocalNetworkDownloadCa = useLocalNetworkDownloadCa;
60167
+ exports.useLocalNetworkGetAddresses = useLocalNetworkGetAddresses;
59952
60168
  exports.useLocalNetworkGetAllowedAddresses = useLocalNetworkGetAllowedAddresses;
59953
60169
  exports.useLocalNetworkGetConnectionEndpoints = useLocalNetworkGetConnectionEndpoints;
60170
+ exports.useLocalNetworkGetHubAddresses = useLocalNetworkGetHubAddresses;
59954
60171
  exports.useLocalNetworkGetNotificationEndpoint = useLocalNetworkGetNotificationEndpoint;
59955
60172
  exports.useLocalNetworkGetPreferred = useLocalNetworkGetPreferred;
59956
60173
  exports.useLocalNetworkGetTlsStatus = useLocalNetworkGetTlsStatus;
59957
60174
  exports.useLocalNetworkGetViewerEndpoints = useLocalNetworkGetViewerEndpoints;
59958
60175
  exports.useLocalNetworkList = useLocalNetworkList;
59959
60176
  exports.useLocalNetworkRegenerateCertificate = useLocalNetworkRegenerateCertificate;
60177
+ exports.useLocalNetworkRemoveCustomAddress = useLocalNetworkRemoveCustomAddress;
59960
60178
  exports.useLocalNetworkResetAllowlistToBestMatch = useLocalNetworkResetAllowlistToBestMatch;
59961
60179
  exports.useLocalNetworkRevertToGeneratedCertificate = useLocalNetworkRevertToGeneratedCertificate;
60180
+ exports.useLocalNetworkSetAddressRole = useLocalNetworkSetAddressRole;
59962
60181
  exports.useLocalNetworkSetAllowedAddresses = useLocalNetworkSetAllowedAddresses;
59963
60182
  exports.useLocalNetworkSetNotificationEndpoint = useLocalNetworkSetNotificationEndpoint;
59964
60183
  exports.useLocalNetworkSetViewerEndpoints = useLocalNetworkSetViewerEndpoints;
@@ -60635,6 +60854,7 @@ exports.useWaterHeaterSetOperationMode = useWaterHeaterSetOperationMode;
60635
60854
  exports.useWaterHeaterSetTargetTemp = useWaterHeaterSetTargetTemp;
60636
60855
  exports.useWeatherGetStatus = useWeatherGetStatus;
60637
60856
  exports.useWebrtcSessionAddIceCandidate = useWebrtcSessionAddIceCandidate;
60857
+ exports.useWebrtcSessionApplyDownlinkProbe = useWebrtcSessionApplyDownlinkProbe;
60638
60858
  exports.useWebrtcSessionCloseSession = useWebrtcSessionCloseSession;
60639
60859
  exports.useWebrtcSessionCreateSession = useWebrtcSessionCreateSession;
60640
60860
  exports.useWebrtcSessionGetIceCandidates = useWebrtcSessionGetIceCandidates;