@camstack/addon-model-studio 1.1.205 → 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 (17) hide show
  1. package/dist/{MotionZonesSettings-DbxM42-u.mjs → MotionZonesSettings-C5YcAYX9.mjs} +2 -2
  2. package/dist/{PrivacyMaskSettings-B6QbcEYx.mjs → PrivacyMaskSettings-B1M6BI5F.mjs} +4 -4
  3. package/dist/{SceneMonitorEditor-C_sWam-G.mjs → SceneMonitorEditor-8Nn6yjHl.mjs} +3 -3
  4. package/dist/_stub.js +5099 -4906
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-CNTVW6pB.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-Cl1eNNmq.mjs → _virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-2O40KrYq.mjs} +1 -1
  7. package/dist/{hostInit-Cz9X-HIo.mjs → hostInit-95eIoNUt.mjs} +2 -2
  8. package/dist/model-studio.addon.js +192 -35
  9. package/dist/model-studio.addon.mjs +192 -35
  10. package/dist/{player-overlays-p1wcAk-U.mjs → player-overlays-lpfo2ab8.mjs} +1 -1
  11. package/dist/remoteEntry.js +1 -1
  12. package/dist/{responsive-a5o_RHI6.mjs → responsive-CO_rI6GL.mjs} +1 -1
  13. package/dist/{scene-monitor-copy-DRLPt6W9.mjs → scene-monitor-copy-Dx3Cq-sD.mjs} +1 -1
  14. package/dist/{square-Bfh8RaUF.mjs → square-DQHBxjsn.mjs} +1 -1
  15. package/dist/{trash-2-D-9uzmfU.mjs → trash-2-C6kfv9xl.mjs} +1 -1
  16. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-CrDmlyYW.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-CXb44kDc.mjs} +1 -1
  17. package/package.json +1 -1
@@ -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({
@@ -31244,7 +31271,7 @@ var GetConnectionEndpointsResultSchema = object({
31244
31271
  localPort: AdvertisedLocalPortSchema
31245
31272
  });
31246
31273
  /**
31247
- * The address flagged for notification links. `baseUrl: null` = AUTO, which
31274
+ * The address flagged for notification taps (`notificationTaps`). `baseUrl: null` = AUTO, which
31248
31275
  * since D709 is the CamStack app link; `resolved` reports the effective value,
31249
31276
  * so the UI can show it either way.
31250
31277
  */
@@ -31279,8 +31306,8 @@ var ViewerEndpointsSchema = object({
31279
31306
  });
31280
31307
  /**
31281
31308
  * DEPRECATED compatibility shape of `getHubAddresses` (D729 amendment, kept
31282
- * one release for addons built against it): `cluster` is the `agents` role,
31283
- * `browser` the `signIn` role; `override` is the flagged address.
31309
+ * for addons built against it): `cluster` is the `internal` role, `browser`
31310
+ * the `publicEndpoint` role; `override` is the flagged address.
31284
31311
  */
31285
31312
  var HubAddressSnapshotSchema = object({
31286
31313
  override: string().nullable(),
@@ -31296,15 +31323,11 @@ var HubAddressesSchema = object({
31296
31323
  browser: HubAddressSnapshotSchema
31297
31324
  });
31298
31325
  /**
31299
- * What an address is used for (`hub-address.ts`). `viewer` is many-valued;
31300
- * `notifications`, `agents` and `signIn` are carried by at most one address.
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.
31301
31329
  */
31302
- var HubAddressRoleSchema = _enum([
31303
- "viewer",
31304
- "notifications",
31305
- "agents",
31306
- "signIn"
31307
- ]);
31330
+ var HubAddressRoleSchema = _enum(HUB_ADDRESS_ROLES);
31308
31331
  var RoleSourceSchema = _enum([
31309
31332
  "flagged",
31310
31333
  "auto",
@@ -31318,8 +31341,8 @@ var HubAddressRoleResolutionSchema = object({
31318
31341
  resolved: string().nullable(),
31319
31342
  source: RoleSourceSchema
31320
31343
  });
31321
- var HubViewerResolutionSchema = object({
31322
- /** The addresses flagged `viewer`, or empty = automatic. */
31344
+ var HubAppConnectionsResolutionSchema = object({
31345
+ /** The addresses flagged `appConnections`, or empty = automatic. */
31323
31346
  flagged: array(string()).readonly(),
31324
31347
  /** What the apps race right now. */
31325
31348
  resolved: array(string()).readonly(),
@@ -31339,10 +31362,10 @@ var HubAddressBookSchema = object({
31339
31362
  eligible: array(HubAddressRoleSchema).readonly()
31340
31363
  })).readonly(),
31341
31364
  roles: object({
31342
- viewer: HubViewerResolutionSchema,
31343
- notifications: HubAddressRoleResolutionSchema,
31344
- agents: HubAddressRoleResolutionSchema,
31345
- signIn: HubAddressRoleResolutionSchema
31365
+ appConnections: HubAppConnectionsResolutionSchema,
31366
+ notificationTaps: HubAddressRoleResolutionSchema,
31367
+ internal: HubAddressRoleResolutionSchema,
31368
+ publicEndpoint: HubAddressRoleResolutionSchema
31346
31369
  }),
31347
31370
  /** The `hub=` an app link carries (D709), or null. */
31348
31371
  appLinkHub: string().nullable()
@@ -31390,7 +31413,7 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
31390
31413
  * tunnel always emits `https://` regardless. */
31391
31414
  scheme: _enum(["http", "https"]).optional(),
31392
31415
  /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
31393
- * an address notification links can be flagged with, not a hub
31416
+ * an address notification taps can be flagged with, not a hub
31394
31417
  * route. Default `false`: racers and rankers never see it. */
31395
31418
  includeAppLink: boolean().optional()
31396
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({
@@ -49488,6 +49511,35 @@ function buildModelConvertProvider(deps) {
49488
49511
  };
49489
49512
  } };
49490
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
+ }
49491
49543
  //#endregion
49492
49544
  //#region src/model-registry.ts
49493
49545
  /**
@@ -49519,22 +49571,18 @@ function upsertAvailability(map, modelId, nodeId, entry) {
49519
49571
  });
49520
49572
  }
49521
49573
  /**
49522
- * Pure registry operations over the custom-model list. Kept side-effect-free
49523
- * so they unit-test without a live `BaseAddon` context — the addon class is a
49524
- * thin durable-state wrapper around these.
49525
- */
49526
- /**
49527
49574
  * Insert or replace a descriptor keyed by `entry.id`. When the id already
49528
49575
  * exists, **formats are merged** (Scrypted multi-format accumulate) — sibling
49529
49576
  * formats on the prior entry are never dropped. Returns a new array.
49530
49577
  */
49531
- function upsertCustomModel(list, descriptor) {
49578
+ function upsertCustomModel(list, descriptor, nameOrigin = "source") {
49532
49579
  const prev = list.find((m) => m.entry.id === descriptor.entry.id);
49533
49580
  const merged = prev === void 0 ? descriptor : {
49534
49581
  stepId: descriptor.stepId,
49535
49582
  entry: {
49536
49583
  ...prev.entry,
49537
49584
  ...descriptor.entry,
49585
+ name: nameOrigin === "operator" ? descriptor.entry.name : prev.entry.name,
49538
49586
  formats: {
49539
49587
  ...prev.entry.formats,
49540
49588
  ...descriptor.entry.formats
@@ -49543,6 +49591,43 @@ function upsertCustomModel(list, descriptor) {
49543
49591
  };
49544
49592
  return [...list.filter((m) => m.entry.id !== descriptor.entry.id), merged];
49545
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
+ }
49546
49631
  /** Drop the descriptor with the given `entry.id`. Reports whether anything changed. */
49547
49632
  function removeCustomModel(list, modelId) {
49548
49633
  const next = list.filter((m) => m.entry.id !== modelId);
@@ -50390,6 +50475,19 @@ var modelStudioActions = defineCustomActions({
50390
50475
  clearedPins: number().int().nonnegative()
50391
50476
  }), { kind: "mutation" }),
50392
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
+ /**
50393
50491
  * Delete one format of a Library model from one node. Keeps the registry row
50394
50492
  * and sibling formats/nodes; clears only that availability chip.
50395
50493
  */
@@ -50478,6 +50576,8 @@ var modelStudioActions = defineCustomActions({
50478
50576
  })),
50479
50577
  importScryptedCatalogModel: customAction(object({
50480
50578
  manifestId: string(),
50579
+ /** Optional display name typed on the import card. */
50580
+ name: string().optional(),
50481
50581
  targetNodeId: string().optional()
50482
50582
  }), CustomModelDescriptorSchema, { kind: "mutation" }),
50483
50583
  /**
@@ -50507,7 +50607,8 @@ var modelStudioActions = defineCustomActions({
50507
50607
  modelId: string().optional(),
50508
50608
  name: string().optional(),
50509
50609
  /**
50510
- * Native artifact class from the list row (`plusDownloadTraits`). The
50610
+ * Native artifact class of the chosen build (`plusImportLane`: neural →
50611
+ * onnx, coral → tflite). The
50511
50612
  * model-detail API does not always carry `supportedDetectors`, so the
50512
50613
  * browser passes what the catalog already knew (fixes ssd/yolo-generic
50513
50614
  * EdgeTPU rows that `plusTypeTraits` alone would call onnx).
@@ -50559,6 +50660,8 @@ var modelStudioActions = defineCustomActions({
50559
50660
  //#region src/model-studio.addon.ts
50560
50661
  var KEY_CUSTOM_MODELS = "customModels";
50561
50662
  var KEY_AVAILABILITY = "availability";
50663
+ var KEY_IMPORTED_NAMES = "importedNames";
50664
+ var ImportedNamesSchema = record(string(), string());
50562
50665
  var CustomModelsSchema = array(CustomModelDescriptorSchema);
50563
50666
  var pythonDirCache;
50564
50667
  function resolveAddonPythonDir() {
@@ -50621,12 +50724,18 @@ function spawnRunConvert(jobPath, pythonPath, onProgress, _sessionId) {
50621
50724
  });
50622
50725
  });
50623
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
+ }
50624
50731
  /** Global-fetch adapter for the injected Frigate+ client seam. */
50625
50732
  var plusFetch = async (url, init) => fetch(url, init);
50626
50733
  var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50627
50734
  id = "model-studio";
50628
50735
  modelsState = null;
50629
50736
  availabilityState = null;
50737
+ /** modelId → the name a renamed registry row had before its first rename. */
50738
+ importedNamesState = null;
50630
50739
  /** Lazily-built chunked-upload store (hub only) — see `upload/model-upload-store.ts`. */
50631
50740
  uploadStore = null;
50632
50741
  /**
@@ -50709,6 +50818,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50709
50818
  const state = this.state(KEY_CUSTOM_MODELS, CustomModelsSchema, []);
50710
50819
  this.modelsState = state;
50711
50820
  this.availabilityState = this.state(KEY_AVAILABILITY, ModelAvailabilityMapSchema, {});
50821
+ this.importedNamesState = this.state(KEY_IMPORTED_NAMES, ImportedNamesSchema, {});
50712
50822
  const rawNode = this.ctx.kernel?.localNodeId ?? "hub";
50713
50823
  const isHub = (rawNode.includes("/") ? rawNode.split("/")[0] : rawNode) === "hub";
50714
50824
  const pagesProvider = {
@@ -50742,7 +50852,11 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50742
50852
  return result;
50743
50853
  },
50744
50854
  abortModelUpload: async (input) => (await this.getUploadStore()).abort(input),
50745
- 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),
50746
50860
  ensureFormatsThenDistribute: async (input) => this.ensureFormatsThenDistribute_(input.modelId),
50747
50861
  ensureModelFormats: async (input) => this.ensureModelFormats_(input.modelId, input.formats),
50748
50862
  removeCustomModel: async (input) => this.removeCustomModel(input),
@@ -50894,6 +51008,35 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50894
51008
  if (!this.modelsState) throw new Error("ModelStudioAddon: durable state not initialized");
50895
51009
  return this.modelsState;
50896
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
+ }
50897
51040
  get availability_() {
50898
51041
  if (!this.availabilityState) throw new Error("ModelStudioAddon: availability state not initialized");
50899
51042
  return this.availabilityState;
@@ -51015,6 +51158,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51015
51158
  * `targetNodeId` lands the HF URL on that node via download-on-target.
51016
51159
  */
51017
51160
  async importScryptedCatalogFromManifest(input) {
51161
+ const name = optionalImportName(input.name);
51018
51162
  const entry = getScryptedManifestEntry(input.manifestId);
51019
51163
  if (!entry) throw new Error(`importScryptedCatalogModel: unknown manifest id "${input.manifestId}"`);
51020
51164
  let drift;
@@ -51034,8 +51178,15 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51034
51178
  };
51035
51179
  }
51036
51180
  if (drift.status !== "ok") throw new Error(`importScryptedCatalogModel: entry "${entry.id}" is blocked by drift (${drift.status}${drift.details ? `: ${drift.details}` : ""})`);
51037
- const descriptor = buildScryptedRegistryDescriptor(entry);
51038
- 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));
51039
51190
  if (input.targetNodeId !== void 0 && input.targetNodeId.length > 0) await this.distributeModel({
51040
51191
  modelId: descriptor.entry.id,
51041
51192
  nodeId: input.targetNodeId,
@@ -51057,11 +51208,12 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51057
51208
  async importFrigateCatalogModel(input) {
51058
51209
  const cat = FRIGATE_PUBLIC_CATALOG.find((c) => c.id === input.catalogId);
51059
51210
  if (!cat) throw new Error(`importFrigateCatalogModel: unknown catalog id "${input.catalogId}"`);
51211
+ const name = optionalImportName(input.name);
51060
51212
  const descriptor = buildFrigateCatalogDescriptor(input.catalogId, {
51061
51213
  ...input.modelId !== void 0 ? { modelId: input.modelId } : {},
51062
- ...input.name !== void 0 ? { name: input.name } : {}
51214
+ ...name !== void 0 ? { name } : {}
51063
51215
  });
51064
- await this.registerCustomModel(descriptor);
51216
+ await this.registerCustomModel(descriptor, nameOriginOf(name));
51065
51217
  let bytes = Math.round(cat.sizeMB * 1e6);
51066
51218
  if (input.targetNodeId !== void 0 && input.targetNodeId.length > 0) {
51067
51219
  const landed = await this.distributeModel({
@@ -51092,6 +51244,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51092
51244
  */
51093
51245
  async importFrigatePlusModel(input) {
51094
51246
  const modelId = frigatePlusRegistryId(input.plusModelId);
51247
+ const operatorName = optionalImportName(input.name);
51095
51248
  const key = this.config.frigatePlusApiKey ?? "";
51096
51249
  const { info, downloadUrl } = await requestPlusModelDownload(plusFetch, key, input.plusModelId);
51097
51250
  const typeTraits = plusTypeTraits(info.type);
@@ -51116,12 +51269,15 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51116
51269
  const sha256 = await ModelStudioAddon.fileSha256(destPath);
51117
51270
  const descriptor = plusInfoToDescriptor(info, {
51118
51271
  modelId,
51119
- name: input.name?.trim() || `Frigate+ ${info.type} (${input.plusModelId.slice(0, 8)})`,
51272
+ name: operatorName ?? `Frigate+ ${info.type} (${input.plusModelId.slice(0, 8)})`,
51120
51273
  filename,
51121
51274
  sizeMB: Number((bytes / 1e6).toFixed(1)),
51122
51275
  ...input.format !== void 0 ? { format: input.format } : {}
51123
51276
  });
51124
- await this.registerCustomModel(descriptor, { ensureEngines: false });
51277
+ await this.registerCustomModel(descriptor, {
51278
+ ensureEngines: false,
51279
+ ...nameOriginOf(operatorName)
51280
+ });
51125
51281
  await this.availability_.update((prev) => upsertAvailability(prev, modelId, "hub", {
51126
51282
  format: traits.format,
51127
51283
  sha256,
@@ -51154,7 +51310,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51154
51310
  async registerCustomModel(input, opts = {}) {
51155
51311
  if (!input.stepId.trim()) throw new Error("registerCustomModel: stepId is required");
51156
51312
  if (!input.entry.id.trim()) throw new Error("registerCustomModel: entry.id is required");
51157
- await this.state_.update((prev) => upsertCustomModel(prev, input));
51313
+ await this.state_.update((prev) => upsertCustomModel(prev, input, opts.nameOrigin));
51158
51314
  this.ctx.logger.info("Registered custom model", { meta: {
51159
51315
  modelId: input.entry.id,
51160
51316
  stepId: input.stepId
@@ -51411,6 +51567,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51411
51567
  removed = result.removed;
51412
51568
  return result.next;
51413
51569
  });
51570
+ await this.importedNames_.update((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => id !== input.modelId)));
51414
51571
  this.ctx.logger.info("Removed custom model", { meta: {
51415
51572
  modelId: input.modelId,
51416
51573
  removed,