@camstack/addon-model-studio 1.1.204 → 1.1.206

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.
Files changed (18) hide show
  1. package/dist/{MotionZonesSettings-f-Rb4FPx.mjs → MotionZonesSettings-C5YcAYX9.mjs} +2 -2
  2. package/dist/{PrivacyMaskSettings-DzrwiikK.mjs → PrivacyMaskSettings-B1M6BI5F.mjs} +4 -4
  3. package/dist/{SceneMonitorEditor-pr_u7JDi.mjs → SceneMonitorEditor-8Nn6yjHl.mjs} +3 -3
  4. package/dist/_stub.js +5102 -4909
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-DD6MdVfP.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-B4UQ5mJi.mjs} +3 -3
  6. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-2O40KrYq.mjs +26 -0
  7. package/dist/{hostInit-dZ-iNlYE.mjs → hostInit-95eIoNUt.mjs} +2 -2
  8. package/dist/model-studio.addon.js +289 -34
  9. package/dist/model-studio.addon.mjs +289 -34
  10. package/dist/{player-overlays-DyoPwbMr.mjs → player-overlays-lpfo2ab8.mjs} +1 -1
  11. package/dist/remoteEntry.js +1 -1
  12. package/dist/{responsive-_Hjo-SPS.mjs → responsive-CO_rI6GL.mjs} +1 -1
  13. package/dist/{scene-monitor-copy-DxG4-n1Z.mjs → scene-monitor-copy-Dx3Cq-sD.mjs} +1 -1
  14. package/dist/{square-BIirO7Z1.mjs → square-DQHBxjsn.mjs} +1 -1
  15. package/dist/{trash-2-B9qboTSI.mjs → trash-2-C6kfv9xl.mjs} +1 -1
  16. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-DExLMP5B.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-CXb44kDc.mjs} +1 -1
  17. package/package.json +1 -1
  18. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Cjm3a0Rl.mjs +0 -26
@@ -5773,7 +5773,7 @@ var ZodIssueCode = {
5773
5773
  var ZodFirstPartyTypeKind;
5774
5774
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5775
5775
  //#endregion
5776
- //#region ../types/dist/sleep-DXKLNFvA.mjs
5776
+ //#region ../types/dist/sleep-C16NCZR2.mjs
5777
5777
  /**
5778
5778
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5779
5779
  * window to float samples (D455).
@@ -11150,6 +11150,19 @@ var ModelProviderIdSchema = _enum([
11150
11150
  "custom"
11151
11151
  ]);
11152
11152
  /**
11153
+ * Resolve a catalog entry's picker provider. An explicit stamp always wins;
11154
+ * otherwise Frigate/Scrypted are recognised from id prefix or description.
11155
+ * Unstamped BYO registry rows fall through to `custom`. Family `yolov9` is
11156
+ * NOT a Scrypted signal — CamStack ships that family too.
11157
+ */
11158
+ function inferModelProvider(entry) {
11159
+ if (entry.provider !== void 0) return entry.provider;
11160
+ const haystack = `${entry.id} ${entry.description ?? ""}`;
11161
+ if (/scrypted/i.test(haystack)) return "scrypted";
11162
+ if (/frigate/i.test(haystack)) return "frigate";
11163
+ return "custom";
11164
+ }
11165
+ /**
11153
11166
  * The licence of a model, as a record rather than a bare SPDX string: a
11154
11167
  * permissive CODE licence routinely sits on top of restrictive WEIGHTS or
11155
11168
  * DATA, and the obligations (attribution, modification notices, source offers)
@@ -16549,6 +16562,12 @@ method(_void(), array(string()).readonly(), { auth: "admin" }), method(object({
16549
16562
  kind: "mutation",
16550
16563
  auth: "admin"
16551
16564
  });
16565
+ var HUB_ADDRESS_ROLES = [
16566
+ "appConnections",
16567
+ "notificationTaps",
16568
+ "internal",
16569
+ "publicEndpoint"
16570
+ ];
16552
16571
  /**
16553
16572
  * Transport bounds for AI capability methods.
16554
16573
  *
@@ -20726,7 +20745,13 @@ var NcAlarmModeDelaysListSchema = array(object({
20726
20745
  * Absent = false (logged, not sent). Even when true it is not sent when that same sensor
20727
20746
  * is the one that starts the entry delay — the entry-delay notification already names it.
20728
20747
  */
20729
- notifySensorRewatched: boolean().optional()
20748
+ notifySensorRewatched: boolean().optional(),
20749
+ /**
20750
+ * D735: notify "ignored" when a sensor this arm bypassed really MOVED while armed (its own
20751
+ * change time is after the arm) and set nothing off. Absent = false (logged, not sent). A
20752
+ * re-sync, a recovery from `unavailable` or a duplicate is never announced, switch or not.
20753
+ */
20754
+ notifySensorIgnored: boolean().optional()
20730
20755
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
20731
20756
  var NcAlarmModeDelaysEditListSchema = array(object({
20732
20757
  mode: AlarmArmModeSchema,
@@ -20739,7 +20764,9 @@ var NcAlarmModeDelaysEditListSchema = array(object({
20739
20764
  /** D706: set the switch, or `null` to clear it (back to the default, false). */
20740
20765
  notifyDuringEntryDelay: boolean().nullable().optional(),
20741
20766
  /** D706: set the switch, or `null` to clear it (back to the default, false). */
20742
- notifySensorRewatched: boolean().nullable().optional()
20767
+ notifySensorRewatched: boolean().nullable().optional(),
20768
+ /** D735: set the switch, or `null` to clear it (back to the default, false). */
20769
+ notifySensorIgnored: boolean().nullable().optional()
20743
20770
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
20744
20771
  /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
20745
20772
  var NcAlarmNonBlockingEditSchema = object({
@@ -28491,6 +28518,14 @@ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), ar
28491
28518
  */
28492
28519
  alive: boolean()
28493
28520
  })), method(object({
28521
+ deviceId: number().int().nonnegative(),
28522
+ sessionId: string(),
28523
+ downlinkMbps: number().nonnegative()
28524
+ }), object({
28525
+ outcome: _enum(["switching", "held"]),
28526
+ tier: CamProfileSchema.nullable(),
28527
+ reason: string()
28528
+ }), { kind: "mutation" }), method(object({
28494
28529
  deviceId: number().int().nonnegative(),
28495
28530
  sessionId: string()
28496
28531
  }), WebrtcSessionDebugSchema, { kind: "query" });
@@ -31166,7 +31201,8 @@ var ConnectionEndpointSchema = object({
31166
31201
  "lan-ipv6",
31167
31202
  "public",
31168
31203
  "loopback",
31169
- "app-link"
31204
+ "app-link",
31205
+ "custom"
31170
31206
  ]),
31171
31207
  /**
31172
31208
  * Originating-interface kind — `lan`/`wifi`/`docker`/`vpn`/`other`/
@@ -31235,7 +31271,7 @@ var GetConnectionEndpointsResultSchema = object({
31235
31271
  localPort: AdvertisedLocalPortSchema
31236
31272
  });
31237
31273
  /**
31238
- * The address flagged for notification links. `baseUrl: null` = AUTO, which
31274
+ * The address flagged for notification taps (`notificationTaps`). `baseUrl: null` = AUTO, which
31239
31275
  * since D709 is the CamStack app link; `resolved` reports the effective value,
31240
31276
  * so the UI can show it either way.
31241
31277
  */
@@ -31269,13 +31305,12 @@ var ViewerEndpointsSchema = object({
31269
31305
  resolved: array(string()).readonly()
31270
31306
  });
31271
31307
  /**
31272
- * One audience's hub address (see `hub-address.ts`): the operator's stored
31273
- * override, the address in use, and where it came from.
31308
+ * DEPRECATED compatibility shape of `getHubAddresses` (D729 amendment, kept
31309
+ * for addons built against it): `cluster` is the `internal` role, `browser`
31310
+ * the `publicEndpoint` role; `override` is the flagged address.
31274
31311
  */
31275
31312
  var HubAddressSnapshotSchema = object({
31276
- /** The operator's override, or null for automatic. */
31277
31313
  override: string().nullable(),
31278
- /** The address in use, or null when nothing qualifies (never loopback). */
31279
31314
  resolved: string().nullable(),
31280
31315
  source: _enum([
31281
31316
  "override",
@@ -31284,11 +31319,57 @@ var HubAddressSnapshotSchema = object({
31284
31319
  ])
31285
31320
  });
31286
31321
  var HubAddressesSchema = object({
31287
- /** "Hub address for agents" — agents' bundle/model pulls, LAN devices. */
31288
31322
  cluster: HubAddressSnapshotSchema,
31289
- /** "Public address for sign-in links" — e-mail links, OIDC, WebAuthn, OAuth2. */
31290
31323
  browser: HubAddressSnapshotSchema
31291
31324
  });
31325
+ /**
31326
+ * What an address is used for (`hub-address.ts`, D734). `appConnections` is
31327
+ * many-valued; `notificationTaps`, `publicEndpoint` and `internal` are carried
31328
+ * by at most one address.
31329
+ */
31330
+ var HubAddressRoleSchema = _enum(HUB_ADDRESS_ROLES);
31331
+ var RoleSourceSchema = _enum([
31332
+ "flagged",
31333
+ "auto",
31334
+ "none"
31335
+ ]);
31336
+ /** One single-valued role: the flagged address, the one in use, and why. */
31337
+ var HubAddressRoleResolutionSchema = object({
31338
+ /** The address carrying the flag, or null = automatic. */
31339
+ flagged: string().nullable(),
31340
+ /** The address in use, or null when nothing qualifies (never loopback). */
31341
+ resolved: string().nullable(),
31342
+ source: RoleSourceSchema
31343
+ });
31344
+ var HubAppConnectionsResolutionSchema = object({
31345
+ /** The addresses flagged `appConnections`, or empty = automatic. */
31346
+ flagged: array(string()).readonly(),
31347
+ /** What the apps race right now. */
31348
+ resolved: array(string()).readonly(),
31349
+ source: RoleSourceSchema
31350
+ });
31351
+ var HubAddressBookSchema = object({
31352
+ rows: array(ConnectionEndpointSchema.extend({
31353
+ /** In the hub's detected palette right now. */
31354
+ detected: boolean(),
31355
+ /** Entered by the operator (removable). */
31356
+ custom: boolean(),
31357
+ /** The roles explicitly flagged on this address. */
31358
+ flags: array(HubAddressRoleSchema).readonly(),
31359
+ /** The roles this address serves right now — flagged or automatic. */
31360
+ inUse: array(HubAddressRoleSchema).readonly(),
31361
+ /** The roles this address may be flagged with. */
31362
+ eligible: array(HubAddressRoleSchema).readonly()
31363
+ })).readonly(),
31364
+ roles: object({
31365
+ appConnections: HubAppConnectionsResolutionSchema,
31366
+ notificationTaps: HubAddressRoleResolutionSchema,
31367
+ internal: HubAddressRoleResolutionSchema,
31368
+ publicEndpoint: HubAddressRoleResolutionSchema
31369
+ }),
31370
+ /** The `hub=` an app link carries (D709), or null. */
31371
+ appLinkHub: string().nullable()
31372
+ });
31292
31373
  var AllowedAddressesSchema = object({
31293
31374
  /**
31294
31375
  * Allowlist of interface addresses operators have explicitly opted
@@ -31332,16 +31413,27 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
31332
31413
  * tunnel always emits `https://` regardless. */
31333
31414
  scheme: _enum(["http", "https"]).optional(),
31334
31415
  /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
31335
- * an address notification links can be flagged with, not a hub
31416
+ * an address notification taps can be flagged with, not a hub
31336
31417
  * route. Default `false`: racers and rankers never see it. */
31337
31418
  includeAppLink: boolean().optional()
31338
- }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), HubAddressesSchema), method(object({
31339
- audience: _enum(["cluster", "browser"]),
31340
- baseUrl: string().nullable()
31341
- }), HubAddressesSchema, {
31419
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(object({ scheme: _enum(["http", "https"]).optional() }).optional(), HubAddressBookSchema), method(object({
31420
+ baseUrl: string(),
31421
+ label: string().max(80)
31422
+ }), HubAddressBookSchema, {
31342
31423
  kind: "mutation",
31343
31424
  auth: "admin"
31344
- }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
31425
+ }), method(object({ baseUrl: string() }), HubAddressBookSchema, {
31426
+ kind: "mutation",
31427
+ auth: "admin"
31428
+ }), method(object({
31429
+ role: HubAddressRoleSchema,
31430
+ baseUrl: string().nullable(),
31431
+ enabled: boolean(),
31432
+ acceptInsecureHttp: boolean().optional()
31433
+ }), HubAddressBookSchema, {
31434
+ kind: "mutation",
31435
+ auth: "admin"
31436
+ }), method(_void(), HubAddressesSchema), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
31345
31437
  kind: "mutation",
31346
31438
  auth: "admin"
31347
31439
  }), method(object({
@@ -39868,12 +39960,24 @@ Object.freeze({
39868
39960
  addonId: null,
39869
39961
  access: "view"
39870
39962
  },
39963
+ "localNetwork.addCustomAddress": {
39964
+ capName: "local-network",
39965
+ capScope: "system",
39966
+ addonId: null,
39967
+ access: "create"
39968
+ },
39871
39969
  "localNetwork.downloadCa": {
39872
39970
  capName: "local-network",
39873
39971
  capScope: "system",
39874
39972
  addonId: null,
39875
39973
  access: "view"
39876
39974
  },
39975
+ "localNetwork.getAddresses": {
39976
+ capName: "local-network",
39977
+ capScope: "system",
39978
+ addonId: null,
39979
+ access: "view"
39980
+ },
39877
39981
  "localNetwork.getAllowedAddresses": {
39878
39982
  capName: "local-network",
39879
39983
  capScope: "system",
@@ -39928,6 +40032,12 @@ Object.freeze({
39928
40032
  addonId: null,
39929
40033
  access: "create"
39930
40034
  },
40035
+ "localNetwork.removeCustomAddress": {
40036
+ capName: "local-network",
40037
+ capScope: "system",
40038
+ addonId: null,
40039
+ access: "delete"
40040
+ },
39931
40041
  "localNetwork.resetAllowlistToBestMatch": {
39932
40042
  capName: "local-network",
39933
40043
  capScope: "system",
@@ -39940,13 +40050,13 @@ Object.freeze({
39940
40050
  addonId: null,
39941
40051
  access: "create"
39942
40052
  },
39943
- "localNetwork.setAllowedAddresses": {
40053
+ "localNetwork.setAddressRole": {
39944
40054
  capName: "local-network",
39945
40055
  capScope: "system",
39946
40056
  addonId: null,
39947
40057
  access: "create"
39948
40058
  },
39949
- "localNetwork.setHubAddress": {
40059
+ "localNetwork.setAllowedAddresses": {
39950
40060
  capName: "local-network",
39951
40061
  capScope: "system",
39952
40062
  addonId: null,
@@ -44200,6 +44310,12 @@ Object.freeze({
44200
44310
  addonId: null,
44201
44311
  access: "create"
44202
44312
  },
44313
+ "webrtcSession.applyDownlinkProbe": {
44314
+ capName: "webrtc-session",
44315
+ capScope: "device",
44316
+ addonId: null,
44317
+ access: "create"
44318
+ },
44203
44319
  "webrtcSession.closeSession": {
44204
44320
  capName: "webrtc-session",
44205
44321
  capScope: "device",
@@ -48122,6 +48238,11 @@ Object.freeze({
48122
48238
  form: "single",
48123
48239
  optional: false
48124
48240
  }],
48241
+ "webrtcSession.applyDownlinkProbe": [{
48242
+ name: "deviceId",
48243
+ form: "single",
48244
+ optional: false
48245
+ }],
48125
48246
  "webrtcSession.closeSession": [{
48126
48247
  name: "deviceId",
48127
48248
  form: "single",
@@ -49390,6 +49511,35 @@ function buildModelConvertProvider(deps) {
49390
49511
  };
49391
49512
  } };
49392
49513
  }
49514
+ var CONTROL_CHARS = /[\u0000-\u001f\u007f]/;
49515
+ /** Whitespace runs collapse to one space; leading/trailing whitespace goes. */
49516
+ function parseDisplayName(raw) {
49517
+ const name = raw.replace(/\s+/g, " ").trim();
49518
+ if (name.length === 0) return { kind: "reset" };
49519
+ if (CONTROL_CHARS.test(name)) return {
49520
+ kind: "invalid",
49521
+ reason: "The name contains control characters."
49522
+ };
49523
+ if (name.length > 80) return {
49524
+ kind: "invalid",
49525
+ reason: `The name is ${name.length} characters long; the limit is 80.`
49526
+ };
49527
+ return {
49528
+ kind: "set",
49529
+ name
49530
+ };
49531
+ }
49532
+ /**
49533
+ * The optional display name typed on an import card: `undefined` when left
49534
+ * empty (the source's own default applies), the cleaned name otherwise.
49535
+ * Throws with the reason when the input is not acceptable.
49536
+ */
49537
+ function optionalImportName(raw) {
49538
+ if (raw === void 0) return void 0;
49539
+ const parsed = parseDisplayName(raw);
49540
+ if (parsed.kind === "invalid") throw new Error(`Display name rejected: ${parsed.reason}`);
49541
+ return parsed.kind === "set" ? parsed.name : void 0;
49542
+ }
49393
49543
  //#endregion
49394
49544
  //#region src/model-registry.ts
49395
49545
  /**
@@ -49421,22 +49571,18 @@ function upsertAvailability(map, modelId, nodeId, entry) {
49421
49571
  });
49422
49572
  }
49423
49573
  /**
49424
- * Pure registry operations over the custom-model list. Kept side-effect-free
49425
- * so they unit-test without a live `BaseAddon` context — the addon class is a
49426
- * thin durable-state wrapper around these.
49427
- */
49428
- /**
49429
49574
  * Insert or replace a descriptor keyed by `entry.id`. When the id already
49430
49575
  * exists, **formats are merged** (Scrypted multi-format accumulate) — sibling
49431
49576
  * formats on the prior entry are never dropped. Returns a new array.
49432
49577
  */
49433
- function upsertCustomModel(list, descriptor) {
49578
+ function upsertCustomModel(list, descriptor, nameOrigin = "source") {
49434
49579
  const prev = list.find((m) => m.entry.id === descriptor.entry.id);
49435
49580
  const merged = prev === void 0 ? descriptor : {
49436
49581
  stepId: descriptor.stepId,
49437
49582
  entry: {
49438
49583
  ...prev.entry,
49439
49584
  ...descriptor.entry,
49585
+ name: nameOrigin === "operator" ? descriptor.entry.name : prev.entry.name,
49440
49586
  formats: {
49441
49587
  ...prev.entry.formats,
49442
49588
  ...descriptor.entry.formats
@@ -49445,6 +49591,43 @@ function upsertCustomModel(list, descriptor) {
49445
49591
  };
49446
49592
  return [...list.filter((m) => m.entry.id !== descriptor.entry.id), merged];
49447
49593
  }
49594
+ /**
49595
+ * Rename one IMPORTED registry row in place. Only `entry.name` changes — the
49596
+ * id, step, formats and every pin keyed by the id stay exactly as they were.
49597
+ * An empty name resets the row to the name it was imported with. Throws on an
49598
+ * unknown id, a CamStack catalog row, or a name `parseDisplayName` rejects.
49599
+ */
49600
+ function renameCustomModel(list, importedNames, modelId, rawName) {
49601
+ const row = list.find((m) => m.entry.id === modelId);
49602
+ if (row === void 0) throw new Error(`renameCustomModel: model "${modelId}" is not registered`);
49603
+ if (!isRenameableEntry(row.entry)) throw new Error(`renameCustomModel: "${modelId}" is a CamStack catalog model and keeps its name`);
49604
+ const parsed = parseDisplayName(rawName);
49605
+ if (parsed.kind === "invalid") throw new Error(`renameCustomModel: ${parsed.reason}`);
49606
+ const original = importedNames[modelId] ?? row.entry.name;
49607
+ const name = parsed.kind === "set" ? parsed.name : original;
49608
+ const nextNames = { ...importedNames };
49609
+ if (parsed.kind === "set" && name !== original) nextNames[modelId] = original;
49610
+ else delete nextNames[modelId];
49611
+ return {
49612
+ list: list.map((m) => m.entry.id === modelId ? {
49613
+ stepId: m.stepId,
49614
+ entry: {
49615
+ ...m.entry,
49616
+ name
49617
+ }
49618
+ } : m),
49619
+ importedNames: nextNames,
49620
+ name
49621
+ };
49622
+ }
49623
+ /** An imported row can be renamed; a CamStack catalog model cannot. */
49624
+ function isRenameableEntry(entry) {
49625
+ return inferModelProvider({
49626
+ id: entry.id,
49627
+ description: entry.description,
49628
+ provider: entry.provider
49629
+ }) !== "camstack";
49630
+ }
49448
49631
  /** Drop the descriptor with the given `entry.id`. Reports whether anything changed. */
49449
49632
  function removeCustomModel(list, modelId) {
49450
49633
  const next = list.filter((m) => m.entry.id !== modelId);
@@ -50292,6 +50475,19 @@ var modelStudioActions = defineCustomActions({
50292
50475
  clearedPins: number().int().nonnegative()
50293
50476
  }), { kind: "mutation" }),
50294
50477
  /**
50478
+ * Rename an IMPORTED registry row (Frigate, Frigate+, Scrypted, uploads,
50479
+ * conversions) in place. Only `entry.name` changes — never the id, so pins
50480
+ * and availability are untouched. An empty name restores the name the model
50481
+ * was imported with. CamStack catalog models are refused.
50482
+ */
50483
+ renameCustomModel: customAction(object({
50484
+ modelId: string().min(1),
50485
+ name: string()
50486
+ }), object({
50487
+ modelId: string(),
50488
+ name: string()
50489
+ }), { kind: "mutation" }),
50490
+ /**
50295
50491
  * Delete one format of a Library model from one node. Keeps the registry row
50296
50492
  * and sibling formats/nodes; clears only that availability chip.
50297
50493
  */
@@ -50380,6 +50576,8 @@ var modelStudioActions = defineCustomActions({
50380
50576
  })),
50381
50577
  importScryptedCatalogModel: customAction(object({
50382
50578
  manifestId: string(),
50579
+ /** Optional display name typed on the import card. */
50580
+ name: string().optional(),
50383
50581
  targetNodeId: string().optional()
50384
50582
  }), CustomModelDescriptorSchema, { kind: "mutation" }),
50385
50583
  /**
@@ -50409,7 +50607,8 @@ var modelStudioActions = defineCustomActions({
50409
50607
  modelId: string().optional(),
50410
50608
  name: string().optional(),
50411
50609
  /**
50412
- * Native artifact class from the list row (`plusDownloadTraits`). The
50610
+ * Native artifact class of the chosen build (`plusImportLane`: neural →
50611
+ * onnx, coral → tflite). The
50413
50612
  * model-detail API does not always carry `supportedDetectors`, so the
50414
50613
  * browser passes what the catalog already knew (fixes ssd/yolo-generic
50415
50614
  * EdgeTPU rows that `plusTypeTraits` alone would call onnx).
@@ -50461,6 +50660,8 @@ var modelStudioActions = defineCustomActions({
50461
50660
  //#region src/model-studio.addon.ts
50462
50661
  var KEY_CUSTOM_MODELS = "customModels";
50463
50662
  var KEY_AVAILABILITY = "availability";
50663
+ var KEY_IMPORTED_NAMES = "importedNames";
50664
+ var ImportedNamesSchema = record(string(), string());
50464
50665
  var CustomModelsSchema = array(CustomModelDescriptorSchema);
50465
50666
  var pythonDirCache;
50466
50667
  function resolveAddonPythonDir() {
@@ -50523,12 +50724,18 @@ function spawnRunConvert(jobPath, pythonPath, onProgress, _sessionId) {
50523
50724
  });
50524
50725
  });
50525
50726
  }
50727
+ /** A name the operator typed replaces an existing row's name; a derived one never does. */
50728
+ function nameOriginOf(operatorName) {
50729
+ return operatorName !== void 0 ? { nameOrigin: "operator" } : {};
50730
+ }
50526
50731
  /** Global-fetch adapter for the injected Frigate+ client seam. */
50527
50732
  var plusFetch = async (url, init) => fetch(url, init);
50528
50733
  var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50529
50734
  id = "model-studio";
50530
50735
  modelsState = null;
50531
50736
  availabilityState = null;
50737
+ /** modelId → the name a renamed registry row had before its first rename. */
50738
+ importedNamesState = null;
50532
50739
  /** Lazily-built chunked-upload store (hub only) — see `upload/model-upload-store.ts`. */
50533
50740
  uploadStore = null;
50534
50741
  /**
@@ -50611,6 +50818,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50611
50818
  const state = this.state(KEY_CUSTOM_MODELS, CustomModelsSchema, []);
50612
50819
  this.modelsState = state;
50613
50820
  this.availabilityState = this.state(KEY_AVAILABILITY, ModelAvailabilityMapSchema, {});
50821
+ this.importedNamesState = this.state(KEY_IMPORTED_NAMES, ImportedNamesSchema, {});
50614
50822
  const rawNode = this.ctx.kernel?.localNodeId ?? "hub";
50615
50823
  const isHub = (rawNode.includes("/") ? rawNode.split("/")[0] : rawNode) === "hub";
50616
50824
  const pagesProvider = {
@@ -50644,7 +50852,11 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50644
50852
  return result;
50645
50853
  },
50646
50854
  abortModelUpload: async (input) => (await this.getUploadStore()).abort(input),
50647
- registerCustomModel: async (input) => this.registerCustomModel(input, { ensureEngines: true }),
50855
+ registerCustomModel: async (input) => this.registerCustomModel(input, {
50856
+ ensureEngines: true,
50857
+ nameOrigin: "operator"
50858
+ }),
50859
+ renameCustomModel: async (input) => this.renameCustomModel_(input),
50648
50860
  ensureFormatsThenDistribute: async (input) => this.ensureFormatsThenDistribute_(input.modelId),
50649
50861
  ensureModelFormats: async (input) => this.ensureModelFormats_(input.modelId, input.formats),
50650
50862
  removeCustomModel: async (input) => this.removeCustomModel(input),
@@ -50796,6 +51008,35 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50796
51008
  if (!this.modelsState) throw new Error("ModelStudioAddon: durable state not initialized");
50797
51009
  return this.modelsState;
50798
51010
  }
51011
+ get importedNames_() {
51012
+ if (!this.importedNamesState) throw new Error("ModelStudioAddon: imported-names state not initialized");
51013
+ return this.importedNamesState;
51014
+ }
51015
+ /**
51016
+ * Rename an imported registry row in place (`renameCustomModel` decides).
51017
+ * The id is untouched, so pins, availability and files are unaffected; the
51018
+ * pipeline's catalog view picks the new name up on its next registry read.
51019
+ */
51020
+ async renameCustomModel_(input) {
51021
+ const names = await this.importedNames_.get();
51022
+ const outcome = {};
51023
+ await this.state_.update((prev) => {
51024
+ const renamed = renameCustomModel(prev, names, input.modelId, input.name);
51025
+ outcome.result = renamed;
51026
+ return renamed.list;
51027
+ });
51028
+ const result = outcome.result;
51029
+ if (result === void 0) throw new Error("renameCustomModel: registry update did not run");
51030
+ await this.importedNames_.set(result.importedNames);
51031
+ this.ctx.logger.info("Renamed custom model", { meta: {
51032
+ modelId: input.modelId,
51033
+ name: result.name
51034
+ } });
51035
+ return {
51036
+ modelId: input.modelId,
51037
+ name: result.name
51038
+ };
51039
+ }
50799
51040
  get availability_() {
50800
51041
  if (!this.availabilityState) throw new Error("ModelStudioAddon: availability state not initialized");
50801
51042
  return this.availabilityState;
@@ -50917,6 +51158,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50917
51158
  * `targetNodeId` lands the HF URL on that node via download-on-target.
50918
51159
  */
50919
51160
  async importScryptedCatalogFromManifest(input) {
51161
+ const name = optionalImportName(input.name);
50920
51162
  const entry = getScryptedManifestEntry(input.manifestId);
50921
51163
  if (!entry) throw new Error(`importScryptedCatalogModel: unknown manifest id "${input.manifestId}"`);
50922
51164
  let drift;
@@ -50936,8 +51178,15 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50936
51178
  };
50937
51179
  }
50938
51180
  if (drift.status !== "ok") throw new Error(`importScryptedCatalogModel: entry "${entry.id}" is blocked by drift (${drift.status}${drift.details ? `: ${drift.details}` : ""})`);
50939
- const descriptor = buildScryptedRegistryDescriptor(entry);
50940
- await this.registerCustomModel(descriptor);
51181
+ const built = buildScryptedRegistryDescriptor(entry);
51182
+ const descriptor = name !== void 0 ? {
51183
+ stepId: built.stepId,
51184
+ entry: {
51185
+ ...built.entry,
51186
+ name
51187
+ }
51188
+ } : built;
51189
+ await this.registerCustomModel(descriptor, nameOriginOf(name));
50941
51190
  if (input.targetNodeId !== void 0 && input.targetNodeId.length > 0) await this.distributeModel({
50942
51191
  modelId: descriptor.entry.id,
50943
51192
  nodeId: input.targetNodeId,
@@ -50959,11 +51208,12 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50959
51208
  async importFrigateCatalogModel(input) {
50960
51209
  const cat = FRIGATE_PUBLIC_CATALOG.find((c) => c.id === input.catalogId);
50961
51210
  if (!cat) throw new Error(`importFrigateCatalogModel: unknown catalog id "${input.catalogId}"`);
51211
+ const name = optionalImportName(input.name);
50962
51212
  const descriptor = buildFrigateCatalogDescriptor(input.catalogId, {
50963
51213
  ...input.modelId !== void 0 ? { modelId: input.modelId } : {},
50964
- ...input.name !== void 0 ? { name: input.name } : {}
51214
+ ...name !== void 0 ? { name } : {}
50965
51215
  });
50966
- await this.registerCustomModel(descriptor);
51216
+ await this.registerCustomModel(descriptor, nameOriginOf(name));
50967
51217
  let bytes = Math.round(cat.sizeMB * 1e6);
50968
51218
  if (input.targetNodeId !== void 0 && input.targetNodeId.length > 0) {
50969
51219
  const landed = await this.distributeModel({
@@ -50994,6 +51244,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50994
51244
  */
50995
51245
  async importFrigatePlusModel(input) {
50996
51246
  const modelId = frigatePlusRegistryId(input.plusModelId);
51247
+ const operatorName = optionalImportName(input.name);
50997
51248
  const key = this.config.frigatePlusApiKey ?? "";
50998
51249
  const { info, downloadUrl } = await requestPlusModelDownload(plusFetch, key, input.plusModelId);
50999
51250
  const typeTraits = plusTypeTraits(info.type);
@@ -51018,12 +51269,15 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51018
51269
  const sha256 = await ModelStudioAddon.fileSha256(destPath);
51019
51270
  const descriptor = plusInfoToDescriptor(info, {
51020
51271
  modelId,
51021
- name: input.name?.trim() || `Frigate+ ${info.type} (${input.plusModelId.slice(0, 8)})`,
51272
+ name: operatorName ?? `Frigate+ ${info.type} (${input.plusModelId.slice(0, 8)})`,
51022
51273
  filename,
51023
51274
  sizeMB: Number((bytes / 1e6).toFixed(1)),
51024
51275
  ...input.format !== void 0 ? { format: input.format } : {}
51025
51276
  });
51026
- await this.registerCustomModel(descriptor, { ensureEngines: false });
51277
+ await this.registerCustomModel(descriptor, {
51278
+ ensureEngines: false,
51279
+ ...nameOriginOf(operatorName)
51280
+ });
51027
51281
  await this.availability_.update((prev) => upsertAvailability(prev, modelId, "hub", {
51028
51282
  format: traits.format,
51029
51283
  sha256,
@@ -51056,7 +51310,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51056
51310
  async registerCustomModel(input, opts = {}) {
51057
51311
  if (!input.stepId.trim()) throw new Error("registerCustomModel: stepId is required");
51058
51312
  if (!input.entry.id.trim()) throw new Error("registerCustomModel: entry.id is required");
51059
- await this.state_.update((prev) => upsertCustomModel(prev, input));
51313
+ await this.state_.update((prev) => upsertCustomModel(prev, input, opts.nameOrigin));
51060
51314
  this.ctx.logger.info("Registered custom model", { meta: {
51061
51315
  modelId: input.entry.id,
51062
51316
  stepId: input.stepId
@@ -51313,6 +51567,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51313
51567
  removed = result.removed;
51314
51568
  return result.next;
51315
51569
  });
51570
+ await this.importedNames_.update((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => id !== input.modelId)));
51316
51571
  this.ctx.logger.info("Removed custom model", { meta: {
51317
51572
  modelId: input.modelId,
51318
51573
  removed,