@camstack/addon-model-studio 1.1.205 → 1.1.207

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 (19) hide show
  1. package/THIRD_PARTY_MODELS.md +3 -8
  2. package/dist/{MotionZonesSettings-DbxM42-u.mjs → MotionZonesSettings-Dglm7NRe.mjs} +2 -2
  3. package/dist/{PrivacyMaskSettings-B6QbcEYx.mjs → PrivacyMaskSettings-4VnzM7CJ.mjs} +4 -4
  4. package/dist/{SceneMonitorEditor-C_sWam-G.mjs → SceneMonitorEditor-C2DUWSMy.mjs} +3 -3
  5. package/dist/_stub.js +5885 -5676
  6. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-CNTVW6pB.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-q5-p_N0m.mjs} +3 -3
  7. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-_AxpyMKA.mjs +26 -0
  8. package/dist/{hostInit-Cz9X-HIo.mjs → hostInit-De6t1H5z.mjs} +2 -2
  9. package/dist/model-studio.addon.js +233 -54
  10. package/dist/model-studio.addon.mjs +233 -54
  11. package/dist/{player-overlays-p1wcAk-U.mjs → player-overlays-DnuiNM0v.mjs} +1 -1
  12. package/dist/remoteEntry.js +1 -1
  13. package/dist/{responsive-a5o_RHI6.mjs → responsive-CIrYx2bw.mjs} +1 -1
  14. package/dist/{scene-monitor-copy-DRLPt6W9.mjs → scene-monitor-copy-AfaY2iTT.mjs} +1 -1
  15. package/dist/{square-Bfh8RaUF.mjs → square-v2U1PMC1.mjs} +1 -1
  16. package/dist/{trash-2-D-9uzmfU.mjs → trash-2-BQe8qX4b.mjs} +1 -1
  17. 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-H0G7-sT2.mjs} +1 -1
  18. package/package.json +1 -1
  19. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Cl1eNNmq.mjs +0 -26
@@ -10934,7 +10934,18 @@ var LABEL_SUPPLY_STEP_IDS = [
10934
10934
  ];
10935
10935
  /** Steps whose box a root detection can BE. */
10936
10936
  var CROP_SUPPLY_STEP_IDS = ["plate-detection", "face-detection"];
10937
- [...LABEL_SUPPLY_STEP_IDS, ...CROP_SUPPLY_STEP_IDS];
10937
+ /**
10938
+ * Steps whose per-detection MASK a root detection can carry (D737). The live
10939
+ * mask step is `segmentation` — a frame-plane refiner that runs its own model
10940
+ * once per detection crop. (`instance-segmentation` is a dormant step id that
10941
+ * no camera tree carries.)
10942
+ */
10943
+ var MASK_SUPPLY_STEP_IDS = ["segmentation"];
10944
+ [
10945
+ ...LABEL_SUPPLY_STEP_IDS,
10946
+ ...CROP_SUPPLY_STEP_IDS,
10947
+ ...MASK_SUPPLY_STEP_IDS
10948
+ ];
10938
10949
  /**
10939
10950
  * The EFFECT vocabulary of each label-kind step: exactly the strings the step
10940
10951
  * itself emits, in its own spelling. A linked label is one of these and nothing
@@ -11150,6 +11161,19 @@ var ModelProviderIdSchema = _enum([
11150
11161
  "custom"
11151
11162
  ]);
11152
11163
  /**
11164
+ * Resolve a catalog entry's picker provider. An explicit stamp always wins;
11165
+ * otherwise Frigate/Scrypted are recognised from id prefix or description.
11166
+ * Unstamped BYO registry rows fall through to `custom`. Family `yolov9` is
11167
+ * NOT a Scrypted signal — CamStack ships that family too.
11168
+ */
11169
+ function inferModelProvider(entry) {
11170
+ if (entry.provider !== void 0) return entry.provider;
11171
+ const haystack = `${entry.id} ${entry.description ?? ""}`;
11172
+ if (/scrypted/i.test(haystack)) return "scrypted";
11173
+ if (/frigate/i.test(haystack)) return "frigate";
11174
+ return "custom";
11175
+ }
11176
+ /**
11153
11177
  * The licence of a model, as a record rather than a bare SPDX string: a
11154
11178
  * permissive CODE licence routinely sits on top of restrictive WEIGHTS or
11155
11179
  * DATA, and the obligations (attribution, modification notices, source offers)
@@ -11202,25 +11226,36 @@ var ModelLicenseSchema = preprocess((value) => typeof value === "string" ? { wei
11202
11226
  * - `crop`: the root draws the box the step would have found (`license_plate`
11203
11227
  * for plate-detection, `face` for face-detection). The box becomes a detail
11204
11228
  * child of the `parentMacro` detection that contains it — never a track.
11229
+ * - `mask`: the root's decode emits an instance mask with every detection (a
11230
+ * YOLO26-seg root, D737) — the `segmentation` step's result, carried on the
11231
+ * detection in the shape that step produces. Derived from the model's
11232
+ * postprocessor on read, never stored by an import.
11205
11233
  */
11206
- var RootStepSupplySchema = discriminatedUnion("kind", [object({
11207
- kind: literal("label"),
11208
- stepId: _enum(LABEL_SUPPLY_STEP_IDS),
11209
- labelToEffect: record(string(), string()),
11210
- uncovered: array(string()).readonly()
11211
- }), object({
11212
- kind: literal("crop"),
11213
- stepId: _enum(CROP_SUPPLY_STEP_IDS),
11214
- rootLabels: array(string()).readonly(),
11215
- parentMacro: _enum(["vehicle", "person"]),
11216
- childClass: _enum(["plate", "face"]),
11217
- /**
11218
- * The root's smaller input side, stamped on read by `rootSuppliesOf` from
11219
- * the model entry — what a step's `minRootInputPx` gate judges. Never
11220
- * stored by an import.
11221
- */
11222
- rootInputPx: number().int().positive().optional()
11223
- })]);
11234
+ var RootStepSupplySchema = discriminatedUnion("kind", [
11235
+ object({
11236
+ kind: literal("label"),
11237
+ stepId: _enum(LABEL_SUPPLY_STEP_IDS),
11238
+ labelToEffect: record(string(), string()),
11239
+ uncovered: array(string()).readonly()
11240
+ }),
11241
+ object({
11242
+ kind: literal("crop"),
11243
+ stepId: _enum(CROP_SUPPLY_STEP_IDS),
11244
+ rootLabels: array(string()).readonly(),
11245
+ parentMacro: _enum(["vehicle", "person"]),
11246
+ childClass: _enum(["plate", "face"]),
11247
+ /**
11248
+ * The root's smaller input side, stamped on read by `rootSuppliesOf` from
11249
+ * the model entry — what a step's `minRootInputPx` gate judges. Never
11250
+ * stored by an import.
11251
+ */
11252
+ rootInputPx: number().int().positive().optional()
11253
+ }),
11254
+ object({
11255
+ kind: literal("mask"),
11256
+ stepId: _enum(MASK_SUPPLY_STEP_IDS)
11257
+ })
11258
+ ]);
11224
11259
  var ModelCatalogEntrySchema = object({
11225
11260
  id: string(),
11226
11261
  name: string(),
@@ -16549,6 +16584,12 @@ method(_void(), array(string()).readonly(), { auth: "admin" }), method(object({
16549
16584
  kind: "mutation",
16550
16585
  auth: "admin"
16551
16586
  });
16587
+ var HUB_ADDRESS_ROLES = [
16588
+ "appConnections",
16589
+ "notificationTaps",
16590
+ "internal",
16591
+ "publicEndpoint"
16592
+ ];
16552
16593
  /**
16553
16594
  * Transport bounds for AI capability methods.
16554
16595
  *
@@ -20726,7 +20767,13 @@ var NcAlarmModeDelaysListSchema = array(object({
20726
20767
  * Absent = false (logged, not sent). Even when true it is not sent when that same sensor
20727
20768
  * is the one that starts the entry delay — the entry-delay notification already names it.
20728
20769
  */
20729
- notifySensorRewatched: boolean().optional()
20770
+ notifySensorRewatched: boolean().optional(),
20771
+ /**
20772
+ * D735: notify "ignored" when a sensor this arm bypassed really MOVED while armed (its own
20773
+ * change time is after the arm) and set nothing off. Absent = false (logged, not sent). A
20774
+ * re-sync, a recovery from `unavailable` or a duplicate is never announced, switch or not.
20775
+ */
20776
+ notifySensorIgnored: boolean().optional()
20730
20777
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
20731
20778
  var NcAlarmModeDelaysEditListSchema = array(object({
20732
20779
  mode: AlarmArmModeSchema,
@@ -20739,7 +20786,9 @@ var NcAlarmModeDelaysEditListSchema = array(object({
20739
20786
  /** D706: set the switch, or `null` to clear it (back to the default, false). */
20740
20787
  notifyDuringEntryDelay: boolean().nullable().optional(),
20741
20788
  /** D706: set the switch, or `null` to clear it (back to the default, false). */
20742
- notifySensorRewatched: boolean().nullable().optional()
20789
+ notifySensorRewatched: boolean().nullable().optional(),
20790
+ /** D735: set the switch, or `null` to clear it (back to the default, false). */
20791
+ notifySensorIgnored: boolean().nullable().optional()
20743
20792
  })).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
20744
20793
  /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
20745
20794
  var NcAlarmNonBlockingEditSchema = object({
@@ -31244,7 +31293,7 @@ var GetConnectionEndpointsResultSchema = object({
31244
31293
  localPort: AdvertisedLocalPortSchema
31245
31294
  });
31246
31295
  /**
31247
- * The address flagged for notification links. `baseUrl: null` = AUTO, which
31296
+ * The address flagged for notification taps (`notificationTaps`). `baseUrl: null` = AUTO, which
31248
31297
  * since D709 is the CamStack app link; `resolved` reports the effective value,
31249
31298
  * so the UI can show it either way.
31250
31299
  */
@@ -31279,8 +31328,8 @@ var ViewerEndpointsSchema = object({
31279
31328
  });
31280
31329
  /**
31281
31330
  * 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.
31331
+ * for addons built against it): `cluster` is the `internal` role, `browser`
31332
+ * the `publicEndpoint` role; `override` is the flagged address.
31284
31333
  */
31285
31334
  var HubAddressSnapshotSchema = object({
31286
31335
  override: string().nullable(),
@@ -31296,15 +31345,11 @@ var HubAddressesSchema = object({
31296
31345
  browser: HubAddressSnapshotSchema
31297
31346
  });
31298
31347
  /**
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.
31348
+ * What an address is used for (`hub-address.ts`, D734). `appConnections` is
31349
+ * many-valued; `notificationTaps`, `publicEndpoint` and `internal` are carried
31350
+ * by at most one address.
31301
31351
  */
31302
- var HubAddressRoleSchema = _enum([
31303
- "viewer",
31304
- "notifications",
31305
- "agents",
31306
- "signIn"
31307
- ]);
31352
+ var HubAddressRoleSchema = _enum(HUB_ADDRESS_ROLES);
31308
31353
  var RoleSourceSchema = _enum([
31309
31354
  "flagged",
31310
31355
  "auto",
@@ -31318,8 +31363,8 @@ var HubAddressRoleResolutionSchema = object({
31318
31363
  resolved: string().nullable(),
31319
31364
  source: RoleSourceSchema
31320
31365
  });
31321
- var HubViewerResolutionSchema = object({
31322
- /** The addresses flagged `viewer`, or empty = automatic. */
31366
+ var HubAppConnectionsResolutionSchema = object({
31367
+ /** The addresses flagged `appConnections`, or empty = automatic. */
31323
31368
  flagged: array(string()).readonly(),
31324
31369
  /** What the apps race right now. */
31325
31370
  resolved: array(string()).readonly(),
@@ -31339,10 +31384,10 @@ var HubAddressBookSchema = object({
31339
31384
  eligible: array(HubAddressRoleSchema).readonly()
31340
31385
  })).readonly(),
31341
31386
  roles: object({
31342
- viewer: HubViewerResolutionSchema,
31343
- notifications: HubAddressRoleResolutionSchema,
31344
- agents: HubAddressRoleResolutionSchema,
31345
- signIn: HubAddressRoleResolutionSchema
31387
+ appConnections: HubAppConnectionsResolutionSchema,
31388
+ notificationTaps: HubAddressRoleResolutionSchema,
31389
+ internal: HubAddressRoleResolutionSchema,
31390
+ publicEndpoint: HubAddressRoleResolutionSchema
31346
31391
  }),
31347
31392
  /** The `hub=` an app link carries (D709), or null. */
31348
31393
  appLinkHub: string().nullable()
@@ -31390,7 +31435,7 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
31390
31435
  * tunnel always emits `https://` regardless. */
31391
31436
  scheme: _enum(["http", "https"]).optional(),
31392
31437
  /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
31393
- * an address notification links can be flagged with, not a hub
31438
+ * an address notification taps can be flagged with, not a hub
31394
31439
  * route. Default `false`: racers and rankers never see it. */
31395
31440
  includeAppLink: boolean().optional()
31396
31441
  }), 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 +49533,35 @@ function buildModelConvertProvider(deps) {
49488
49533
  };
49489
49534
  } };
49490
49535
  }
49536
+ var CONTROL_CHARS = /[\u0000-\u001f\u007f]/;
49537
+ /** Whitespace runs collapse to one space; leading/trailing whitespace goes. */
49538
+ function parseDisplayName(raw) {
49539
+ const name = raw.replace(/\s+/g, " ").trim();
49540
+ if (name.length === 0) return { kind: "reset" };
49541
+ if (CONTROL_CHARS.test(name)) return {
49542
+ kind: "invalid",
49543
+ reason: "The name contains control characters."
49544
+ };
49545
+ if (name.length > 80) return {
49546
+ kind: "invalid",
49547
+ reason: `The name is ${name.length} characters long; the limit is 80.`
49548
+ };
49549
+ return {
49550
+ kind: "set",
49551
+ name
49552
+ };
49553
+ }
49554
+ /**
49555
+ * The optional display name typed on an import card: `undefined` when left
49556
+ * empty (the source's own default applies), the cleaned name otherwise.
49557
+ * Throws with the reason when the input is not acceptable.
49558
+ */
49559
+ function optionalImportName(raw) {
49560
+ if (raw === void 0) return void 0;
49561
+ const parsed = parseDisplayName(raw);
49562
+ if (parsed.kind === "invalid") throw new Error(`Display name rejected: ${parsed.reason}`);
49563
+ return parsed.kind === "set" ? parsed.name : void 0;
49564
+ }
49491
49565
  //#endregion
49492
49566
  //#region src/model-registry.ts
49493
49567
  /**
@@ -49519,22 +49593,18 @@ function upsertAvailability(map, modelId, nodeId, entry) {
49519
49593
  });
49520
49594
  }
49521
49595
  /**
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
49596
  * Insert or replace a descriptor keyed by `entry.id`. When the id already
49528
49597
  * exists, **formats are merged** (Scrypted multi-format accumulate) — sibling
49529
49598
  * formats on the prior entry are never dropped. Returns a new array.
49530
49599
  */
49531
- function upsertCustomModel(list, descriptor) {
49600
+ function upsertCustomModel(list, descriptor, nameOrigin = "source") {
49532
49601
  const prev = list.find((m) => m.entry.id === descriptor.entry.id);
49533
49602
  const merged = prev === void 0 ? descriptor : {
49534
49603
  stepId: descriptor.stepId,
49535
49604
  entry: {
49536
49605
  ...prev.entry,
49537
49606
  ...descriptor.entry,
49607
+ name: nameOrigin === "operator" ? descriptor.entry.name : prev.entry.name,
49538
49608
  formats: {
49539
49609
  ...prev.entry.formats,
49540
49610
  ...descriptor.entry.formats
@@ -49543,6 +49613,43 @@ function upsertCustomModel(list, descriptor) {
49543
49613
  };
49544
49614
  return [...list.filter((m) => m.entry.id !== descriptor.entry.id), merged];
49545
49615
  }
49616
+ /**
49617
+ * Rename one IMPORTED registry row in place. Only `entry.name` changes — the
49618
+ * id, step, formats and every pin keyed by the id stay exactly as they were.
49619
+ * An empty name resets the row to the name it was imported with. Throws on an
49620
+ * unknown id, a CamStack catalog row, or a name `parseDisplayName` rejects.
49621
+ */
49622
+ function renameCustomModel(list, importedNames, modelId, rawName) {
49623
+ const row = list.find((m) => m.entry.id === modelId);
49624
+ if (row === void 0) throw new Error(`renameCustomModel: model "${modelId}" is not registered`);
49625
+ if (!isRenameableEntry(row.entry)) throw new Error(`renameCustomModel: "${modelId}" is a CamStack catalog model and keeps its name`);
49626
+ const parsed = parseDisplayName(rawName);
49627
+ if (parsed.kind === "invalid") throw new Error(`renameCustomModel: ${parsed.reason}`);
49628
+ const original = importedNames[modelId] ?? row.entry.name;
49629
+ const name = parsed.kind === "set" ? parsed.name : original;
49630
+ const nextNames = { ...importedNames };
49631
+ if (parsed.kind === "set" && name !== original) nextNames[modelId] = original;
49632
+ else delete nextNames[modelId];
49633
+ return {
49634
+ list: list.map((m) => m.entry.id === modelId ? {
49635
+ stepId: m.stepId,
49636
+ entry: {
49637
+ ...m.entry,
49638
+ name
49639
+ }
49640
+ } : m),
49641
+ importedNames: nextNames,
49642
+ name
49643
+ };
49644
+ }
49645
+ /** An imported row can be renamed; a CamStack catalog model cannot. */
49646
+ function isRenameableEntry(entry) {
49647
+ return inferModelProvider({
49648
+ id: entry.id,
49649
+ description: entry.description,
49650
+ provider: entry.provider
49651
+ }) !== "camstack";
49652
+ }
49546
49653
  /** Drop the descriptor with the given `entry.id`. Reports whether anything changed. */
49547
49654
  function removeCustomModel(list, modelId) {
49548
49655
  const next = list.filter((m) => m.entry.id !== modelId);
@@ -50390,6 +50497,19 @@ var modelStudioActions = defineCustomActions({
50390
50497
  clearedPins: number().int().nonnegative()
50391
50498
  }), { kind: "mutation" }),
50392
50499
  /**
50500
+ * Rename an IMPORTED registry row (Frigate, Frigate+, Scrypted, uploads,
50501
+ * conversions) in place. Only `entry.name` changes — never the id, so pins
50502
+ * and availability are untouched. An empty name restores the name the model
50503
+ * was imported with. CamStack catalog models are refused.
50504
+ */
50505
+ renameCustomModel: customAction(object({
50506
+ modelId: string().min(1),
50507
+ name: string()
50508
+ }), object({
50509
+ modelId: string(),
50510
+ name: string()
50511
+ }), { kind: "mutation" }),
50512
+ /**
50393
50513
  * Delete one format of a Library model from one node. Keeps the registry row
50394
50514
  * and sibling formats/nodes; clears only that availability chip.
50395
50515
  */
@@ -50478,6 +50598,8 @@ var modelStudioActions = defineCustomActions({
50478
50598
  })),
50479
50599
  importScryptedCatalogModel: customAction(object({
50480
50600
  manifestId: string(),
50601
+ /** Optional display name typed on the import card. */
50602
+ name: string().optional(),
50481
50603
  targetNodeId: string().optional()
50482
50604
  }), CustomModelDescriptorSchema, { kind: "mutation" }),
50483
50605
  /**
@@ -50507,7 +50629,8 @@ var modelStudioActions = defineCustomActions({
50507
50629
  modelId: string().optional(),
50508
50630
  name: string().optional(),
50509
50631
  /**
50510
- * Native artifact class from the list row (`plusDownloadTraits`). The
50632
+ * Native artifact class of the chosen build (`plusImportLane`: neural →
50633
+ * onnx, coral → tflite). The
50511
50634
  * model-detail API does not always carry `supportedDetectors`, so the
50512
50635
  * browser passes what the catalog already knew (fixes ssd/yolo-generic
50513
50636
  * EdgeTPU rows that `plusTypeTraits` alone would call onnx).
@@ -50559,6 +50682,8 @@ var modelStudioActions = defineCustomActions({
50559
50682
  //#region src/model-studio.addon.ts
50560
50683
  var KEY_CUSTOM_MODELS = "customModels";
50561
50684
  var KEY_AVAILABILITY = "availability";
50685
+ var KEY_IMPORTED_NAMES = "importedNames";
50686
+ var ImportedNamesSchema = record(string(), string());
50562
50687
  var CustomModelsSchema = array(CustomModelDescriptorSchema);
50563
50688
  var pythonDirCache;
50564
50689
  function resolveAddonPythonDir() {
@@ -50621,12 +50746,18 @@ function spawnRunConvert(jobPath, pythonPath, onProgress, _sessionId) {
50621
50746
  });
50622
50747
  });
50623
50748
  }
50749
+ /** A name the operator typed replaces an existing row's name; a derived one never does. */
50750
+ function nameOriginOf(operatorName) {
50751
+ return operatorName !== void 0 ? { nameOrigin: "operator" } : {};
50752
+ }
50624
50753
  /** Global-fetch adapter for the injected Frigate+ client seam. */
50625
50754
  var plusFetch = async (url, init) => fetch(url, init);
50626
50755
  var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50627
50756
  id = "model-studio";
50628
50757
  modelsState = null;
50629
50758
  availabilityState = null;
50759
+ /** modelId → the name a renamed registry row had before its first rename. */
50760
+ importedNamesState = null;
50630
50761
  /** Lazily-built chunked-upload store (hub only) — see `upload/model-upload-store.ts`. */
50631
50762
  uploadStore = null;
50632
50763
  /**
@@ -50709,6 +50840,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50709
50840
  const state = this.state(KEY_CUSTOM_MODELS, CustomModelsSchema, []);
50710
50841
  this.modelsState = state;
50711
50842
  this.availabilityState = this.state(KEY_AVAILABILITY, ModelAvailabilityMapSchema, {});
50843
+ this.importedNamesState = this.state(KEY_IMPORTED_NAMES, ImportedNamesSchema, {});
50712
50844
  const rawNode = this.ctx.kernel?.localNodeId ?? "hub";
50713
50845
  const isHub = (rawNode.includes("/") ? rawNode.split("/")[0] : rawNode) === "hub";
50714
50846
  const pagesProvider = {
@@ -50742,7 +50874,11 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50742
50874
  return result;
50743
50875
  },
50744
50876
  abortModelUpload: async (input) => (await this.getUploadStore()).abort(input),
50745
- registerCustomModel: async (input) => this.registerCustomModel(input, { ensureEngines: true }),
50877
+ registerCustomModel: async (input) => this.registerCustomModel(input, {
50878
+ ensureEngines: true,
50879
+ nameOrigin: "operator"
50880
+ }),
50881
+ renameCustomModel: async (input) => this.renameCustomModel_(input),
50746
50882
  ensureFormatsThenDistribute: async (input) => this.ensureFormatsThenDistribute_(input.modelId),
50747
50883
  ensureModelFormats: async (input) => this.ensureModelFormats_(input.modelId, input.formats),
50748
50884
  removeCustomModel: async (input) => this.removeCustomModel(input),
@@ -50894,6 +51030,35 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
50894
51030
  if (!this.modelsState) throw new Error("ModelStudioAddon: durable state not initialized");
50895
51031
  return this.modelsState;
50896
51032
  }
51033
+ get importedNames_() {
51034
+ if (!this.importedNamesState) throw new Error("ModelStudioAddon: imported-names state not initialized");
51035
+ return this.importedNamesState;
51036
+ }
51037
+ /**
51038
+ * Rename an imported registry row in place (`renameCustomModel` decides).
51039
+ * The id is untouched, so pins, availability and files are unaffected; the
51040
+ * pipeline's catalog view picks the new name up on its next registry read.
51041
+ */
51042
+ async renameCustomModel_(input) {
51043
+ const names = await this.importedNames_.get();
51044
+ const outcome = {};
51045
+ await this.state_.update((prev) => {
51046
+ const renamed = renameCustomModel(prev, names, input.modelId, input.name);
51047
+ outcome.result = renamed;
51048
+ return renamed.list;
51049
+ });
51050
+ const result = outcome.result;
51051
+ if (result === void 0) throw new Error("renameCustomModel: registry update did not run");
51052
+ await this.importedNames_.set(result.importedNames);
51053
+ this.ctx.logger.info("Renamed custom model", { meta: {
51054
+ modelId: input.modelId,
51055
+ name: result.name
51056
+ } });
51057
+ return {
51058
+ modelId: input.modelId,
51059
+ name: result.name
51060
+ };
51061
+ }
50897
51062
  get availability_() {
50898
51063
  if (!this.availabilityState) throw new Error("ModelStudioAddon: availability state not initialized");
50899
51064
  return this.availabilityState;
@@ -51015,6 +51180,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51015
51180
  * `targetNodeId` lands the HF URL on that node via download-on-target.
51016
51181
  */
51017
51182
  async importScryptedCatalogFromManifest(input) {
51183
+ const name = optionalImportName(input.name);
51018
51184
  const entry = getScryptedManifestEntry(input.manifestId);
51019
51185
  if (!entry) throw new Error(`importScryptedCatalogModel: unknown manifest id "${input.manifestId}"`);
51020
51186
  let drift;
@@ -51034,8 +51200,15 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51034
51200
  };
51035
51201
  }
51036
51202
  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);
51203
+ const built = buildScryptedRegistryDescriptor(entry);
51204
+ const descriptor = name !== void 0 ? {
51205
+ stepId: built.stepId,
51206
+ entry: {
51207
+ ...built.entry,
51208
+ name
51209
+ }
51210
+ } : built;
51211
+ await this.registerCustomModel(descriptor, nameOriginOf(name));
51039
51212
  if (input.targetNodeId !== void 0 && input.targetNodeId.length > 0) await this.distributeModel({
51040
51213
  modelId: descriptor.entry.id,
51041
51214
  nodeId: input.targetNodeId,
@@ -51057,11 +51230,12 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51057
51230
  async importFrigateCatalogModel(input) {
51058
51231
  const cat = FRIGATE_PUBLIC_CATALOG.find((c) => c.id === input.catalogId);
51059
51232
  if (!cat) throw new Error(`importFrigateCatalogModel: unknown catalog id "${input.catalogId}"`);
51233
+ const name = optionalImportName(input.name);
51060
51234
  const descriptor = buildFrigateCatalogDescriptor(input.catalogId, {
51061
51235
  ...input.modelId !== void 0 ? { modelId: input.modelId } : {},
51062
- ...input.name !== void 0 ? { name: input.name } : {}
51236
+ ...name !== void 0 ? { name } : {}
51063
51237
  });
51064
- await this.registerCustomModel(descriptor);
51238
+ await this.registerCustomModel(descriptor, nameOriginOf(name));
51065
51239
  let bytes = Math.round(cat.sizeMB * 1e6);
51066
51240
  if (input.targetNodeId !== void 0 && input.targetNodeId.length > 0) {
51067
51241
  const landed = await this.distributeModel({
@@ -51092,6 +51266,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51092
51266
  */
51093
51267
  async importFrigatePlusModel(input) {
51094
51268
  const modelId = frigatePlusRegistryId(input.plusModelId);
51269
+ const operatorName = optionalImportName(input.name);
51095
51270
  const key = this.config.frigatePlusApiKey ?? "";
51096
51271
  const { info, downloadUrl } = await requestPlusModelDownload(plusFetch, key, input.plusModelId);
51097
51272
  const typeTraits = plusTypeTraits(info.type);
@@ -51116,12 +51291,15 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51116
51291
  const sha256 = await ModelStudioAddon.fileSha256(destPath);
51117
51292
  const descriptor = plusInfoToDescriptor(info, {
51118
51293
  modelId,
51119
- name: input.name?.trim() || `Frigate+ ${info.type} (${input.plusModelId.slice(0, 8)})`,
51294
+ name: operatorName ?? `Frigate+ ${info.type} (${input.plusModelId.slice(0, 8)})`,
51120
51295
  filename,
51121
51296
  sizeMB: Number((bytes / 1e6).toFixed(1)),
51122
51297
  ...input.format !== void 0 ? { format: input.format } : {}
51123
51298
  });
51124
- await this.registerCustomModel(descriptor, { ensureEngines: false });
51299
+ await this.registerCustomModel(descriptor, {
51300
+ ensureEngines: false,
51301
+ ...nameOriginOf(operatorName)
51302
+ });
51125
51303
  await this.availability_.update((prev) => upsertAvailability(prev, modelId, "hub", {
51126
51304
  format: traits.format,
51127
51305
  sha256,
@@ -51154,7 +51332,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51154
51332
  async registerCustomModel(input, opts = {}) {
51155
51333
  if (!input.stepId.trim()) throw new Error("registerCustomModel: stepId is required");
51156
51334
  if (!input.entry.id.trim()) throw new Error("registerCustomModel: entry.id is required");
51157
- await this.state_.update((prev) => upsertCustomModel(prev, input));
51335
+ await this.state_.update((prev) => upsertCustomModel(prev, input, opts.nameOrigin));
51158
51336
  this.ctx.logger.info("Registered custom model", { meta: {
51159
51337
  modelId: input.entry.id,
51160
51338
  stepId: input.stepId
@@ -51411,6 +51589,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
51411
51589
  removed = result.removed;
51412
51590
  return result.next;
51413
51591
  });
51592
+ await this.importedNames_.update((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => id !== input.modelId)));
51414
51593
  this.ctx.logger.info("Removed custom model", { meta: {
51415
51594
  modelId: input.modelId,
51416
51595
  removed,