@camstack/addon-post-analysis 1.1.3 → 1.1.5
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/dist/{dist-BUE-vLMt.mjs → dist-BpGP9ago.mjs} +57 -12
- package/dist/{dist-BY1NQ5oi.js → dist-n-zJ0Uox.js} +62 -11
- package/dist/embedding-encoder/index.js +273 -97
- package/dist/embedding-encoder/index.mjs +271 -95
- package/dist/enrichment-engine/index.js +2 -2
- package/dist/enrichment-engine/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D5LAGg5T.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DgAlWohT.mjs} +3 -3
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BFIbMkkd.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DBkzwlqD.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-D8QpRf30.mjs → hostInit-D51b7QGW.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +316 -2
- package/dist/pipeline-analytics/index.mjs +315 -1
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/dist/{resolve-frame-B2Do-e7z.js → resolve-frame-DB2NdMu2.js} +1 -1
- package/package.json +2 -1
- package/python/raw_tensor_inference.py +73 -0
- package/python/requirements-embedding.txt +8 -0
- package/python/tensor_frames.py +70 -0
- package/python/test_text_encoder.py +48 -0
- package/python/text_encoder_inference.py +76 -0
|
@@ -2,13 +2,13 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
6
|
-
let node_path = require("node:path");
|
|
7
|
-
let node_path$1 = require_dist.__toESM(node_path, 1);
|
|
8
|
-
node_path = require_dist.__toESM(node_path);
|
|
5
|
+
const require_dist = require("../dist-n-zJ0Uox.js");
|
|
9
6
|
let node_fs = require("node:fs");
|
|
10
7
|
let node_fs$1 = require_dist.__toESM(node_fs, 1);
|
|
11
8
|
node_fs = require_dist.__toESM(node_fs);
|
|
9
|
+
let node_path = require("node:path");
|
|
10
|
+
let node_path$1 = require_dist.__toESM(node_path, 1);
|
|
11
|
+
node_path = require_dist.__toESM(node_path);
|
|
12
12
|
require("node:crypto");
|
|
13
13
|
let node_child_process = require("node:child_process");
|
|
14
14
|
//#region ../system/dist/model-download-service-C-IHWnXx.mjs
|
|
@@ -262,98 +262,164 @@ var ModelDownloadService = class {
|
|
|
262
262
|
};
|
|
263
263
|
//#endregion
|
|
264
264
|
//#region src/embedding-encoder/catalogs/embedding-models.ts
|
|
265
|
+
var HF_REPO = "camstack/camstack-models";
|
|
266
|
+
var hf = (path) => require_dist.hfModelUrl(HF_REPO, path);
|
|
267
|
+
/**
|
|
268
|
+
* The CLIP BPE tokenizer (HF `tokenizers` format: vocab 49408 + 48894 merges).
|
|
269
|
+
* Hosted next to every text-encoder onnx (`.../onnx/tokenizer.json`) and fetched
|
|
270
|
+
* as a sibling file so it lands beside the model in the shared models dir.
|
|
271
|
+
*/
|
|
272
|
+
var TOKENIZER_FILE = "tokenizer.json";
|
|
273
|
+
var ovFormat = (url, sizeMB) => {
|
|
274
|
+
const base = url.split("/").pop() ?? "";
|
|
275
|
+
const files = base.endsWith(".xml") ? [base.replace(/\.xml$/, ".bin")] : void 0;
|
|
276
|
+
return {
|
|
277
|
+
url,
|
|
278
|
+
sizeMB,
|
|
279
|
+
runtimes: ["python"],
|
|
280
|
+
...files ? { files } : {}
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Files inside an .mlpackage directory bundle.
|
|
285
|
+
* Must be fetched alongside the package root when isDirectory is true.
|
|
286
|
+
*/
|
|
287
|
+
var MLPACKAGE_FILES = [
|
|
288
|
+
"Manifest.json",
|
|
289
|
+
"Data/com.apple.CoreML/model.mlmodel",
|
|
290
|
+
"Data/com.apple.CoreML/weights/weight.bin"
|
|
291
|
+
];
|
|
265
292
|
var CLIP_IMAGE_MODELS = [
|
|
266
293
|
{
|
|
267
|
-
id: "
|
|
268
|
-
name: "
|
|
269
|
-
description: "
|
|
294
|
+
id: "mobileclip-s0",
|
|
295
|
+
name: "MobileCLIP S0",
|
|
296
|
+
description: "Apple MobileCLIP S0 — fast vision encoder, 512-dim, 256×256 (40 MB ONNX)",
|
|
270
297
|
inputSize: {
|
|
271
|
-
width:
|
|
272
|
-
height:
|
|
298
|
+
width: 256,
|
|
299
|
+
height: 256
|
|
273
300
|
},
|
|
274
301
|
labels: [],
|
|
275
|
-
inputLayout: "nchw",
|
|
276
302
|
inputNormalization: "none",
|
|
277
|
-
formats: {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
303
|
+
formats: {
|
|
304
|
+
onnx: {
|
|
305
|
+
url: hf("clip/mobileclip-s0/onnx/camstack-mobileclip-s0-vision.onnx"),
|
|
306
|
+
sizeMB: 40
|
|
307
|
+
},
|
|
308
|
+
openvino: ovFormat(hf("clip/mobileclip-s0/openvino/camstack-mobileclip-s0-vision.xml"), 25),
|
|
309
|
+
coreml: {
|
|
310
|
+
url: hf("clip/mobileclip-s0/coreml/camstack-mobileclip-s0-vision.mlpackage"),
|
|
311
|
+
sizeMB: 30,
|
|
312
|
+
isDirectory: true,
|
|
313
|
+
files: [...MLPACKAGE_FILES],
|
|
314
|
+
runtimes: ["python"]
|
|
315
|
+
}
|
|
316
|
+
}
|
|
281
317
|
},
|
|
282
318
|
{
|
|
283
|
-
id: "
|
|
284
|
-
name: "
|
|
285
|
-
description: "
|
|
319
|
+
id: "mobileclip-s1",
|
|
320
|
+
name: "MobileCLIP S1",
|
|
321
|
+
description: "Apple MobileCLIP S1 — balanced vision encoder, 512-dim, 256×256 (90 MB ONNX)",
|
|
286
322
|
inputSize: {
|
|
287
|
-
width:
|
|
288
|
-
height:
|
|
323
|
+
width: 256,
|
|
324
|
+
height: 256
|
|
289
325
|
},
|
|
290
326
|
labels: [],
|
|
291
|
-
inputLayout: "nchw",
|
|
292
327
|
inputNormalization: "none",
|
|
293
|
-
formats: {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
328
|
+
formats: {
|
|
329
|
+
onnx: {
|
|
330
|
+
url: hf("clip/mobileclip-s1/onnx/camstack-mobileclip-s1-vision.onnx"),
|
|
331
|
+
sizeMB: 90
|
|
332
|
+
},
|
|
333
|
+
openvino: ovFormat(hf("clip/mobileclip-s1/openvino/camstack-mobileclip-s1-vision.xml"), 55),
|
|
334
|
+
coreml: {
|
|
335
|
+
url: hf("clip/mobileclip-s1/coreml/camstack-mobileclip-s1-vision.mlpackage"),
|
|
336
|
+
sizeMB: 65,
|
|
337
|
+
isDirectory: true,
|
|
338
|
+
files: [...MLPACKAGE_FILES],
|
|
339
|
+
runtimes: ["python"]
|
|
340
|
+
}
|
|
341
|
+
}
|
|
297
342
|
},
|
|
298
343
|
{
|
|
299
|
-
id: "
|
|
300
|
-
name: "
|
|
301
|
-
description: "
|
|
344
|
+
id: "mobileclip-s2",
|
|
345
|
+
name: "MobileCLIP S2",
|
|
346
|
+
description: "Apple MobileCLIP S2 — high-accuracy vision encoder, 512-dim, 256×256 (150 MB ONNX)",
|
|
302
347
|
inputSize: {
|
|
303
348
|
width: 256,
|
|
304
349
|
height: 256
|
|
305
350
|
},
|
|
306
351
|
labels: [],
|
|
307
|
-
inputLayout: "nchw",
|
|
308
352
|
inputNormalization: "none",
|
|
309
|
-
formats: {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
353
|
+
formats: {
|
|
354
|
+
onnx: {
|
|
355
|
+
url: hf("clip/mobileclip-s2/onnx/camstack-mobileclip-s2-vision.onnx"),
|
|
356
|
+
sizeMB: 150
|
|
357
|
+
},
|
|
358
|
+
openvino: ovFormat(hf("clip/mobileclip-s2/openvino/camstack-mobileclip-s2-vision.xml"), 90),
|
|
359
|
+
coreml: {
|
|
360
|
+
url: hf("clip/mobileclip-s2/coreml/camstack-mobileclip-s2-vision.mlpackage"),
|
|
361
|
+
sizeMB: 110,
|
|
362
|
+
isDirectory: true,
|
|
363
|
+
files: [...MLPACKAGE_FILES],
|
|
364
|
+
runtimes: ["python"]
|
|
365
|
+
}
|
|
366
|
+
}
|
|
313
367
|
}
|
|
314
368
|
];
|
|
315
369
|
var CLIP_TEXT_MODELS = [
|
|
316
370
|
{
|
|
317
|
-
id: "
|
|
318
|
-
name: "
|
|
319
|
-
description: "Text encoder for
|
|
371
|
+
id: "mobileclip-s0-text",
|
|
372
|
+
name: "MobileCLIP S0 Text Encoder",
|
|
373
|
+
description: "Text encoder for MobileCLIP S0, 512-dim, int8 quantized (35 MB)",
|
|
320
374
|
inputSize: {
|
|
321
375
|
width: 0,
|
|
322
376
|
height: 0
|
|
323
377
|
},
|
|
324
378
|
labels: [],
|
|
325
|
-
formats: {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
379
|
+
formats: {
|
|
380
|
+
onnx: {
|
|
381
|
+
url: hf("clip/mobileclip-s0/onnx/camstack-mobileclip-s0-text.onnx"),
|
|
382
|
+
sizeMB: 35,
|
|
383
|
+
files: [TOKENIZER_FILE]
|
|
384
|
+
},
|
|
385
|
+
openvino: ovFormat(hf("clip/mobileclip-s0/openvino/camstack-mobileclip-s0-text.xml"), 22)
|
|
386
|
+
}
|
|
329
387
|
},
|
|
330
388
|
{
|
|
331
|
-
id: "
|
|
332
|
-
name: "
|
|
333
|
-
description: "Text encoder for
|
|
389
|
+
id: "mobileclip-s1-text",
|
|
390
|
+
name: "MobileCLIP S1 Text Encoder",
|
|
391
|
+
description: "Text encoder for MobileCLIP S1, 512-dim, int8 quantized (35 MB)",
|
|
334
392
|
inputSize: {
|
|
335
393
|
width: 0,
|
|
336
394
|
height: 0
|
|
337
395
|
},
|
|
338
396
|
labels: [],
|
|
339
|
-
formats: {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
397
|
+
formats: {
|
|
398
|
+
onnx: {
|
|
399
|
+
url: hf("clip/mobileclip-s1/onnx/camstack-mobileclip-s1-text.onnx"),
|
|
400
|
+
sizeMB: 35,
|
|
401
|
+
files: [TOKENIZER_FILE]
|
|
402
|
+
},
|
|
403
|
+
openvino: ovFormat(hf("clip/mobileclip-s1/openvino/camstack-mobileclip-s1-text.xml"), 22)
|
|
404
|
+
}
|
|
343
405
|
},
|
|
344
406
|
{
|
|
345
|
-
id: "
|
|
346
|
-
name: "
|
|
347
|
-
description: "Text encoder for
|
|
407
|
+
id: "mobileclip-s2-text",
|
|
408
|
+
name: "MobileCLIP S2 Text Encoder",
|
|
409
|
+
description: "Text encoder for MobileCLIP S2, 512-dim, int8 quantized (35 MB)",
|
|
348
410
|
inputSize: {
|
|
349
411
|
width: 0,
|
|
350
412
|
height: 0
|
|
351
413
|
},
|
|
352
414
|
labels: [],
|
|
353
|
-
formats: {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
415
|
+
formats: {
|
|
416
|
+
onnx: {
|
|
417
|
+
url: hf("clip/mobileclip-s2/onnx/camstack-mobileclip-s2-text.onnx"),
|
|
418
|
+
sizeMB: 35,
|
|
419
|
+
files: [TOKENIZER_FILE]
|
|
420
|
+
},
|
|
421
|
+
openvino: ovFormat(hf("clip/mobileclip-s2/openvino/camstack-mobileclip-s2-text.xml"), 22)
|
|
422
|
+
}
|
|
357
423
|
}
|
|
358
424
|
];
|
|
359
425
|
//#endregion
|
|
@@ -485,33 +551,155 @@ var PythonRawTensorEngine = class {
|
|
|
485
551
|
}
|
|
486
552
|
};
|
|
487
553
|
//#endregion
|
|
554
|
+
//#region src/embedding-encoder/shared/python-text-encoder-engine.ts
|
|
555
|
+
/**
|
|
556
|
+
* CLIP text-encoder engine backed by an embedded-Python subprocess
|
|
557
|
+
* (`text_encoder_inference.py`). Tokenization happens IN Python via the HF
|
|
558
|
+
* `tokenizers` Rust BPE (exact by construction) — this replaces the former
|
|
559
|
+
* hand-rolled TypeScript CLIP BPE.
|
|
560
|
+
*
|
|
561
|
+
* The caller sends raw UTF-8 text; Python tokenizes (truncate/pad to 77), runs
|
|
562
|
+
* onnxruntime, and returns the embedding tensor. Wire protocol = length-prefixed
|
|
563
|
+
* binary frames ([4B LE length][payload]):
|
|
564
|
+
* ready (in): [0x01]
|
|
565
|
+
* request (out): UTF-8 text bytes
|
|
566
|
+
* response (in): [1B ndims][dims × 4B LE uint32][float32 LE data]
|
|
567
|
+
*/
|
|
568
|
+
var PythonTextEncoderEngine = class {
|
|
569
|
+
pythonPath;
|
|
570
|
+
scriptPath;
|
|
571
|
+
modelPath;
|
|
572
|
+
tokenizerPath;
|
|
573
|
+
process = null;
|
|
574
|
+
receiveBuffer = Buffer.alloc(0);
|
|
575
|
+
pendingResolve = null;
|
|
576
|
+
pendingReject = null;
|
|
577
|
+
log;
|
|
578
|
+
constructor(pythonPath, scriptPath, modelPath, tokenizerPath, logger) {
|
|
579
|
+
this.pythonPath = pythonPath;
|
|
580
|
+
this.scriptPath = scriptPath;
|
|
581
|
+
this.modelPath = modelPath;
|
|
582
|
+
this.tokenizerPath = tokenizerPath;
|
|
583
|
+
this.log = logger ?? createNoopLogger();
|
|
584
|
+
}
|
|
585
|
+
async initialize() {
|
|
586
|
+
this.process = (0, node_child_process.spawn)(this.pythonPath, [
|
|
587
|
+
this.scriptPath,
|
|
588
|
+
this.modelPath,
|
|
589
|
+
this.tokenizerPath
|
|
590
|
+
], { stdio: [
|
|
591
|
+
"pipe",
|
|
592
|
+
"pipe",
|
|
593
|
+
"pipe"
|
|
594
|
+
] });
|
|
595
|
+
this.process.stderr?.on("data", (chunk) => {
|
|
596
|
+
const text = chunk.toString().trim();
|
|
597
|
+
if (text) this.log.warn(text);
|
|
598
|
+
});
|
|
599
|
+
this.process.on("error", (err) => {
|
|
600
|
+
this.log.error("Python text-encoder process error", { meta: { error: err.message } });
|
|
601
|
+
this.pendingReject?.(err);
|
|
602
|
+
this.pendingReject = null;
|
|
603
|
+
this.pendingResolve = null;
|
|
604
|
+
});
|
|
605
|
+
this.process.on("exit", (code) => {
|
|
606
|
+
if (code !== 0 && code !== null) {
|
|
607
|
+
const err = /* @__PURE__ */ new Error(`PythonTextEncoderEngine: process exited with code ${code}`);
|
|
608
|
+
this.pendingReject?.(err);
|
|
609
|
+
this.pendingReject = null;
|
|
610
|
+
this.pendingResolve = null;
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
this.process.stdout.on("data", (chunk) => {
|
|
614
|
+
this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]);
|
|
615
|
+
this.tryReceive();
|
|
616
|
+
});
|
|
617
|
+
const ready = await this.receiveFrame();
|
|
618
|
+
if (ready.length !== 1 || ready[0] !== 1) throw new Error("PythonTextEncoderEngine: unexpected ready frame");
|
|
619
|
+
this.log.info("CLIP text-encoder engine ready (embedded Python)", { meta: {
|
|
620
|
+
modelPath: this.modelPath,
|
|
621
|
+
tokenizerPath: this.tokenizerPath
|
|
622
|
+
} });
|
|
623
|
+
}
|
|
624
|
+
/** Tokenize + encode `text` into the model embedding (float32). */
|
|
625
|
+
async encode(text) {
|
|
626
|
+
if (!this.process?.stdin) throw new Error("PythonTextEncoderEngine: not initialized — call initialize() first");
|
|
627
|
+
const payload = Buffer.from(text, "utf-8");
|
|
628
|
+
const lenBuf = Buffer.allocUnsafe(4);
|
|
629
|
+
lenBuf.writeUInt32LE(payload.length, 0);
|
|
630
|
+
this.process.stdin.write(Buffer.concat([lenBuf, payload]));
|
|
631
|
+
const resp = await this.receiveFrame();
|
|
632
|
+
const floatStart = 1 + resp.readUInt8(0) * 4;
|
|
633
|
+
const count = (resp.length - floatStart) / 4;
|
|
634
|
+
const out = new Float32Array(count);
|
|
635
|
+
for (let i = 0; i < count; i++) out[i] = resp.readFloatLE(floatStart + i * 4);
|
|
636
|
+
return out;
|
|
637
|
+
}
|
|
638
|
+
async dispose() {
|
|
639
|
+
const proc = this.process;
|
|
640
|
+
if (!proc) return;
|
|
641
|
+
this.process = null;
|
|
642
|
+
proc.stdin?.end();
|
|
643
|
+
proc.kill("SIGTERM");
|
|
644
|
+
await new Promise((resolve) => {
|
|
645
|
+
const timer = setTimeout(() => {
|
|
646
|
+
try {
|
|
647
|
+
proc.kill("SIGKILL");
|
|
648
|
+
} catch {}
|
|
649
|
+
resolve();
|
|
650
|
+
}, 5e3);
|
|
651
|
+
proc.once("exit", () => {
|
|
652
|
+
clearTimeout(timer);
|
|
653
|
+
resolve();
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
receiveFrame() {
|
|
658
|
+
return new Promise((resolve, reject) => {
|
|
659
|
+
this.pendingResolve = resolve;
|
|
660
|
+
this.pendingReject = reject;
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
tryReceive() {
|
|
664
|
+
if (this.receiveBuffer.length < 4) return;
|
|
665
|
+
const length = this.receiveBuffer.readUInt32LE(0);
|
|
666
|
+
if (this.receiveBuffer.length < 4 + length) return;
|
|
667
|
+
const payload = Buffer.from(this.receiveBuffer.subarray(4, 4 + length));
|
|
668
|
+
this.receiveBuffer = this.receiveBuffer.subarray(4 + length);
|
|
669
|
+
const resolve = this.pendingResolve;
|
|
670
|
+
this.pendingResolve = null;
|
|
671
|
+
this.pendingReject = null;
|
|
672
|
+
resolve?.(payload);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
//#endregion
|
|
488
676
|
//#region src/embedding-encoder/addon/clip-models.ts
|
|
489
677
|
var CLIP_MODEL_META = {
|
|
490
|
-
"
|
|
491
|
-
imageModelId: "
|
|
492
|
-
textModelId: "
|
|
678
|
+
"mobileclip-s0": {
|
|
679
|
+
imageModelId: "mobileclip-s0",
|
|
680
|
+
textModelId: "mobileclip-s0-text",
|
|
493
681
|
embeddingDim: 512,
|
|
494
|
-
inputSize:
|
|
682
|
+
inputSize: 256,
|
|
495
683
|
tokenizerType: "clip"
|
|
496
684
|
},
|
|
497
|
-
"
|
|
498
|
-
imageModelId: "
|
|
499
|
-
textModelId: "
|
|
685
|
+
"mobileclip-s1": {
|
|
686
|
+
imageModelId: "mobileclip-s1",
|
|
687
|
+
textModelId: "mobileclip-s1-text",
|
|
500
688
|
embeddingDim: 512,
|
|
501
|
-
inputSize:
|
|
689
|
+
inputSize: 256,
|
|
502
690
|
tokenizerType: "clip"
|
|
503
691
|
},
|
|
504
|
-
"
|
|
505
|
-
imageModelId: "
|
|
506
|
-
textModelId: "
|
|
507
|
-
embeddingDim:
|
|
692
|
+
"mobileclip-s2": {
|
|
693
|
+
imageModelId: "mobileclip-s2",
|
|
694
|
+
textModelId: "mobileclip-s2-text",
|
|
695
|
+
embeddingDim: 512,
|
|
508
696
|
inputSize: 256,
|
|
509
|
-
tokenizerType: "
|
|
697
|
+
tokenizerType: "clip"
|
|
510
698
|
}
|
|
511
699
|
};
|
|
512
|
-
var DEFAULT_CLIP_MODEL = "
|
|
700
|
+
var DEFAULT_CLIP_MODEL = "mobileclip-s1";
|
|
513
701
|
function getModelMeta(modelId) {
|
|
514
|
-
return CLIP_MODEL_META[modelId] ?? CLIP_MODEL_META["
|
|
702
|
+
return CLIP_MODEL_META[modelId] ?? CLIP_MODEL_META["mobileclip-s1"];
|
|
515
703
|
}
|
|
516
704
|
//#endregion
|
|
517
705
|
//#region src/embedding-encoder/addon/clip-preprocessing.ts
|
|
@@ -559,7 +747,7 @@ function l2Normalize(vec) {
|
|
|
559
747
|
//#region src/embedding-encoder/addon/index.ts
|
|
560
748
|
var EmbeddingEncoderAddon = class extends require_dist.BaseAddon {
|
|
561
749
|
imageRawEngine = null;
|
|
562
|
-
|
|
750
|
+
textEngine = null;
|
|
563
751
|
models = null;
|
|
564
752
|
constructor() {
|
|
565
753
|
super({
|
|
@@ -569,10 +757,7 @@ var EmbeddingEncoderAddon = class extends require_dist.BaseAddon {
|
|
|
569
757
|
});
|
|
570
758
|
}
|
|
571
759
|
async onInitialize() {
|
|
572
|
-
const modelsDir = await this.
|
|
573
|
-
location: "models",
|
|
574
|
-
relativePath: ""
|
|
575
|
-
}).catch(() => "camstack-data/models");
|
|
760
|
+
const modelsDir = await this.resolveModelsDir();
|
|
576
761
|
this.models = new ModelDownloadService(modelsDir, []);
|
|
577
762
|
return [{
|
|
578
763
|
capability: require_dist.embeddingEncoderCapability,
|
|
@@ -603,9 +788,8 @@ var EmbeddingEncoderAddon = class extends require_dist.BaseAddon {
|
|
|
603
788
|
await this.ensureTextEngine();
|
|
604
789
|
const meta = getModelMeta(this.config.modelId);
|
|
605
790
|
const start = Date.now();
|
|
606
|
-
|
|
607
|
-
const
|
|
608
|
-
const output = await this.textRawEngine.run(inputTensor, [1, tokenIds.length]);
|
|
791
|
+
if (!this.textEngine) throw new Error("EmbeddingEncoder: text engine not loaded — ensureTextEngine() must run first");
|
|
792
|
+
const output = await this.textEngine.encode(text);
|
|
609
793
|
const sliced = output.length > meta.embeddingDim ? output.slice(0, meta.embeddingDim) : output;
|
|
610
794
|
const normalized = l2Normalize(new Float32Array(sliced));
|
|
611
795
|
return {
|
|
@@ -629,7 +813,7 @@ var EmbeddingEncoderAddon = class extends require_dist.BaseAddon {
|
|
|
629
813
|
await this.resolveForEntry(imageEntry, "image");
|
|
630
814
|
}
|
|
631
815
|
async ensureTextEngine() {
|
|
632
|
-
if (this.
|
|
816
|
+
if (this.textEngine) return;
|
|
633
817
|
const meta = getModelMeta(this.config.modelId);
|
|
634
818
|
const textEntry = CLIP_TEXT_MODELS.find((m) => m.id === meta.textModelId);
|
|
635
819
|
if (!textEntry) throw new Error(`EmbeddingEncoderAddon: unknown text model "${meta.textModelId}"`);
|
|
@@ -646,14 +830,21 @@ var EmbeddingEncoderAddon = class extends require_dist.BaseAddon {
|
|
|
646
830
|
if (!pythonPath) throw new Error("EmbeddingEncoder: embedded Python is unavailable — cannot run ONNX embeddings. ctx.deps.ensurePython() returned null (portable Python download likely failed).");
|
|
647
831
|
const pythonDir = resolveEmbeddingPythonDir();
|
|
648
832
|
await this.ctx.deps.installPythonRequirements(node_path.join(pythonDir, "requirements-embedding.txt"));
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
833
|
+
if (target === "image") {
|
|
834
|
+
const rawEngine = new PythonRawTensorEngine(pythonPath, node_path.join(pythonDir, "raw_tensor_inference.py"), modelPath, engineLogger);
|
|
835
|
+
await rawEngine.initialize();
|
|
836
|
+
this.imageRawEngine = rawEngine;
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const tokenizerPath = node_path.join(node_path.dirname(modelPath), TOKENIZER_FILE);
|
|
840
|
+
if (!node_fs.existsSync(tokenizerPath)) throw new Error(`EmbeddingEncoder: CLIP tokenizer not found at "${tokenizerPath}" — the tokenizer.json sibling download likely failed.`);
|
|
841
|
+
const textEngine = new PythonTextEncoderEngine(pythonPath, node_path.join(pythonDir, "text_encoder_inference.py"), modelPath, tokenizerPath, engineLogger);
|
|
842
|
+
await textEngine.initialize();
|
|
843
|
+
this.textEngine = textEngine;
|
|
653
844
|
}
|
|
654
845
|
async onShutdown() {
|
|
655
846
|
await this.imageRawEngine?.dispose();
|
|
656
|
-
await this.
|
|
847
|
+
await this.textEngine?.dispose();
|
|
657
848
|
}
|
|
658
849
|
globalSettingsSchema() {
|
|
659
850
|
return this.schema({ sections: [{
|
|
@@ -709,21 +900,6 @@ var EmbeddingEncoderAddon = class extends require_dist.BaseAddon {
|
|
|
709
900
|
async onConfigChanged() {}
|
|
710
901
|
};
|
|
711
902
|
/**
|
|
712
|
-
* Minimal CLIP tokenizer — encodes ASCII text to token IDs.
|
|
713
|
-
* Production implementations should use a proper BPE tokenizer;
|
|
714
|
-
* this is a simplified placeholder that maps characters to IDs
|
|
715
|
-
* with SOT/EOT tokens for basic functionality.
|
|
716
|
-
*/
|
|
717
|
-
function clipTokenize(text, maxLength = 77) {
|
|
718
|
-
const SOT_TOKEN = 49406;
|
|
719
|
-
const EOT_TOKEN = 49407;
|
|
720
|
-
const tokens = [SOT_TOKEN];
|
|
721
|
-
for (let i = 0; i < text.length && tokens.length < maxLength - 1; i++) tokens.push(text.charCodeAt(i) + 256);
|
|
722
|
-
tokens.push(EOT_TOKEN);
|
|
723
|
-
while (tokens.length < maxLength) tokens.push(0);
|
|
724
|
-
return tokens;
|
|
725
|
-
}
|
|
726
|
-
/**
|
|
727
903
|
* Locate the addon's bundled `python/` dir (holds `raw_tensor_inference.py` +
|
|
728
904
|
* `requirements-embedding.txt`). Published package first, then `__dirname`
|
|
729
905
|
* candidates for the in-tree dev build.
|