rag-lite-ts 1.0.2 → 2.0.0

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 (202) hide show
  1. package/README.md +606 -93
  2. package/dist/cli/indexer.js +192 -4
  3. package/dist/cli/search.js +50 -11
  4. package/dist/cli.js +183 -26
  5. package/dist/core/abstract-embedder.d.ts +125 -0
  6. package/dist/core/abstract-embedder.js +264 -0
  7. package/dist/core/actionable-error-messages.d.ts +60 -0
  8. package/dist/core/actionable-error-messages.js +397 -0
  9. package/dist/core/batch-processing-optimizer.d.ts +155 -0
  10. package/dist/core/batch-processing-optimizer.js +541 -0
  11. package/dist/core/chunker.d.ts +2 -0
  12. package/dist/core/cli-database-utils.d.ts +53 -0
  13. package/dist/core/cli-database-utils.js +239 -0
  14. package/dist/core/config.js +10 -3
  15. package/dist/core/content-errors.d.ts +111 -0
  16. package/dist/core/content-errors.js +362 -0
  17. package/dist/core/content-manager.d.ts +343 -0
  18. package/dist/core/content-manager.js +1504 -0
  19. package/dist/core/content-performance-optimizer.d.ts +150 -0
  20. package/dist/core/content-performance-optimizer.js +516 -0
  21. package/dist/core/content-resolver.d.ts +104 -0
  22. package/dist/core/content-resolver.js +285 -0
  23. package/dist/core/cross-modal-search.d.ts +164 -0
  24. package/dist/core/cross-modal-search.js +342 -0
  25. package/dist/core/database-connection-manager.d.ts +109 -0
  26. package/dist/core/database-connection-manager.js +304 -0
  27. package/dist/core/db.d.ts +141 -2
  28. package/dist/core/db.js +631 -89
  29. package/dist/core/embedder-factory.d.ts +176 -0
  30. package/dist/core/embedder-factory.js +338 -0
  31. package/dist/core/index.d.ts +3 -1
  32. package/dist/core/index.js +4 -1
  33. package/dist/core/ingestion.d.ts +85 -15
  34. package/dist/core/ingestion.js +510 -45
  35. package/dist/core/lazy-dependency-loader.d.ts +152 -0
  36. package/dist/core/lazy-dependency-loader.js +453 -0
  37. package/dist/core/mode-detection-service.d.ts +150 -0
  38. package/dist/core/mode-detection-service.js +565 -0
  39. package/dist/core/mode-model-validator.d.ts +92 -0
  40. package/dist/core/mode-model-validator.js +203 -0
  41. package/dist/core/model-registry.d.ts +120 -0
  42. package/dist/core/model-registry.js +415 -0
  43. package/dist/core/model-validator.d.ts +217 -0
  44. package/dist/core/model-validator.js +782 -0
  45. package/dist/core/polymorphic-search-factory.d.ts +154 -0
  46. package/dist/core/polymorphic-search-factory.js +344 -0
  47. package/dist/core/raglite-paths.d.ts +121 -0
  48. package/dist/core/raglite-paths.js +145 -0
  49. package/dist/core/reranking-config.d.ts +42 -0
  50. package/dist/core/reranking-config.js +156 -0
  51. package/dist/core/reranking-factory.d.ts +92 -0
  52. package/dist/core/reranking-factory.js +591 -0
  53. package/dist/core/reranking-strategies.d.ts +325 -0
  54. package/dist/core/reranking-strategies.js +720 -0
  55. package/dist/core/resource-cleanup.d.ts +163 -0
  56. package/dist/core/resource-cleanup.js +371 -0
  57. package/dist/core/resource-manager.d.ts +212 -0
  58. package/dist/core/resource-manager.js +564 -0
  59. package/dist/core/search.d.ts +28 -1
  60. package/dist/core/search.js +83 -5
  61. package/dist/core/streaming-operations.d.ts +145 -0
  62. package/dist/core/streaming-operations.js +409 -0
  63. package/dist/core/types.d.ts +3 -0
  64. package/dist/core/universal-embedder.d.ts +177 -0
  65. package/dist/core/universal-embedder.js +139 -0
  66. package/dist/core/validation-messages.d.ts +99 -0
  67. package/dist/core/validation-messages.js +334 -0
  68. package/dist/core/vector-index.js +7 -8
  69. package/dist/factories/index.d.ts +1 -1
  70. package/dist/factories/text-factory.d.ts +128 -34
  71. package/dist/factories/text-factory.js +346 -97
  72. package/dist/file-processor.d.ts +88 -2
  73. package/dist/file-processor.js +720 -17
  74. package/dist/index.d.ts +9 -0
  75. package/dist/index.js +11 -0
  76. package/dist/ingestion.d.ts +16 -0
  77. package/dist/ingestion.js +21 -0
  78. package/dist/mcp-server.d.ts +35 -3
  79. package/dist/mcp-server.js +1107 -31
  80. package/dist/multimodal/clip-embedder.d.ts +314 -0
  81. package/dist/multimodal/clip-embedder.js +945 -0
  82. package/dist/multimodal/index.d.ts +6 -0
  83. package/dist/multimodal/index.js +6 -0
  84. package/dist/run-error-recovery-tests.d.ts +7 -0
  85. package/dist/run-error-recovery-tests.js +101 -0
  86. package/dist/search.d.ts +26 -0
  87. package/dist/search.js +54 -1
  88. package/dist/test-utils.d.ts +8 -26
  89. package/dist/text/chunker.d.ts +1 -0
  90. package/dist/text/embedder.js +15 -8
  91. package/dist/text/index.d.ts +1 -0
  92. package/dist/text/index.js +1 -0
  93. package/dist/text/reranker.d.ts +1 -2
  94. package/dist/text/reranker.js +17 -47
  95. package/dist/text/sentence-transformer-embedder.d.ts +96 -0
  96. package/dist/text/sentence-transformer-embedder.js +340 -0
  97. package/dist/types.d.ts +39 -0
  98. package/dist/utils/vector-math.d.ts +31 -0
  99. package/dist/utils/vector-math.js +70 -0
  100. package/package.json +15 -3
  101. package/dist/api-errors.d.ts.map +0 -1
  102. package/dist/api-errors.js.map +0 -1
  103. package/dist/cli/indexer.d.ts.map +0 -1
  104. package/dist/cli/indexer.js.map +0 -1
  105. package/dist/cli/search.d.ts.map +0 -1
  106. package/dist/cli/search.js.map +0 -1
  107. package/dist/cli.d.ts.map +0 -1
  108. package/dist/cli.js.map +0 -1
  109. package/dist/config.d.ts.map +0 -1
  110. package/dist/config.js.map +0 -1
  111. package/dist/core/adapters.d.ts.map +0 -1
  112. package/dist/core/adapters.js.map +0 -1
  113. package/dist/core/chunker.d.ts.map +0 -1
  114. package/dist/core/chunker.js.map +0 -1
  115. package/dist/core/config.d.ts.map +0 -1
  116. package/dist/core/config.js.map +0 -1
  117. package/dist/core/db.d.ts.map +0 -1
  118. package/dist/core/db.js.map +0 -1
  119. package/dist/core/error-handler.d.ts.map +0 -1
  120. package/dist/core/error-handler.js.map +0 -1
  121. package/dist/core/index.d.ts.map +0 -1
  122. package/dist/core/index.js.map +0 -1
  123. package/dist/core/ingestion.d.ts.map +0 -1
  124. package/dist/core/ingestion.js.map +0 -1
  125. package/dist/core/interfaces.d.ts.map +0 -1
  126. package/dist/core/interfaces.js.map +0 -1
  127. package/dist/core/path-manager.d.ts.map +0 -1
  128. package/dist/core/path-manager.js.map +0 -1
  129. package/dist/core/search-example.d.ts +0 -25
  130. package/dist/core/search-example.d.ts.map +0 -1
  131. package/dist/core/search-example.js +0 -138
  132. package/dist/core/search-example.js.map +0 -1
  133. package/dist/core/search-pipeline-example.d.ts +0 -21
  134. package/dist/core/search-pipeline-example.d.ts.map +0 -1
  135. package/dist/core/search-pipeline-example.js +0 -188
  136. package/dist/core/search-pipeline-example.js.map +0 -1
  137. package/dist/core/search-pipeline.d.ts.map +0 -1
  138. package/dist/core/search-pipeline.js.map +0 -1
  139. package/dist/core/search.d.ts.map +0 -1
  140. package/dist/core/search.js.map +0 -1
  141. package/dist/core/types.d.ts.map +0 -1
  142. package/dist/core/types.js.map +0 -1
  143. package/dist/core/vector-index.d.ts.map +0 -1
  144. package/dist/core/vector-index.js.map +0 -1
  145. package/dist/dom-polyfills.d.ts.map +0 -1
  146. package/dist/dom-polyfills.js.map +0 -1
  147. package/dist/examples/clean-api-examples.d.ts +0 -44
  148. package/dist/examples/clean-api-examples.d.ts.map +0 -1
  149. package/dist/examples/clean-api-examples.js +0 -206
  150. package/dist/examples/clean-api-examples.js.map +0 -1
  151. package/dist/factories/index.d.ts.map +0 -1
  152. package/dist/factories/index.js.map +0 -1
  153. package/dist/factories/text-factory.d.ts.map +0 -1
  154. package/dist/factories/text-factory.js.map +0 -1
  155. package/dist/file-processor.d.ts.map +0 -1
  156. package/dist/file-processor.js.map +0 -1
  157. package/dist/index-manager.d.ts.map +0 -1
  158. package/dist/index-manager.js.map +0 -1
  159. package/dist/index.d.ts.map +0 -1
  160. package/dist/index.js.map +0 -1
  161. package/dist/indexer.d.ts.map +0 -1
  162. package/dist/indexer.js.map +0 -1
  163. package/dist/ingestion.d.ts.map +0 -1
  164. package/dist/ingestion.js.map +0 -1
  165. package/dist/mcp-server.d.ts.map +0 -1
  166. package/dist/mcp-server.js.map +0 -1
  167. package/dist/preprocess.d.ts.map +0 -1
  168. package/dist/preprocess.js.map +0 -1
  169. package/dist/preprocessors/index.d.ts.map +0 -1
  170. package/dist/preprocessors/index.js.map +0 -1
  171. package/dist/preprocessors/mdx.d.ts.map +0 -1
  172. package/dist/preprocessors/mdx.js.map +0 -1
  173. package/dist/preprocessors/mermaid.d.ts.map +0 -1
  174. package/dist/preprocessors/mermaid.js.map +0 -1
  175. package/dist/preprocessors/registry.d.ts.map +0 -1
  176. package/dist/preprocessors/registry.js.map +0 -1
  177. package/dist/search-standalone.d.ts.map +0 -1
  178. package/dist/search-standalone.js.map +0 -1
  179. package/dist/search.d.ts.map +0 -1
  180. package/dist/search.js.map +0 -1
  181. package/dist/test-utils.d.ts.map +0 -1
  182. package/dist/test-utils.js.map +0 -1
  183. package/dist/text/chunker.d.ts.map +0 -1
  184. package/dist/text/chunker.js.map +0 -1
  185. package/dist/text/embedder.d.ts.map +0 -1
  186. package/dist/text/embedder.js.map +0 -1
  187. package/dist/text/index.d.ts.map +0 -1
  188. package/dist/text/index.js.map +0 -1
  189. package/dist/text/preprocessors/index.d.ts.map +0 -1
  190. package/dist/text/preprocessors/index.js.map +0 -1
  191. package/dist/text/preprocessors/mdx.d.ts.map +0 -1
  192. package/dist/text/preprocessors/mdx.js.map +0 -1
  193. package/dist/text/preprocessors/mermaid.d.ts.map +0 -1
  194. package/dist/text/preprocessors/mermaid.js.map +0 -1
  195. package/dist/text/preprocessors/registry.d.ts.map +0 -1
  196. package/dist/text/preprocessors/registry.js.map +0 -1
  197. package/dist/text/reranker.d.ts.map +0 -1
  198. package/dist/text/reranker.js.map +0 -1
  199. package/dist/text/tokenizer.d.ts.map +0 -1
  200. package/dist/text/tokenizer.js.map +0 -1
  201. package/dist/types.d.ts.map +0 -1
  202. package/dist/types.js.map +0 -1
@@ -0,0 +1,945 @@
1
+ /**
2
+ * MULTIMODAL IMPLEMENTATION — CLIP Embedder Implementation
3
+ *
4
+ * Implements UniversalEmbedder interface for CLIP models with full multimodal support.
5
+ * Provides reliable text and image embedding using CLIPTextModelWithProjection and
6
+ * CLIPVisionModelWithProjection for true cross-modal search capabilities.
7
+ *
8
+ * Features:
9
+ * - Text embedding using CLIP text encoder (512-dimensional vectors)
10
+ * - Image embedding using CLIP vision encoder (512-dimensional vectors)
11
+ * - Unified embedding space enabling cross-modal similarity search
12
+ * - Text queries can find semantically similar images
13
+ * - Image queries can find semantically similar text
14
+ * - Batch processing optimization for both text and images
15
+ *
16
+ * Supported Models:
17
+ * - Xenova/clip-vit-base-patch32 (recommended, faster)
18
+ * - Xenova/clip-vit-base-patch16 (higher accuracy, slower)
19
+ */
20
+ import { BaseUniversalEmbedder } from '../core/abstract-embedder.js';
21
+ import { getResourceManager } from '../core/resource-manager.js';
22
+ // =============================================================================
23
+ // CLIP EMBEDDER IMPLEMENTATION
24
+ // =============================================================================
25
+ /**
26
+ * CLIP embedder implementation for multimodal content
27
+ *
28
+ * Provides reliable text and image embedding using separate CLIP model components:
29
+ * - CLIPTextModelWithProjection for text-only embedding (no pixel_values errors)
30
+ * - CLIPVisionModelWithProjection for image embedding
31
+ * - AutoTokenizer for proper text tokenization with CLIP's 77 token limit
32
+ *
33
+ * All embeddings are 512-dimensional vectors in a unified embedding space,
34
+ * enabling true cross-modal search where text queries can find images and
35
+ * image queries can find text based on semantic similarity.
36
+ *
37
+ * Example Usage:
38
+ * ```typescript
39
+ * const embedder = await createEmbedder('Xenova/clip-vit-base-patch32');
40
+ *
41
+ * // Embed text
42
+ * const textResult = await embedder.embedText('a red sports car');
43
+ *
44
+ * // Embed image
45
+ * const imageResult = await embedder.embedImage('./car.jpg');
46
+ *
47
+ * // Calculate cross-modal similarity
48
+ * const similarity = cosineSimilarity(textResult.vector, imageResult.vector);
49
+ * ```
50
+ */
51
+ export class CLIPEmbedder extends BaseUniversalEmbedder {
52
+ tokenizer = null;
53
+ textModel = null;
54
+ imageModel = null; // Placeholder for future image support
55
+ resourceManager = getResourceManager();
56
+ embedderResourceId;
57
+ tokenizerResourceId;
58
+ textModelResourceId;
59
+ imageModelResourceId;
60
+ constructor(modelName, options = {}) {
61
+ super(modelName, options);
62
+ // Validate that this is a supported CLIP model
63
+ this.validateCLIPModel();
64
+ // Register this embedder with the resource manager
65
+ this.embedderResourceId = this.resourceManager.registerEmbedder(this);
66
+ }
67
+ // =============================================================================
68
+ // MODEL LIFECYCLE METHODS
69
+ // =============================================================================
70
+ /**
71
+ * Load the CLIP model components
72
+ *
73
+ * Loads three separate components for reliable multimodal embedding:
74
+ * 1. AutoTokenizer - Handles text tokenization with CLIP's 77 token limit
75
+ * 2. CLIPTextModelWithProjection - Generates text embeddings without pixel_values errors
76
+ * 3. CLIPVisionModelWithProjection - Generates image embeddings
77
+ *
78
+ * All components are registered with the resource manager for proper cleanup.
79
+ * Models are cached locally after first download for faster subsequent loads.
80
+ *
81
+ * @throws {Error} If model loading fails or components are not available
82
+ */
83
+ async loadModel() {
84
+ // Check if already loaded
85
+ if (this._isLoaded && this.textModel) {
86
+ return;
87
+ }
88
+ try {
89
+ this.logModelLoading('Loading CLIP model');
90
+ // Use the validated CLIPTextModelWithProjection approach instead of feature-extraction pipeline
91
+ const { AutoTokenizer, CLIPTextModelWithProjection, CLIPVisionModelWithProjection } = await import('@huggingface/transformers');
92
+ this.logModelLoading('Loading CLIP tokenizer and text model components');
93
+ // Load tokenizer and text model separately (validated approach from task 1.1)
94
+ if (!this.textModel) {
95
+ // Import config for cache path
96
+ const { config } = await import('../core/config.js');
97
+ // Load tokenizer
98
+ this.logModelLoading('Loading CLIP tokenizer...');
99
+ this.tokenizer = await AutoTokenizer.from_pretrained(this.modelName, {
100
+ cache_dir: config.model_cache_path,
101
+ local_files_only: false,
102
+ progress_callback: (progress) => {
103
+ if (progress.status === 'downloading') {
104
+ this.logModelLoading(`Downloading tokenizer: ${Math.round(progress.progress || 0)}%`);
105
+ }
106
+ }
107
+ });
108
+ // Load text model using CLIPTextModelWithProjection
109
+ this.logModelLoading('Loading CLIP text model...');
110
+ this.textModel = await CLIPTextModelWithProjection.from_pretrained(this.modelName, {
111
+ cache_dir: config.model_cache_path,
112
+ local_files_only: false,
113
+ dtype: 'fp32',
114
+ progress_callback: (progress) => {
115
+ if (progress.status === 'downloading') {
116
+ this.logModelLoading(`Downloading text model: ${Math.round(progress.progress || 0)}%`);
117
+ }
118
+ }
119
+ });
120
+ // Load vision model using CLIPVisionModelWithProjection for image embedding
121
+ this.logModelLoading('Loading CLIP vision model...');
122
+ this.imageModel = await CLIPVisionModelWithProjection.from_pretrained(this.modelName, {
123
+ cache_dir: config.model_cache_path,
124
+ local_files_only: false,
125
+ dtype: 'fp32',
126
+ progress_callback: (progress) => {
127
+ if (progress.status === 'downloading') {
128
+ this.logModelLoading(`Downloading vision model: ${Math.round(progress.progress || 0)}%`);
129
+ }
130
+ }
131
+ });
132
+ }
133
+ // Register the text model with resource manager if not already registered
134
+ if (!this.textModelResourceId) {
135
+ this.textModelResourceId = this.resourceManager.registerModel(this.textModel, this.modelName, 'clip-text');
136
+ }
137
+ // Register the image model with resource manager if not already registered
138
+ if (!this.imageModelResourceId && this.imageModel) {
139
+ this.imageModelResourceId = this.resourceManager.registerModel(this.imageModel, this.modelName, 'clip-vision');
140
+ }
141
+ // Verify models are actually loaded
142
+ if (this.textModel && this.imageModel) {
143
+ this._isLoaded = true;
144
+ this.logModelLoading('CLIP text and vision models loaded successfully');
145
+ }
146
+ else {
147
+ const missingModels = [];
148
+ if (!this.textModel)
149
+ missingModels.push('text model');
150
+ if (!this.imageModel)
151
+ missingModels.push('vision model');
152
+ throw new Error(`CLIP model loading failed - ${missingModels.join(' and ')} ${missingModels.length === 1 ? 'is' : 'are'} null`);
153
+ }
154
+ }
155
+ catch (error) {
156
+ // Reset state on failure
157
+ this._isLoaded = false;
158
+ this.textModel = null;
159
+ throw error;
160
+ }
161
+ }
162
+ /**
163
+ * Clean up model resources with comprehensive disposal
164
+ *
165
+ * Properly disposes of all CLIP model components:
166
+ * - Tokenizer resources
167
+ * - Text model resources
168
+ * - Vision model resources
169
+ *
170
+ * Uses the resource manager for coordinated cleanup and forces garbage
171
+ * collection to free memory from CLIP models which can be memory intensive.
172
+ *
173
+ * This method is safe to call multiple times and will not throw errors
174
+ * during cleanup - errors are logged but don't prevent cleanup completion.
175
+ */
176
+ async cleanup() {
177
+ let cleanupErrors = [];
178
+ try {
179
+ // Clean up tokenizer resources
180
+ if (this.tokenizer) {
181
+ try {
182
+ // Use resource manager for proper cleanup
183
+ if (this.tokenizerResourceId) {
184
+ await this.resourceManager.cleanupResource(this.tokenizerResourceId);
185
+ this.tokenizerResourceId = undefined;
186
+ }
187
+ // Clear tokenizer reference
188
+ this.tokenizer = null;
189
+ this.logModelLoading('CLIP tokenizer disposed');
190
+ }
191
+ catch (error) {
192
+ const errorMsg = `Failed to dispose CLIP tokenizer: ${error instanceof Error ? error.message : 'Unknown error'}`;
193
+ cleanupErrors.push(errorMsg);
194
+ console.warn(errorMsg);
195
+ // Force clear reference even if disposal failed
196
+ this.tokenizer = null;
197
+ }
198
+ }
199
+ // Clean up text model resources
200
+ if (this.textModel) {
201
+ try {
202
+ // Use resource manager for proper cleanup
203
+ if (this.textModelResourceId) {
204
+ await this.resourceManager.cleanupResource(this.textModelResourceId);
205
+ this.textModelResourceId = undefined;
206
+ }
207
+ // Clear model reference
208
+ this.textModel = null;
209
+ this.logModelLoading('CLIP text model disposed');
210
+ }
211
+ catch (error) {
212
+ const errorMsg = `Failed to dispose CLIP text model: ${error instanceof Error ? error.message : 'Unknown error'}`;
213
+ cleanupErrors.push(errorMsg);
214
+ console.warn(errorMsg);
215
+ // Force clear reference even if disposal failed
216
+ this.textModel = null;
217
+ }
218
+ }
219
+ // Clean up image model resources (when implemented)
220
+ if (this.imageModel) {
221
+ try {
222
+ // Use resource manager for proper cleanup
223
+ if (this.imageModelResourceId) {
224
+ await this.resourceManager.cleanupResource(this.imageModelResourceId);
225
+ this.imageModelResourceId = undefined;
226
+ }
227
+ // Clear model reference
228
+ this.imageModel = null;
229
+ this.logModelLoading('CLIP image model disposed');
230
+ }
231
+ catch (error) {
232
+ const errorMsg = `Failed to dispose CLIP image model: ${error instanceof Error ? error.message : 'Unknown error'}`;
233
+ cleanupErrors.push(errorMsg);
234
+ console.warn(errorMsg);
235
+ // Force clear reference even if disposal failed
236
+ this.imageModel = null;
237
+ }
238
+ }
239
+ // Clear embedder resource registration (don't call resource manager to avoid circular cleanup)
240
+ if (this.embedderResourceId) {
241
+ this.embedderResourceId = undefined;
242
+ }
243
+ }
244
+ finally {
245
+ // Always clear loaded state regardless of cleanup success
246
+ this._isLoaded = false;
247
+ // Remove from lazy loading cache to ensure fresh instances
248
+ try {
249
+ const { LazyEmbedderLoader } = await import('../core/lazy-dependency-loader.js');
250
+ LazyEmbedderLoader.removeEmbedderFromCache(this.modelName, 'clip');
251
+ }
252
+ catch (error) {
253
+ console.warn('Failed to remove embedder from cache:', error);
254
+ }
255
+ // Force garbage collection for CLIP models (they can be memory intensive)
256
+ if (global.gc) {
257
+ global.gc();
258
+ this.logModelLoading('Forced garbage collection after CLIP model cleanup');
259
+ }
260
+ // Log cleanup completion
261
+ if (cleanupErrors.length === 0) {
262
+ this.logModelLoading('CLIP model resources cleaned up successfully');
263
+ }
264
+ else {
265
+ this.logModelLoading(`CLIP model cleanup completed with ${cleanupErrors.length} errors`);
266
+ // Don't throw errors during cleanup - just log them
267
+ }
268
+ }
269
+ }
270
+ // =============================================================================
271
+ // TEXT EMBEDDING METHODS
272
+ // =============================================================================
273
+ /**
274
+ * Embed text using CLIP text encoder
275
+ *
276
+ * Uses CLIPTextModelWithProjection for reliable text-only embedding without
277
+ * pixel_values errors. Text is tokenized with CLIP's 77 token limit and
278
+ * automatically truncated if necessary.
279
+ *
280
+ * Returns a 512-dimensional embedding vector in the unified CLIP embedding space,
281
+ * which is directly comparable to image embeddings for cross-modal search.
282
+ *
283
+ * @param text - The text to embed (will be trimmed and validated)
284
+ * @returns EmbeddingResult with 512-dimensional vector and metadata
285
+ * @throws {Error} If text is empty, model not loaded, or embedding fails
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * const result = await embedder.embedText('a red sports car');
290
+ * console.log(result.vector.length); // 512
291
+ * console.log(result.contentType); // 'text'
292
+ * ```
293
+ */
294
+ async embedText(text) {
295
+ // Enhanced input validation and preprocessing
296
+ if (typeof text !== 'string') {
297
+ throw new Error('Input must be a string');
298
+ }
299
+ const processedText = text.trim();
300
+ if (processedText.length === 0) {
301
+ throw new Error('Empty text provided to CLIP embedder');
302
+ }
303
+ this.ensureLoaded();
304
+ // Update resource usage tracking
305
+ if (this.embedderResourceId) {
306
+ this.resourceManager.updateResourceUsage(this.embedderResourceId);
307
+ }
308
+ if (this.textModelResourceId) {
309
+ this.resourceManager.updateResourceUsage(this.textModelResourceId);
310
+ }
311
+ if (!this.textModel || !this.tokenizer) {
312
+ throw new Error('CLIP text model or tokenizer not initialized');
313
+ }
314
+ try {
315
+ // Validate and truncate text if necessary (CLIP has a 77 token limit)
316
+ this.validateTextLength(text);
317
+ const finalProcessedText = this.truncateText(processedText);
318
+ // Use the validated CLIPTextModelWithProjection approach (no pixel_values errors)
319
+ // Tokenize text with CLIP's requirements
320
+ const tokens = await this.tokenizer(finalProcessedText, {
321
+ padding: true,
322
+ truncation: true,
323
+ max_length: 77, // CLIP's text sequence length limit
324
+ return_tensors: 'pt'
325
+ });
326
+ // Log token information for debugging (only in development)
327
+ if (process.env.NODE_ENV === 'development') {
328
+ const tokenIds = tokens.input_ids?.data || [];
329
+ const actualTokenCount = Array.from(tokenIds).filter((id) => id !== 0).length;
330
+ if (actualTokenCount >= 77) {
331
+ console.warn(`Text truncated: "${finalProcessedText.substring(0, 50)}..." (${actualTokenCount}+ tokens -> 77 tokens)`);
332
+ }
333
+ }
334
+ // Generate text embedding using CLIPTextModelWithProjection
335
+ const output = await this.textModel(tokens);
336
+ // Extract embedding from text_embeds (no pixel_values dependency)
337
+ const embedding = new Float32Array(output.text_embeds.data);
338
+ // Validate embedding dimensions and values
339
+ if (embedding.length !== this.dimensions) {
340
+ throw new Error(`CLIP embedding dimension mismatch: expected ${this.dimensions}, got ${embedding.length}`);
341
+ }
342
+ // Validate that all values are finite numbers
343
+ const invalidValues = Array.from(embedding).filter(val => !isFinite(val) || isNaN(val));
344
+ if (invalidValues.length > 0) {
345
+ throw new Error(`CLIP embedding contains ${invalidValues.length} invalid values`);
346
+ }
347
+ // Validate embedding quality - should not be all zeros
348
+ const nonZeroValues = Array.from(embedding).filter(val => Math.abs(val) > 1e-8);
349
+ if (nonZeroValues.length === 0) {
350
+ throw new Error('CLIP embedding is all zeros');
351
+ }
352
+ // Calculate embedding magnitude for quality assessment
353
+ const magnitude = Math.sqrt(Array.from(embedding).reduce((sum, val) => sum + val * val, 0));
354
+ if (magnitude < 1e-6) {
355
+ throw new Error(`CLIP embedding has critically low magnitude: ${magnitude.toExponential(3)}`);
356
+ }
357
+ // Generate unique embedding ID
358
+ const embeddingId = this.generateEmbeddingId(finalProcessedText, 'text');
359
+ return {
360
+ embedding_id: embeddingId,
361
+ vector: embedding,
362
+ contentType: 'text',
363
+ metadata: {
364
+ originalText: text,
365
+ processedText: finalProcessedText,
366
+ textLength: finalProcessedText.length,
367
+ embeddingMagnitude: magnitude,
368
+ modelName: this.modelName,
369
+ modelType: this.modelType,
370
+ dimensions: this.dimensions
371
+ }
372
+ };
373
+ }
374
+ catch (error) {
375
+ throw error;
376
+ }
377
+ }
378
+ // =============================================================================
379
+ // IMAGE EMBEDDING METHODS
380
+ // =============================================================================
381
+ /**
382
+ * Embed image using CLIP vision encoder
383
+ *
384
+ * Uses CLIPVisionModelWithProjection to generate image embeddings in the same
385
+ * unified embedding space as text embeddings, enabling true cross-modal search.
386
+ *
387
+ * Supports both local file paths and URLs. Images are automatically preprocessed:
388
+ * - Resized to 224x224 pixels (CLIP's expected input size)
389
+ * - Converted to proper pixel_values format using AutoProcessor
390
+ * - Normalized for CLIP vision model
391
+ *
392
+ * Returns a 512-dimensional embedding vector directly comparable to text embeddings.
393
+ *
394
+ * @param imagePath - Local file path or URL to the image
395
+ * @returns EmbeddingResult with 512-dimensional vector and metadata
396
+ * @throws {Error} If image not found, unsupported format, or embedding fails
397
+ *
398
+ * @example
399
+ * ```typescript
400
+ * // Local file
401
+ * const result = await embedder.embedImage('./car.jpg');
402
+ *
403
+ * // URL
404
+ * const result = await embedder.embedImage('https://example.com/car.jpg');
405
+ *
406
+ * console.log(result.vector.length); // 512
407
+ * console.log(result.contentType); // 'image'
408
+ * ```
409
+ *
410
+ * Supported formats: PNG, JPEG, GIF, BMP, WebP
411
+ */
412
+ async embedImage(imagePath) {
413
+ // Enhanced input validation and preprocessing
414
+ if (typeof imagePath !== 'string') {
415
+ throw new Error('Image path must be a string');
416
+ }
417
+ const processedPath = imagePath.trim();
418
+ if (processedPath.length === 0) {
419
+ throw new Error('Image path cannot be empty');
420
+ }
421
+ // Validate that the model supports images
422
+ if (!this.supportedContentTypes.includes('image')) {
423
+ throw new Error(`Model '${this.modelName}' does not support image embeddings`);
424
+ }
425
+ this.ensureLoaded();
426
+ // Update resource usage tracking
427
+ if (this.embedderResourceId) {
428
+ this.resourceManager.updateResourceUsage(this.embedderResourceId);
429
+ }
430
+ if (this.imageModelResourceId) {
431
+ this.resourceManager.updateResourceUsage(this.imageModelResourceId);
432
+ }
433
+ if (!this.imageModel) {
434
+ throw new Error('CLIP vision model not initialized');
435
+ }
436
+ try {
437
+ // Load and preprocess image using transformers.js utilities
438
+ const image = await this.loadAndPreprocessImage(processedPath);
439
+ // Use AutoProcessor to convert image to proper pixel_values format
440
+ const { AutoProcessor } = await import('@huggingface/transformers');
441
+ const processor = await AutoProcessor.from_pretrained(this.modelName);
442
+ const processedInputs = await processor(image);
443
+ // Generate image embedding using CLIPVisionModelWithProjection
444
+ // The model expects pixel_values as input
445
+ const output = await this.imageModel(processedInputs);
446
+ // Extract embedding from image_embeds output (similar to text_embeds)
447
+ const embedding = new Float32Array(output.image_embeds.data);
448
+ // Validate embedding dimensions and values
449
+ if (embedding.length !== this.dimensions) {
450
+ throw new Error(`CLIP image embedding dimension mismatch: expected ${this.dimensions}, got ${embedding.length}`);
451
+ }
452
+ // Validate that all values are finite numbers
453
+ const invalidValues = Array.from(embedding).filter(val => !isFinite(val) || isNaN(val));
454
+ if (invalidValues.length > 0) {
455
+ throw new Error(`CLIP image embedding contains ${invalidValues.length} invalid values`);
456
+ }
457
+ // Validate embedding quality - should not be all zeros
458
+ const nonZeroValues = Array.from(embedding).filter(val => Math.abs(val) > 1e-8);
459
+ if (nonZeroValues.length === 0) {
460
+ throw new Error('CLIP image embedding is all zeros');
461
+ }
462
+ // Calculate embedding magnitude for quality assessment
463
+ const magnitude = Math.sqrt(Array.from(embedding).reduce((sum, val) => sum + val * val, 0));
464
+ if (magnitude < 1e-6) {
465
+ throw new Error(`CLIP image embedding has critically low magnitude: ${magnitude.toExponential(3)}`);
466
+ }
467
+ // Generate unique embedding ID
468
+ const embeddingId = this.generateEmbeddingId(processedPath, 'image');
469
+ return {
470
+ embedding_id: embeddingId,
471
+ vector: embedding,
472
+ contentType: 'image',
473
+ metadata: {
474
+ imagePath: processedPath,
475
+ embeddingMagnitude: magnitude,
476
+ modelName: this.modelName,
477
+ modelType: this.modelType,
478
+ dimensions: this.dimensions
479
+ }
480
+ };
481
+ }
482
+ catch (error) {
483
+ if (error instanceof Error) {
484
+ // Provide more context for common errors
485
+ if (error.message.includes('ENOENT') || error.message.includes('no such file')) {
486
+ throw new Error(`Image file not found: ${processedPath}`);
487
+ }
488
+ if (error.message.includes('unsupported format') || error.message.includes('invalid image')) {
489
+ throw new Error(`Unsupported image format or corrupted file: ${processedPath}`);
490
+ }
491
+ }
492
+ throw error;
493
+ }
494
+ }
495
+ // =============================================================================
496
+ // IMAGE PREPROCESSING UTILITIES
497
+ // =============================================================================
498
+ /**
499
+ * Load and preprocess image for CLIP vision model
500
+ *
501
+ * Handles image loading from both local files and URLs with automatic format
502
+ * detection and preprocessing. Uses Sharp library when available for better
503
+ * Node.js support, falls back to RawImage for browser compatibility.
504
+ *
505
+ * Preprocessing steps:
506
+ * 1. Load image from path or URL
507
+ * 2. Resize to 224x224 pixels (CLIP's expected input size)
508
+ * 3. Convert to RGB format if needed
509
+ * 4. Return RawImage object for AutoProcessor
510
+ *
511
+ * @param imagePath - Local file path or URL to the image
512
+ * @returns RawImage object ready for AutoProcessor
513
+ * @throws {Error} If image loading or preprocessing fails
514
+ * @private
515
+ */
516
+ async loadAndPreprocessImage(imagePath) {
517
+ try {
518
+ // Import required utilities
519
+ const { RawImage } = await import('@huggingface/transformers');
520
+ const path = await import('path');
521
+ const fs = await import('fs');
522
+ // Get CLIP model variant info for preprocessing parameters
523
+ const variant = this.getModelVariant();
524
+ // Check if this is a URL or local file path
525
+ const isUrl = imagePath.startsWith('http://') || imagePath.startsWith('https://');
526
+ if (isUrl) {
527
+ // Load from URL using RawImage
528
+ // Temporarily suppress ALL console output to avoid logging base64 data
529
+ const originalConsoleLog = console.log;
530
+ const originalConsoleWarn = console.warn;
531
+ const originalConsoleInfo = console.info;
532
+ const originalConsoleError = console.error;
533
+ const originalConsoleDebug = console.debug;
534
+ try {
535
+ // Suppress ALL console output during image loading
536
+ console.log = () => { };
537
+ console.warn = () => { };
538
+ console.info = () => { };
539
+ console.error = () => { };
540
+ console.debug = () => { };
541
+ const image = await RawImage.fromURL(imagePath);
542
+ const processedImage = await image.resize(variant.imageSize, variant.imageSize);
543
+ return processedImage;
544
+ }
545
+ finally {
546
+ // Restore ALL console output
547
+ console.log = originalConsoleLog;
548
+ console.warn = originalConsoleWarn;
549
+ console.info = originalConsoleInfo;
550
+ console.error = originalConsoleError;
551
+ console.debug = originalConsoleDebug;
552
+ }
553
+ }
554
+ // For local files, try Sharp first (if available), then fall back to RawImage
555
+ // Check if file exists
556
+ if (!fs.existsSync(imagePath)) {
557
+ throw new Error(`Image file not found: ${imagePath}`);
558
+ }
559
+ const absolutePath = path.resolve(imagePath);
560
+ // Try to use Sharp for better Node.js support
561
+ try {
562
+ const sharp = await import('sharp');
563
+ // Use Sharp to load and get raw pixel data
564
+ const { data, info } = await sharp.default(absolutePath)
565
+ .resize(variant.imageSize, variant.imageSize, {
566
+ fit: 'cover',
567
+ position: 'center'
568
+ })
569
+ .raw()
570
+ .toBuffer({ resolveWithObject: true });
571
+ // Create RawImage directly from pixel data (avoids data URL logging)
572
+ const { RawImage } = await import('@huggingface/transformers');
573
+ const image = new RawImage(new Uint8ClampedArray(data), info.width, info.height, info.channels);
574
+ return image;
575
+ }
576
+ catch (sharpError) {
577
+ // Sharp not available or failed, fall back to RawImage.read()
578
+ console.warn('Sharp not available, using RawImage fallback:', sharpError instanceof Error ? sharpError.message : 'Unknown error');
579
+ const image = await RawImage.read(absolutePath);
580
+ const processedImage = await image.resize(variant.imageSize, variant.imageSize);
581
+ return processedImage;
582
+ }
583
+ }
584
+ catch (error) {
585
+ if (error instanceof Error) {
586
+ // Provide helpful error messages for common issues
587
+ if (error.message.includes('fetch') || error.message.includes('Failed to load image')) {
588
+ throw new Error(`Failed to load image from path: ${imagePath}. Ensure the path is correct and accessible.`);
589
+ }
590
+ if (error.message.includes('decode') || error.message.includes('IDAT') || error.message.includes('PNG')) {
591
+ throw new Error(`Failed to decode image: ${imagePath}. The file may be corrupted or in an unsupported format. Supported formats: PNG, JPEG, GIF, BMP, WebP.`);
592
+ }
593
+ if (error.message.includes('not found') || error.message.includes('ENOENT')) {
594
+ throw new Error(`Image file not found: ${imagePath}`);
595
+ }
596
+ }
597
+ throw new Error(`Image preprocessing failed for ${imagePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
598
+ }
599
+ }
600
+ // =============================================================================
601
+ // BATCH PROCESSING OPTIMIZATION
602
+ // =============================================================================
603
+ /**
604
+ * Optimized batch processing for CLIP models
605
+ *
606
+ * Processes mixed batches of text and image content efficiently using the
607
+ * BatchProcessingOptimizer for memory management and progress tracking.
608
+ *
609
+ * Features:
610
+ * - Automatic separation of text and image items
611
+ * - Memory-efficient processing for large batches
612
+ * - Progress reporting for batches > 20 items
613
+ * - Garbage collection between batches
614
+ * - Detailed statistics logging
615
+ *
616
+ * @param batch - Array of items with content, contentType, and optional metadata
617
+ * @returns Array of EmbeddingResult objects in the same order as input
618
+ * @throws {Error} If batch processing fails
619
+ * @protected
620
+ */
621
+ async processBatch(batch) {
622
+ this.ensureLoaded();
623
+ // Separate text and image items
624
+ const textItems = batch.filter(item => item.contentType === 'text');
625
+ const imageItems = batch.filter(item => item.contentType === 'image');
626
+ const results = [];
627
+ // Process text items with optimization
628
+ if (textItems.length > 0) {
629
+ // For small batches, use direct processing
630
+ if (textItems.length <= 5) {
631
+ const textResults = await this.processBatchText(textItems);
632
+ results.push(...textResults);
633
+ }
634
+ else {
635
+ // For larger batches, use BatchProcessingOptimizer
636
+ try {
637
+ const { createTextBatchProcessor } = await import('../core/batch-processing-optimizer.js');
638
+ const batchProcessor = createTextBatchProcessor();
639
+ // Convert to EmbeddingBatchItem format
640
+ const batchItems = textItems.map(item => ({
641
+ content: this.truncateText(item.content.trim()),
642
+ contentType: item.contentType,
643
+ metadata: item.metadata
644
+ }));
645
+ // Create embed function that uses this CLIP embedder
646
+ const embedFunction = async (item) => {
647
+ const result = await this.embedText(item.content);
648
+ // Validate dimensions
649
+ if (result.vector.length !== this.dimensions) {
650
+ throw new Error(`CLIP embedding dimension mismatch: expected ${this.dimensions}, got ${result.vector.length}`);
651
+ }
652
+ return result;
653
+ };
654
+ // Process with optimization and progress reporting
655
+ const batchResult = await batchProcessor.processBatch(batchItems, embedFunction, (stats) => {
656
+ if (stats.totalItems > 20) { // Log for moderate-sized batches
657
+ console.log(`CLIP text embedding progress: ${stats.processedItems}/${stats.totalItems} (${Math.round((stats.processedItems / stats.totalItems) * 100)}%)`);
658
+ }
659
+ });
660
+ // Log final statistics for larger batches
661
+ if (batchResult.stats.totalItems > 20) {
662
+ console.log(`✓ CLIP text embedding complete: ${batchResult.stats.processedItems} processed, ${batchResult.stats.failedItems} failed`);
663
+ console.log(` Processing time: ${Math.round(batchResult.stats.processingTimeMs / 1000)}s, Rate: ${Math.round(batchResult.stats.itemsPerSecond)} items/sec`);
664
+ if (batchResult.stats.peakMemoryUsageMB > 100) {
665
+ console.log(` Peak memory usage: ${batchResult.stats.peakMemoryUsageMB}MB`);
666
+ }
667
+ }
668
+ results.push(...batchResult.results);
669
+ }
670
+ catch (error) {
671
+ console.error('Text batch processing failed:', error);
672
+ throw error;
673
+ }
674
+ }
675
+ }
676
+ // Process image items with memory-efficient optimization (placeholder for future implementation)
677
+ if (imageItems.length > 0) {
678
+ console.warn(`Processing ${imageItems.length} image items - using placeholder implementation`);
679
+ // Future implementation will use createImageBatchProcessor() for memory-efficient image processing
680
+ try {
681
+ const { createImageBatchProcessor } = await import('../core/batch-processing-optimizer.js');
682
+ const imageBatchProcessor = createImageBatchProcessor();
683
+ // Convert to EmbeddingBatchItem format
684
+ const imageBatchItems = imageItems.map(item => ({
685
+ content: item.content,
686
+ contentType: item.contentType,
687
+ metadata: item.metadata
688
+ }));
689
+ // Create placeholder embed function for images
690
+ const imageEmbedFunction = async (item) => {
691
+ // TODO: Replace with actual image embedding when implemented
692
+ console.warn(`Placeholder: Would embed image ${item.content}`);
693
+ // Return placeholder result
694
+ const zeroVector = new Float32Array(this.dimensions).fill(0);
695
+ return {
696
+ embedding_id: `image_placeholder_${Date.now()}_${Math.random()}`,
697
+ vector: zeroVector,
698
+ contentType: 'image'
699
+ };
700
+ };
701
+ // Process with memory-efficient image batch processor
702
+ const imageBatchResult = await imageBatchProcessor.processBatch(imageBatchItems, imageEmbedFunction, (stats) => {
703
+ console.log(`Image processing progress: ${stats.processedItems}/${stats.totalItems} (${Math.round((stats.processedItems / stats.totalItems) * 100)}%)`);
704
+ console.log(` Memory usage: ${stats.memoryUsageMB}MB (peak: ${stats.peakMemoryUsageMB}MB)`);
705
+ });
706
+ console.log(`✓ Image processing complete: ${imageBatchResult.stats.processedItems} processed`);
707
+ console.log(` Memory efficiency: Peak usage ${imageBatchResult.stats.peakMemoryUsageMB}MB`);
708
+ results.push(...imageBatchResult.results);
709
+ }
710
+ catch (error) {
711
+ console.error('Image batch processing failed:', error);
712
+ throw error;
713
+ }
714
+ }
715
+ return results;
716
+ }
717
+ /**
718
+ * Process batch of text items using CLIPTextModelWithProjection
719
+ *
720
+ * Efficiently processes multiple text items by tokenizing all texts first,
721
+ * then generating embeddings sequentially. This approach balances memory
722
+ * usage with processing speed.
723
+ *
724
+ * @param textItems - Array of text items to process
725
+ * @returns Array of EmbeddingResult objects
726
+ * @throws {Error} If batch processing fails or dimension mismatch occurs
727
+ * @private
728
+ */
729
+ async processBatchText(textItems) {
730
+ // Prepare texts for batch processing
731
+ const texts = textItems.map(item => this.truncateText(item.content.trim()));
732
+ // Tokenize all texts in batch
733
+ const tokensBatch = await Promise.all(texts.map(text => this.tokenizer(text, {
734
+ padding: true,
735
+ truncation: true,
736
+ max_length: 77, // CLIP's text sequence length limit
737
+ return_tensors: 'pt'
738
+ })));
739
+ // Process each tokenized text through the CLIP text model
740
+ const results = [];
741
+ for (let i = 0; i < textItems.length; i++) {
742
+ const item = textItems[i];
743
+ const tokens = tokensBatch[i];
744
+ // Generate embedding using CLIPTextModelWithProjection
745
+ const output = await this.textModel(tokens);
746
+ // Extract embedding from text_embeds (no pixel_values dependency)
747
+ const embedding = new Float32Array(output.text_embeds.data);
748
+ // Validate dimensions
749
+ if (embedding.length !== this.dimensions) {
750
+ throw new Error(`CLIP embedding dimension mismatch for item ${i}: expected ${this.dimensions}, got ${embedding.length}`);
751
+ }
752
+ const embeddingId = this.generateEmbeddingId(item.content, 'text');
753
+ results.push({
754
+ embedding_id: embeddingId,
755
+ vector: embedding,
756
+ contentType: 'text'
757
+ });
758
+ }
759
+ return results;
760
+ }
761
+ // =============================================================================
762
+ // UTILITY METHODS
763
+ // =============================================================================
764
+ /**
765
+ * Get comprehensive model information including CLIP-specific capabilities
766
+ *
767
+ * Extends base model info with CLIP-specific capabilities including multimodal
768
+ * support, zero-shot classification, and cross-modal retrieval features.
769
+ *
770
+ * @returns Object with model information and capabilities
771
+ */
772
+ getModelInfo() {
773
+ const baseInfo = super.getModelInfo();
774
+ return {
775
+ ...baseInfo,
776
+ capabilities: {
777
+ ...baseInfo.capabilities,
778
+ // CLIP-specific capabilities
779
+ supportsMultimodal: true,
780
+ supportsZeroShotClassification: true,
781
+ supportsImageTextSimilarity: true, // Fully implemented
782
+ supportsTextImageRetrieval: true, // Fully implemented
783
+ recommendedUseCase: 'multimodal similarity and zero-shot classification',
784
+ imageEmbeddingStatus: 'implemented' // Image embedding is fully functional
785
+ }
786
+ };
787
+ }
788
+ /**
789
+ * Check if the model is suitable for a specific task
790
+ *
791
+ * CLIP models excel at similarity, classification, retrieval, and multimodal
792
+ * tasks due to their unified embedding space and zero-shot capabilities.
793
+ *
794
+ * @param task - The task type to check
795
+ * @returns true if CLIP is suitable for the task, false otherwise
796
+ */
797
+ isSuitableForTask(task) {
798
+ const supportedTasks = ['similarity', 'classification', 'retrieval', 'multimodal'];
799
+ return supportedTasks.includes(task);
800
+ }
801
+ /**
802
+ * Get information about multimodal capabilities
803
+ *
804
+ * Returns detailed information about what content types are supported and
805
+ * what features are planned for future implementation.
806
+ *
807
+ * @returns Object describing multimodal support status
808
+ */
809
+ getMultimodalCapabilities() {
810
+ return {
811
+ textSupport: true,
812
+ imageSupport: true, // Now implemented
813
+ videoSupport: false,
814
+ audioSupport: false,
815
+ plannedFeatures: [
816
+ 'Zero-shot image classification',
817
+ 'Advanced image preprocessing options',
818
+ 'Batch image processing optimization',
819
+ 'Video frame extraction and embedding'
820
+ ]
821
+ };
822
+ }
823
+ // =============================================================================
824
+ // CLIP-SPECIFIC METHODS
825
+ // =============================================================================
826
+ /**
827
+ * Get CLIP model variant information
828
+ *
829
+ * Extracts architecture details from the model name to provide variant-specific
830
+ * configuration parameters like patch size, image size, and text length limits.
831
+ *
832
+ * @returns Object with architecture details
833
+ */
834
+ getModelVariant() {
835
+ // Extract information from model name
836
+ const modelName = this.modelName.toLowerCase();
837
+ if (modelName.includes('patch32')) {
838
+ return {
839
+ architecture: 'ViT-B/32',
840
+ patchSize: 32,
841
+ imageSize: 224,
842
+ textMaxLength: 77
843
+ };
844
+ }
845
+ else if (modelName.includes('patch16')) {
846
+ return {
847
+ architecture: 'ViT-B/16',
848
+ patchSize: 16,
849
+ imageSize: 224,
850
+ textMaxLength: 77
851
+ };
852
+ }
853
+ else {
854
+ // Default to patch32 if unclear
855
+ return {
856
+ architecture: 'ViT-B/32',
857
+ patchSize: 32,
858
+ imageSize: 224,
859
+ textMaxLength: 77
860
+ };
861
+ }
862
+ }
863
+ /**
864
+ * Check if text length is within CLIP's token limit
865
+ *
866
+ * Estimates token count based on character length (rough approximation of
867
+ * ~4 characters per token for English text). CLIP has a hard limit of 77 tokens.
868
+ *
869
+ * @param text - Text to validate
870
+ * @returns true if text is within token limit, false otherwise
871
+ */
872
+ isTextLengthValid(text) {
873
+ const variant = this.getModelVariant();
874
+ // Rough estimation: ~4 characters per token for English text
875
+ const estimatedTokens = Math.ceil(text.length / 4);
876
+ return estimatedTokens <= variant.textMaxLength;
877
+ }
878
+ /**
879
+ * Get performance characteristics for this CLIP variant
880
+ *
881
+ * Provides guidance on speed, accuracy, memory usage, and recommended batch
882
+ * sizes based on the CLIP model variant (patch32 vs patch16).
883
+ *
884
+ * @returns Object with performance characteristics
885
+ */
886
+ getPerformanceInfo() {
887
+ const variant = this.getModelVariant();
888
+ if (variant.patchSize === 32) {
889
+ return {
890
+ speed: 'fast',
891
+ accuracy: 'good',
892
+ memoryUsage: 'medium',
893
+ recommendedBatchSize: 8
894
+ };
895
+ }
896
+ else if (variant.patchSize === 16) {
897
+ return {
898
+ speed: 'medium',
899
+ accuracy: 'better',
900
+ memoryUsage: 'high',
901
+ recommendedBatchSize: 4
902
+ };
903
+ }
904
+ else {
905
+ return {
906
+ speed: 'medium',
907
+ accuracy: 'good',
908
+ memoryUsage: 'medium',
909
+ recommendedBatchSize: 6
910
+ };
911
+ }
912
+ }
913
+ /**
914
+ * Check if all CLIP model components are loaded
915
+ *
916
+ * Verifies that tokenizer, text model, and vision model are all loaded and
917
+ * ready for use. All three components must be available for the embedder
918
+ * to be considered fully loaded.
919
+ *
920
+ * @returns true if all components are loaded, false otherwise
921
+ */
922
+ isLoaded() {
923
+ return this._isLoaded && this.tokenizer !== null && this.textModel !== null && this.imageModel !== null;
924
+ }
925
+ /**
926
+ * Validate that this is a supported CLIP model
927
+ *
928
+ * Checks the model name against the list of supported CLIP models. Currently
929
+ * supports Xenova/clip-vit-base-patch32 and Xenova/clip-vit-base-patch16.
930
+ *
931
+ * @throws {Error} If model is not in the supported list
932
+ * @private
933
+ */
934
+ validateCLIPModel() {
935
+ const supportedModels = [
936
+ 'Xenova/clip-vit-base-patch32',
937
+ 'Xenova/clip-vit-base-patch16'
938
+ ];
939
+ if (!supportedModels.includes(this.modelName)) {
940
+ throw new Error(`Unsupported CLIP model: ${this.modelName}. ` +
941
+ `Supported models: ${supportedModels.join(', ')}`);
942
+ }
943
+ }
944
+ }
945
+ //# sourceMappingURL=clip-embedder.js.map