@camstack/addon-model-studio 1.1.199 → 1.1.200
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_MODELS.md +6 -30
- package/dist/{MotionZonesSettings-JRWr-847.mjs → MotionZonesSettings-DXWpYKPD.mjs} +2 -2
- package/dist/{PrivacyMaskSettings-BfpUuDZo.mjs → PrivacyMaskSettings-BcaCmwR2.mjs} +4 -4
- package/dist/{SceneMonitorEditor-B6q_snqj.mjs → SceneMonitorEditor-D8IoPtTi.mjs} +3 -3
- package/dist/_stub.js +9033 -6124
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-BNn0AWfL.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-ChSw1hrV.mjs} +3 -3
- package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C0rOztYh.mjs +26 -0
- package/dist/addon-model-studio.css +1 -1
- package/dist/{hostInit-CnCPORCz.mjs → hostInit-TRVWPuo-.mjs} +2 -2
- package/dist/model-studio.addon.js +870 -431
- package/dist/model-studio.addon.mjs +870 -436
- package/dist/{player-overlays-RAKUtQ68.mjs → player-overlays-4Vb0Si7c.mjs} +2 -2
- package/dist/remoteEntry.js +1 -1
- package/dist/{responsive-Cr8pn1rO.mjs → responsive-D8JojVmZ.mjs} +1 -1
- package/dist/{scene-monitor-copy-NIVM76QJ.mjs → scene-monitor-copy-Cw3r5D6b.mjs} +1 -1
- package/dist/{square-BOlfCWYp.mjs → square-C4Y02RV4.mjs} +1 -1
- package/dist/{trash-2-aAfBxjgh.mjs → trash-2-D-gDRhvd.mjs} +1 -1
- package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-yi-EovNM.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-Bs1AQFNt.mjs} +1 -1
- package/package.json +2 -1
- package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BKQUo9FB.mjs +0 -26
|
@@ -6,33 +6,10 @@ import fs from "node:fs";
|
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import * as path$1 from "node:path";
|
|
8
8
|
import path from "node:path";
|
|
9
|
-
import { downloadFile } from "@camstack/system/addon-utils";
|
|
9
|
+
import { deleteModelFromDisk, downloadFile } from "@camstack/system/addon-utils";
|
|
10
10
|
//#region \0rolldown/runtime.js
|
|
11
11
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
12
12
|
//#endregion
|
|
13
|
-
//#region src/scrypted/scrypted-download-policy.ts
|
|
14
|
-
/**
|
|
15
|
-
* Strict per-hop allow-list for Scrypted catalog downloads.
|
|
16
|
-
*
|
|
17
|
-
* Hosts are the ones observed from the pinned revision
|
|
18
|
-
* `187402b20a3be0a7fab9bceee21d857ed6ae0064` — not a generic CDN wildcard.
|
|
19
|
-
*/
|
|
20
|
-
var SCRYPTED_DOWNLOAD_REDIRECT_HOSTS = ["huggingface.co", "us.aws.cdn.hf.co"];
|
|
21
|
-
var LOCAL_HOSTS = new Set([
|
|
22
|
-
"localhost",
|
|
23
|
-
"127.0.0.1",
|
|
24
|
-
"::1",
|
|
25
|
-
"0.0.0.0"
|
|
26
|
-
]);
|
|
27
|
-
var IPV4_RE = /^(?:\d{1,3}\.){3}\d{1,3}$/;
|
|
28
|
-
function assertScryptedDownloadUrl(url, _hop) {
|
|
29
|
-
if (url.protocol !== "https:") throw new Error(`http downgrade refused: ${url.protocol}`);
|
|
30
|
-
if (url.username || url.password) throw new Error("userinfo not allowed on download URL");
|
|
31
|
-
const host = url.hostname.toLowerCase();
|
|
32
|
-
if (LOCAL_HOSTS.has(host) || IPV4_RE.test(host) || host.includes(":")) throw new Error(`host not allowed: ${host}`);
|
|
33
|
-
if (!SCRYPTED_DOWNLOAD_REDIRECT_HOSTS.includes(host)) throw new Error(`host not allowed: ${host}`);
|
|
34
|
-
}
|
|
35
|
-
//#endregion
|
|
36
13
|
//#region src/scrypted/scrypted-yolo-manifest-data.json
|
|
37
14
|
var scrypted_yolo_manifest_data_default = /*#__PURE__*/ JSON.parse("[{\"id\":\"scrypted-yolov9t-relu-coreml\",\"label\":\"Scrypted YOLOv9T ReLU CoreML\",\"family\":\"yolov9t\",\"variant\":\"relu\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9t_relu/scrypted_yolov9t_relu.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"0a8733949d77e3204dba6418e81a11a8cc177015711457ce2c171266f23382d6\"},{\"relativePath\":\"coreml/scrypted_yolov9t_relu/scrypted_yolov9t_relu.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":322229,\"sha256\":\"f5a62f196f1e72a84efa1637119dffb6501621af9cf6f867bc76b4b59c9242a0\"},{\"relativePath\":\"coreml/scrypted_yolov9t_relu/scrypted_yolov9t_relu.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":3797160,\"sha256\":\"b994419b8b68cfd6b89c3c6f3fe6b37458c86cc1d87fcca3df5034476a99dd38\"}],\"sizeMB\":4.12},{\"id\":\"scrypted-yolov9t-relu-onnx\",\"label\":\"Scrypted YOLOv9T ReLU ONNX\",\"family\":\"yolov9t\",\"variant\":\"relu\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9t_relu/scrypted_yolov9t_relu.onnx\",\"size\":8499266,\"sha256\":\"3f11ea2071b27c285b58c16636a44d6a0a27bdd1ca8b64112dfadabd39cfea47\"}],\"sizeMB\":8.5},{\"id\":\"scrypted-yolov9t-relu-test-coreml\",\"label\":\"Scrypted YOLOv9T ReLU test CoreML\",\"family\":\"yolov9t\",\"variant\":\"relu_test\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9t_relu_test/scrypted_yolov9t_relu_test.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"a5e0128b643f1a66f3e07d4b332f9ccbca97ccab75651c794f0b9f1fb41c8491\"},{\"relativePath\":\"coreml/scrypted_yolov9t_relu_test/scrypted_yolov9t_relu_test.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":322229,\"sha256\":\"79f4f647f2a0c79db3b560c84251b48c7ba7cbe9c1fecc9674d1a603a389dfa3\"},{\"relativePath\":\"coreml/scrypted_yolov9t_relu_test/scrypted_yolov9t_relu_test.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":3797160,\"sha256\":\"72efc9dc6af9241b5e5ee7de24a4f55741f6425d0e279d797f0f223e3d355f84\"}],\"sizeMB\":4.12},{\"id\":\"scrypted-yolov9t-relu-test-onnx\",\"label\":\"Scrypted YOLOv9T ReLU test ONNX\",\"family\":\"yolov9t\",\"variant\":\"relu_test\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9t_relu_test/scrypted_yolov9t_relu_test.onnx\",\"size\":8499266,\"sha256\":\"d20aa0e6923a3af7f5e84e3a6d28fb00e8ecd592e3225f986f93bd34b634e2f8\"}],\"sizeMB\":8.5},{\"id\":\"scrypted-yolov9s-relu-coreml\",\"label\":\"Scrypted YOLOv9S ReLU CoreML\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9s_relu/scrypted_yolov9s_relu.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"3bcdb820b82f1e89266f2d11bae1604d3ee4ca6d11dc5bd5c57055ff4157e9f3\"},{\"relativePath\":\"coreml/scrypted_yolov9s_relu/scrypted_yolov9s_relu.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":322664,\"sha256\":\"9a9c3c9cbca406ec2f0418ec867ed6b878ec6d02c16636d30e0bbd83d2ff7c40\"},{\"relativePath\":\"coreml/scrypted_yolov9s_relu/scrypted_yolov9s_relu.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":14190120,\"sha256\":\"fd51eda58a993997268f6a2e424b536ba173207ad7974ab8bee2a8ff76d52dab\"}],\"sizeMB\":14.51},{\"id\":\"scrypted-yolov9s-relu-onnx\",\"label\":\"Scrypted YOLOv9S ReLU ONNX\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9s_relu/scrypted_yolov9s_relu.onnx\",\"size\":29287339,\"sha256\":\"ca0b5bd98d5f59eb2029c6cb5129d53af59873f62a317a028beda463bbb96399\"}],\"sizeMB\":29.29},{\"id\":\"scrypted-yolov9s-relu-test-coreml\",\"label\":\"Scrypted YOLOv9S ReLU test CoreML\",\"family\":\"yolov9s\",\"variant\":\"relu_test\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9s_relu_test/scrypted_yolov9s_relu_test.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"e2d24de0ce7e8573f19572029765e55abc714f36a85175ad7fcb95c001a76653\"},{\"relativePath\":\"coreml/scrypted_yolov9s_relu_test/scrypted_yolov9s_relu_test.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":322664,\"sha256\":\"186b147cdfd15c5ed7a381ec3a426a22cee0b5c52d56f14efd26da06357f5cb0\"},{\"relativePath\":\"coreml/scrypted_yolov9s_relu_test/scrypted_yolov9s_relu_test.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":14190120,\"sha256\":\"e50651924c2cbffd56008a8953569e66c3de69f21807fcf7d825390ce30f21f8\"}],\"sizeMB\":14.51},{\"id\":\"scrypted-yolov9s-relu-test-onnx\",\"label\":\"Scrypted YOLOv9S ReLU test ONNX\",\"family\":\"yolov9s\",\"variant\":\"relu_test\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9s_relu_test/scrypted_yolov9s_relu_test.onnx\",\"size\":29287339,\"sha256\":\"115ddb83333762a0f2599421335678696454a5c3571ade5df9bac8e9cb5fb4cf\"}],\"sizeMB\":29.29},{\"id\":\"scrypted-yolov9m-relu-coreml\",\"label\":\"Scrypted YOLOv9M ReLU CoreML\",\"family\":\"yolov9m\",\"variant\":\"relu\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9m_relu/scrypted_yolov9m_relu.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"c548c6e1ff768b2b01bd71e230aa712a1635ae2baa30e0369557a42fc531ecbf\"},{\"relativePath\":\"coreml/scrypted_yolov9m_relu/scrypted_yolov9m_relu.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":247061,\"sha256\":\"1c40e02fc2a2559cb906ea6cd9da9c730d4a78f5019290cbb38d2fcaae379fe7\"},{\"relativePath\":\"coreml/scrypted_yolov9m_relu/scrypted_yolov9m_relu.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":39880616,\"sha256\":\"4cf938e5bfcc3ee1644813e4eb4702a7de450cc0581df94c719cf43d0ed2e408\"}],\"sizeMB\":40.13},{\"id\":\"scrypted-yolov9m-relu-onnx\",\"label\":\"Scrypted YOLOv9M ReLU ONNX\",\"family\":\"yolov9m\",\"variant\":\"relu\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9m_relu/scrypted_yolov9m_relu.onnx\",\"size\":80380404,\"sha256\":\"e7a81fb33224a0105d2a0a594c3fa43c398c361de623f1d14156ae826e7312d5\"}],\"sizeMB\":80.38},{\"id\":\"scrypted-yolov9m-relu-test-coreml\",\"label\":\"Scrypted YOLOv9M ReLU test CoreML\",\"family\":\"yolov9m\",\"variant\":\"relu_test\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9m_relu_test/scrypted_yolov9m_relu_test.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"8e2684b286d02b4bdf9db1027c84cb89bf8e4830cfb58269f3ec457c5a010650\"},{\"relativePath\":\"coreml/scrypted_yolov9m_relu_test/scrypted_yolov9m_relu_test.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":247061,\"sha256\":\"53d5946e74b13b15c5f8616081c9f15e8a95a5df3d5b45b830fc5a7f49561c16\"},{\"relativePath\":\"coreml/scrypted_yolov9m_relu_test/scrypted_yolov9m_relu_test.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":39880616,\"sha256\":\"69774bf0032c37707601b8cdc1e1820dbd9f8288eb5d6f3879618a5d34ff5199\"}],\"sizeMB\":40.13},{\"id\":\"scrypted-yolov9m-relu-test-onnx\",\"label\":\"Scrypted YOLOv9M ReLU test ONNX\",\"family\":\"yolov9m\",\"variant\":\"relu_test\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9m_relu_test/scrypted_yolov9m_relu_test.onnx\",\"size\":80380404,\"sha256\":\"8bb054f099295909ec518e2a6598d63db24d3f90edc8105a0f016903f2a205a2\"}],\"sizeMB\":80.38},{\"id\":\"scrypted-yolov9c-relu-coreml\",\"label\":\"Scrypted YOLOv9C ReLU CoreML\",\"family\":\"yolov9c\",\"variant\":\"relu\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9c_relu/scrypted_yolov9c_relu.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"813a306ebd69cabfd31f00434f5a961feeca7d237833f3e11805dcf4ad120ba9\"},{\"relativePath\":\"coreml/scrypted_yolov9c_relu/scrypted_yolov9c_relu.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":263210,\"sha256\":\"862f082619075e470738306b0ff0a37ae70a7d08e684ca6a6dfa1971d404e0c3\"},{\"relativePath\":\"coreml/scrypted_yolov9c_relu/scrypted_yolov9c_relu.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":50489960,\"sha256\":\"ed89747e58943758e2a87221d1d404f6a4466cbc039c3c6f96e327cadc403d27\"}],\"sizeMB\":50.75},{\"id\":\"scrypted-yolov9c-relu-onnx\",\"label\":\"Scrypted YOLOv9C ReLU ONNX\",\"family\":\"yolov9c\",\"variant\":\"relu\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9c_relu/scrypted_yolov9c_relu.onnx\",\"size\":101631962,\"sha256\":\"b9372c4428daf4cd944c114b1e80d1a9ff0487d743e53a77216a051cfbef7116\"}],\"sizeMB\":101.63},{\"id\":\"scrypted-yolov9c-relu-test-coreml\",\"label\":\"Scrypted YOLOv9C ReLU test CoreML\",\"family\":\"yolov9c\",\"variant\":\"relu_test\",\"format\":\"coreml\",\"precision\":\"fp16\",\"files\":[{\"relativePath\":\"coreml/scrypted_yolov9c_relu_test/scrypted_yolov9c_relu_test.mlpackage/Manifest.json\",\"size\":617,\"sha256\":\"966ef82a93cb0ccf1036a8c9729c065fd372fa9590a97d0945e581cc674f7ad2\"},{\"relativePath\":\"coreml/scrypted_yolov9c_relu_test/scrypted_yolov9c_relu_test.mlpackage/Data/com.apple.CoreML/model.mlmodel\",\"size\":263210,\"sha256\":\"08718ecd3e9552d21a702bd8df60911cad0eec7b2ae382466809550f14634711\"},{\"relativePath\":\"coreml/scrypted_yolov9c_relu_test/scrypted_yolov9c_relu_test.mlpackage/Data/com.apple.CoreML/weights/weight.bin\",\"size\":50489960,\"sha256\":\"fe5936b9430ab4c87f30b148220131d6ec998b2377223d033e2fa2a00e60aada\"}],\"sizeMB\":50.75},{\"id\":\"scrypted-yolov9c-relu-test-onnx\",\"label\":\"Scrypted YOLOv9C ReLU test ONNX\",\"family\":\"yolov9c\",\"variant\":\"relu_test\",\"format\":\"onnx\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"onnx/scrypted_yolov9c_relu_test/scrypted_yolov9c_relu_test.onnx\",\"size\":101631962,\"sha256\":\"2f4e2031e8efb4cbc871594338b2e875d78ab4af3ee0258d253273b1aac84742\"}],\"sizeMB\":101.63},{\"id\":\"scrypted-yolov9t-relu-openvino-fp32\",\"label\":\"Scrypted YOLOv9T ReLU OpenVINO FP32\",\"family\":\"yolov9t\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9t_relu/best-converted.xml\",\"size\":628222,\"sha256\":\"01b5a9d02a0f281e1aaf4860e1b0ed3a7f16b9d78975be8ff985ae0ee283d877\"},{\"relativePath\":\"openvino/scrypted_yolov9t_relu/best-converted.bin\",\"size\":7542204,\"sha256\":\"4fe9ce8dbdf94514fdd450cbfc907f257153e4898a0c1489a44e4912d8e32cc7\"}],\"sizeMB\":8.17},{\"id\":\"scrypted-yolov9s-relu-openvino-fp32\",\"label\":\"Scrypted YOLOv9S ReLU OpenVINO FP32\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9s_relu/best-converted.xml\",\"size\":629249,\"sha256\":\"5b4a229bf7229425698b63bd065a403e440a6151a6ddff0034867659cc4a480d\"},{\"relativePath\":\"openvino/scrypted_yolov9s_relu/best-converted.bin\",\"size\":28329724,\"sha256\":\"c030bd219208ea94082578c81f383d4ef9ec45d7150eec1c2c2ff2569cf49b35\"}],\"sizeMB\":28.96},{\"id\":\"scrypted-yolov9m-relu-openvino-fp32\",\"label\":\"Scrypted YOLOv9M ReLU OpenVINO FP32\",\"family\":\"yolov9m\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9m_relu/best-converted.xml\",\"size\":485439,\"sha256\":\"c66f9f1965bf70fd3635d4dfc3cb94c72c06b344c256713237d77706725b9a0c\"},{\"relativePath\":\"openvino/scrypted_yolov9m_relu/best-converted.bin\",\"size\":79717372,\"sha256\":\"dfc2d8952bbca9ad0e116168e3fd5c754e601c327d78c4f4d0d4cae12f0e1d2a\"}],\"sizeMB\":80.2},{\"id\":\"scrypted-yolov9c-relu-openvino-fp32\",\"label\":\"Scrypted YOLOv9C ReLU OpenVINO FP32\",\"family\":\"yolov9c\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"fp32\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9c_relu/best-converted.xml\",\"size\":535047,\"sha256\":\"836c1e998ebf76108ff64d554fdfa07aa72c42624f1a5448701bb409f27b8d68\"},{\"relativePath\":\"openvino/scrypted_yolov9c_relu/best-converted.bin\",\"size\":100942972,\"sha256\":\"11a1b330ac9843b349927977b3c7c7f17bb42af73a7ff102969132f27ab81814\"}],\"sizeMB\":101.48},{\"id\":\"scrypted-yolov9t-relu-openvino-int8\",\"label\":\"Scrypted YOLOv9T ReLU OpenVINO INT8\",\"family\":\"yolov9t\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9t_relu_int8/best-converted.xml\",\"size\":1380379,\"sha256\":\"e5a76bf88ac69fc0ebc50c04b61ff91550dd9c2349af1f6d7e4cac949c640cec\"},{\"relativePath\":\"openvino/scrypted_yolov9t_relu_int8/best-converted.bin\",\"size\":1954956,\"sha256\":\"98fb5b6826d7c6697a585f69ba96754873772d9d61fbed8335aab761e8248659\"}],\"sizeMB\":3.34},{\"id\":\"scrypted-yolov9t-relu-test-openvino-int8\",\"label\":\"Scrypted YOLOv9T ReLU test OpenVINO INT8\",\"family\":\"yolov9t\",\"variant\":\"relu_test\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9t_relu_test_int8/best-converted.xml\",\"size\":1380383,\"sha256\":\"9e4119284e923e08c9929e78fd31e2552e602a552ac538ae99bf34a50dd4d172\"},{\"relativePath\":\"openvino/scrypted_yolov9t_relu_test_int8/best-converted.bin\",\"size\":1954960,\"sha256\":\"efe5b0a2fd2f5ddef5923eeb9e592a7ef14719061bb7605c86e05b35c2c58ea9\"}],\"sizeMB\":3.34},{\"id\":\"scrypted-yolov9s-relu-openvino-int8\",\"label\":\"Scrypted YOLOv9S ReLU OpenVINO INT8\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9s_relu_int8/best-converted.xml\",\"size\":1382088,\"sha256\":\"4e6c24cf24afbab21462f7e1a15bd05b1cbbe5915046000e03c2435d8110bbbf\"},{\"relativePath\":\"openvino/scrypted_yolov9s_relu_int8/best-converted.bin\",\"size\":7197308,\"sha256\":\"23fd3d666a938c41db232d9c6239baec1134716cfb4ed4ddd3d602dd20eb8ac0\"}],\"sizeMB\":8.58},{\"id\":\"scrypted-yolov9s-relu-test-openvino-int8\",\"label\":\"Scrypted YOLOv9S ReLU test OpenVINO INT8\",\"family\":\"yolov9s\",\"variant\":\"relu_test\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9s_relu_test_int8/best-converted.xml\",\"size\":1382092,\"sha256\":\"49865bfeafe7576afce089ba6f5c3e25a9192c8b514eb96265c90fe877795641\"},{\"relativePath\":\"openvino/scrypted_yolov9s_relu_test_int8/best-converted.bin\",\"size\":7197312,\"sha256\":\"78e9d36e6b74754db2b624ea3ec0b3ff57ed6dfed59a4f2225e1a271054597de\"}],\"sizeMB\":8.58},{\"id\":\"scrypted-yolov9m-relu-openvino-int8\",\"label\":\"Scrypted YOLOv9M ReLU OpenVINO INT8\",\"family\":\"yolov9m\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9m_relu_int8/best-converted.xml\",\"size\":1024595,\"sha256\":\"1242ab8d8c73b2e80c5dd541177ade6e102a2684114b059136acb4cad6efb034\"},{\"relativePath\":\"openvino/scrypted_yolov9m_relu_int8/best-converted.bin\",\"size\":20090516,\"sha256\":\"da8e44af8bee66478e6f359e9504bfb13ab4b76f4bd8a2f1b10cfbc22febcf6e\"}],\"sizeMB\":21.12},{\"id\":\"scrypted-yolov9m-relu-test-openvino-int8\",\"label\":\"Scrypted YOLOv9M ReLU test OpenVINO INT8\",\"family\":\"yolov9m\",\"variant\":\"relu_test\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9m_relu_test_int8/best-converted.xml\",\"size\":1024601,\"sha256\":\"24395ae5fdbb321304c865372cd2b905cdc55abb7610520f3963702e2371decb\"},{\"relativePath\":\"openvino/scrypted_yolov9m_relu_test_int8/best-converted.bin\",\"size\":20090520,\"sha256\":\"4360fe0312067f92c49f180c7b8a10271f14d58b8909aa9b96b13af4ab9289aa\"}],\"sizeMB\":21.12},{\"id\":\"scrypted-yolov9c-relu-openvino-int8\",\"label\":\"Scrypted YOLOv9C ReLU OpenVINO INT8\",\"family\":\"yolov9c\",\"variant\":\"relu\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9c_relu_int8/best-converted.xml\",\"size\":1092695,\"sha256\":\"a45c62940120c4301d8fff9fb3b275c29e3618f403ea68a785f2f3220cefec7a\"},{\"relativePath\":\"openvino/scrypted_yolov9c_relu_int8/best-converted.bin\",\"size\":25428280,\"sha256\":\"0643403a56178c3ed1e066c7884458d8ea6780115d662bdc38d10754d7a11c79\"}],\"sizeMB\":26.52},{\"id\":\"scrypted-yolov9c-relu-test-openvino-int8\",\"label\":\"Scrypted YOLOv9C ReLU test OpenVINO INT8\",\"family\":\"yolov9c\",\"variant\":\"relu_test\",\"format\":\"openvino\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"openvino/scrypted_yolov9c_relu_test_int8/best-converted.xml\",\"size\":1092695,\"sha256\":\"a8a1899848ebd060af2326d3c8d3c09ea83354f0bedff6cce6fa08b284ea5519\"},{\"relativePath\":\"openvino/scrypted_yolov9c_relu_test_int8/best-converted.bin\",\"size\":25428280,\"sha256\":\"2eb6bb3d0a03a9b239b6f623f9e7bced62eea61fbe4d8ff89c1b3e2e2b7978a2\"}],\"sizeMB\":26.52},{\"id\":\"scrypted-yolov9s-relu-sep-320-tflite-float16\",\"label\":\"Scrypted YOLOv9s ReLU sep 320 TFLite float16\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"tflite\",\"precision\":\"float16\",\"files\":[{\"relativePath\":\"tflite/scrypted_yolov9s_relu_sep_320/best_float16.tflite\",\"size\":14482836,\"sha256\":\"04889db3b81af361c361e41ffa99327330d37b71d680a2f889e6cdf1e94c0d8c\"}],\"sizeMB\":14.48},{\"id\":\"scrypted-yolov9s-relu-sep-320-tflite-float32\",\"label\":\"Scrypted YOLOv9s ReLU sep 320 TFLite float32\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"tflite\",\"precision\":\"float32\",\"files\":[{\"relativePath\":\"tflite/scrypted_yolov9s_relu_sep_320/best_float32.tflite\",\"size\":28777068,\"sha256\":\"f5d7d08fc1d8f2262633d2df4391638e0481740331aaa1837880a47c33001a96\"}],\"sizeMB\":28.78},{\"id\":\"scrypted-yolov9s-relu-sep-320-tflite-int8\",\"label\":\"Scrypted YOLOv9s ReLU sep 320 TFLite int8\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"tflite\",\"precision\":\"int8\",\"files\":[{\"relativePath\":\"tflite/scrypted_yolov9s_relu_sep_320/best_int8.tflite\",\"size\":7325616,\"sha256\":\"1e9866b16fe80931383bf3c94dc629fe93a37ba3dc1465ea019eb0a50bd428df\"}],\"sizeMB\":7.33},{\"id\":\"scrypted-yolov9s-relu-sep-320-tflite-integer_quant\",\"label\":\"Scrypted YOLOv9s ReLU sep 320 TFLite integer_quant\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"tflite\",\"precision\":\"integer_quant\",\"files\":[{\"relativePath\":\"tflite/scrypted_yolov9s_relu_sep_320/best_integer_quant.tflite\",\"size\":7342552,\"sha256\":\"bc1c902e9a9865637ee2403f70c6bf5c29c695d9163e58ad658feb29bb097ebc\"}],\"sizeMB\":7.34},{\"id\":\"scrypted-yolov9s-relu-sep-320-tflite-full_integer_quant\",\"label\":\"Scrypted YOLOv9s ReLU sep 320 TFLite full_integer_quant\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"tflite\",\"precision\":\"full_integer_quant\",\"files\":[{\"relativePath\":\"tflite/scrypted_yolov9s_relu_sep_320/best_full_integer_quant.tflite\",\"size\":7341704,\"sha256\":\"483ce38dbe33ee5a35f40a82f56c77629a44fa5d95efd8ade9fa97e4d2a261fd\"}],\"sizeMB\":7.34},{\"id\":\"scrypted-yolov9s-relu-sep-320-tflite-edgetpu\",\"label\":\"Scrypted YOLOv9s ReLU sep 320 TFLite edgetpu\",\"family\":\"yolov9s\",\"variant\":\"relu\",\"format\":\"tflite\",\"precision\":\"edgetpu\",\"files\":[{\"relativePath\":\"tflite/scrypted_yolov9s_relu_sep_320/best_full_integer_quant_edgetpu.tflite\",\"size\":8164480,\"sha256\":\"e1e6b074a3b9fdf19a171baf615553ad375a046aa7de2a3fef9a1950175fab22\"}],\"sizeMB\":8.16}]");
|
|
38
15
|
//#endregion
|
|
@@ -129,44 +106,55 @@ function packageDirectory(files) {
|
|
|
129
106
|
return assertScryptedRelativePath(manifest.relativePath.replace(/\/Manifest\.json$/, ""));
|
|
130
107
|
}
|
|
131
108
|
function buildModel(spec) {
|
|
109
|
+
const tier = spec.family.slice(-1);
|
|
110
|
+
const int8 = spec.precision === "int8" || spec.precision === "uint8" || spec.precision === "integer_quant" || spec.precision === "full_integer_quant" || spec.precision === "edgetpu";
|
|
132
111
|
const group = {
|
|
133
112
|
family: "yolov9",
|
|
134
|
-
tier
|
|
113
|
+
tier,
|
|
135
114
|
resolution: 320,
|
|
136
|
-
...spec.variant === "relu_test" ? { optimization: "
|
|
137
|
-
...
|
|
115
|
+
...spec.variant === "relu_test" ? { optimization: "test" } : {},
|
|
116
|
+
...int8 ? { precision: "int8" } : {}
|
|
117
|
+
};
|
|
118
|
+
const sizeLabel = {
|
|
119
|
+
t: "Tiny",
|
|
120
|
+
s: "Small",
|
|
121
|
+
m: "Medium",
|
|
122
|
+
c: "Compact"
|
|
123
|
+
};
|
|
124
|
+
const canonicalId = `scrypted-yolov9${tier}-${spec.variant}${int8 ? "-int8" : ""}-320`;
|
|
125
|
+
const nameParts = [`YOLOv9 ${sizeLabel[tier] ?? tier.toUpperCase()} @320`];
|
|
126
|
+
if (spec.variant === "relu_test") nameParts.push("(Test)");
|
|
127
|
+
if (int8) nameParts.push("INT8");
|
|
128
|
+
const name = nameParts.join(" ");
|
|
129
|
+
const description = `${name} — imported from the local Scrypted YOLO catalog.`;
|
|
130
|
+
if (spec.format === "coreml") return {
|
|
131
|
+
id: canonicalId,
|
|
132
|
+
name,
|
|
133
|
+
description,
|
|
134
|
+
formats: { coreml: {
|
|
135
|
+
url: buildScryptedResolveUrl(packageDirectory(spec.files)),
|
|
136
|
+
sizeMB: spec.sizeMB,
|
|
137
|
+
isDirectory: true,
|
|
138
|
+
files: [...MLPACKAGE_INNER_FILES],
|
|
139
|
+
runtimes: ["python"]
|
|
140
|
+
} },
|
|
141
|
+
inputSize: INPUT_SIZE,
|
|
142
|
+
labels: SCRYPTED_LABEL_DEFS,
|
|
143
|
+
preprocessMode: "resize",
|
|
144
|
+
classMap: SCRYPTED_IDENTITY_CLASS_MAP,
|
|
145
|
+
group,
|
|
146
|
+
provider: "scrypted",
|
|
147
|
+
license: SCRYPTED_YOLOV9_UNKNOWN
|
|
138
148
|
};
|
|
139
|
-
if (spec.format === "coreml") {
|
|
140
|
-
const dir = packageDirectory(spec.files);
|
|
141
|
-
return {
|
|
142
|
-
id: spec.id,
|
|
143
|
-
name: spec.label,
|
|
144
|
-
description: `${spec.label} — imported from the local Scrypted YOLO catalog.`,
|
|
145
|
-
formats: { coreml: {
|
|
146
|
-
url: buildScryptedResolveUrl(dir),
|
|
147
|
-
sizeMB: spec.sizeMB,
|
|
148
|
-
isDirectory: true,
|
|
149
|
-
files: [...MLPACKAGE_INNER_FILES],
|
|
150
|
-
runtimes: ["python"]
|
|
151
|
-
} },
|
|
152
|
-
inputSize: INPUT_SIZE,
|
|
153
|
-
labels: SCRYPTED_LABEL_DEFS,
|
|
154
|
-
preprocessMode: "resize",
|
|
155
|
-
classMap: SCRYPTED_IDENTITY_CLASS_MAP,
|
|
156
|
-
group,
|
|
157
|
-
provider: "scrypted",
|
|
158
|
-
license: SCRYPTED_YOLOV9_UNKNOWN
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
149
|
if (spec.format === "openvino") {
|
|
162
150
|
const xml = spec.files.find((file) => file.relativePath.endsWith(".xml"));
|
|
163
151
|
const bin = spec.files.find((file) => file.relativePath.endsWith(".bin"));
|
|
164
152
|
if (!xml || !bin) throw new Error(`OpenVINO entry ${spec.id} is missing xml/bin`);
|
|
165
153
|
const binName = bin.relativePath.split("/").pop() ?? "best-converted.bin";
|
|
166
154
|
return {
|
|
167
|
-
id:
|
|
168
|
-
name
|
|
169
|
-
description
|
|
155
|
+
id: canonicalId,
|
|
156
|
+
name,
|
|
157
|
+
description,
|
|
170
158
|
formats: { openvino: {
|
|
171
159
|
url: buildScryptedResolveUrl(xml.relativePath),
|
|
172
160
|
sizeMB: spec.sizeMB,
|
|
@@ -185,9 +173,9 @@ function buildModel(spec) {
|
|
|
185
173
|
const main = spec.files[0];
|
|
186
174
|
if (!main) throw new Error(`Entry ${spec.id} has no files`);
|
|
187
175
|
return {
|
|
188
|
-
id:
|
|
189
|
-
name
|
|
190
|
-
description
|
|
176
|
+
id: canonicalId,
|
|
177
|
+
name,
|
|
178
|
+
description,
|
|
191
179
|
formats: { [spec.format]: {
|
|
192
180
|
url: buildScryptedResolveUrl(main.relativePath),
|
|
193
181
|
sizeMB: spec.sizeMB,
|
|
@@ -199,7 +187,8 @@ function buildModel(spec) {
|
|
|
199
187
|
classMap: SCRYPTED_IDENTITY_CLASS_MAP,
|
|
200
188
|
group,
|
|
201
189
|
provider: "scrypted",
|
|
202
|
-
license: SCRYPTED_YOLOV9_UNKNOWN
|
|
190
|
+
license: SCRYPTED_YOLOV9_UNKNOWN,
|
|
191
|
+
...spec.format === "tflite" ? { postprocessor: "yolo-split-dfl" } : {}
|
|
203
192
|
};
|
|
204
193
|
}
|
|
205
194
|
function freezeEntry(spec) {
|
|
@@ -361,192 +350,21 @@ async function fetchScryptedRemoteTree(opts) {
|
|
|
361
350
|
//#endregion
|
|
362
351
|
//#region src/scrypted/scrypted-import.ts
|
|
363
352
|
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
var SCRYPTED_IMPORT_MAX_FILE_BYTES = 256 * 1024 * 1024;
|
|
369
|
-
/** Deterministic bundle digest: sorted `path\\0sha256` lines, never the primary file alone. */
|
|
370
|
-
function scryptedBundleFingerprint(entry) {
|
|
371
|
-
const hash = createHash("sha256");
|
|
372
|
-
const lines = entry.files.map((file) => `${file.relativePath}\0${file.sha256}`).toSorted();
|
|
373
|
-
hash.update(lines.join("\n"));
|
|
374
|
-
return hash.digest("hex");
|
|
375
|
-
}
|
|
376
|
-
var LFS_POINTER_PREFIX = "version https://git-lfs.github.com/spec/v1";
|
|
377
|
-
var SIZE_TOLERANCE = .01;
|
|
378
|
-
function isLfsPointer(filePath) {
|
|
379
|
-
const fd = fs$1.openSync(filePath, "r");
|
|
380
|
-
try {
|
|
381
|
-
const buf = Buffer.alloc(128);
|
|
382
|
-
const n = fs$1.readSync(fd, buf, 0, buf.length, 0);
|
|
383
|
-
return buf.subarray(0, n).toString("utf8").startsWith(LFS_POINTER_PREFIX);
|
|
384
|
-
} finally {
|
|
385
|
-
fs$1.closeSync(fd);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
async function hashFileSha256(filePath) {
|
|
389
|
-
const hash = createHash("sha256");
|
|
390
|
-
await new Promise((resolve, reject) => {
|
|
391
|
-
fs$1.createReadStream(filePath).on("data", (chunk) => hash.update(chunk)).on("end", () => resolve()).on("error", reject);
|
|
392
|
-
});
|
|
393
|
-
return hash.digest("hex");
|
|
394
|
-
}
|
|
395
|
-
function assertInside(root, dest) {
|
|
396
|
-
const rootResolved = path$1.resolve(root);
|
|
397
|
-
const destResolved = path$1.resolve(dest);
|
|
398
|
-
const prefix = rootResolved.endsWith(path$1.sep) ? rootResolved : `${rootResolved}${path$1.sep}`;
|
|
399
|
-
if (destResolved !== rootResolved && !destResolved.startsWith(prefix)) throw new Error(`Refusing to write outside the destination root: ${dest}`);
|
|
400
|
-
return destResolved;
|
|
401
|
-
}
|
|
402
|
-
function rejectSymlink(filePath) {
|
|
403
|
-
try {
|
|
404
|
-
if (fs$1.lstatSync(filePath).isSymbolicLink()) throw new Error(`Refusing symlink at ${filePath}`);
|
|
405
|
-
} catch (err) {
|
|
406
|
-
if (err.code === "ENOENT") return;
|
|
407
|
-
throw err;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
function sizeOutOfTolerance(actual, declared) {
|
|
411
|
-
if (declared <= 0) return true;
|
|
412
|
-
return Math.abs(actual - declared) / declared > SIZE_TOLERANCE;
|
|
413
|
-
}
|
|
414
|
-
function localBasename(entry) {
|
|
415
|
-
if (entry.format === "coreml") return (entry.files.find((file) => file.relativePath.endsWith("/Manifest.json"))?.relativePath.replace(/\/Manifest\.json$/, "") ?? `${entry.id}.mlpackage`).split("/").pop() ?? `${entry.id}.mlpackage`;
|
|
416
|
-
if (entry.format === "openvino") return `${entry.id}.xml`;
|
|
417
|
-
if (entry.format === "tflite") return `${entry.id}.tflite`;
|
|
418
|
-
return entry.files[0]?.relativePath.split("/").pop() ?? `${entry.id}.onnx`;
|
|
419
|
-
}
|
|
420
|
-
function buildImportedDescriptor(entry, localName) {
|
|
421
|
-
const catalog = {
|
|
422
|
-
...entry.model,
|
|
423
|
-
classMap: SCRYPTED_IDENTITY_CLASS_MAP,
|
|
424
|
-
formats: { ...entry.model.formats }
|
|
425
|
-
};
|
|
426
|
-
const format = entry.format;
|
|
427
|
-
const previous = catalog.formats[format];
|
|
428
|
-
if (format === "coreml") catalog.formats = {
|
|
429
|
-
...catalog.formats,
|
|
430
|
-
coreml: {
|
|
431
|
-
url: `camstack-local://${entry.id}/${localName}`,
|
|
432
|
-
sizeMB: previous?.sizeMB ?? entry.files.reduce((sum, file) => sum + file.size, 0) / 1e6,
|
|
433
|
-
isDirectory: true,
|
|
434
|
-
files: previous?.files ?? [
|
|
435
|
-
"Manifest.json",
|
|
436
|
-
"Data/com.apple.CoreML/model.mlmodel",
|
|
437
|
-
"Data/com.apple.CoreML/weights/weight.bin"
|
|
438
|
-
],
|
|
439
|
-
runtimes: ["python"]
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
else if (format === "openvino") catalog.formats = {
|
|
443
|
-
...catalog.formats,
|
|
444
|
-
openvino: {
|
|
445
|
-
url: `camstack-local://${entry.id}/${localName}`,
|
|
446
|
-
sizeMB: previous?.sizeMB ?? entry.files.reduce((sum, file) => sum + file.size, 0) / 1e6,
|
|
447
|
-
files: [`${entry.id}.bin`],
|
|
448
|
-
runtimes: ["python"]
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
else catalog.formats = {
|
|
452
|
-
...catalog.formats,
|
|
453
|
-
[format]: {
|
|
454
|
-
url: `camstack-local://${entry.id}/${localName}`,
|
|
455
|
-
sizeMB: previous?.sizeMB ?? entry.files.reduce((sum, file) => sum + file.size, 0) / 1e6,
|
|
456
|
-
...previous?.runtimes !== void 0 ? { runtimes: previous.runtimes } : {}
|
|
457
|
-
}
|
|
458
|
-
};
|
|
353
|
+
* Library registration descriptor: keeps manifesto remote URLs (HF resolve).
|
|
354
|
+
* Files land on a node only via Distribute / downloadModel — not hub staging.
|
|
355
|
+
*/
|
|
356
|
+
function buildScryptedRegistryDescriptor(entry) {
|
|
459
357
|
return {
|
|
460
358
|
stepId: "object-detection",
|
|
461
|
-
entry:
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const format = entry.format;
|
|
469
|
-
if (!descriptor.entry.formats[format]) throw new Error(`resolveScryptedImportedFormat: descriptor for "${manifestId}" is missing manifesto format ${format}`);
|
|
470
|
-
return format;
|
|
471
|
-
}
|
|
472
|
-
async function verifyScryptedStagedFile(destPath, file, deps) {
|
|
473
|
-
const maxBytes = deps.maxFileBytes ?? 268435456;
|
|
474
|
-
if (file.size > maxBytes) throw new Error(`Declared size ${file.size} exceeds the ${maxBytes}-byte limit`);
|
|
475
|
-
if (!fs$1.existsSync(destPath)) throw new Error(`missing downloaded file for ${file.relativePath}`);
|
|
476
|
-
rejectSymlink(destPath);
|
|
477
|
-
const actual = fs$1.statSync(destPath).size;
|
|
478
|
-
if (actual > maxBytes) throw new Error(`Downloaded ${actual} bytes exceeds the ${maxBytes}-byte limit`);
|
|
479
|
-
if (isLfsPointer(destPath)) throw new Error(`Downloaded an LFS pointer instead of the artifact (${file.relativePath})`);
|
|
480
|
-
if (sizeOutOfTolerance(actual, file.size)) throw new Error(`Downloaded size ${actual} is outside tolerance of declared ${file.size} for ${file.relativePath}`);
|
|
481
|
-
const expected = assertScryptedFileSha256(file.sha256, file.relativePath);
|
|
482
|
-
if (await (deps.fileSha256 ?? hashFileSha256)(destPath) !== expected) throw new Error(`sha256 checksum mismatch for ${file.relativePath}`);
|
|
483
|
-
}
|
|
484
|
-
async function importScryptedCatalogModel(input, deps) {
|
|
485
|
-
const entry = (deps.resolveEntry ?? getScryptedManifestEntry)(input.manifestId);
|
|
486
|
-
if (!entry) throw new Error(`importScryptedCatalogModel: unknown manifest id "${input.manifestId}"`);
|
|
487
|
-
for (const file of entry.files) assertScryptedFileSha256(file.sha256, file.relativePath);
|
|
488
|
-
const drift = normalizeScryptedDrift(await deps.driftFor(entry.id));
|
|
489
|
-
if (drift.status !== "ok") throw new Error(`importScryptedCatalogModel: entry "${entry.id}" is blocked by drift (${drift.status}${drift.details ? `: ${drift.details}` : ""})`);
|
|
490
|
-
fs$1.mkdirSync(deps.stagingDir, { recursive: true });
|
|
491
|
-
fs$1.mkdirSync(deps.modelsDir, { recursive: true });
|
|
492
|
-
const stagingRoot = path$1.join(deps.stagingDir, `imp-${randomUUID()}`);
|
|
493
|
-
fs$1.mkdirSync(stagingRoot, { recursive: true });
|
|
494
|
-
try {
|
|
495
|
-
const staged = [];
|
|
496
|
-
for (const file of entry.files) {
|
|
497
|
-
const relative = assertScryptedRelativePath(file.relativePath);
|
|
498
|
-
const dest = assertInside(stagingRoot, path$1.join(stagingRoot, ...relative.split("/")));
|
|
499
|
-
const url = buildScryptedResolveUrl(relative);
|
|
500
|
-
await deps.downloadFile(url, dest);
|
|
501
|
-
await verifyScryptedStagedFile(dest, file, deps);
|
|
502
|
-
staged.push({
|
|
503
|
-
file,
|
|
504
|
-
dest
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
if (entry.format === "coreml") {
|
|
508
|
-
const hasManifest = staged.some((row) => row.file.relativePath.endsWith("Manifest.json"));
|
|
509
|
-
const hasModel = staged.some((row) => row.file.relativePath.endsWith("model.mlmodel"));
|
|
510
|
-
const hasWeight = staged.some((row) => row.file.relativePath.endsWith("weight.bin"));
|
|
511
|
-
if (!hasManifest || !hasModel || !hasWeight || staged.length < 3) throw new Error("CoreML payload is incomplete — Manifest.json, model and weights are required");
|
|
512
|
-
}
|
|
513
|
-
if (entry.format === "openvino") {
|
|
514
|
-
const hasXml = staged.some((row) => row.file.relativePath.endsWith(".xml"));
|
|
515
|
-
const hasBin = staged.some((row) => row.file.relativePath.endsWith(".bin"));
|
|
516
|
-
if (!hasXml || !hasBin) throw new Error("OpenVINO payload is incomplete — xml and bin siblings are required");
|
|
517
|
-
}
|
|
518
|
-
const localName = localBasename(entry);
|
|
519
|
-
const finalMain = assertInside(deps.modelsDir, path$1.join(deps.modelsDir, localName));
|
|
520
|
-
rejectSymlink(finalMain);
|
|
521
|
-
if (entry.format === "coreml") {
|
|
522
|
-
const pkgRel = staged.find((row) => row.file.relativePath.endsWith("Manifest.json"))?.file.relativePath.replace(/\/Manifest\.json$/, "");
|
|
523
|
-
if (!pkgRel) throw new Error("CoreML payload is incomplete — Manifest.json missing");
|
|
524
|
-
const stagedPkg = path$1.join(stagingRoot, ...pkgRel.split("/"));
|
|
525
|
-
fs$1.rmSync(finalMain, {
|
|
526
|
-
recursive: true,
|
|
527
|
-
force: true
|
|
528
|
-
});
|
|
529
|
-
fs$1.renameSync(stagedPkg, finalMain);
|
|
530
|
-
} else if (entry.format === "openvino") {
|
|
531
|
-
const xml = staged.find((row) => row.file.relativePath.endsWith(".xml"));
|
|
532
|
-
const bin = staged.find((row) => row.file.relativePath.endsWith(".bin"));
|
|
533
|
-
if (!xml || !bin) throw new Error("OpenVINO payload is incomplete");
|
|
534
|
-
const xmlDest = finalMain;
|
|
535
|
-
const binDest = assertInside(deps.modelsDir, path$1.join(deps.modelsDir, `${entry.id}.bin`));
|
|
536
|
-
fs$1.renameSync(xml.dest, xmlDest);
|
|
537
|
-
fs$1.renameSync(bin.dest, binDest);
|
|
538
|
-
} else {
|
|
539
|
-
const only = staged[0];
|
|
540
|
-
if (!only) throw new Error("payload is incomplete");
|
|
541
|
-
fs$1.renameSync(only.dest, finalMain);
|
|
359
|
+
entry: {
|
|
360
|
+
...entry.model,
|
|
361
|
+
classMap: SCRYPTED_IDENTITY_CLASS_MAP,
|
|
362
|
+
formats: { ...entry.model.formats },
|
|
363
|
+
name: entry.model.name,
|
|
364
|
+
description: entry.model.description,
|
|
365
|
+
provider: "scrypted"
|
|
542
366
|
}
|
|
543
|
-
|
|
544
|
-
} finally {
|
|
545
|
-
fs$1.rmSync(stagingRoot, {
|
|
546
|
-
recursive: true,
|
|
547
|
-
force: true
|
|
548
|
-
});
|
|
549
|
-
}
|
|
367
|
+
};
|
|
550
368
|
}
|
|
551
369
|
//#endregion
|
|
552
370
|
//#region ../types/dist/event-category-BGmz_oxW.mjs
|
|
@@ -11151,11 +10969,11 @@ var ModelVariantGroupSchema = object({
|
|
|
11151
10969
|
/** Quantization axis. Omit ⇒ the fp32 base build. */
|
|
11152
10970
|
precision: _enum(["fp32", "int8"]).optional(),
|
|
11153
10971
|
/**
|
|
11154
|
-
*
|
|
11155
|
-
*
|
|
11156
|
-
*
|
|
10972
|
+
* Training / latency-variant axis. Omit ⇒ the standard build. `test` marks
|
|
10973
|
+
* an alternate training of the same family (Scrypted `relu_test`); chip
|
|
10974
|
+
* label is "Test".
|
|
11157
10975
|
*/
|
|
11158
|
-
optimization: _enum(["standard", "
|
|
10976
|
+
optimization: _enum(["standard", "test"]).optional(),
|
|
11159
10977
|
/**
|
|
11160
10978
|
* Input-resolution axis (square input side, px). Omit ⇒ the family's native
|
|
11161
10979
|
* resolution (640 for yolo26). Reduced-input builds (320 / 256) are a big,
|
|
@@ -11178,9 +10996,10 @@ var ModelProviderIdSchema = _enum([
|
|
|
11178
10996
|
* are per upstream. A persisted custom-model row written when this field was a
|
|
11179
10997
|
* string still parses — the string is lifted into `{ weights }`.
|
|
11180
10998
|
*
|
|
11181
|
-
* Every licence surface (the
|
|
11182
|
-
*
|
|
11183
|
-
*
|
|
10999
|
+
* Every machine licence surface (`THIRD_PARTY_MODELS.md`, the Hugging Face
|
|
11000
|
+
* model table) is GENERATED from these records by
|
|
11001
|
+
* `scripts/gen-model-licenses.ts` (D661). Operator-facing docs live in
|
|
11002
|
+
* `camstack/docs` → `/docs/detection/models`.
|
|
11184
11003
|
*/
|
|
11185
11004
|
var ModelLicenseSchema = preprocess((value) => typeof value === "string" ? { weights: value } : value, object({
|
|
11186
11005
|
/** SPDX id of the WEIGHTS' terms, a `LicenseRef-*` for non-SPDX terms, or `UNKNOWN`. */
|
|
@@ -17733,7 +17552,7 @@ var modelConvertCapability = {
|
|
|
17733
17552
|
}) }
|
|
17734
17553
|
};
|
|
17735
17554
|
method(object({
|
|
17736
|
-
|
|
17555
|
+
targetNodeId: string(),
|
|
17737
17556
|
modelId: string(),
|
|
17738
17557
|
format: _enum(MODEL_FORMATS),
|
|
17739
17558
|
entry: ModelCatalogEntrySchema
|
|
@@ -21662,39 +21481,19 @@ Object.freeze(Object.fromEntries([{
|
|
|
21662
21481
|
label: "Face recognition model",
|
|
21663
21482
|
defaultModelId: "auraface-r100",
|
|
21664
21483
|
indexName: "the enrolled face gallery",
|
|
21665
|
-
options: [
|
|
21666
|
-
|
|
21667
|
-
|
|
21668
|
-
|
|
21669
|
-
},
|
|
21670
|
-
{
|
|
21671
|
-
id: "arcface-r100",
|
|
21672
|
-
label: "ArcFace (ResNet34)"
|
|
21673
|
-
},
|
|
21674
|
-
{
|
|
21675
|
-
id: "inception-resnet-v1",
|
|
21676
|
-
label: "Inception ResNet V1"
|
|
21677
|
-
}
|
|
21678
|
-
]
|
|
21484
|
+
options: [{
|
|
21485
|
+
id: "auraface-r100",
|
|
21486
|
+
label: "AuraFace R100"
|
|
21487
|
+
}]
|
|
21679
21488
|
}, {
|
|
21680
21489
|
stepId: "clip-embedding",
|
|
21681
21490
|
label: "Semantic search model",
|
|
21682
|
-
defaultModelId: "
|
|
21491
|
+
defaultModelId: "siglip2-b16-224",
|
|
21683
21492
|
indexName: "the object semantic-search index",
|
|
21684
|
-
options: [
|
|
21685
|
-
|
|
21686
|
-
|
|
21687
|
-
|
|
21688
|
-
},
|
|
21689
|
-
{
|
|
21690
|
-
id: "mobileclip-s2",
|
|
21691
|
-
label: "MobileCLIP S2"
|
|
21692
|
-
},
|
|
21693
|
-
{
|
|
21694
|
-
id: "siglip2-b16-224",
|
|
21695
|
-
label: "SigLIP2 B/16 (Apache-2.0)"
|
|
21696
|
-
}
|
|
21697
|
-
]
|
|
21493
|
+
options: [{
|
|
21494
|
+
id: "siglip2-b16-224",
|
|
21495
|
+
label: "SigLIP2 B/16 (Apache-2.0)"
|
|
21496
|
+
}]
|
|
21698
21497
|
}].map((s) => [s.stepId, s.defaultModelId])));
|
|
21699
21498
|
var CLUSTER_STEP_SETTING_FIELDS = [{
|
|
21700
21499
|
stepId: "face-embedding",
|
|
@@ -32406,7 +32205,13 @@ var OsdStatusSchema = object({ overlays: array(object({
|
|
|
32406
32205
|
*/
|
|
32407
32206
|
availablePositions: array(OsdPositionEnum).readonly(),
|
|
32408
32207
|
/** Firmware does not allow editing the `text` field (e.g. timestamp). */
|
|
32409
|
-
readOnly: boolean().optional()
|
|
32208
|
+
readOnly: boolean().optional(),
|
|
32209
|
+
/**
|
|
32210
|
+
* Firmware text ceiling for this overlay. Absent = no known limit.
|
|
32211
|
+
* The OSD manager truncates here on every write — never stores its own
|
|
32212
|
+
* copy on the binding.
|
|
32213
|
+
*/
|
|
32214
|
+
maxCharacters: number().int().positive().optional()
|
|
32410
32215
|
})).readonly() });
|
|
32411
32216
|
var OsdOverlayPatchSchema = object({
|
|
32412
32217
|
enabled: boolean().optional(),
|
|
@@ -32457,7 +32262,9 @@ var OsdTemplateBindingSchema = object({
|
|
|
32457
32262
|
valuePath: string().min(1).max(64),
|
|
32458
32263
|
/** Convert TO this unit at render (`tryConvertUnit`). Absent = source unit. */
|
|
32459
32264
|
displayUnit: string().max(16).optional(),
|
|
32460
|
-
maxDecimals: number().int().min(0).max(4).default(1)
|
|
32265
|
+
maxDecimals: number().int().min(0).max(4).default(1),
|
|
32266
|
+
/** Raw value → display text (same shape as slot binding). Empty string hides. */
|
|
32267
|
+
valueMap: record(string(), string()).optional()
|
|
32461
32268
|
});
|
|
32462
32269
|
/**
|
|
32463
32270
|
* A named template: bindings (alias → leaf field) + a parser that
|
|
@@ -32507,18 +32314,11 @@ var OsdSlotBindingSchema = object({
|
|
|
32507
32314
|
source: OsdSourceSchema,
|
|
32508
32315
|
/** `${value}` and `${unit}` are substituted; every occurrence. */
|
|
32509
32316
|
template: string().max(96).default("${value}"),
|
|
32510
|
-
/** Truncate with an ellipsis past this length. Absent = no limit. */
|
|
32511
|
-
maxCharacters: number().int().min(4).max(64).optional(),
|
|
32512
32317
|
/**
|
|
32513
32318
|
* Decimal places for a numeric value. `0` yields an integer — the
|
|
32514
32319
|
* documented workaround for firmwares that reject `.` in overlay text.
|
|
32515
32320
|
*/
|
|
32516
32321
|
maxDecimals: number().int().min(0).max(4).default(1),
|
|
32517
|
-
/**
|
|
32518
|
-
* Appended via `${unit}` when no unit conversion produces a label.
|
|
32519
|
-
* Prefer `source.displayUnit` for converted device-state values.
|
|
32520
|
-
*/
|
|
32521
|
-
unitLabel: string().max(8).optional(),
|
|
32522
32322
|
/** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
|
|
32523
32323
|
valueMap: record(string(), string()).optional(),
|
|
32524
32324
|
/** Time windows in which the slot is shown. Absent = always. */
|
|
@@ -32527,9 +32327,7 @@ var OsdSlotBindingSchema = object({
|
|
|
32527
32327
|
* Display gate, in the notification centre's condition vocabulary.
|
|
32528
32328
|
* Only the keys reported by `getConditionSupport` are accepted.
|
|
32529
32329
|
*/
|
|
32530
|
-
conditions: NcConditionsSchema.optional()
|
|
32531
|
-
/** Rendered when the gate is closed or the value unreadable. Empty = hide. */
|
|
32532
|
-
fallbackText: string().max(64).default("")
|
|
32330
|
+
conditions: NcConditionsSchema.optional()
|
|
32533
32331
|
});
|
|
32534
32332
|
/** One camera slot, as the operator sees it: firmware truth + our binding. */
|
|
32535
32333
|
var OsdSlotViewSchema = object({
|
|
@@ -48501,6 +48299,8 @@ var STATE_SUMMARY_VARS = [
|
|
|
48501
48299
|
[...RULE_TEXT],
|
|
48502
48300
|
[...RULE_TEXT],
|
|
48503
48301
|
[...RULE_TEXT],
|
|
48302
|
+
[...RULE_TEXT],
|
|
48303
|
+
[...RULE_TEXT],
|
|
48504
48304
|
...STATE_SUMMARY_VARS
|
|
48505
48305
|
];
|
|
48506
48306
|
new Set([
|
|
@@ -49081,7 +48881,9 @@ async function convertModel(input, deps) {
|
|
|
49081
48881
|
var ConvertProgressSchema = object({
|
|
49082
48882
|
phase: string(),
|
|
49083
48883
|
pct: number().optional(),
|
|
49084
|
-
detail: string().optional()
|
|
48884
|
+
detail: string().optional(),
|
|
48885
|
+
/** Raw process line (stderr) — UI log pane, not a phase step. */
|
|
48886
|
+
log: string().optional()
|
|
49085
48887
|
});
|
|
49086
48888
|
var ConvertResultLineSchema = object({ result: object({ artifacts: array(ConvertArtifactSchema) }) });
|
|
49087
48889
|
function buildEntry(metadata, artifacts) {
|
|
@@ -49150,29 +48952,57 @@ function buildModelConvertProvider(deps) {
|
|
|
49150
48952
|
//#endregion
|
|
49151
48953
|
//#region src/model-registry.ts
|
|
49152
48954
|
/**
|
|
49153
|
-
*
|
|
49154
|
-
*
|
|
48955
|
+
* Record that `modelId` now has `format` on `nodeId`. Merges into the node's
|
|
48956
|
+
* formats map — never drops a sibling format on the same node.
|
|
49155
48957
|
*/
|
|
49156
|
-
function
|
|
48958
|
+
function upsertAvailabilityFormat(map, modelId, nodeId, format, presence) {
|
|
48959
|
+
const prevNode = map[modelId]?.[nodeId] ?? { formats: {} };
|
|
49157
48960
|
return {
|
|
49158
48961
|
...map,
|
|
49159
48962
|
[modelId]: {
|
|
49160
48963
|
...map[modelId],
|
|
49161
|
-
[nodeId]:
|
|
48964
|
+
[nodeId]: { formats: {
|
|
48965
|
+
...prevNode.formats,
|
|
48966
|
+
[format]: presence
|
|
48967
|
+
} }
|
|
49162
48968
|
}
|
|
49163
48969
|
};
|
|
49164
48970
|
}
|
|
49165
48971
|
/**
|
|
48972
|
+
* @deprecated Prefer {@link upsertAvailabilityFormat}. Kept for call sites that
|
|
48973
|
+
* still pass a single-format legacy shape — folds into multi-format.
|
|
48974
|
+
*/
|
|
48975
|
+
function upsertAvailability(map, modelId, nodeId, entry) {
|
|
48976
|
+
return upsertAvailabilityFormat(map, modelId, nodeId, entry.format, {
|
|
48977
|
+
sha256: entry.sha256,
|
|
48978
|
+
bytes: entry.bytes,
|
|
48979
|
+
at: entry.at
|
|
48980
|
+
});
|
|
48981
|
+
}
|
|
48982
|
+
/**
|
|
49166
48983
|
* Pure registry operations over the custom-model list. Kept side-effect-free
|
|
49167
48984
|
* so they unit-test without a live `BaseAddon` context — the addon class is a
|
|
49168
48985
|
* thin durable-state wrapper around these.
|
|
49169
48986
|
*/
|
|
49170
48987
|
/**
|
|
49171
|
-
* Insert or replace a descriptor keyed by `entry.id
|
|
49172
|
-
*
|
|
48988
|
+
* Insert or replace a descriptor keyed by `entry.id`. When the id already
|
|
48989
|
+
* exists, **formats are merged** (Scrypted multi-format accumulate) — sibling
|
|
48990
|
+
* formats on the prior entry are never dropped. Returns a new array.
|
|
49173
48991
|
*/
|
|
49174
48992
|
function upsertCustomModel(list, descriptor) {
|
|
49175
|
-
|
|
48993
|
+
const prev = list.find((m) => m.entry.id === descriptor.entry.id);
|
|
48994
|
+
const merged = prev === void 0 ? descriptor : {
|
|
48995
|
+
stepId: descriptor.stepId,
|
|
48996
|
+
entry: {
|
|
48997
|
+
...prev.entry,
|
|
48998
|
+
...descriptor.entry,
|
|
48999
|
+
formats: {
|
|
49000
|
+
...prev.entry.formats,
|
|
49001
|
+
...descriptor.entry.formats
|
|
49002
|
+
}
|
|
49003
|
+
}
|
|
49004
|
+
};
|
|
49005
|
+
return [...list.filter((m) => m.entry.id !== descriptor.entry.id), merged];
|
|
49176
49006
|
}
|
|
49177
49007
|
/** Drop the descriptor with the given `entry.id`. Reports whether anything changed. */
|
|
49178
49008
|
function removeCustomModel(list, modelId) {
|
|
@@ -49182,6 +49012,114 @@ function removeCustomModel(list, modelId) {
|
|
|
49182
49012
|
removed: next.length !== list.length
|
|
49183
49013
|
};
|
|
49184
49014
|
}
|
|
49015
|
+
/** Drop every node entry for `modelId` from the availability map. */
|
|
49016
|
+
function clearAvailability(map, modelId) {
|
|
49017
|
+
if (map[modelId] === void 0) return {
|
|
49018
|
+
next: map,
|
|
49019
|
+
hadEntry: false
|
|
49020
|
+
};
|
|
49021
|
+
const next = { ...map };
|
|
49022
|
+
delete next[modelId];
|
|
49023
|
+
return {
|
|
49024
|
+
next,
|
|
49025
|
+
hadEntry: true
|
|
49026
|
+
};
|
|
49027
|
+
}
|
|
49028
|
+
/**
|
|
49029
|
+
* Drop one `(modelId, nodeId, format)` from availability. Empty nodes and empty
|
|
49030
|
+
* model rows are pruned so Library "On nodes" does not keep a ghost chip.
|
|
49031
|
+
*/
|
|
49032
|
+
function clearAvailabilityFormat(map, modelId, nodeId, format) {
|
|
49033
|
+
const perNode = map[modelId];
|
|
49034
|
+
if (perNode === void 0) return {
|
|
49035
|
+
next: map,
|
|
49036
|
+
cleared: false
|
|
49037
|
+
};
|
|
49038
|
+
const node = perNode[nodeId];
|
|
49039
|
+
if (node === void 0 || node.formats[format] === void 0) return {
|
|
49040
|
+
next: map,
|
|
49041
|
+
cleared: false
|
|
49042
|
+
};
|
|
49043
|
+
const formats = { ...node.formats };
|
|
49044
|
+
delete formats[format];
|
|
49045
|
+
const nextNodes = { ...perNode };
|
|
49046
|
+
if (Object.keys(formats).length === 0) delete nextNodes[nodeId];
|
|
49047
|
+
else nextNodes[nodeId] = { formats };
|
|
49048
|
+
if (Object.keys(nextNodes).length === 0) {
|
|
49049
|
+
const next = { ...map };
|
|
49050
|
+
delete next[modelId];
|
|
49051
|
+
return {
|
|
49052
|
+
next,
|
|
49053
|
+
cleared: true
|
|
49054
|
+
};
|
|
49055
|
+
}
|
|
49056
|
+
return {
|
|
49057
|
+
next: {
|
|
49058
|
+
...map,
|
|
49059
|
+
[modelId]: nextNodes
|
|
49060
|
+
},
|
|
49061
|
+
cleared: true
|
|
49062
|
+
};
|
|
49063
|
+
}
|
|
49064
|
+
/**
|
|
49065
|
+
* Drop availability rows whose modelId is not in the Library registry.
|
|
49066
|
+
* Orphans left Sources showing "imported" after a registry-only wipe.
|
|
49067
|
+
*/
|
|
49068
|
+
function pruneOrphanAvailability(map, registeredIds) {
|
|
49069
|
+
const pruned = [];
|
|
49070
|
+
let next = null;
|
|
49071
|
+
for (const modelId of Object.keys(map)) {
|
|
49072
|
+
if (registeredIds.has(modelId)) continue;
|
|
49073
|
+
if (next === null) next = { ...map };
|
|
49074
|
+
delete next[modelId];
|
|
49075
|
+
pruned.push(modelId);
|
|
49076
|
+
}
|
|
49077
|
+
return {
|
|
49078
|
+
next: next ?? map,
|
|
49079
|
+
pruned
|
|
49080
|
+
};
|
|
49081
|
+
}
|
|
49082
|
+
function stripModelIdFromInferenceDevices(devices, modelId) {
|
|
49083
|
+
let cleared = 0;
|
|
49084
|
+
const next = {};
|
|
49085
|
+
for (const [key, device] of Object.entries(devices)) {
|
|
49086
|
+
const stepsIn = device.steps;
|
|
49087
|
+
if (stepsIn === void 0) {
|
|
49088
|
+
next[key] = {
|
|
49089
|
+
enabled: device.enabled,
|
|
49090
|
+
...device.weight !== void 0 ? { weight: device.weight } : {},
|
|
49091
|
+
...device.maxSessions !== void 0 ? { maxSessions: device.maxSessions } : {}
|
|
49092
|
+
};
|
|
49093
|
+
continue;
|
|
49094
|
+
}
|
|
49095
|
+
const stepsOut = {};
|
|
49096
|
+
for (const [stepId, step] of Object.entries(stepsIn)) {
|
|
49097
|
+
if (step.modelId === modelId) {
|
|
49098
|
+
cleared += 1;
|
|
49099
|
+
const kept = {};
|
|
49100
|
+
if (step.settings !== void 0) kept.settings = { ...step.settings };
|
|
49101
|
+
if (step.jumpDeviceKey !== void 0) kept.jumpDeviceKey = step.jumpDeviceKey;
|
|
49102
|
+
if (Object.keys(kept).length > 0) stepsOut[stepId] = kept;
|
|
49103
|
+
continue;
|
|
49104
|
+
}
|
|
49105
|
+
stepsOut[stepId] = {
|
|
49106
|
+
...step.modelId !== void 0 ? { modelId: step.modelId } : {},
|
|
49107
|
+
...step.settings !== void 0 ? { settings: { ...step.settings } } : {},
|
|
49108
|
+
...step.jumpDeviceKey !== void 0 ? { jumpDeviceKey: step.jumpDeviceKey } : {}
|
|
49109
|
+
};
|
|
49110
|
+
}
|
|
49111
|
+
next[key] = {
|
|
49112
|
+
enabled: device.enabled,
|
|
49113
|
+
...device.weight !== void 0 ? { weight: device.weight } : {},
|
|
49114
|
+
...device.maxSessions !== void 0 ? { maxSessions: device.maxSessions } : {},
|
|
49115
|
+
...Object.keys(stepsOut).length > 0 ? { steps: stepsOut } : {}
|
|
49116
|
+
};
|
|
49117
|
+
}
|
|
49118
|
+
return {
|
|
49119
|
+
next,
|
|
49120
|
+
cleared
|
|
49121
|
+
};
|
|
49122
|
+
}
|
|
49185
49123
|
//#endregion
|
|
49186
49124
|
//#region src/frigate/frigate-import.ts
|
|
49187
49125
|
/**
|
|
@@ -49417,6 +49355,64 @@ async function runPlusConversions(deps, args) {
|
|
|
49417
49355
|
return outcomes;
|
|
49418
49356
|
}
|
|
49419
49357
|
//#endregion
|
|
49358
|
+
//#region src/ensure-formats.ts
|
|
49359
|
+
var ENGINE_FORMATS = ["openvino", "coreml"];
|
|
49360
|
+
function missingEngineFormats(entry) {
|
|
49361
|
+
return ENGINE_FORMATS.filter((f) => entry.formats[f] === void 0);
|
|
49362
|
+
}
|
|
49363
|
+
async function ensureEngineFormats(entry, deps, opts = {}) {
|
|
49364
|
+
if (entry.formats.onnx === void 0) return {
|
|
49365
|
+
entry,
|
|
49366
|
+
conversions: []
|
|
49367
|
+
};
|
|
49368
|
+
const missing = (opts.targetFormats ?? ENGINE_FORMATS).filter((f) => entry.formats[f] === void 0);
|
|
49369
|
+
if (missing.length === 0) return {
|
|
49370
|
+
entry,
|
|
49371
|
+
conversions: []
|
|
49372
|
+
};
|
|
49373
|
+
return {
|
|
49374
|
+
entry,
|
|
49375
|
+
conversions: await runPlusConversions(deps, {
|
|
49376
|
+
entry,
|
|
49377
|
+
targetFormats: missing
|
|
49378
|
+
})
|
|
49379
|
+
};
|
|
49380
|
+
}
|
|
49381
|
+
/**
|
|
49382
|
+
* Ensure OV/CoreML from ONNX when missing, then push every declared format to
|
|
49383
|
+
* every agent (hub is skipped — it already hosts imports).
|
|
49384
|
+
*/
|
|
49385
|
+
async function ensureFormatsThenDistribute(modelId, deps) {
|
|
49386
|
+
const initial = await deps.getEntry();
|
|
49387
|
+
if (initial === void 0) throw new Error(`ensureFormatsThenDistribute: model "${modelId}" is not registered`);
|
|
49388
|
+
let conversions = [];
|
|
49389
|
+
if (initial.formats.onnx !== void 0 && missingEngineFormats(initial).length > 0) conversions = (await ensureEngineFormats(initial, deps)).conversions;
|
|
49390
|
+
const entry = await deps.getEntry() ?? initial;
|
|
49391
|
+
const formats = Object.keys(entry.formats).filter((f) => entry.formats[f] !== void 0);
|
|
49392
|
+
const agents = await deps.listAgentNodeIds();
|
|
49393
|
+
const distributed = [];
|
|
49394
|
+
for (const nodeId of agents) for (const format of formats) try {
|
|
49395
|
+
const res = await deps.distribute(modelId, nodeId, format);
|
|
49396
|
+
distributed.push({
|
|
49397
|
+
nodeId,
|
|
49398
|
+
format,
|
|
49399
|
+
ok: true,
|
|
49400
|
+
bytes: res.bytes
|
|
49401
|
+
});
|
|
49402
|
+
} catch (err) {
|
|
49403
|
+
distributed.push({
|
|
49404
|
+
nodeId,
|
|
49405
|
+
format,
|
|
49406
|
+
ok: false,
|
|
49407
|
+
error: err instanceof Error ? err.message : String(err)
|
|
49408
|
+
});
|
|
49409
|
+
}
|
|
49410
|
+
return {
|
|
49411
|
+
conversions,
|
|
49412
|
+
distributed
|
|
49413
|
+
};
|
|
49414
|
+
}
|
|
49415
|
+
//#endregion
|
|
49420
49416
|
//#region src/frigate/frigate-plus.ts
|
|
49421
49417
|
var PLUS_API_HOST = "https://api.frigate.video";
|
|
49422
49418
|
var PLUS_KEY_RE = /^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}:[a-z0-9]{40}$/;
|
|
@@ -49520,6 +49516,12 @@ var PLUS_TYPE_MAP = {
|
|
|
49520
49516
|
postprocessor: "ssd",
|
|
49521
49517
|
extension: ".tflite"
|
|
49522
49518
|
},
|
|
49519
|
+
/** List API uses `ssd` for MobileDet rows. */
|
|
49520
|
+
ssd: {
|
|
49521
|
+
format: "tflite",
|
|
49522
|
+
postprocessor: "ssd",
|
|
49523
|
+
extension: ".tflite"
|
|
49524
|
+
},
|
|
49523
49525
|
yolonas: {
|
|
49524
49526
|
format: "onnx",
|
|
49525
49527
|
postprocessor: "yolonas",
|
|
@@ -49528,6 +49530,10 @@ var PLUS_TYPE_MAP = {
|
|
|
49528
49530
|
yolov9: {
|
|
49529
49531
|
format: "onnx",
|
|
49530
49532
|
extension: ".onnx"
|
|
49533
|
+
},
|
|
49534
|
+
"yolo-generic": {
|
|
49535
|
+
format: "onnx",
|
|
49536
|
+
extension: ".onnx"
|
|
49531
49537
|
}
|
|
49532
49538
|
};
|
|
49533
49539
|
function plusTypeTraits(type) {
|
|
@@ -49552,7 +49558,16 @@ function orderedLabels(labelMap) {
|
|
|
49552
49558
|
* Distribute.
|
|
49553
49559
|
*/
|
|
49554
49560
|
function plusInfoToDescriptor(info, opts) {
|
|
49555
|
-
const
|
|
49561
|
+
const base = plusTypeTraits(info.type);
|
|
49562
|
+
const traits = opts.format === "tflite" ? {
|
|
49563
|
+
format: "tflite",
|
|
49564
|
+
extension: ".tflite",
|
|
49565
|
+
...info.type === "ssd" || info.type === "mobiledet" ? { postprocessor: "ssd" } : {}
|
|
49566
|
+
} : opts.format === "onnx" ? {
|
|
49567
|
+
format: "onnx",
|
|
49568
|
+
extension: ".onnx",
|
|
49569
|
+
...base.postprocessor !== void 0 ? { postprocessor: base.postprocessor } : {}
|
|
49570
|
+
} : base;
|
|
49556
49571
|
const labels = orderedLabels(info.labelMap);
|
|
49557
49572
|
return {
|
|
49558
49573
|
stepId: "object-detection",
|
|
@@ -49670,15 +49685,33 @@ var ModelUploadStore = class {
|
|
|
49670
49685
|
* only the read-array `listModels` (auto-concatenated across providers); the
|
|
49671
49686
|
* mutations live here so the cap stays a clean collection.
|
|
49672
49687
|
*/
|
|
49673
|
-
/** One
|
|
49674
|
-
var
|
|
49675
|
-
format: _enum(MODEL_FORMATS),
|
|
49688
|
+
/** One format's presence on a node. */
|
|
49689
|
+
var ModelFormatPresenceSchema = object({
|
|
49676
49690
|
sha256: string(),
|
|
49677
49691
|
bytes: number(),
|
|
49678
49692
|
at: number()
|
|
49679
49693
|
});
|
|
49680
|
-
/**
|
|
49694
|
+
/**
|
|
49695
|
+
* Per-node availability — multi-format. Legacy durable rows were
|
|
49696
|
+
* `{ format, sha256, bytes, at }`; preprocess folds them into `formats`.
|
|
49697
|
+
*/
|
|
49698
|
+
var ModelNodeAvailabilitySchema = preprocess((raw) => {
|
|
49699
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return raw;
|
|
49700
|
+
const rec = raw;
|
|
49701
|
+
if (typeof rec.formats === "object" && rec.formats !== null && !Array.isArray(rec.formats)) return { formats: rec.formats };
|
|
49702
|
+
if (typeof rec.format === "string") return { formats: { [rec.format]: {
|
|
49703
|
+
sha256: typeof rec.sha256 === "string" ? rec.sha256 : "",
|
|
49704
|
+
bytes: typeof rec.bytes === "number" ? rec.bytes : 0,
|
|
49705
|
+
at: typeof rec.at === "number" ? rec.at : 0
|
|
49706
|
+
} } };
|
|
49707
|
+
return raw;
|
|
49708
|
+
}, object({ formats: partialRecord(_enum(MODEL_FORMATS), ModelFormatPresenceSchema) }));
|
|
49709
|
+
/** `modelId → nodeId → { formats: { [format]: presence } }`. */
|
|
49681
49710
|
var ModelAvailabilityMapSchema = record(string(), record(string(), ModelNodeAvailabilitySchema));
|
|
49711
|
+
/** Stable Library id for a Frigate+ training (chip id ↔ registry). */
|
|
49712
|
+
function frigatePlusRegistryId(plusModelId) {
|
|
49713
|
+
return `frigate-plus-${plusModelId}`;
|
|
49714
|
+
}
|
|
49682
49715
|
var ScryptedManifestFileSchema = object({
|
|
49683
49716
|
relativePath: string(),
|
|
49684
49717
|
size: number(),
|
|
@@ -49751,8 +49784,75 @@ var FrigatePlusModelSchema = object({
|
|
|
49751
49784
|
height: number().optional()
|
|
49752
49785
|
});
|
|
49753
49786
|
var modelStudioActions = defineCustomActions({
|
|
49754
|
-
registerCustomModel: customAction(CustomModelDescriptorSchema, object({
|
|
49755
|
-
|
|
49787
|
+
registerCustomModel: customAction(CustomModelDescriptorSchema, object({
|
|
49788
|
+
ok: literal(true),
|
|
49789
|
+
conversions: array(object({
|
|
49790
|
+
format: _enum(["openvino", "coreml"]),
|
|
49791
|
+
ok: boolean(),
|
|
49792
|
+
nodeId: string().optional(),
|
|
49793
|
+
error: string().optional()
|
|
49794
|
+
})).readonly()
|
|
49795
|
+
}), { kind: "mutation" }),
|
|
49796
|
+
/**
|
|
49797
|
+
* Ensure OV/CoreML from ONNX when missing, then distribute every declared
|
|
49798
|
+
* format to every online agent (hub skipped).
|
|
49799
|
+
*/
|
|
49800
|
+
ensureFormatsThenDistribute: customAction(object({ modelId: string() }), object({
|
|
49801
|
+
conversions: array(object({
|
|
49802
|
+
format: _enum(["openvino", "coreml"]),
|
|
49803
|
+
ok: boolean(),
|
|
49804
|
+
nodeId: string().optional(),
|
|
49805
|
+
error: string().optional()
|
|
49806
|
+
})).readonly(),
|
|
49807
|
+
distributed: array(object({
|
|
49808
|
+
nodeId: string(),
|
|
49809
|
+
format: _enum(MODEL_FORMATS),
|
|
49810
|
+
ok: boolean(),
|
|
49811
|
+
error: string().optional(),
|
|
49812
|
+
bytes: number().optional()
|
|
49813
|
+
})).readonly()
|
|
49814
|
+
}), { kind: "mutation" }),
|
|
49815
|
+
/**
|
|
49816
|
+
* Convert missing OpenVINO / CoreML from an already-registered ONNX entry
|
|
49817
|
+
* (Frigate+ / Custom per-format Import). Only the requested formats run.
|
|
49818
|
+
*/
|
|
49819
|
+
ensureModelFormats: customAction(object({
|
|
49820
|
+
modelId: string(),
|
|
49821
|
+
formats: array(_enum(["openvino", "coreml"])).min(1).readonly()
|
|
49822
|
+
}), object({ conversions: array(object({
|
|
49823
|
+
format: _enum(["openvino", "coreml"]),
|
|
49824
|
+
ok: boolean(),
|
|
49825
|
+
nodeId: string().optional(),
|
|
49826
|
+
error: string().optional()
|
|
49827
|
+
})).readonly() }), { kind: "mutation" }),
|
|
49828
|
+
removeCustomModel: customAction(object({ modelId: string() }), object({
|
|
49829
|
+
removed: boolean(),
|
|
49830
|
+
deletedNodes: array(string()).readonly(),
|
|
49831
|
+
clearedPins: number().int().nonnegative()
|
|
49832
|
+
}), { kind: "mutation" }),
|
|
49833
|
+
/**
|
|
49834
|
+
* Delete one format of a Library model from one node. Keeps the registry row
|
|
49835
|
+
* and sibling formats/nodes; clears only that availability chip.
|
|
49836
|
+
*/
|
|
49837
|
+
removeModelFromNode: customAction(object({
|
|
49838
|
+
modelId: string(),
|
|
49839
|
+
nodeId: string(),
|
|
49840
|
+
format: _enum(MODEL_FORMATS)
|
|
49841
|
+
}), object({
|
|
49842
|
+
deleted: boolean(),
|
|
49843
|
+
clearedAvailability: boolean()
|
|
49844
|
+
}), { kind: "mutation" }),
|
|
49845
|
+
/**
|
|
49846
|
+
* Wipe one CamStack seed format from a node's modelsDir (Installed Remove).
|
|
49847
|
+
* Routes via `nodePin` — `pipelineExecutor.deleteModel` has no `nodeId` in
|
|
49848
|
+
* its input schema, so a bare tRPC mutate always hit the hub.
|
|
49849
|
+
*/
|
|
49850
|
+
deleteSeedFromNode: customAction(object({
|
|
49851
|
+
addonId: string(),
|
|
49852
|
+
modelId: string(),
|
|
49853
|
+
format: _enum(MODEL_FORMATS),
|
|
49854
|
+
nodeId: string()
|
|
49855
|
+
}), object({ success: literal(true) }), { kind: "mutation" }),
|
|
49756
49856
|
listCustomModels: customAction(_void(), array(CustomModelDescriptorSchema).readonly()),
|
|
49757
49857
|
/**
|
|
49758
49858
|
* Distribute a registered model's FORMAT (resident on the hub) to a target
|
|
@@ -49792,17 +49892,16 @@ var modelStudioActions = defineCustomActions({
|
|
|
49792
49892
|
}), { kind: "mutation" }),
|
|
49793
49893
|
abortModelUpload: customAction(object({ uploadId: string() }), object({ ok: literal(true) }), { kind: "mutation" }),
|
|
49794
49894
|
/**
|
|
49795
|
-
* Curated PUBLIC Frigate model catalog (D214 addendum)
|
|
49796
|
-
*
|
|
49797
|
-
*
|
|
49798
|
-
* the hub's modelsDir and registers the descriptor with the public URL
|
|
49799
|
-
* (other nodes download on demand — no Distribute needed).
|
|
49895
|
+
* Curated PUBLIC Frigate model catalog (D214 addendum). Import registers the
|
|
49896
|
+
* descriptor with its public URL (Library only). Optional `targetNodeId`
|
|
49897
|
+
* downloads on that node — no hub staging disk.
|
|
49800
49898
|
*/
|
|
49801
49899
|
listFrigateCatalog: customAction(_void(), array(FrigateCatalogEntrySchema).readonly()),
|
|
49802
49900
|
importFrigateCatalogModel: customAction(object({
|
|
49803
49901
|
catalogId: string(),
|
|
49804
49902
|
modelId: string().optional(),
|
|
49805
|
-
name: string().optional()
|
|
49903
|
+
name: string().optional(),
|
|
49904
|
+
targetNodeId: string().optional()
|
|
49806
49905
|
}), object({
|
|
49807
49906
|
ok: literal(true),
|
|
49808
49907
|
modelId: string(),
|
|
@@ -49811,13 +49910,17 @@ var modelStudioActions = defineCustomActions({
|
|
|
49811
49910
|
/**
|
|
49812
49911
|
* Local Scrypted YOLO catalog (Task 7B). Listing is a projection of the
|
|
49813
49912
|
* revision-pinned manifesto plus a read-only HF drift check. Import accepts
|
|
49814
|
-
*
|
|
49913
|
+
* `manifestId` (+ optional land-on-node) — host/repo/revision/path/labels/
|
|
49914
|
+
* checksum stay server-owned.
|
|
49815
49915
|
*/
|
|
49816
49916
|
listScryptedCatalog: customAction(_void(), object({
|
|
49817
49917
|
entries: array(ScryptedManifestEntrySchema).readonly(),
|
|
49818
49918
|
drift: array(ScryptedCatalogDriftSchema).readonly()
|
|
49819
49919
|
})),
|
|
49820
|
-
importScryptedCatalogModel: customAction(object({
|
|
49920
|
+
importScryptedCatalogModel: customAction(object({
|
|
49921
|
+
manifestId: string(),
|
|
49922
|
+
targetNodeId: string().optional()
|
|
49923
|
+
}), CustomModelDescriptorSchema, { kind: "mutation" }),
|
|
49821
49924
|
/**
|
|
49822
49925
|
* Frigate+ personal models. The API key lives in the addon's SETTINGS
|
|
49823
49926
|
* (password field) and never leaves the hub: list / test / import all run
|
|
@@ -49827,9 +49930,9 @@ var modelStudioActions = defineCustomActions({
|
|
|
49827
49930
|
/**
|
|
49828
49931
|
* Nodes whose engine can actually LOAD a tflite artifact (the pool's only
|
|
49829
49932
|
* tflite runtime binds the Coral EdgeTPU delegate — no Coral, no tflite).
|
|
49830
|
-
*
|
|
49933
|
+
* Empty object — `addons.custom` cannot carry zod `void`.
|
|
49831
49934
|
*/
|
|
49832
|
-
tfliteSupport: customAction(
|
|
49935
|
+
tfliteSupport: customAction(object({}).optional(), object({ nodes: array(string()).readonly() })),
|
|
49833
49936
|
frigatePlusStatus: customAction(_void(), object({
|
|
49834
49937
|
configured: boolean(),
|
|
49835
49938
|
keyFormatValid: boolean()
|
|
@@ -49841,8 +49944,21 @@ var modelStudioActions = defineCustomActions({
|
|
|
49841
49944
|
frigatePlusListModels: customAction(_void(), array(FrigatePlusModelSchema).readonly()),
|
|
49842
49945
|
importFrigatePlusModel: customAction(object({
|
|
49843
49946
|
plusModelId: string(),
|
|
49844
|
-
|
|
49845
|
-
|
|
49947
|
+
/** Ignored for identity — registry id is always `frigate-plus-<plusModelId>`. */
|
|
49948
|
+
modelId: string().optional(),
|
|
49949
|
+
name: string().optional(),
|
|
49950
|
+
/**
|
|
49951
|
+
* Native artifact class from the list row (`plusDownloadTraits`). The
|
|
49952
|
+
* model-detail API does not always carry `supportedDetectors`, so the
|
|
49953
|
+
* browser passes what the catalog already knew (fixes ssd/yolo-generic
|
|
49954
|
+
* EdgeTPU rows that `plusTypeTraits` alone would call onnx).
|
|
49955
|
+
*/
|
|
49956
|
+
format: _enum(["onnx", "tflite"]).optional(),
|
|
49957
|
+
/**
|
|
49958
|
+
* Engine formats to convert after the native artifact lands. Empty =
|
|
49959
|
+
* download + register only (per-format Import clicks OV/CoreML later).
|
|
49960
|
+
*/
|
|
49961
|
+
ensureFormats: array(_enum(["openvino", "coreml"])).readonly().optional()
|
|
49846
49962
|
}), object({
|
|
49847
49963
|
ok: literal(true),
|
|
49848
49964
|
modelId: string(),
|
|
@@ -49925,6 +50041,15 @@ function spawnRunConvert(jobPath, pythonPath, onProgress, _sessionId) {
|
|
|
49925
50041
|
});
|
|
49926
50042
|
proc.stderr.on("data", (chunk) => {
|
|
49927
50043
|
stderrChunks.push(chunk);
|
|
50044
|
+
for (const line of chunk.toString().split("\n")) {
|
|
50045
|
+
const trimmed = line.trimEnd();
|
|
50046
|
+
if (trimmed.length === 0) continue;
|
|
50047
|
+
onProgress({
|
|
50048
|
+
phase: "log",
|
|
50049
|
+
log: trimmed,
|
|
50050
|
+
detail: trimmed
|
|
50051
|
+
});
|
|
50052
|
+
}
|
|
49928
50053
|
});
|
|
49929
50054
|
proc.on("close", (code) => {
|
|
49930
50055
|
if (code !== 0) {
|
|
@@ -50014,6 +50139,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
|
|
|
50014
50139
|
phase: p.phase,
|
|
50015
50140
|
pct: p.pct,
|
|
50016
50141
|
detail: p.detail,
|
|
50142
|
+
log: p.log,
|
|
50017
50143
|
sessionId
|
|
50018
50144
|
}
|
|
50019
50145
|
});
|
|
@@ -50057,11 +50183,21 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
|
|
|
50057
50183
|
return result;
|
|
50058
50184
|
},
|
|
50059
50185
|
abortModelUpload: async (input) => (await this.getUploadStore()).abort(input),
|
|
50060
|
-
registerCustomModel: async (input) => this.registerCustomModel(input),
|
|
50186
|
+
registerCustomModel: async (input) => this.registerCustomModel(input, { ensureEngines: true }),
|
|
50187
|
+
ensureFormatsThenDistribute: async (input) => this.ensureFormatsThenDistribute_(input.modelId),
|
|
50188
|
+
ensureModelFormats: async (input) => this.ensureModelFormats_(input.modelId, input.formats),
|
|
50061
50189
|
removeCustomModel: async (input) => this.removeCustomModel(input),
|
|
50062
|
-
|
|
50190
|
+
removeModelFromNode: async (input) => this.removeModelFromNode(input),
|
|
50191
|
+
deleteSeedFromNode: async (input) => this.deleteSeedFromNode(input),
|
|
50192
|
+
listCustomModels: async () => {
|
|
50193
|
+
await this.pruneOrphanAvailability_();
|
|
50194
|
+
return this.listCustomModels();
|
|
50195
|
+
},
|
|
50063
50196
|
distributeModel: async (input) => this.distributeModel(input),
|
|
50064
|
-
listAvailability: async () =>
|
|
50197
|
+
listAvailability: async () => {
|
|
50198
|
+
await this.pruneOrphanAvailability_();
|
|
50199
|
+
return this.availability_.get();
|
|
50200
|
+
},
|
|
50065
50201
|
listFrigateCatalog: async () => FRIGATE_PUBLIC_CATALOG.map((c) => ({
|
|
50066
50202
|
id: c.id,
|
|
50067
50203
|
label: c.label,
|
|
@@ -50206,10 +50342,37 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
|
|
|
50206
50342
|
async distributeModel(input) {
|
|
50207
50343
|
const descriptor = (await this.state_.get()).find((m) => m.entry.id === input.modelId);
|
|
50208
50344
|
if (!descriptor) throw new Error(`distributeModel: model "${input.modelId}" is not registered`);
|
|
50345
|
+
const formatEntry = descriptor.entry.formats[input.format];
|
|
50346
|
+
if (formatEntry === void 0) throw new Error(`distributeModel: model "${input.modelId}" has no ${input.format} format in the Library`);
|
|
50209
50347
|
const api = this.ctx.api;
|
|
50210
50348
|
if (!api) throw new Error("distributeModel: ctx.api unavailable");
|
|
50349
|
+
if (/^https?:\/\//i.test(formatEntry.url)) {
|
|
50350
|
+
const downloaded = await api.pipelineExecutor.downloadModel.mutate({
|
|
50351
|
+
modelId: input.modelId,
|
|
50352
|
+
format: input.format,
|
|
50353
|
+
addonId: descriptor.stepId
|
|
50354
|
+
}, nodePin(input.nodeId));
|
|
50355
|
+
const bytes = Math.round(downloaded.sizeMB * 1e6);
|
|
50356
|
+
await this.availability_.update((prev) => upsertAvailability(prev, input.modelId, input.nodeId, {
|
|
50357
|
+
format: input.format,
|
|
50358
|
+
sha256: "",
|
|
50359
|
+
bytes,
|
|
50360
|
+
at: Date.now()
|
|
50361
|
+
}));
|
|
50362
|
+
this.ctx.logger.info("Downloaded custom model on node", { meta: {
|
|
50363
|
+
modelId: input.modelId,
|
|
50364
|
+
nodeId: input.nodeId,
|
|
50365
|
+
format: input.format,
|
|
50366
|
+
bytes
|
|
50367
|
+
} });
|
|
50368
|
+
return {
|
|
50369
|
+
ok: true,
|
|
50370
|
+
sha256: "",
|
|
50371
|
+
bytes
|
|
50372
|
+
};
|
|
50373
|
+
}
|
|
50211
50374
|
const res = await api.modelDistributor.distributeModel.mutate({
|
|
50212
|
-
|
|
50375
|
+
targetNodeId: input.nodeId,
|
|
50213
50376
|
modelId: input.modelId,
|
|
50214
50377
|
format: input.format,
|
|
50215
50378
|
entry: descriptor.entry
|
|
@@ -50288,59 +50451,49 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
|
|
|
50288
50451
|
}
|
|
50289
50452
|
}
|
|
50290
50453
|
/**
|
|
50291
|
-
*
|
|
50454
|
+
* Register one manifesto entry by id into the Library. Host/repo/revision
|
|
50455
|
+
* stay server-owned. Artifacts are NOT staged on the hub — optional
|
|
50456
|
+
* `targetNodeId` lands the HF URL on that node via download-on-target.
|
|
50292
50457
|
*/
|
|
50293
50458
|
async importScryptedCatalogFromManifest(input) {
|
|
50294
|
-
const
|
|
50295
|
-
|
|
50296
|
-
|
|
50297
|
-
|
|
50298
|
-
|
|
50299
|
-
|
|
50300
|
-
|
|
50301
|
-
|
|
50302
|
-
|
|
50303
|
-
|
|
50304
|
-
|
|
50305
|
-
|
|
50306
|
-
|
|
50307
|
-
|
|
50308
|
-
|
|
50309
|
-
|
|
50310
|
-
|
|
50311
|
-
|
|
50312
|
-
|
|
50313
|
-
|
|
50314
|
-
manifestId,
|
|
50315
|
-
status: "unavailable",
|
|
50316
|
-
details: err instanceof Error ? err.message : "Hugging Face tree unavailable"
|
|
50317
|
-
};
|
|
50318
|
-
}
|
|
50319
|
-
}
|
|
50320
|
-
});
|
|
50459
|
+
const entry = getScryptedManifestEntry(input.manifestId);
|
|
50460
|
+
if (!entry) throw new Error(`importScryptedCatalogModel: unknown manifest id "${input.manifestId}"`);
|
|
50461
|
+
let drift;
|
|
50462
|
+
try {
|
|
50463
|
+
const tree = await fetchScryptedRemoteTree({ fetchImpl: fetch });
|
|
50464
|
+
const row = compareScryptedManifestDrift(SCRYPTED_YOLO_MANIFEST.filter((e) => e.id === input.manifestId), tree)[0];
|
|
50465
|
+
drift = normalizeScryptedDrift(row ?? {
|
|
50466
|
+
manifestId: input.manifestId,
|
|
50467
|
+
status: "missing",
|
|
50468
|
+
details: "entry missing from manifesto"
|
|
50469
|
+
});
|
|
50470
|
+
} catch (err) {
|
|
50471
|
+
drift = {
|
|
50472
|
+
manifestId: input.manifestId,
|
|
50473
|
+
status: "unavailable",
|
|
50474
|
+
details: err instanceof Error ? err.message : "Hugging Face tree unavailable"
|
|
50475
|
+
};
|
|
50476
|
+
}
|
|
50477
|
+
if (drift.status !== "ok") throw new Error(`importScryptedCatalogModel: entry "${entry.id}" is blocked by drift (${drift.status}${drift.details ? `: ${drift.details}` : ""})`);
|
|
50478
|
+
const descriptor = buildScryptedRegistryDescriptor(entry);
|
|
50321
50479
|
await this.registerCustomModel(descriptor);
|
|
50322
|
-
|
|
50323
|
-
|
|
50324
|
-
|
|
50325
|
-
|
|
50326
|
-
|
|
50327
|
-
|
|
50328
|
-
sha256: scryptedBundleFingerprint(manifestEntry),
|
|
50329
|
-
bytes,
|
|
50330
|
-
at: Date.now()
|
|
50331
|
-
}));
|
|
50332
|
-
this.ctx.logger.info("Imported Scrypted catalog model", { meta: {
|
|
50480
|
+
if (input.targetNodeId !== void 0 && input.targetNodeId.length > 0) await this.distributeModel({
|
|
50481
|
+
modelId: descriptor.entry.id,
|
|
50482
|
+
nodeId: input.targetNodeId,
|
|
50483
|
+
format: entry.format
|
|
50484
|
+
});
|
|
50485
|
+
this.ctx.logger.info("Registered Scrypted catalog model", { meta: {
|
|
50333
50486
|
manifestId: input.manifestId,
|
|
50334
50487
|
modelId: descriptor.entry.id,
|
|
50335
|
-
|
|
50488
|
+
format: entry.format,
|
|
50489
|
+
...input.targetNodeId !== void 0 ? { targetNodeId: input.targetNodeId } : {}
|
|
50336
50490
|
} });
|
|
50337
50491
|
return descriptor;
|
|
50338
50492
|
}
|
|
50339
50493
|
/**
|
|
50340
|
-
*
|
|
50341
|
-
*
|
|
50342
|
-
*
|
|
50343
|
-
* and record hub availability.
|
|
50494
|
+
* Register a curated PUBLIC Frigate catalog model into the Library. The
|
|
50495
|
+
* entry keeps its public URL — no hub staging. Optional `targetNodeId`
|
|
50496
|
+
* downloads on that node (same path as Library → Distribute).
|
|
50344
50497
|
*/
|
|
50345
50498
|
async importFrigateCatalogModel(input) {
|
|
50346
50499
|
const cat = FRIGATE_PUBLIC_CATALOG.find((c) => c.id === input.catalogId);
|
|
@@ -50349,29 +50502,21 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
|
|
|
50349
50502
|
...input.modelId !== void 0 ? { modelId: input.modelId } : {},
|
|
50350
50503
|
...input.name !== void 0 ? { name: input.name } : {}
|
|
50351
50504
|
});
|
|
50352
|
-
const modelsDir = await this.resolveModelsDir();
|
|
50353
|
-
const filename = cat.url.split("/").at(-1) ?? `${descriptor.entry.id}.${cat.format}`;
|
|
50354
|
-
const destPath = path.join(modelsDir, filename);
|
|
50355
|
-
await downloadFile(cat.url, destPath);
|
|
50356
|
-
const bytes = (await fs.promises.stat(destPath)).size;
|
|
50357
|
-
const declaredBytes = cat.sizeMB * 1e6;
|
|
50358
|
-
if (bytes < declaredBytes * .5 || bytes > declaredBytes * 2) {
|
|
50359
|
-
await fs.promises.rm(destPath, { force: true });
|
|
50360
|
-
throw new Error(`importFrigateCatalogModel: downloaded ${bytes} bytes but the catalog declares ~${Math.round(declaredBytes)} — refusing to register a suspicious artifact`);
|
|
50361
|
-
}
|
|
50362
|
-
const sha256 = await ModelStudioAddon.fileSha256(destPath);
|
|
50363
50505
|
await this.registerCustomModel(descriptor);
|
|
50364
|
-
|
|
50365
|
-
|
|
50366
|
-
|
|
50367
|
-
|
|
50368
|
-
|
|
50369
|
-
|
|
50370
|
-
|
|
50506
|
+
let bytes = Math.round(cat.sizeMB * 1e6);
|
|
50507
|
+
if (input.targetNodeId !== void 0 && input.targetNodeId.length > 0) {
|
|
50508
|
+
const landed = await this.distributeModel({
|
|
50509
|
+
modelId: descriptor.entry.id,
|
|
50510
|
+
nodeId: input.targetNodeId,
|
|
50511
|
+
format: cat.format
|
|
50512
|
+
});
|
|
50513
|
+
bytes = landed.bytes > 0 ? landed.bytes : bytes;
|
|
50514
|
+
}
|
|
50515
|
+
this.ctx.logger.info("Registered Frigate catalog model", { meta: {
|
|
50371
50516
|
catalogId: input.catalogId,
|
|
50372
50517
|
modelId: descriptor.entry.id,
|
|
50373
|
-
|
|
50374
|
-
|
|
50518
|
+
...input.targetNodeId !== void 0 ? { targetNodeId: input.targetNodeId } : {},
|
|
50519
|
+
bytes
|
|
50375
50520
|
} });
|
|
50376
50521
|
return {
|
|
50377
50522
|
ok: true,
|
|
@@ -50387,11 +50532,20 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
|
|
|
50387
50532
|
* is hub-local: other nodes get it via Models → Distribute.
|
|
50388
50533
|
*/
|
|
50389
50534
|
async importFrigatePlusModel(input) {
|
|
50390
|
-
|
|
50535
|
+
const modelId = frigatePlusRegistryId(input.plusModelId);
|
|
50391
50536
|
const key = this.config.frigatePlusApiKey ?? "";
|
|
50392
50537
|
const { info, downloadUrl } = await requestPlusModelDownload(plusFetch, key, input.plusModelId);
|
|
50393
|
-
const
|
|
50394
|
-
const
|
|
50538
|
+
const typeTraits = plusTypeTraits(info.type);
|
|
50539
|
+
const traits = input.format === "tflite" ? {
|
|
50540
|
+
format: "tflite",
|
|
50541
|
+
extension: ".tflite",
|
|
50542
|
+
...info.type === "ssd" || info.type === "mobiledet" ? { postprocessor: "ssd" } : {}
|
|
50543
|
+
} : input.format === "onnx" ? {
|
|
50544
|
+
format: "onnx",
|
|
50545
|
+
extension: ".onnx",
|
|
50546
|
+
...typeTraits.postprocessor !== void 0 ? { postprocessor: typeTraits.postprocessor } : {}
|
|
50547
|
+
} : typeTraits;
|
|
50548
|
+
const filename = `${modelId}${traits.extension}`;
|
|
50395
50549
|
const modelsDir = await this.resolveModelsDir();
|
|
50396
50550
|
const destPath = path.join(modelsDir, filename);
|
|
50397
50551
|
await downloadFile(downloadUrl, destPath);
|
|
@@ -50402,13 +50556,14 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
|
|
|
50402
50556
|
}
|
|
50403
50557
|
const sha256 = await ModelStudioAddon.fileSha256(destPath);
|
|
50404
50558
|
const descriptor = plusInfoToDescriptor(info, {
|
|
50405
|
-
modelId
|
|
50559
|
+
modelId,
|
|
50406
50560
|
name: input.name?.trim() || `Frigate+ ${info.type} (${input.plusModelId.slice(0, 8)})`,
|
|
50407
50561
|
filename,
|
|
50408
|
-
sizeMB: Number((bytes / 1e6).toFixed(1))
|
|
50562
|
+
sizeMB: Number((bytes / 1e6).toFixed(1)),
|
|
50563
|
+
...input.format !== void 0 ? { format: input.format } : {}
|
|
50409
50564
|
});
|
|
50410
|
-
await this.registerCustomModel(descriptor);
|
|
50411
|
-
await this.availability_.update((prev) => upsertAvailability(prev,
|
|
50565
|
+
await this.registerCustomModel(descriptor, { ensureEngines: false });
|
|
50566
|
+
await this.availability_.update((prev) => upsertAvailability(prev, modelId, "hub", {
|
|
50412
50567
|
format: traits.format,
|
|
50413
50568
|
sha256,
|
|
50414
50569
|
bytes,
|
|
@@ -50416,59 +50571,28 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
|
|
|
50416
50571
|
}));
|
|
50417
50572
|
this.ctx.logger.info("Imported Frigate+ model", { meta: {
|
|
50418
50573
|
plusModelId: input.plusModelId,
|
|
50419
|
-
modelId
|
|
50574
|
+
modelId,
|
|
50420
50575
|
type: info.type,
|
|
50421
|
-
bytes
|
|
50576
|
+
bytes,
|
|
50577
|
+
format: traits.format
|
|
50422
50578
|
} });
|
|
50579
|
+
const wanted = input.ensureFormats ?? [];
|
|
50423
50580
|
let conversions = [];
|
|
50424
|
-
if (traits.format === "onnx") {
|
|
50425
|
-
|
|
50426
|
-
|
|
50427
|
-
|
|
50428
|
-
|
|
50429
|
-
|
|
50430
|
-
freshSourceUrl: async () => (await requestPlusModelDownload(plusFetch, key, input.plusModelId)).downloadUrl,
|
|
50431
|
-
upsertEntry: async (entry) => {
|
|
50432
|
-
await this.state_.update((prev) => upsertCustomModel(prev, {
|
|
50433
|
-
stepId: descriptor.stepId,
|
|
50434
|
-
entry
|
|
50435
|
-
}));
|
|
50436
|
-
},
|
|
50437
|
-
markAvailable: async (nId, mId, artifacts) => {
|
|
50438
|
-
await this.availability_.update((prev) => {
|
|
50439
|
-
const seen = /* @__PURE__ */ new Set();
|
|
50440
|
-
let next = prev;
|
|
50441
|
-
for (const artifact of artifacts) {
|
|
50442
|
-
if (seen.has(artifact.format)) continue;
|
|
50443
|
-
seen.add(artifact.format);
|
|
50444
|
-
next = upsertAvailability(next, mId, nId, {
|
|
50445
|
-
format: artifact.format,
|
|
50446
|
-
sha256: "",
|
|
50447
|
-
bytes: Math.round(artifact.sizeMB * 1e6),
|
|
50448
|
-
at: Date.now()
|
|
50449
|
-
});
|
|
50450
|
-
}
|
|
50451
|
-
return next;
|
|
50452
|
-
});
|
|
50453
|
-
}
|
|
50454
|
-
}, {
|
|
50455
|
-
entry: descriptor.entry,
|
|
50456
|
-
targetFormats: ["openvino", "coreml"]
|
|
50457
|
-
});
|
|
50458
|
-
this.ctx.logger.info("Frigate+ import — engine conversions", { meta: {
|
|
50459
|
-
modelId: input.modelId,
|
|
50460
|
-
outcomes: conversions.map((o) => ({ ...o }))
|
|
50461
|
-
} });
|
|
50462
|
-
}
|
|
50581
|
+
if (traits.format === "onnx" && wanted.length > 0) {
|
|
50582
|
+
conversions = (await ensureEngineFormats(descriptor.entry, this.plusConversionDeps(async () => (await requestPlusModelDownload(plusFetch, key, input.plusModelId)).downloadUrl, descriptor.stepId), { targetFormats: wanted })).conversions;
|
|
50583
|
+
this.ctx.logger.info("Frigate+ import — engine conversions", { meta: {
|
|
50584
|
+
modelId,
|
|
50585
|
+
outcomes: conversions.map((o) => ({ ...o }))
|
|
50586
|
+
} });
|
|
50463
50587
|
}
|
|
50464
50588
|
return {
|
|
50465
50589
|
ok: true,
|
|
50466
|
-
modelId
|
|
50590
|
+
modelId,
|
|
50467
50591
|
bytes,
|
|
50468
50592
|
conversions
|
|
50469
50593
|
};
|
|
50470
50594
|
}
|
|
50471
|
-
async registerCustomModel(input) {
|
|
50595
|
+
async registerCustomModel(input, opts = {}) {
|
|
50472
50596
|
if (!input.stepId.trim()) throw new Error("registerCustomModel: stepId is required");
|
|
50473
50597
|
if (!input.entry.id.trim()) throw new Error("registerCustomModel: entry.id is required");
|
|
50474
50598
|
await this.state_.update((prev) => upsertCustomModel(prev, input));
|
|
@@ -50476,9 +50600,252 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
|
|
|
50476
50600
|
modelId: input.entry.id,
|
|
50477
50601
|
stepId: input.stepId
|
|
50478
50602
|
} });
|
|
50479
|
-
|
|
50603
|
+
let conversions = [];
|
|
50604
|
+
if (opts.ensureEngines === true && input.entry.formats.onnx !== void 0) {
|
|
50605
|
+
const onnxUrl = input.entry.formats.onnx.url;
|
|
50606
|
+
if (typeof onnxUrl === "string" && onnxUrl.length > 0) {
|
|
50607
|
+
const deps = this.plusConversionDeps(() => Promise.resolve(onnxUrl), input.stepId);
|
|
50608
|
+
conversions = (await ensureEngineFormats(input.entry, deps)).conversions;
|
|
50609
|
+
if (conversions.length > 0) this.ctx.logger.info("Custom model — engine conversions", { meta: {
|
|
50610
|
+
modelId: input.entry.id,
|
|
50611
|
+
outcomes: conversions.map((o) => ({ ...o }))
|
|
50612
|
+
} });
|
|
50613
|
+
}
|
|
50614
|
+
}
|
|
50615
|
+
return {
|
|
50616
|
+
ok: true,
|
|
50617
|
+
conversions
|
|
50618
|
+
};
|
|
50619
|
+
}
|
|
50620
|
+
plusConversionDeps(freshSourceUrl, stepId) {
|
|
50621
|
+
const api = this.ctx.api;
|
|
50622
|
+
return {
|
|
50623
|
+
recommend: (i) => recommendConvertNode(i, this.recommendDeps()),
|
|
50624
|
+
convert: async (convertInput, nId) => {
|
|
50625
|
+
if (!api) throw new Error("modelConvert unavailable");
|
|
50626
|
+
return api.modelConvert.convert.mutate(convertInput, nodePin(nId));
|
|
50627
|
+
},
|
|
50628
|
+
freshSourceUrl,
|
|
50629
|
+
upsertEntry: async (entry) => {
|
|
50630
|
+
await this.state_.update((prev) => upsertCustomModel(prev, {
|
|
50631
|
+
stepId,
|
|
50632
|
+
entry
|
|
50633
|
+
}));
|
|
50634
|
+
},
|
|
50635
|
+
markAvailable: async (nId, mId, artifacts) => {
|
|
50636
|
+
await this.availability_.update((prev) => {
|
|
50637
|
+
const seen = /* @__PURE__ */ new Set();
|
|
50638
|
+
let next = prev;
|
|
50639
|
+
for (const artifact of artifacts) {
|
|
50640
|
+
if (seen.has(artifact.format)) continue;
|
|
50641
|
+
seen.add(artifact.format);
|
|
50642
|
+
next = upsertAvailability(next, mId, nId, {
|
|
50643
|
+
format: artifact.format,
|
|
50644
|
+
sha256: "",
|
|
50645
|
+
bytes: Math.round(artifact.sizeMB * 1e6),
|
|
50646
|
+
at: Date.now()
|
|
50647
|
+
});
|
|
50648
|
+
}
|
|
50649
|
+
return next;
|
|
50650
|
+
});
|
|
50651
|
+
}
|
|
50652
|
+
};
|
|
50653
|
+
}
|
|
50654
|
+
async ensureModelFormats_(modelId, formats) {
|
|
50655
|
+
const row = (await this.state_.get()).find((m) => m.entry.id === modelId);
|
|
50656
|
+
if (row === void 0) throw new Error(`ensureModelFormats: model "${modelId}" is not registered`);
|
|
50657
|
+
const onnxUrl = row.entry.formats.onnx?.url;
|
|
50658
|
+
if (typeof onnxUrl !== "string" || onnxUrl.length === 0) throw new Error(`ensureModelFormats: model "${modelId}" has no ONNX to convert from`);
|
|
50659
|
+
const ensured = await ensureEngineFormats(row.entry, this.plusConversionDeps(async () => {
|
|
50660
|
+
const url = ((await this.state_.get()).find((m) => m.entry.id === modelId)?.entry)?.formats.onnx?.url ?? onnxUrl;
|
|
50661
|
+
if (typeof url !== "string" || url.length === 0) throw new Error(`ensureModelFormats: no ONNX url for "${modelId}"`);
|
|
50662
|
+
return url;
|
|
50663
|
+
}, row.stepId), { targetFormats: formats });
|
|
50664
|
+
this.ctx.logger.info("ensureModelFormats", { meta: {
|
|
50665
|
+
modelId,
|
|
50666
|
+
outcomes: ensured.conversions.map((o) => ({ ...o }))
|
|
50667
|
+
} });
|
|
50668
|
+
return { conversions: ensured.conversions };
|
|
50480
50669
|
}
|
|
50670
|
+
async ensureFormatsThenDistribute_(modelId) {
|
|
50671
|
+
const api = this.ctx.api;
|
|
50672
|
+
if (!api) throw new Error("ensureFormatsThenDistribute: ctx.api unavailable");
|
|
50673
|
+
const getEntry = async () => {
|
|
50674
|
+
return (await this.state_.get()).find((m) => m.entry.id === modelId)?.entry;
|
|
50675
|
+
};
|
|
50676
|
+
const entry = await getEntry();
|
|
50677
|
+
if (entry === void 0) throw new Error(`ensureFormatsThenDistribute: model "${modelId}" is not registered`);
|
|
50678
|
+
const onnxUrl = entry.formats.onnx?.url;
|
|
50679
|
+
const stepId = (await this.state_.get()).find((m) => m.entry.id === modelId)?.stepId ?? "object-detection";
|
|
50680
|
+
return ensureFormatsThenDistribute(modelId, {
|
|
50681
|
+
...this.plusConversionDeps(async () => {
|
|
50682
|
+
const url = (await getEntry())?.formats.onnx?.url ?? onnxUrl;
|
|
50683
|
+
if (typeof url !== "string" || url.length === 0) throw new Error(`ensureFormatsThenDistribute: no ONNX url for "${modelId}"`);
|
|
50684
|
+
return url;
|
|
50685
|
+
}, stepId),
|
|
50686
|
+
getEntry,
|
|
50687
|
+
listAgentNodeIds: async () => {
|
|
50688
|
+
return (await api.nodes.directory.query()).nodes.filter((n) => n.isOnline && !n.isHub).map((n) => n.id);
|
|
50689
|
+
},
|
|
50690
|
+
distribute: async (mId, nodeId, format) => {
|
|
50691
|
+
const res = await this.distributeModel({
|
|
50692
|
+
modelId: mId,
|
|
50693
|
+
nodeId,
|
|
50694
|
+
format
|
|
50695
|
+
});
|
|
50696
|
+
return {
|
|
50697
|
+
sha256: res.sha256,
|
|
50698
|
+
bytes: res.bytes
|
|
50699
|
+
};
|
|
50700
|
+
}
|
|
50701
|
+
});
|
|
50702
|
+
}
|
|
50703
|
+
/**
|
|
50704
|
+
* Delete one CamStack seed format on one node. Physically removes files from
|
|
50705
|
+
* that node's modelsDir via `pipelineExecutor.deleteModel` + `nodePin`.
|
|
50706
|
+
*/
|
|
50707
|
+
async deleteSeedFromNode(input) {
|
|
50708
|
+
const api = this.ctx.api;
|
|
50709
|
+
if (!api) throw new Error("deleteSeedFromNode: API not available");
|
|
50710
|
+
await api.pipelineExecutor.deleteModel.mutate({
|
|
50711
|
+
addonId: input.addonId,
|
|
50712
|
+
modelId: input.modelId,
|
|
50713
|
+
format: input.format
|
|
50714
|
+
}, nodePin(input.nodeId));
|
|
50715
|
+
this.ctx.logger.info("CamStack seed deleted from node disk", { meta: {
|
|
50716
|
+
modelId: input.modelId,
|
|
50717
|
+
format: input.format,
|
|
50718
|
+
nodeId: input.nodeId,
|
|
50719
|
+
addonId: input.addonId
|
|
50720
|
+
} });
|
|
50721
|
+
return { success: true };
|
|
50722
|
+
}
|
|
50723
|
+
/**
|
|
50724
|
+
* Delete one format on one node. Registry row stays; only that availability
|
|
50725
|
+
* chip is cleared. Pins are left alone — re-Send to node restores files.
|
|
50726
|
+
*/
|
|
50727
|
+
async removeModelFromNode(input) {
|
|
50728
|
+
const descriptor = (await this.state_.get()).find((m) => m.entry.id === input.modelId);
|
|
50729
|
+
if (!descriptor) throw new Error(`removeModelFromNode: model "${input.modelId}" is not registered`);
|
|
50730
|
+
if (descriptor.entry.formats[input.format] === void 0) throw new Error(`removeModelFromNode: model "${input.modelId}" has no ${input.format} format in the Library`);
|
|
50731
|
+
let deleted = false;
|
|
50732
|
+
const api = this.ctx.api;
|
|
50733
|
+
try {
|
|
50734
|
+
if (api) {
|
|
50735
|
+
await api.pipelineExecutor.deleteModel.mutate({
|
|
50736
|
+
addonId: descriptor.stepId,
|
|
50737
|
+
modelId: input.modelId,
|
|
50738
|
+
format: input.format
|
|
50739
|
+
}, nodePin(input.nodeId));
|
|
50740
|
+
deleted = true;
|
|
50741
|
+
}
|
|
50742
|
+
} catch (err) {
|
|
50743
|
+
this.ctx.logger.warn("removeModelFromNode: remote delete failed (continuing)", { meta: {
|
|
50744
|
+
modelId: input.modelId,
|
|
50745
|
+
nodeId: input.nodeId,
|
|
50746
|
+
format: input.format,
|
|
50747
|
+
error: err instanceof Error ? err.message : String(err)
|
|
50748
|
+
} });
|
|
50749
|
+
}
|
|
50750
|
+
if (input.nodeId === "hub") try {
|
|
50751
|
+
deleteModelFromDisk(await this.resolveModelsDir(), descriptor.entry, input.format);
|
|
50752
|
+
deleted = true;
|
|
50753
|
+
} catch (err) {
|
|
50754
|
+
this.ctx.logger.warn("removeModelFromNode: hub disk wipe failed", { meta: {
|
|
50755
|
+
modelId: input.modelId,
|
|
50756
|
+
format: input.format,
|
|
50757
|
+
error: err instanceof Error ? err.message : String(err)
|
|
50758
|
+
} });
|
|
50759
|
+
}
|
|
50760
|
+
let clearedAvailability = false;
|
|
50761
|
+
await this.availability_.update((prev) => {
|
|
50762
|
+
const result = clearAvailabilityFormat(prev, input.modelId, input.nodeId, input.format);
|
|
50763
|
+
clearedAvailability = result.cleared;
|
|
50764
|
+
return result.next;
|
|
50765
|
+
});
|
|
50766
|
+
this.ctx.logger.info("Removed custom model format from node", { meta: {
|
|
50767
|
+
modelId: input.modelId,
|
|
50768
|
+
nodeId: input.nodeId,
|
|
50769
|
+
format: input.format,
|
|
50770
|
+
deleted,
|
|
50771
|
+
clearedAvailability
|
|
50772
|
+
} });
|
|
50773
|
+
return {
|
|
50774
|
+
deleted,
|
|
50775
|
+
clearedAvailability
|
|
50776
|
+
};
|
|
50777
|
+
}
|
|
50778
|
+
/**
|
|
50779
|
+
* Full purge of a Library model: delete artifacts on every node that had
|
|
50780
|
+
* them, clear availability (so Sources no longer shows "imported"), strip
|
|
50781
|
+
* pipeline pins that named this modelId (agents revert to hardware defaults
|
|
50782
|
+
* for that step), then drop the registry row.
|
|
50783
|
+
*/
|
|
50481
50784
|
async removeCustomModel(input) {
|
|
50785
|
+
const descriptor = (await this.state_.get()).find((m) => m.entry.id === input.modelId);
|
|
50786
|
+
if (!descriptor) {
|
|
50787
|
+
await this.availability_.update((prev) => clearAvailability(prev, input.modelId).next);
|
|
50788
|
+
return {
|
|
50789
|
+
removed: false,
|
|
50790
|
+
deletedNodes: [],
|
|
50791
|
+
clearedPins: 0
|
|
50792
|
+
};
|
|
50793
|
+
}
|
|
50794
|
+
const formats = Object.keys(descriptor.entry.formats).filter((f) => descriptor.entry.formats[f] !== void 0);
|
|
50795
|
+
const avail = await this.availability_.get();
|
|
50796
|
+
const nodeIds = new Set(["hub", ...Object.keys(avail[input.modelId] ?? {})]);
|
|
50797
|
+
const deletedNodes = [];
|
|
50798
|
+
const api = this.ctx.api;
|
|
50799
|
+
for (const nodeId of nodeIds) {
|
|
50800
|
+
let anyDeleted = false;
|
|
50801
|
+
for (const format of formats) try {
|
|
50802
|
+
if (api) {
|
|
50803
|
+
await api.pipelineExecutor.deleteModel.mutate({
|
|
50804
|
+
addonId: descriptor.stepId,
|
|
50805
|
+
modelId: input.modelId,
|
|
50806
|
+
format
|
|
50807
|
+
}, nodePin(nodeId));
|
|
50808
|
+
anyDeleted = true;
|
|
50809
|
+
}
|
|
50810
|
+
} catch (err) {
|
|
50811
|
+
this.ctx.logger.warn("removeCustomModel: remote delete failed (continuing)", { meta: {
|
|
50812
|
+
modelId: input.modelId,
|
|
50813
|
+
nodeId,
|
|
50814
|
+
format,
|
|
50815
|
+
error: err instanceof Error ? err.message : String(err)
|
|
50816
|
+
} });
|
|
50817
|
+
}
|
|
50818
|
+
if (anyDeleted) deletedNodes.push(nodeId);
|
|
50819
|
+
}
|
|
50820
|
+
try {
|
|
50821
|
+
const modelsDir = await this.resolveModelsDir();
|
|
50822
|
+
for (const format of formats) deleteModelFromDisk(modelsDir, descriptor.entry, format);
|
|
50823
|
+
} catch (err) {
|
|
50824
|
+
this.ctx.logger.warn("removeCustomModel: hub disk wipe failed", { meta: {
|
|
50825
|
+
modelId: input.modelId,
|
|
50826
|
+
error: err instanceof Error ? err.message : String(err)
|
|
50827
|
+
} });
|
|
50828
|
+
}
|
|
50829
|
+
let clearedPins = 0;
|
|
50830
|
+
if (api) {
|
|
50831
|
+
try {
|
|
50832
|
+
clearedPins += await this.clearAgentModelPins(input.modelId);
|
|
50833
|
+
} catch (err) {
|
|
50834
|
+
this.ctx.logger.warn("removeCustomModel: agent pin clear failed", { meta: {
|
|
50835
|
+
modelId: input.modelId,
|
|
50836
|
+
error: err instanceof Error ? err.message : String(err)
|
|
50837
|
+
} });
|
|
50838
|
+
}
|
|
50839
|
+
try {
|
|
50840
|
+
clearedPins += await this.clearCameraModelPins(input.modelId, descriptor.stepId);
|
|
50841
|
+
} catch (err) {
|
|
50842
|
+
this.ctx.logger.warn("removeCustomModel: camera pin clear failed", { meta: {
|
|
50843
|
+
modelId: input.modelId,
|
|
50844
|
+
error: err instanceof Error ? err.message : String(err)
|
|
50845
|
+
} });
|
|
50846
|
+
}
|
|
50847
|
+
}
|
|
50848
|
+
await this.availability_.update((prev) => clearAvailability(prev, input.modelId).next);
|
|
50482
50849
|
let removed = false;
|
|
50483
50850
|
await this.state_.update((prev) => {
|
|
50484
50851
|
const result = removeCustomModel(prev, input.modelId);
|
|
@@ -50487,13 +50854,80 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
|
|
|
50487
50854
|
});
|
|
50488
50855
|
this.ctx.logger.info("Removed custom model", { meta: {
|
|
50489
50856
|
modelId: input.modelId,
|
|
50490
|
-
removed
|
|
50857
|
+
removed,
|
|
50858
|
+
deletedNodes,
|
|
50859
|
+
clearedPins
|
|
50491
50860
|
} });
|
|
50492
|
-
return {
|
|
50861
|
+
return {
|
|
50862
|
+
removed,
|
|
50863
|
+
deletedNodes,
|
|
50864
|
+
clearedPins
|
|
50865
|
+
};
|
|
50866
|
+
}
|
|
50867
|
+
/** Strip `modelId` from every agent's inferenceDevices step pins. */
|
|
50868
|
+
async clearAgentModelPins(modelId) {
|
|
50869
|
+
const api = this.ctx.api;
|
|
50870
|
+
if (!api) return 0;
|
|
50871
|
+
const agents = await api.pipelineOrchestrator.listAgentSettings.query();
|
|
50872
|
+
let cleared = 0;
|
|
50873
|
+
for (const agent of agents) {
|
|
50874
|
+
const devices = agent.settings.inferenceDevices;
|
|
50875
|
+
if (devices === void 0) continue;
|
|
50876
|
+
const { next, cleared: n } = stripModelIdFromInferenceDevices(devices, modelId);
|
|
50877
|
+
if (n === 0) continue;
|
|
50878
|
+
await api.pipelineOrchestrator.setAgentInferenceDevices.mutate({
|
|
50879
|
+
agentNodeId: agent.nodeId,
|
|
50880
|
+
inferenceDevices: next
|
|
50881
|
+
});
|
|
50882
|
+
cleared += n;
|
|
50883
|
+
}
|
|
50884
|
+
return cleared;
|
|
50885
|
+
}
|
|
50886
|
+
/**
|
|
50887
|
+
* Clear per-camera step overrides that pin `modelId` for `stepId`.
|
|
50888
|
+
* `patch: null` drops the override so the camera inherits the agent default.
|
|
50889
|
+
*/
|
|
50890
|
+
async clearCameraModelPins(modelId, stepId) {
|
|
50891
|
+
const api = this.ctx.api;
|
|
50892
|
+
if (!api) return 0;
|
|
50893
|
+
const devices = await api.deviceManager.listAll.query({ projection: "slim" });
|
|
50894
|
+
let cleared = 0;
|
|
50895
|
+
for (const device of devices) {
|
|
50896
|
+
const overrides = await api.pipelineOrchestrator.getCameraStepOverrides.query({ deviceId: device.id });
|
|
50897
|
+
if (overrides === null) continue;
|
|
50898
|
+
for (const [agentNodeId, byDeviceKey] of Object.entries(overrides)) for (const [deviceKey, byStep] of Object.entries(byDeviceKey)) {
|
|
50899
|
+
if (byStep[stepId]?.modelId !== modelId) continue;
|
|
50900
|
+
await api.pipelineOrchestrator.setCameraStepOverride.mutate({
|
|
50901
|
+
deviceId: device.id,
|
|
50902
|
+
agentNodeId,
|
|
50903
|
+
deviceKey,
|
|
50904
|
+
addonId: stepId,
|
|
50905
|
+
patch: null
|
|
50906
|
+
});
|
|
50907
|
+
cleared += 1;
|
|
50908
|
+
}
|
|
50909
|
+
}
|
|
50910
|
+
return cleared;
|
|
50493
50911
|
}
|
|
50494
50912
|
async listCustomModels() {
|
|
50495
50913
|
return this.state_.get();
|
|
50496
50914
|
}
|
|
50915
|
+
/**
|
|
50916
|
+
* Self-heal: drop availability rows for modelIds no longer in the Library
|
|
50917
|
+
* registry. Import and Library both trigger this so an orphan cannot keep
|
|
50918
|
+
* Sources stuck on "imported" after a registry wipe.
|
|
50919
|
+
*/
|
|
50920
|
+
async pruneOrphanAvailability_() {
|
|
50921
|
+
try {
|
|
50922
|
+
const [avail, models] = await Promise.all([this.availability_.get(), this.state_.get()]);
|
|
50923
|
+
const { next, pruned } = pruneOrphanAvailability(avail, new Set(models.map((m) => m.entry.id)));
|
|
50924
|
+
if (pruned.length === 0) return;
|
|
50925
|
+
await this.availability_.update(() => next);
|
|
50926
|
+
this.ctx.logger.info("Pruned orphan model availability", { meta: { pruned } });
|
|
50927
|
+
} catch (err) {
|
|
50928
|
+
this.ctx.logger.warn("pruneOrphanAvailability failed (continuing)", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
50929
|
+
}
|
|
50930
|
+
}
|
|
50497
50931
|
};
|
|
50498
50932
|
//#endregion
|
|
50499
50933
|
export { ModelStudioAddon, modelStudioActions as t };
|