@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.
Files changed (20) hide show
  1. package/dist/{dist-BUE-vLMt.mjs → dist-BpGP9ago.mjs} +57 -12
  2. package/dist/{dist-BY1NQ5oi.js → dist-n-zJ0Uox.js} +62 -11
  3. package/dist/embedding-encoder/index.js +273 -97
  4. package/dist/embedding-encoder/index.mjs +271 -95
  5. package/dist/enrichment-engine/index.js +2 -2
  6. package/dist/enrichment-engine/index.mjs +1 -1
  7. package/dist/pipeline-analytics/_stub.js +1 -1
  8. 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
  9. 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
  10. package/dist/pipeline-analytics/{hostInit-D8QpRf30.mjs → hostInit-D51b7QGW.mjs} +3 -3
  11. package/dist/pipeline-analytics/index.js +316 -2
  12. package/dist/pipeline-analytics/index.mjs +315 -1
  13. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  14. package/dist/{resolve-frame-B2Do-e7z.js → resolve-frame-DB2NdMu2.js} +1 -1
  15. package/package.json +2 -1
  16. package/python/raw_tensor_inference.py +73 -0
  17. package/python/requirements-embedding.txt +8 -0
  18. package/python/tensor_frames.py +70 -0
  19. package/python/test_text_encoder.py +48 -0
  20. 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-BY1NQ5oi.js");
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: "clip-vit-b32",
268
- name: "CLIP ViT-B/32",
269
- description: "OpenAI CLIP ViT-B/32 — fast, 512-dim, int8 quantized (85 MB)",
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: 224,
272
- height: 224
298
+ width: 256,
299
+ height: 256
273
300
  },
274
301
  labels: [],
275
- inputLayout: "nchw",
276
302
  inputNormalization: "none",
277
- formats: { onnx: {
278
- url: "https://huggingface.co/Xenova/clip-vit-base-patch32/resolve/main/onnx/vision_model_quantized.onnx",
279
- sizeMB: 85
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: "clip-vit-b16",
284
- name: "CLIP ViT-B/16",
285
- description: "OpenAI CLIP ViT-B/16higher accuracy, 512-dim, int8 quantized (83 MB)",
319
+ id: "mobileclip-s1",
320
+ name: "MobileCLIP S1",
321
+ description: "Apple MobileCLIP S1balanced vision encoder, 512-dim, 256×256 (90 MB ONNX)",
286
322
  inputSize: {
287
- width: 224,
288
- height: 224
323
+ width: 256,
324
+ height: 256
289
325
  },
290
326
  labels: [],
291
- inputLayout: "nchw",
292
327
  inputNormalization: "none",
293
- formats: { onnx: {
294
- url: "https://huggingface.co/Xenova/clip-vit-base-patch16/resolve/main/onnx/vision_model_quantized.onnx",
295
- sizeMB: 83
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: "siglip2-b16-256",
300
- name: "SigLIP2 Base/16 256",
301
- description: "Google SigLIP2superior scene understanding, 768-dim, int8 quantized (90 MB)",
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: { onnx: {
310
- url: "https://huggingface.co/onnx-community/siglip2-base-patch16-256-ONNX/resolve/main/onnx/vision_model_quantized.onnx",
311
- sizeMB: 90
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: "clip-vit-b32-text",
318
- name: "CLIP ViT-B/32 Text Encoder",
319
- description: "Text encoder for CLIP ViT-B/32, int8 quantized (62 MB)",
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: { onnx: {
326
- url: "https://huggingface.co/Xenova/clip-vit-base-patch32/resolve/main/onnx/text_model_quantized.onnx",
327
- sizeMB: 62
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: "clip-vit-b16-text",
332
- name: "CLIP ViT-B/16 Text Encoder",
333
- description: "Text encoder for CLIP ViT-B/16, int8 quantized (62 MB)",
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: { onnx: {
340
- url: "https://huggingface.co/Xenova/clip-vit-base-patch16/resolve/main/onnx/text_model_quantized.onnx",
341
- sizeMB: 62
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: "siglip2-b16-256-text",
346
- name: "SigLIP2 Base/16 256 Text Encoder",
347
- description: "Text encoder for SigLIP2, int8 quantized (270 MB)",
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: { onnx: {
354
- url: "https://huggingface.co/onnx-community/siglip2-base-patch16-256-ONNX/resolve/main/onnx/text_model_quantized.onnx",
355
- sizeMB: 270
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
- "clip-vit-b32": {
491
- imageModelId: "clip-vit-b32",
492
- textModelId: "clip-vit-b32-text",
678
+ "mobileclip-s0": {
679
+ imageModelId: "mobileclip-s0",
680
+ textModelId: "mobileclip-s0-text",
493
681
  embeddingDim: 512,
494
- inputSize: 224,
682
+ inputSize: 256,
495
683
  tokenizerType: "clip"
496
684
  },
497
- "clip-vit-b16": {
498
- imageModelId: "clip-vit-b16",
499
- textModelId: "clip-vit-b16-text",
685
+ "mobileclip-s1": {
686
+ imageModelId: "mobileclip-s1",
687
+ textModelId: "mobileclip-s1-text",
500
688
  embeddingDim: 512,
501
- inputSize: 224,
689
+ inputSize: 256,
502
690
  tokenizerType: "clip"
503
691
  },
504
- "siglip2-b16-256": {
505
- imageModelId: "siglip2-b16-256",
506
- textModelId: "siglip2-b16-256-text",
507
- embeddingDim: 768,
692
+ "mobileclip-s2": {
693
+ imageModelId: "mobileclip-s2",
694
+ textModelId: "mobileclip-s2-text",
695
+ embeddingDim: 512,
508
696
  inputSize: 256,
509
- tokenizerType: "siglip"
697
+ tokenizerType: "clip"
510
698
  }
511
699
  };
512
- var DEFAULT_CLIP_MODEL = "clip-vit-b32";
700
+ var DEFAULT_CLIP_MODEL = "mobileclip-s1";
513
701
  function getModelMeta(modelId) {
514
- return CLIP_MODEL_META[modelId] ?? CLIP_MODEL_META["clip-vit-b32"];
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
- textRawEngine = null;
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.ctx.api.storage.resolve.query({
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
- const tokenIds = clipTokenize(text);
607
- const inputTensor = new Float32Array(tokenIds);
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.textRawEngine) return;
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
- const rawEngine = new PythonRawTensorEngine(pythonPath, node_path.join(pythonDir, "raw_tensor_inference.py"), modelPath, engineLogger);
650
- await rawEngine.initialize();
651
- if (target === "image") this.imageRawEngine = rawEngine;
652
- else this.textRawEngine = rawEngine;
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.textRawEngine?.dispose();
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.