@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
@@ -5747,7 +5747,7 @@ var ZodIssueCode = {
5747
5747
  var ZodFirstPartyTypeKind;
5748
5748
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5749
5749
  //#endregion
5750
- //#region ../types/dist/sleep-DXKLNFvA.mjs
5750
+ //#region ../types/dist/sleep-C16NCZR2.mjs
5751
5751
  /**
5752
5752
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5753
5753
  * window to float samples (D455).
@@ -11124,6 +11124,19 @@ var ModelProviderIdSchema = _enum([
11124
11124
  "custom"
11125
11125
  ]);
11126
11126
  /**
11127
+ * Resolve a catalog entry's picker provider. An explicit stamp always wins;
11128
+ * otherwise Frigate/Scrypted are recognised from id prefix or description.
11129
+ * Unstamped BYO registry rows fall through to `custom`. Family `yolov9` is
11130
+ * NOT a Scrypted signal — CamStack ships that family too.
11131
+ */
11132
+ function inferModelProvider(entry) {
11133
+ if (entry.provider !== void 0) return entry.provider;
11134
+ const haystack = `${entry.id} ${entry.description ?? ""}`;
11135
+ if (/scrypted/i.test(haystack)) return "scrypted";
11136
+ if (/frigate/i.test(haystack)) return "frigate";
11137
+ return "custom";
11138
+ }
11139
+ /**
11127
11140
  * The licence of a model, as a record rather than a bare SPDX string: a
11128
11141
  * permissive CODE licence routinely sits on top of restrictive WEIGHTS or
11129
11142
  * DATA, and the obligations (attribution, modification notices, source offers)
@@ -16523,6 +16536,12 @@ method(_void(), array(string()).readonly(), { auth: "admin" }), method(object({
16523
16536
  kind: "mutation",
16524
16537
  auth: "admin"
16525
16538
  });
16539
+ var HUB_ADDRESS_ROLES = [
16540
+ "appConnections",
16541
+ "notificationTaps",
16542
+ "internal",
16543
+ "publicEndpoint"
16544
+ ];
16526
16545
  /**
16527
16546
  * Transport bounds for AI capability methods.
16528
16547
  *
@@ -20700,7 +20719,13 @@ var NcAlarmModeDelaysListSchema = array(object({
20700
20719
  * Absent = false (logged, not sent). Even when true it is not sent when that same sensor
20701
20720
  * is the one that starts the entry delay — the entry-delay notification already names it.
20702
20721
  */
20703
- notifySensorRewatched: boolean().optional()
20722
+ notifySensorRewatched: boolean().optional(),
20723
+ /**
20724
+ * D735: notify "ignored" when a sensor this arm bypassed really MOVED while armed (its own
20725
+ * change time is after the arm) and set nothing off. Absent = false (logged, not sent). A
20726
+ * re-sync, a recovery from `unavailable` or a duplicate is never announced, switch or not.
20727
+ */
20728
+ notifySensorIgnored: boolean().optional()
20704
20729
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
20705
20730
  var NcAlarmModeDelaysEditListSchema = array(object({
20706
20731
  mode: AlarmArmModeSchema,
@@ -20713,7 +20738,9 @@ var NcAlarmModeDelaysEditListSchema = array(object({
20713
20738
  /** D706: set the switch, or `null` to clear it (back to the default, false). */
20714
20739
  notifyDuringEntryDelay: boolean().nullable().optional(),
20715
20740
  /** D706: set the switch, or `null` to clear it (back to the default, false). */
20716
- notifySensorRewatched: boolean().nullable().optional()
20741
+ notifySensorRewatched: boolean().nullable().optional(),
20742
+ /** D735: set the switch, or `null` to clear it (back to the default, false). */
20743
+ notifySensorIgnored: boolean().nullable().optional()
20717
20744
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
20718
20745
  /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
20719
20746
  var NcAlarmNonBlockingEditSchema = object({
@@ -28465,6 +28492,14 @@ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), ar
28465
28492
  */
28466
28493
  alive: boolean()
28467
28494
  })), method(object({
28495
+ deviceId: number().int().nonnegative(),
28496
+ sessionId: string(),
28497
+ downlinkMbps: number().nonnegative()
28498
+ }), object({
28499
+ outcome: _enum(["switching", "held"]),
28500
+ tier: CamProfileSchema.nullable(),
28501
+ reason: string()
28502
+ }), { kind: "mutation" }), method(object({
28468
28503
  deviceId: number().int().nonnegative(),
28469
28504
  sessionId: string()
28470
28505
  }), WebrtcSessionDebugSchema, { kind: "query" });
@@ -31140,7 +31175,8 @@ var ConnectionEndpointSchema = object({
31140
31175
  "lan-ipv6",
31141
31176
  "public",
31142
31177
  "loopback",
31143
- "app-link"
31178
+ "app-link",
31179
+ "custom"
31144
31180
  ]),
31145
31181
  /**
31146
31182
  * Originating-interface kind — `lan`/`wifi`/`docker`/`vpn`/`other`/
@@ -31209,7 +31245,7 @@ var GetConnectionEndpointsResultSchema = object({
31209
31245
  localPort: AdvertisedLocalPortSchema
31210
31246
  });
31211
31247
  /**
31212
- * The address flagged for notification links. `baseUrl: null` = AUTO, which
31248
+ * The address flagged for notification taps (`notificationTaps`). `baseUrl: null` = AUTO, which
31213
31249
  * since D709 is the CamStack app link; `resolved` reports the effective value,
31214
31250
  * so the UI can show it either way.
31215
31251
  */
@@ -31243,13 +31279,12 @@ var ViewerEndpointsSchema = object({
31243
31279
  resolved: array(string()).readonly()
31244
31280
  });
31245
31281
  /**
31246
- * One audience's hub address (see `hub-address.ts`): the operator's stored
31247
- * override, the address in use, and where it came from.
31282
+ * DEPRECATED compatibility shape of `getHubAddresses` (D729 amendment, kept
31283
+ * for addons built against it): `cluster` is the `internal` role, `browser`
31284
+ * the `publicEndpoint` role; `override` is the flagged address.
31248
31285
  */
31249
31286
  var HubAddressSnapshotSchema = object({
31250
- /** The operator's override, or null for automatic. */
31251
31287
  override: string().nullable(),
31252
- /** The address in use, or null when nothing qualifies (never loopback). */
31253
31288
  resolved: string().nullable(),
31254
31289
  source: _enum([
31255
31290
  "override",
@@ -31258,11 +31293,57 @@ var HubAddressSnapshotSchema = object({
31258
31293
  ])
31259
31294
  });
31260
31295
  var HubAddressesSchema = object({
31261
- /** "Hub address for agents" — agents' bundle/model pulls, LAN devices. */
31262
31296
  cluster: HubAddressSnapshotSchema,
31263
- /** "Public address for sign-in links" — e-mail links, OIDC, WebAuthn, OAuth2. */
31264
31297
  browser: HubAddressSnapshotSchema
31265
31298
  });
31299
+ /**
31300
+ * What an address is used for (`hub-address.ts`, D734). `appConnections` is
31301
+ * many-valued; `notificationTaps`, `publicEndpoint` and `internal` are carried
31302
+ * by at most one address.
31303
+ */
31304
+ var HubAddressRoleSchema = _enum(HUB_ADDRESS_ROLES);
31305
+ var RoleSourceSchema = _enum([
31306
+ "flagged",
31307
+ "auto",
31308
+ "none"
31309
+ ]);
31310
+ /** One single-valued role: the flagged address, the one in use, and why. */
31311
+ var HubAddressRoleResolutionSchema = object({
31312
+ /** The address carrying the flag, or null = automatic. */
31313
+ flagged: string().nullable(),
31314
+ /** The address in use, or null when nothing qualifies (never loopback). */
31315
+ resolved: string().nullable(),
31316
+ source: RoleSourceSchema
31317
+ });
31318
+ var HubAppConnectionsResolutionSchema = object({
31319
+ /** The addresses flagged `appConnections`, or empty = automatic. */
31320
+ flagged: array(string()).readonly(),
31321
+ /** What the apps race right now. */
31322
+ resolved: array(string()).readonly(),
31323
+ source: RoleSourceSchema
31324
+ });
31325
+ var HubAddressBookSchema = object({
31326
+ rows: array(ConnectionEndpointSchema.extend({
31327
+ /** In the hub's detected palette right now. */
31328
+ detected: boolean(),
31329
+ /** Entered by the operator (removable). */
31330
+ custom: boolean(),
31331
+ /** The roles explicitly flagged on this address. */
31332
+ flags: array(HubAddressRoleSchema).readonly(),
31333
+ /** The roles this address serves right now — flagged or automatic. */
31334
+ inUse: array(HubAddressRoleSchema).readonly(),
31335
+ /** The roles this address may be flagged with. */
31336
+ eligible: array(HubAddressRoleSchema).readonly()
31337
+ })).readonly(),
31338
+ roles: object({
31339
+ appConnections: HubAppConnectionsResolutionSchema,
31340
+ notificationTaps: HubAddressRoleResolutionSchema,
31341
+ internal: HubAddressRoleResolutionSchema,
31342
+ publicEndpoint: HubAddressRoleResolutionSchema
31343
+ }),
31344
+ /** The `hub=` an app link carries (D709), or null. */
31345
+ appLinkHub: string().nullable()
31346
+ });
31266
31347
  var AllowedAddressesSchema = object({
31267
31348
  /**
31268
31349
  * Allowlist of interface addresses operators have explicitly opted
@@ -31306,16 +31387,27 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
31306
31387
  * tunnel always emits `https://` regardless. */
31307
31388
  scheme: _enum(["http", "https"]).optional(),
31308
31389
  /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
31309
- * an address notification links can be flagged with, not a hub
31390
+ * an address notification taps can be flagged with, not a hub
31310
31391
  * route. Default `false`: racers and rankers never see it. */
31311
31392
  includeAppLink: boolean().optional()
31312
- }), 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({
31313
- audience: _enum(["cluster", "browser"]),
31314
- baseUrl: string().nullable()
31315
- }), HubAddressesSchema, {
31393
+ }), 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({
31394
+ baseUrl: string(),
31395
+ label: string().max(80)
31396
+ }), HubAddressBookSchema, {
31316
31397
  kind: "mutation",
31317
31398
  auth: "admin"
31318
- }), 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, {
31399
+ }), method(object({ baseUrl: string() }), HubAddressBookSchema, {
31400
+ kind: "mutation",
31401
+ auth: "admin"
31402
+ }), method(object({
31403
+ role: HubAddressRoleSchema,
31404
+ baseUrl: string().nullable(),
31405
+ enabled: boolean(),
31406
+ acceptInsecureHttp: boolean().optional()
31407
+ }), HubAddressBookSchema, {
31408
+ kind: "mutation",
31409
+ auth: "admin"
31410
+ }), 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, {
31319
31411
  kind: "mutation",
31320
31412
  auth: "admin"
31321
31413
  }), method(object({
@@ -39842,12 +39934,24 @@ Object.freeze({
39842
39934
  addonId: null,
39843
39935
  access: "view"
39844
39936
  },
39937
+ "localNetwork.addCustomAddress": {
39938
+ capName: "local-network",
39939
+ capScope: "system",
39940
+ addonId: null,
39941
+ access: "create"
39942
+ },
39845
39943
  "localNetwork.downloadCa": {
39846
39944
  capName: "local-network",
39847
39945
  capScope: "system",
39848
39946
  addonId: null,
39849
39947
  access: "view"
39850
39948
  },
39949
+ "localNetwork.getAddresses": {
39950
+ capName: "local-network",
39951
+ capScope: "system",
39952
+ addonId: null,
39953
+ access: "view"
39954
+ },
39851
39955
  "localNetwork.getAllowedAddresses": {
39852
39956
  capName: "local-network",
39853
39957
  capScope: "system",
@@ -39902,6 +40006,12 @@ Object.freeze({
39902
40006
  addonId: null,
39903
40007
  access: "create"
39904
40008
  },
40009
+ "localNetwork.removeCustomAddress": {
40010
+ capName: "local-network",
40011
+ capScope: "system",
40012
+ addonId: null,
40013
+ access: "delete"
40014
+ },
39905
40015
  "localNetwork.resetAllowlistToBestMatch": {
39906
40016
  capName: "local-network",
39907
40017
  capScope: "system",
@@ -39914,13 +40024,13 @@ Object.freeze({
39914
40024
  addonId: null,
39915
40025
  access: "create"
39916
40026
  },
39917
- "localNetwork.setAllowedAddresses": {
40027
+ "localNetwork.setAddressRole": {
39918
40028
  capName: "local-network",
39919
40029
  capScope: "system",
39920
40030
  addonId: null,
39921
40031
  access: "create"
39922
40032
  },
39923
- "localNetwork.setHubAddress": {
40033
+ "localNetwork.setAllowedAddresses": {
39924
40034
  capName: "local-network",
39925
40035
  capScope: "system",
39926
40036
  addonId: null,
@@ -44174,6 +44284,12 @@ Object.freeze({
44174
44284
  addonId: null,
44175
44285
  access: "create"
44176
44286
  },
44287
+ "webrtcSession.applyDownlinkProbe": {
44288
+ capName: "webrtc-session",
44289
+ capScope: "device",
44290
+ addonId: null,
44291
+ access: "create"
44292
+ },
44177
44293
  "webrtcSession.closeSession": {
44178
44294
  capName: "webrtc-session",
44179
44295
  capScope: "device",
@@ -48096,6 +48212,11 @@ Object.freeze({
48096
48212
  form: "single",
48097
48213
  optional: false
48098
48214
  }],
48215
+ "webrtcSession.applyDownlinkProbe": [{
48216
+ name: "deviceId",
48217
+ form: "single",
48218
+ optional: false
48219
+ }],
48099
48220
  "webrtcSession.closeSession": [{
48100
48221
  name: "deviceId",
48101
48222
  form: "single",
@@ -49364,6 +49485,35 @@ function buildModelConvertProvider(deps) {
49364
49485
  };
49365
49486
  } };
49366
49487
  }
49488
+ var CONTROL_CHARS = /[\u0000-\u001f\u007f]/;
49489
+ /** Whitespace runs collapse to one space; leading/trailing whitespace goes. */
49490
+ function parseDisplayName(raw) {
49491
+ const name = raw.replace(/\s+/g, " ").trim();
49492
+ if (name.length === 0) return { kind: "reset" };
49493
+ if (CONTROL_CHARS.test(name)) return {
49494
+ kind: "invalid",
49495
+ reason: "The name contains control characters."
49496
+ };
49497
+ if (name.length > 80) return {
49498
+ kind: "invalid",
49499
+ reason: `The name is ${name.length} characters long; the limit is 80.`
49500
+ };
49501
+ return {
49502
+ kind: "set",
49503
+ name
49504
+ };
49505
+ }
49506
+ /**
49507
+ * The optional display name typed on an import card: `undefined` when left
49508
+ * empty (the source's own default applies), the cleaned name otherwise.
49509
+ * Throws with the reason when the input is not acceptable.
49510
+ */
49511
+ function optionalImportName(raw) {
49512
+ if (raw === void 0) return void 0;
49513
+ const parsed = parseDisplayName(raw);
49514
+ if (parsed.kind === "invalid") throw new Error(`Display name rejected: ${parsed.reason}`);
49515
+ return parsed.kind === "set" ? parsed.name : void 0;
49516
+ }
49367
49517
  //#endregion
49368
49518
  //#region src/model-registry.ts
49369
49519
  /**
@@ -49395,22 +49545,18 @@ function upsertAvailability(map, modelId, nodeId, entry) {
49395
49545
  });
49396
49546
  }
49397
49547
  /**
49398
- * Pure registry operations over the custom-model list. Kept side-effect-free
49399
- * so they unit-test without a live `BaseAddon` context — the addon class is a
49400
- * thin durable-state wrapper around these.
49401
- */
49402
- /**
49403
49548
  * Insert or replace a descriptor keyed by `entry.id`. When the id already
49404
49549
  * exists, **formats are merged** (Scrypted multi-format accumulate) — sibling
49405
49550
  * formats on the prior entry are never dropped. Returns a new array.
49406
49551
  */
49407
- function upsertCustomModel(list, descriptor) {
49552
+ function upsertCustomModel(list, descriptor, nameOrigin = "source") {
49408
49553
  const prev = list.find((m) => m.entry.id === descriptor.entry.id);
49409
49554
  const merged = prev === void 0 ? descriptor : {
49410
49555
  stepId: descriptor.stepId,
49411
49556
  entry: {
49412
49557
  ...prev.entry,
49413
49558
  ...descriptor.entry,
49559
+ name: nameOrigin === "operator" ? descriptor.entry.name : prev.entry.name,
49414
49560
  formats: {
49415
49561
  ...prev.entry.formats,
49416
49562
  ...descriptor.entry.formats
@@ -49419,6 +49565,43 @@ function upsertCustomModel(list, descriptor) {
49419
49565
  };
49420
49566
  return [...list.filter((m) => m.entry.id !== descriptor.entry.id), merged];
49421
49567
  }
49568
+ /**
49569
+ * Rename one IMPORTED registry row in place. Only `entry.name` changes — the
49570
+ * id, step, formats and every pin keyed by the id stay exactly as they were.
49571
+ * An empty name resets the row to the name it was imported with. Throws on an
49572
+ * unknown id, a CamStack catalog row, or a name `parseDisplayName` rejects.
49573
+ */
49574
+ function renameCustomModel(list, importedNames, modelId, rawName) {
49575
+ const row = list.find((m) => m.entry.id === modelId);
49576
+ if (row === void 0) throw new Error(`renameCustomModel: model "${modelId}" is not registered`);
49577
+ if (!isRenameableEntry(row.entry)) throw new Error(`renameCustomModel: "${modelId}" is a CamStack catalog model and keeps its name`);
49578
+ const parsed = parseDisplayName(rawName);
49579
+ if (parsed.kind === "invalid") throw new Error(`renameCustomModel: ${parsed.reason}`);
49580
+ const original = importedNames[modelId] ?? row.entry.name;
49581
+ const name = parsed.kind === "set" ? parsed.name : original;
49582
+ const nextNames = { ...importedNames };
49583
+ if (parsed.kind === "set" && name !== original) nextNames[modelId] = original;
49584
+ else delete nextNames[modelId];
49585
+ return {
49586
+ list: list.map((m) => m.entry.id === modelId ? {
49587
+ stepId: m.stepId,
49588
+ entry: {
49589
+ ...m.entry,
49590
+ name
49591
+ }
49592
+ } : m),
49593
+ importedNames: nextNames,
49594
+ name
49595
+ };
49596
+ }
49597
+ /** An imported row can be renamed; a CamStack catalog model cannot. */
49598
+ function isRenameableEntry(entry) {
49599
+ return inferModelProvider({
49600
+ id: entry.id,
49601
+ description: entry.description,
49602
+ provider: entry.provider
49603
+ }) !== "camstack";
49604
+ }
49422
49605
  /** Drop the descriptor with the given `entry.id`. Reports whether anything changed. */
49423
49606
  function removeCustomModel(list, modelId) {
49424
49607
  const next = list.filter((m) => m.entry.id !== modelId);
@@ -50266,6 +50449,19 @@ var modelStudioActions = defineCustomActions({
50266
50449
  clearedPins: number().int().nonnegative()
50267
50450
  }), { kind: "mutation" }),
50268
50451
  /**
50452
+ * Rename an IMPORTED registry row (Frigate, Frigate+, Scrypted, uploads,
50453
+ * conversions) in place. Only `entry.name` changes — never the id, so pins
50454
+ * and availability are untouched. An empty name restores the name the model
50455
+ * was imported with. CamStack catalog models are refused.
50456
+ */
50457
+ renameCustomModel: customAction(object({
50458
+ modelId: string().min(1),
50459
+ name: string()
50460
+ }), object({
50461
+ modelId: string(),
50462
+ name: string()
50463
+ }), { kind: "mutation" }),
50464
+ /**
50269
50465
  * Delete one format of a Library model from one node. Keeps the registry row
50270
50466
  * and sibling formats/nodes; clears only that availability chip.
50271
50467
  */
@@ -50354,6 +50550,8 @@ var modelStudioActions = defineCustomActions({
50354
50550
  })),
50355
50551
  importScryptedCatalogModel: customAction(object({
50356
50552
  manifestId: string(),
50553
+ /** Optional display name typed on the import card. */
50554
+ name: string().optional(),
50357
50555
  targetNodeId: string().optional()
50358
50556
  }), CustomModelDescriptorSchema, { kind: "mutation" }),
50359
50557
  /**
@@ -50383,7 +50581,8 @@ var modelStudioActions = defineCustomActions({
50383
50581
  modelId: string().optional(),
50384
50582
  name: string().optional(),
50385
50583
  /**
50386
- * Native artifact class from the list row (`plusDownloadTraits`). The
50584
+ * Native artifact class of the chosen build (`plusImportLane`: neural →
50585
+ * onnx, coral → tflite). The
50387
50586
  * model-detail API does not always carry `supportedDetectors`, so the
50388
50587
  * browser passes what the catalog already knew (fixes ssd/yolo-generic
50389
50588
  * EdgeTPU rows that `plusTypeTraits` alone would call onnx).
@@ -50435,6 +50634,8 @@ var modelStudioActions = defineCustomActions({
50435
50634
  //#region src/model-studio.addon.ts
50436
50635
  var KEY_CUSTOM_MODELS = "customModels";
50437
50636
  var KEY_AVAILABILITY = "availability";
50637
+ var KEY_IMPORTED_NAMES = "importedNames";
50638
+ var ImportedNamesSchema = record(string(), string());
50438
50639
  var CustomModelsSchema = array(CustomModelDescriptorSchema);
50439
50640
  var pythonDirCache;
50440
50641
  function resolveAddonPythonDir() {
@@ -50497,12 +50698,18 @@ function spawnRunConvert(jobPath, pythonPath, onProgress, _sessionId) {
50497
50698
  });
50498
50699
  });
50499
50700
  }
50701
+ /** A name the operator typed replaces an existing row's name; a derived one never does. */
50702
+ function nameOriginOf(operatorName) {
50703
+ return operatorName !== void 0 ? { nameOrigin: "operator" } : {};
50704
+ }
50500
50705
  /** Global-fetch adapter for the injected Frigate+ client seam. */
50501
50706
  var plusFetch = async (url, init) => fetch(url, init);
50502
50707
  var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50503
50708
  id = "model-studio";
50504
50709
  modelsState = null;
50505
50710
  availabilityState = null;
50711
+ /** modelId → the name a renamed registry row had before its first rename. */
50712
+ importedNamesState = null;
50506
50713
  /** Lazily-built chunked-upload store (hub only) — see `upload/model-upload-store.ts`. */
50507
50714
  uploadStore = null;
50508
50715
  /**
@@ -50585,6 +50792,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50585
50792
  const state = this.state(KEY_CUSTOM_MODELS, CustomModelsSchema, []);
50586
50793
  this.modelsState = state;
50587
50794
  this.availabilityState = this.state(KEY_AVAILABILITY, ModelAvailabilityMapSchema, {});
50795
+ this.importedNamesState = this.state(KEY_IMPORTED_NAMES, ImportedNamesSchema, {});
50588
50796
  const rawNode = this.ctx.kernel?.localNodeId ?? "hub";
50589
50797
  const isHub = (rawNode.includes("/") ? rawNode.split("/")[0] : rawNode) === "hub";
50590
50798
  const pagesProvider = {
@@ -50618,7 +50826,11 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50618
50826
  return result;
50619
50827
  },
50620
50828
  abortModelUpload: async (input) => (await this.getUploadStore()).abort(input),
50621
- registerCustomModel: async (input) => this.registerCustomModel(input, { ensureEngines: true }),
50829
+ registerCustomModel: async (input) => this.registerCustomModel(input, {
50830
+ ensureEngines: true,
50831
+ nameOrigin: "operator"
50832
+ }),
50833
+ renameCustomModel: async (input) => this.renameCustomModel_(input),
50622
50834
  ensureFormatsThenDistribute: async (input) => this.ensureFormatsThenDistribute_(input.modelId),
50623
50835
  ensureModelFormats: async (input) => this.ensureModelFormats_(input.modelId, input.formats),
50624
50836
  removeCustomModel: async (input) => this.removeCustomModel(input),
@@ -50770,6 +50982,35 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50770
50982
  if (!this.modelsState) throw new Error("ModelStudioAddon: durable state not initialized");
50771
50983
  return this.modelsState;
50772
50984
  }
50985
+ get importedNames_() {
50986
+ if (!this.importedNamesState) throw new Error("ModelStudioAddon: imported-names state not initialized");
50987
+ return this.importedNamesState;
50988
+ }
50989
+ /**
50990
+ * Rename an imported registry row in place (`renameCustomModel` decides).
50991
+ * The id is untouched, so pins, availability and files are unaffected; the
50992
+ * pipeline's catalog view picks the new name up on its next registry read.
50993
+ */
50994
+ async renameCustomModel_(input) {
50995
+ const names = await this.importedNames_.get();
50996
+ const outcome = {};
50997
+ await this.state_.update((prev) => {
50998
+ const renamed = renameCustomModel(prev, names, input.modelId, input.name);
50999
+ outcome.result = renamed;
51000
+ return renamed.list;
51001
+ });
51002
+ const result = outcome.result;
51003
+ if (result === void 0) throw new Error("renameCustomModel: registry update did not run");
51004
+ await this.importedNames_.set(result.importedNames);
51005
+ this.ctx.logger.info("Renamed custom model", { meta: {
51006
+ modelId: input.modelId,
51007
+ name: result.name
51008
+ } });
51009
+ return {
51010
+ modelId: input.modelId,
51011
+ name: result.name
51012
+ };
51013
+ }
50773
51014
  get availability_() {
50774
51015
  if (!this.availabilityState) throw new Error("ModelStudioAddon: availability state not initialized");
50775
51016
  return this.availabilityState;
@@ -50891,6 +51132,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50891
51132
  * `targetNodeId` lands the HF URL on that node via download-on-target.
50892
51133
  */
50893
51134
  async importScryptedCatalogFromManifest(input) {
51135
+ const name = optionalImportName(input.name);
50894
51136
  const entry = getScryptedManifestEntry(input.manifestId);
50895
51137
  if (!entry) throw new Error(`importScryptedCatalogModel: unknown manifest id "${input.manifestId}"`);
50896
51138
  let drift;
@@ -50910,8 +51152,15 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50910
51152
  };
50911
51153
  }
50912
51154
  if (drift.status !== "ok") throw new Error(`importScryptedCatalogModel: entry "${entry.id}" is blocked by drift (${drift.status}${drift.details ? `: ${drift.details}` : ""})`);
50913
- const descriptor = buildScryptedRegistryDescriptor(entry);
50914
- await this.registerCustomModel(descriptor);
51155
+ const built = buildScryptedRegistryDescriptor(entry);
51156
+ const descriptor = name !== void 0 ? {
51157
+ stepId: built.stepId,
51158
+ entry: {
51159
+ ...built.entry,
51160
+ name
51161
+ }
51162
+ } : built;
51163
+ await this.registerCustomModel(descriptor, nameOriginOf(name));
50915
51164
  if (input.targetNodeId !== void 0 && input.targetNodeId.length > 0) await this.distributeModel({
50916
51165
  modelId: descriptor.entry.id,
50917
51166
  nodeId: input.targetNodeId,
@@ -50933,11 +51182,12 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50933
51182
  async importFrigateCatalogModel(input) {
50934
51183
  const cat = FRIGATE_PUBLIC_CATALOG.find((c) => c.id === input.catalogId);
50935
51184
  if (!cat) throw new Error(`importFrigateCatalogModel: unknown catalog id "${input.catalogId}"`);
51185
+ const name = optionalImportName(input.name);
50936
51186
  const descriptor = buildFrigateCatalogDescriptor(input.catalogId, {
50937
51187
  ...input.modelId !== void 0 ? { modelId: input.modelId } : {},
50938
- ...input.name !== void 0 ? { name: input.name } : {}
51188
+ ...name !== void 0 ? { name } : {}
50939
51189
  });
50940
- await this.registerCustomModel(descriptor);
51190
+ await this.registerCustomModel(descriptor, nameOriginOf(name));
50941
51191
  let bytes = Math.round(cat.sizeMB * 1e6);
50942
51192
  if (input.targetNodeId !== void 0 && input.targetNodeId.length > 0) {
50943
51193
  const landed = await this.distributeModel({
@@ -50968,6 +51218,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50968
51218
  */
50969
51219
  async importFrigatePlusModel(input) {
50970
51220
  const modelId = frigatePlusRegistryId(input.plusModelId);
51221
+ const operatorName = optionalImportName(input.name);
50971
51222
  const key = this.config.frigatePlusApiKey ?? "";
50972
51223
  const { info, downloadUrl } = await requestPlusModelDownload(plusFetch, key, input.plusModelId);
50973
51224
  const typeTraits = plusTypeTraits(info.type);
@@ -50992,12 +51243,15 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50992
51243
  const sha256 = await ModelStudioAddon.fileSha256(destPath);
50993
51244
  const descriptor = plusInfoToDescriptor(info, {
50994
51245
  modelId,
50995
- name: input.name?.trim() || `Frigate+ ${info.type} (${input.plusModelId.slice(0, 8)})`,
51246
+ name: operatorName ?? `Frigate+ ${info.type} (${input.plusModelId.slice(0, 8)})`,
50996
51247
  filename,
50997
51248
  sizeMB: Number((bytes / 1e6).toFixed(1)),
50998
51249
  ...input.format !== void 0 ? { format: input.format } : {}
50999
51250
  });
51000
- await this.registerCustomModel(descriptor, { ensureEngines: false });
51251
+ await this.registerCustomModel(descriptor, {
51252
+ ensureEngines: false,
51253
+ ...nameOriginOf(operatorName)
51254
+ });
51001
51255
  await this.availability_.update((prev) => upsertAvailability(prev, modelId, "hub", {
51002
51256
  format: traits.format,
51003
51257
  sha256,
@@ -51030,7 +51284,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51030
51284
  async registerCustomModel(input, opts = {}) {
51031
51285
  if (!input.stepId.trim()) throw new Error("registerCustomModel: stepId is required");
51032
51286
  if (!input.entry.id.trim()) throw new Error("registerCustomModel: entry.id is required");
51033
- await this.state_.update((prev) => upsertCustomModel(prev, input));
51287
+ await this.state_.update((prev) => upsertCustomModel(prev, input, opts.nameOrigin));
51034
51288
  this.ctx.logger.info("Registered custom model", { meta: {
51035
51289
  modelId: input.entry.id,
51036
51290
  stepId: input.stepId
@@ -51287,6 +51541,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51287
51541
  removed = result.removed;
51288
51542
  return result.next;
51289
51543
  });
51544
+ await this.importedNames_.update((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => id !== input.modelId)));
51290
51545
  this.ctx.logger.info("Removed custom model", { meta: {
51291
51546
  modelId: input.modelId,
51292
51547
  removed,
@@ -1,5 +1,5 @@
1
1
  import { h as e, l as t, u as n, y as r } from "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare__react__loadShare__.js-DJDHChgO.mjs";
2
- import { o as i, s as a } from "./responsive-_Hjo-SPS.mjs";
2
+ import { o as i, s as a } from "./responsive-CO_rI6GL.mjs";
3
3
  import { n as o, r as s, t as c } from "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-ds_Ehzaa.mjs";
4
4
  var l = a("chevron-down", [["path", {
5
5
  d: "m6 9 6 6 6-6",
@@ -1,2 +1,2 @@
1
- import { n as e, t } from "./virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-DExLMP5B.mjs";
1
+ import { n as e, t } from "./virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-CXb44kDc.mjs";
2
2
  export { t as get, e as init };
@@ -1,6 +1,6 @@
1
1
  import { c as e, g as t, h as n, l as r, n as i, p as a, r as o, t as s, u as c, y as l } from "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare__react__loadShare__.js-DJDHChgO.mjs";
2
2
  import "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-ds_Ehzaa.mjs";
3
- import { c as u } from "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Cjm3a0Rl.mjs";
3
+ import { c as u } from "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-2O40KrYq.mjs";
4
4
  //#region ../../node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.js
5
5
  l();
6
6
  var d = (...e) => e.filter((e, t, n) => !!e && e.trim() !== "" && n.indexOf(e) === t).join(" ").trim(), f = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), p = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), m = (e) => {
@@ -1,5 +1,5 @@
1
1
  import { c as e, h as t, p as n, y as r } from "./_virtual_mf___mfe_internal__addon_model_studio_page__loadShare__react__loadShare__.js-DJDHChgO.mjs";
2
- import { s as i } from "./responsive-_Hjo-SPS.mjs";
2
+ import { s as i } from "./responsive-CO_rI6GL.mjs";
3
3
  var a = i("camera", [["path", {
4
4
  d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z",
5
5
  key: "18u6gg"
@@ -1,4 +1,4 @@
1
- import { s as e } from "./responsive-_Hjo-SPS.mjs";
1
+ import { s as e } from "./responsive-CO_rI6GL.mjs";
2
2
  var t = e("eye-off", [
3
3
  ["path", {
4
4
  d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49",
@@ -1,4 +1,4 @@
1
- import { s as e } from "./responsive-_Hjo-SPS.mjs";
1
+ import { s as e } from "./responsive-CO_rI6GL.mjs";
2
2
  var t = e("trash-2", [
3
3
  ["path", {
4
4
  d: "M10 11v6",
@@ -2753,7 +2753,7 @@ async function rr(e) {
2753
2753
  }
2754
2754
  }
2755
2755
  async function ir() {
2756
- return tr ||= rr(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-DD6MdVfP.mjs")).catch((e) => {
2756
+ return tr ||= rr(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-B4UQ5mJi.mjs")).catch((e) => {
2757
2757
  throw tr = void 0, e;
2758
2758
  }), tr;
2759
2759
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-model-studio",
3
- "version": "1.1.204",
3
+ "version": "1.1.206",
4
4
  "description": "Custom detection model registry, conversion & distribution for CamStack",
5
5
  "keywords": [
6
6
  "camstack",