ngx-transformers 0.1.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.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # NgxTransformers
2
+
3
+ This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 22.1.0.
4
+
5
+ ## Code scaffolding
6
+
7
+ Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
8
+
9
+ ```bash
10
+ ng generate component component-name
11
+ ```
12
+
13
+ For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
14
+
15
+ ```bash
16
+ ng generate --help
17
+ ```
18
+
19
+ ## Building
20
+
21
+ To build the library, run:
22
+
23
+ ```bash
24
+ ng build ngx-transformers
25
+ ```
26
+
27
+ This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
28
+
29
+ ### Publishing the Library
30
+
31
+ Once the project is built, you can publish your library by following these steps:
32
+
33
+ 1. Navigate to the `dist` directory:
34
+
35
+ ```bash
36
+ cd dist/ngx-transformers
37
+ ```
38
+
39
+ 2. Run the `npm publish` command to publish your library to the npm registry:
40
+ ```bash
41
+ npm publish
42
+ ```
43
+
44
+ ## Running unit tests
45
+
46
+ To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command:
47
+
48
+ ```bash
49
+ ng test
50
+ ```
51
+
52
+ ## Running end-to-end tests
53
+
54
+ For end-to-end (e2e) testing, run:
55
+
56
+ ```bash
57
+ ng e2e
58
+ ```
59
+
60
+ Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
61
+
62
+ ## Additional Resources
63
+
64
+ For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
@@ -0,0 +1,290 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, makeEnvironmentProviders, signal, computed, inject, DestroyRef, input, ChangeDetectionStrategy, Component } from '@angular/core';
3
+
4
+ const defaultPipelineFactory = async (task, model, options) => {
5
+ const { pipeline } = await import('@huggingface/transformers');
6
+ const pipe = await pipeline(task, model, options);
7
+ return pipe;
8
+ };
9
+ const PIPELINE_FACTORY = new InjectionToken('ngx-transformers.pipeline-factory', {
10
+ providedIn: 'root',
11
+ factory: () => defaultPipelineFactory,
12
+ });
13
+ const NGX_TRANSFORMERS_CONFIG = new InjectionToken('ngx-transformers.config', {
14
+ providedIn: 'root',
15
+ factory: () => ({}),
16
+ });
17
+ /**
18
+ * Sets workspace-wide defaults (device, dtype, extra pipeline options):
19
+ *
20
+ * ```ts
21
+ * bootstrapApplication(App, {
22
+ * providers: [provideTransformers({ device: 'webgpu', dtype: 'q8' })],
23
+ * });
24
+ * ```
25
+ */
26
+ function provideTransformers(config) {
27
+ return makeEnvironmentProviders([{ provide: NGX_TRANSFORMERS_CONFIG, useValue: config }]);
28
+ }
29
+
30
+ /**
31
+ * A lazily-loaded Transformers.js pipeline wrapped in signals.
32
+ *
33
+ * The model is not downloaded until load() or the first run() call, so
34
+ * creating handles is free. All state is exposed as signals and safe to
35
+ * bind in zoneless apps.
36
+ */
37
+ class PipelineHandle {
38
+ request;
39
+ factory;
40
+ config;
41
+ /** idle -> loading -> ready <-> busy; error only on load failure. */
42
+ status = signal('idle', /* @ts-ignore */
43
+ ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
44
+ /** Download progress for the file currently transferring, else null. */
45
+ progress = signal(null, /* @ts-ignore */
46
+ ...(ngDevMode ? [{ debugName: "progress" }] : /* istanbul ignore next */ []));
47
+ error = signal(null, /* @ts-ignore */
48
+ ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
49
+ /** True once the model is usable (including while a run is in flight). */
50
+ ready = computed(() => this.status() === 'ready' || this.status() === 'busy', /* @ts-ignore */
51
+ ...(ngDevMode ? [{ debugName: "ready" }] : /* istanbul ignore next */ []));
52
+ busy = computed(() => this.status() === 'busy' || this.status() === 'loading', /* @ts-ignore */
53
+ ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
54
+ pipe = null;
55
+ loading = null;
56
+ constructor(request, factory, config) {
57
+ this.request = request;
58
+ this.factory = factory;
59
+ this.config = config;
60
+ }
61
+ /** Downloads and initializes the model. Idempotent; retries after error. */
62
+ load() {
63
+ if (this.pipe)
64
+ return Promise.resolve();
65
+ this.loading ??= this.doLoad().finally(() => (this.loading = null));
66
+ return this.loading;
67
+ }
68
+ /** Runs the pipeline, loading the model first if needed. */
69
+ async run(input, runOptions) {
70
+ await this.load();
71
+ this.status.set('busy');
72
+ try {
73
+ return (await this.pipe(input, runOptions));
74
+ }
75
+ finally {
76
+ // A failed run leaves the model intact - back to ready either way.
77
+ this.status.set('ready');
78
+ }
79
+ }
80
+ /** Frees the model. The handle can be loaded again afterwards. */
81
+ async dispose() {
82
+ const pipe = this.pipe;
83
+ this.pipe = null;
84
+ this.status.set('idle');
85
+ this.progress.set(null);
86
+ await pipe?.dispose?.();
87
+ }
88
+ async doLoad() {
89
+ this.status.set('loading');
90
+ this.error.set(null);
91
+ const options = {
92
+ ...this.config.pipelineOptions,
93
+ ...this.request.options,
94
+ progress_callback: (event) => this.onProgress(event),
95
+ };
96
+ const device = this.request.device ?? this.config.device;
97
+ const dtype = this.request.dtype ?? this.config.dtype;
98
+ if (device && device !== 'auto')
99
+ options['device'] = device;
100
+ if (dtype)
101
+ options['dtype'] = dtype;
102
+ try {
103
+ this.pipe = await this.factory(this.request.task, this.request.model, options);
104
+ this.progress.set(null);
105
+ this.status.set('ready');
106
+ }
107
+ catch (err) {
108
+ this.error.set(err);
109
+ this.status.set('error');
110
+ throw err;
111
+ }
112
+ }
113
+ onProgress(event) {
114
+ if (event.status !== 'progress' || !event.file)
115
+ return;
116
+ this.progress.set({
117
+ file: event.file,
118
+ progress: Math.round(event.progress ?? 0),
119
+ loadedBytes: event.loaded ?? 0,
120
+ totalBytes: event.total ?? 0,
121
+ });
122
+ }
123
+ }
124
+ /**
125
+ * Creates a PipelineHandle in an injection context (constructor, field
126
+ * initializer, or runInInjectionContext). The handle is disposed with the
127
+ * surrounding component/injector.
128
+ */
129
+ function createPipeline(request) {
130
+ const handle = new PipelineHandle(request, inject(PIPELINE_FACTORY), inject(NGX_TRANSFORMERS_CONFIG));
131
+ inject(DestroyRef, { optional: true })?.onDestroy(() => void handle.dispose());
132
+ return handle;
133
+ }
134
+
135
+ const DEFAULT_TEXT_CLASSIFICATION_MODEL = 'Xenova/distilbert-base-uncased-finetuned-sst-2-english';
136
+ /**
137
+ * Text classification (sentiment by default). The default model is
138
+ * DistilBERT SST-2 (~65 MB, q8) - swap `model` for any Transformers.js
139
+ * compatible text-classification checkpoint.
140
+ */
141
+ class TextClassifier extends PipelineHandle {
142
+ /** Classifies one text; resolves to labels sorted by score (top first). */
143
+ async classify(text, topK = 1) {
144
+ const out = await this.run(text, { top_k: topK });
145
+ // Single input: transformers.js returns a flat array of {label, score}.
146
+ const flat = (Array.isArray(out[0]) ? out[0] : out);
147
+ return [...flat].sort((a, b) => b.score - a.score);
148
+ }
149
+ }
150
+ /** Creates a TextClassifier in an injection context. */
151
+ function createTextClassifier(options = {}) {
152
+ const classifier = new TextClassifier({ task: 'text-classification', model: DEFAULT_TEXT_CLASSIFICATION_MODEL, ...options }, inject(PIPELINE_FACTORY), inject(NGX_TRANSFORMERS_CONFIG));
153
+ inject(DestroyRef, { optional: true })?.onDestroy(() => void classifier.dispose());
154
+ return classifier;
155
+ }
156
+
157
+ const DEFAULT_EMBEDDING_MODEL = 'Xenova/all-MiniLM-L6-v2';
158
+ /** Cosine similarity of two vectors. For normalized vectors this is the dot product. */
159
+ function cosineSimilarity(a, b) {
160
+ if (a.length !== b.length || a.length === 0) {
161
+ throw new Error(`cosineSimilarity: incompatible lengths ${a.length} and ${b.length}`);
162
+ }
163
+ let dot = 0;
164
+ let normA = 0;
165
+ let normB = 0;
166
+ for (let i = 0; i < a.length; i++) {
167
+ dot += a[i] * b[i];
168
+ normA += a[i] * a[i];
169
+ normB += b[i] * b[i];
170
+ }
171
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
172
+ return denom === 0 ? 0 : dot / denom;
173
+ }
174
+ /**
175
+ * Sentence embeddings via feature-extraction (all-MiniLM-L6-v2 by default,
176
+ * ~23 MB q8). Outputs mean-pooled, L2-normalized vectors ready for cosine
177
+ * similarity / semantic search.
178
+ */
179
+ class TextEmbedder extends PipelineHandle {
180
+ /** Embeds one or many texts; always resolves to one vector per text. */
181
+ async embed(texts) {
182
+ const input = Array.isArray(texts) ? texts : [texts];
183
+ if (input.length === 0)
184
+ return [];
185
+ const out = await this.run(input, { pooling: 'mean', normalize: true });
186
+ return toRows(out);
187
+ }
188
+ /** Cosine similarity of two texts in [-1, 1]. */
189
+ async similarity(a, b) {
190
+ const [va, vb] = await this.embed([a, b]);
191
+ return cosineSimilarity(va, vb);
192
+ }
193
+ /** Ranks documents against a query, most similar first. */
194
+ async rank(query, documents) {
195
+ if (documents.length === 0)
196
+ return [];
197
+ const [queryVec, ...docVecs] = await this.embed([query, ...documents]);
198
+ return docVecs
199
+ .map((vec, index) => ({ text: documents[index], score: cosineSimilarity(queryVec, vec), index }))
200
+ .sort((a, b) => b.score - a.score);
201
+ }
202
+ }
203
+ function toRows(out) {
204
+ if (typeof out.tolist === 'function')
205
+ return out.tolist();
206
+ const [rows, cols] = out.dims.length === 2 ? out.dims : [1, out.dims[out.dims.length - 1]];
207
+ const data = out.data;
208
+ const result = [];
209
+ for (let r = 0; r < rows; r++) {
210
+ result.push(Array.from({ length: cols }, (_, c) => data[r * cols + c]));
211
+ }
212
+ return result;
213
+ }
214
+ /** Creates a TextEmbedder in an injection context. */
215
+ function createTextEmbedder(options = {}) {
216
+ const embedder = new TextEmbedder({ task: 'feature-extraction', model: DEFAULT_EMBEDDING_MODEL, ...options }, inject(PIPELINE_FACTORY), inject(NGX_TRANSFORMERS_CONFIG));
217
+ inject(DestroyRef, { optional: true })?.onDestroy(() => void embedder.dispose());
218
+ return embedder;
219
+ }
220
+
221
+ /**
222
+ * Drop-in status line for a PipelineHandle: shows model download progress
223
+ * while loading, then the ready/busy/error state. Themeable via CSS custom
224
+ * properties (--nt-accent, --nt-ink, --nt-muted, --nt-track).
225
+ *
226
+ * ```html
227
+ * <ngx-model-progress [status]="classifier.status()" [progress]="classifier.progress()" />
228
+ * ```
229
+ */
230
+ class ModelProgressComponent {
231
+ status = input.required(/* @ts-ignore */
232
+ ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
233
+ progress = input(null, /* @ts-ignore */
234
+ ...(ngDevMode ? [{ debugName: "progress" }] : /* istanbul ignore next */ []));
235
+ /** Labels per status; override to localize. */
236
+ labels = input({}, /* @ts-ignore */
237
+ ...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
238
+ label = computed(() => {
239
+ const defaults = {
240
+ idle: 'Model not loaded',
241
+ loading: 'Downloading model',
242
+ ready: 'Model ready',
243
+ busy: 'Running',
244
+ error: 'Failed to load model',
245
+ };
246
+ return this.labels()[this.status()] ?? defaults[this.status()];
247
+ }, /* @ts-ignore */
248
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
249
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ModelProgressComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
250
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: ModelProgressComponent, isStandalone: true, selector: "ngx-model-progress", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null }, progress: { classPropertyName: "progress", publicName: "progress", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
251
+ <div class="nt-row" [class.nt-error]="status() === 'error'" role="status">
252
+ <span class="nt-dot" [attr.data-status]="status()"></span>
253
+ <span class="nt-label">{{ label() }}</span>
254
+ @if (status() === 'loading' && progress(); as p) {
255
+ <span class="nt-file">{{ p.file }}</span>
256
+ <div class="nt-track" role="progressbar" [attr.aria-valuenow]="p.progress" aria-valuemin="0" aria-valuemax="100">
257
+ <div class="nt-fill" [style.width.%]="p.progress"></div>
258
+ </div>
259
+ <span class="nt-pct">{{ p.progress }}%</span>
260
+ }
261
+ </div>
262
+ `, isInline: true, styles: [":host{--nt-accent: #f59e0b;--nt-ink: #1e293b;--nt-muted: #64748b;--nt-track: #e2e8f0;display:block;font-size:12.5px;color:var(--nt-ink)}.nt-row{display:flex;align-items:center;gap:8px}.nt-dot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--nt-muted)}.nt-dot[data-status=loading]{background:var(--nt-accent);animation:nt-blink 1s ease-in-out infinite}.nt-dot[data-status=ready]{background:#22c55e}.nt-dot[data-status=busy]{background:#3b82f6;animation:nt-blink .7s ease-in-out infinite}.nt-dot[data-status=error]{background:#dc2626}.nt-label{font-weight:600}.nt-file{color:var(--nt-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px}.nt-track{flex:1;min-width:60px;height:6px;border-radius:3px;background:var(--nt-track);overflow:hidden}.nt-fill{height:100%;background:var(--nt-accent);border-radius:3px;transition:width .12s linear}.nt-pct{font-variant-numeric:tabular-nums;color:var(--nt-muted)}.nt-error .nt-label{color:#dc2626}@keyframes nt-blink{0%,to{opacity:1}50%{opacity:.35}}@media(prefers-reduced-motion:reduce){.nt-dot{animation:none}.nt-fill{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
263
+ }
264
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ModelProgressComponent, decorators: [{
265
+ type: Component,
266
+ args: [{ selector: 'ngx-model-progress', changeDetection: ChangeDetectionStrategy.OnPush, template: `
267
+ <div class="nt-row" [class.nt-error]="status() === 'error'" role="status">
268
+ <span class="nt-dot" [attr.data-status]="status()"></span>
269
+ <span class="nt-label">{{ label() }}</span>
270
+ @if (status() === 'loading' && progress(); as p) {
271
+ <span class="nt-file">{{ p.file }}</span>
272
+ <div class="nt-track" role="progressbar" [attr.aria-valuenow]="p.progress" aria-valuemin="0" aria-valuemax="100">
273
+ <div class="nt-fill" [style.width.%]="p.progress"></div>
274
+ </div>
275
+ <span class="nt-pct">{{ p.progress }}%</span>
276
+ }
277
+ </div>
278
+ `, styles: [":host{--nt-accent: #f59e0b;--nt-ink: #1e293b;--nt-muted: #64748b;--nt-track: #e2e8f0;display:block;font-size:12.5px;color:var(--nt-ink)}.nt-row{display:flex;align-items:center;gap:8px}.nt-dot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--nt-muted)}.nt-dot[data-status=loading]{background:var(--nt-accent);animation:nt-blink 1s ease-in-out infinite}.nt-dot[data-status=ready]{background:#22c55e}.nt-dot[data-status=busy]{background:#3b82f6;animation:nt-blink .7s ease-in-out infinite}.nt-dot[data-status=error]{background:#dc2626}.nt-label{font-weight:600}.nt-file{color:var(--nt-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px}.nt-track{flex:1;min-width:60px;height:6px;border-radius:3px;background:var(--nt-track);overflow:hidden}.nt-fill{height:100%;background:var(--nt-accent);border-radius:3px;transition:width .12s linear}.nt-pct{font-variant-numeric:tabular-nums;color:var(--nt-muted)}.nt-error .nt-label{color:#dc2626}@keyframes nt-blink{0%,to{opacity:1}50%{opacity:.35}}@media(prefers-reduced-motion:reduce){.nt-dot{animation:none}.nt-fill{transition:none}}\n"] }]
279
+ }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], progress: [{ type: i0.Input, args: [{ isSignal: true, alias: "progress", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }] } });
280
+
281
+ /*
282
+ * Public API Surface of ngx-transformers
283
+ */
284
+
285
+ /**
286
+ * Generated bundle index. Do not edit.
287
+ */
288
+
289
+ export { DEFAULT_EMBEDDING_MODEL, DEFAULT_TEXT_CLASSIFICATION_MODEL, ModelProgressComponent, NGX_TRANSFORMERS_CONFIG, PIPELINE_FACTORY, PipelineHandle, TextClassifier, TextEmbedder, cosineSimilarity, createPipeline, createTextClassifier, createTextEmbedder, provideTransformers };
290
+ //# sourceMappingURL=ngx-transformers.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-transformers.mjs","sources":["../../../projects/ngx-transformers/src/lib/transformers.providers.ts","../../../projects/ngx-transformers/src/lib/pipeline.ts","../../../projects/ngx-transformers/src/lib/text-classifier.ts","../../../projects/ngx-transformers/src/lib/text-embedder.ts","../../../projects/ngx-transformers/src/lib/model-progress.component.ts","../../../projects/ngx-transformers/src/public-api.ts","../../../projects/ngx-transformers/src/ngx-transformers.ts"],"sourcesContent":["import { EnvironmentProviders, InjectionToken, makeEnvironmentProviders } from '@angular/core';\nimport type { NgxTransformersConfig } from './transformers.models';\n\n/**\n * The callable returned by Transformers.js pipeline(), reduced to the\n * surface this library relies on.\n */\nexport type PipelineLike = ((input: unknown, options?: Record<string, unknown>) => Promise<unknown>) & {\n dispose?: () => Promise<void>;\n};\n\n/**\n * Creates a pipeline for a task. The default implementation lazy-imports\n * @huggingface/transformers; tests and SSR shims can provide their own.\n */\nexport type PipelineFactory = (\n task: string,\n model: string | undefined,\n options: Record<string, unknown>,\n) => Promise<PipelineLike>;\n\nconst defaultPipelineFactory: PipelineFactory = async (task, model, options) => {\n const { pipeline } = await import('@huggingface/transformers');\n const pipe = await (pipeline as (t: string, m?: string, o?: object) => Promise<unknown>)(\n task,\n model,\n options,\n );\n return pipe as PipelineLike;\n};\n\nexport const PIPELINE_FACTORY = new InjectionToken<PipelineFactory>('ngx-transformers.pipeline-factory', {\n providedIn: 'root',\n factory: () => defaultPipelineFactory,\n});\n\nexport const NGX_TRANSFORMERS_CONFIG = new InjectionToken<NgxTransformersConfig>('ngx-transformers.config', {\n providedIn: 'root',\n factory: () => ({}),\n});\n\n/**\n * Sets workspace-wide defaults (device, dtype, extra pipeline options):\n *\n * ```ts\n * bootstrapApplication(App, {\n * providers: [provideTransformers({ device: 'webgpu', dtype: 'q8' })],\n * });\n * ```\n */\nexport function provideTransformers(config: NgxTransformersConfig): EnvironmentProviders {\n return makeEnvironmentProviders([{ provide: NGX_TRANSFORMERS_CONFIG, useValue: config }]);\n}\n","import { DestroyRef, computed, inject, signal } from '@angular/core';\nimport type { ModelProgress, NgxTransformersConfig, PipelineRequest, PipelineStatus } from './transformers.models';\nimport { NGX_TRANSFORMERS_CONFIG, PIPELINE_FACTORY, type PipelineFactory, type PipelineLike } from './transformers.providers';\n\n/** Shape of Transformers.js progress_callback events (subset we consume). */\ninterface RawProgressEvent {\n status?: string;\n file?: string;\n progress?: number;\n loaded?: number;\n total?: number;\n}\n\n/**\n * A lazily-loaded Transformers.js pipeline wrapped in signals.\n *\n * The model is not downloaded until load() or the first run() call, so\n * creating handles is free. All state is exposed as signals and safe to\n * bind in zoneless apps.\n */\nexport class PipelineHandle<TIn = unknown, TOut = unknown> {\n /** idle -> loading -> ready <-> busy; error only on load failure. */\n readonly status = signal<PipelineStatus>('idle');\n /** Download progress for the file currently transferring, else null. */\n readonly progress = signal<ModelProgress | null>(null);\n readonly error = signal<unknown>(null);\n /** True once the model is usable (including while a run is in flight). */\n readonly ready = computed(() => this.status() === 'ready' || this.status() === 'busy');\n readonly busy = computed(() => this.status() === 'busy' || this.status() === 'loading');\n\n private pipe: PipelineLike | null = null;\n private loading: Promise<void> | null = null;\n\n constructor(\n private readonly request: PipelineRequest,\n private readonly factory: PipelineFactory,\n private readonly config: NgxTransformersConfig,\n ) {}\n\n /** Downloads and initializes the model. Idempotent; retries after error. */\n load(): Promise<void> {\n if (this.pipe) return Promise.resolve();\n this.loading ??= this.doLoad().finally(() => (this.loading = null));\n return this.loading;\n }\n\n /** Runs the pipeline, loading the model first if needed. */\n async run(input: TIn, runOptions?: Record<string, unknown>): Promise<TOut> {\n await this.load();\n this.status.set('busy');\n try {\n return (await this.pipe!(input, runOptions)) as TOut;\n } finally {\n // A failed run leaves the model intact - back to ready either way.\n this.status.set('ready');\n }\n }\n\n /** Frees the model. The handle can be loaded again afterwards. */\n async dispose(): Promise<void> {\n const pipe = this.pipe;\n this.pipe = null;\n this.status.set('idle');\n this.progress.set(null);\n await pipe?.dispose?.();\n }\n\n private async doLoad(): Promise<void> {\n this.status.set('loading');\n this.error.set(null);\n const options: Record<string, unknown> = {\n ...this.config.pipelineOptions,\n ...this.request.options,\n progress_callback: (event: RawProgressEvent) => this.onProgress(event),\n };\n const device = this.request.device ?? this.config.device;\n const dtype = this.request.dtype ?? this.config.dtype;\n if (device && device !== 'auto') options['device'] = device;\n if (dtype) options['dtype'] = dtype;\n\n try {\n this.pipe = await this.factory(this.request.task, this.request.model, options);\n this.progress.set(null);\n this.status.set('ready');\n } catch (err) {\n this.error.set(err);\n this.status.set('error');\n throw err;\n }\n }\n\n private onProgress(event: RawProgressEvent): void {\n if (event.status !== 'progress' || !event.file) return;\n this.progress.set({\n file: event.file,\n progress: Math.round(event.progress ?? 0),\n loadedBytes: event.loaded ?? 0,\n totalBytes: event.total ?? 0,\n });\n }\n}\n\n/**\n * Creates a PipelineHandle in an injection context (constructor, field\n * initializer, or runInInjectionContext). The handle is disposed with the\n * surrounding component/injector.\n */\nexport function createPipeline<TIn = unknown, TOut = unknown>(request: PipelineRequest): PipelineHandle<TIn, TOut> {\n const handle = new PipelineHandle<TIn, TOut>(\n request,\n inject(PIPELINE_FACTORY),\n inject(NGX_TRANSFORMERS_CONFIG),\n );\n inject(DestroyRef, { optional: true })?.onDestroy(() => void handle.dispose());\n return handle;\n}\n","import { DestroyRef, inject } from '@angular/core';\nimport { PipelineHandle } from './pipeline';\nimport type { ClassificationResult, PipelineRequest } from './transformers.models';\nimport { NGX_TRANSFORMERS_CONFIG, PIPELINE_FACTORY } from './transformers.providers';\n\nexport const DEFAULT_TEXT_CLASSIFICATION_MODEL = 'Xenova/distilbert-base-uncased-finetuned-sst-2-english';\n\n/**\n * Text classification (sentiment by default). The default model is\n * DistilBERT SST-2 (~65 MB, q8) - swap `model` for any Transformers.js\n * compatible text-classification checkpoint.\n */\nexport class TextClassifier extends PipelineHandle<string, ClassificationResult[] | ClassificationResult[][]> {\n /** Classifies one text; resolves to labels sorted by score (top first). */\n async classify(text: string, topK = 1): Promise<ClassificationResult[]> {\n const out = await this.run(text, { top_k: topK });\n // Single input: transformers.js returns a flat array of {label, score}.\n const flat = (Array.isArray(out[0]) ? out[0] : out) as ClassificationResult[];\n return [...flat].sort((a, b) => b.score - a.score);\n }\n}\n\n/** Creates a TextClassifier in an injection context. */\nexport function createTextClassifier(options: Partial<Omit<PipelineRequest, 'task'>> = {}): TextClassifier {\n const classifier = new TextClassifier(\n { task: 'text-classification', model: DEFAULT_TEXT_CLASSIFICATION_MODEL, ...options },\n inject(PIPELINE_FACTORY),\n inject(NGX_TRANSFORMERS_CONFIG),\n );\n inject(DestroyRef, { optional: true })?.onDestroy(() => void classifier.dispose());\n return classifier;\n}\n","import { DestroyRef, inject } from '@angular/core';\nimport { PipelineHandle } from './pipeline';\nimport type { PipelineRequest, RankedResult } from './transformers.models';\nimport { NGX_TRANSFORMERS_CONFIG, PIPELINE_FACTORY } from './transformers.providers';\n\nexport const DEFAULT_EMBEDDING_MODEL = 'Xenova/all-MiniLM-L6-v2';\n\n/** Tensor-ish output of feature-extraction: enough shape to read rows. */\ninterface TensorLike {\n dims: number[];\n data: ArrayLike<number>;\n tolist?: () => number[][];\n}\n\n/** Cosine similarity of two vectors. For normalized vectors this is the dot product. */\nexport function cosineSimilarity(a: readonly number[], b: readonly number[]): number {\n if (a.length !== b.length || a.length === 0) {\n throw new Error(`cosineSimilarity: incompatible lengths ${a.length} and ${b.length}`);\n }\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n return denom === 0 ? 0 : dot / denom;\n}\n\n/**\n * Sentence embeddings via feature-extraction (all-MiniLM-L6-v2 by default,\n * ~23 MB q8). Outputs mean-pooled, L2-normalized vectors ready for cosine\n * similarity / semantic search.\n */\nexport class TextEmbedder extends PipelineHandle<string | string[], TensorLike> {\n /** Embeds one or many texts; always resolves to one vector per text. */\n async embed(texts: string | string[]): Promise<number[][]> {\n const input = Array.isArray(texts) ? texts : [texts];\n if (input.length === 0) return [];\n const out = await this.run(input, { pooling: 'mean', normalize: true });\n return toRows(out);\n }\n\n /** Cosine similarity of two texts in [-1, 1]. */\n async similarity(a: string, b: string): Promise<number> {\n const [va, vb] = await this.embed([a, b]);\n return cosineSimilarity(va, vb);\n }\n\n /** Ranks documents against a query, most similar first. */\n async rank(query: string, documents: string[]): Promise<RankedResult[]> {\n if (documents.length === 0) return [];\n const [queryVec, ...docVecs] = await this.embed([query, ...documents]);\n return docVecs\n .map((vec, index) => ({ text: documents[index], score: cosineSimilarity(queryVec, vec), index }))\n .sort((a, b) => b.score - a.score);\n }\n}\n\nfunction toRows(out: TensorLike): number[][] {\n if (typeof out.tolist === 'function') return out.tolist();\n const [rows, cols] = out.dims.length === 2 ? out.dims : [1, out.dims[out.dims.length - 1]];\n const data = out.data;\n const result: number[][] = [];\n for (let r = 0; r < rows; r++) {\n result.push(Array.from({ length: cols }, (_, c) => data[r * cols + c]));\n }\n return result;\n}\n\n/** Creates a TextEmbedder in an injection context. */\nexport function createTextEmbedder(options: Partial<Omit<PipelineRequest, 'task'>> = {}): TextEmbedder {\n const embedder = new TextEmbedder(\n { task: 'feature-extraction', model: DEFAULT_EMBEDDING_MODEL, ...options },\n inject(PIPELINE_FACTORY),\n inject(NGX_TRANSFORMERS_CONFIG),\n );\n inject(DestroyRef, { optional: true })?.onDestroy(() => void embedder.dispose());\n return embedder;\n}\n","import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport type { ModelProgress, PipelineStatus } from './transformers.models';\n\n/**\n * Drop-in status line for a PipelineHandle: shows model download progress\n * while loading, then the ready/busy/error state. Themeable via CSS custom\n * properties (--nt-accent, --nt-ink, --nt-muted, --nt-track).\n *\n * ```html\n * <ngx-model-progress [status]=\"classifier.status()\" [progress]=\"classifier.progress()\" />\n * ```\n */\n@Component({\n selector: 'ngx-model-progress',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"nt-row\" [class.nt-error]=\"status() === 'error'\" role=\"status\">\n <span class=\"nt-dot\" [attr.data-status]=\"status()\"></span>\n <span class=\"nt-label\">{{ label() }}</span>\n @if (status() === 'loading' && progress(); as p) {\n <span class=\"nt-file\">{{ p.file }}</span>\n <div class=\"nt-track\" role=\"progressbar\" [attr.aria-valuenow]=\"p.progress\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <div class=\"nt-fill\" [style.width.%]=\"p.progress\"></div>\n </div>\n <span class=\"nt-pct\">{{ p.progress }}%</span>\n }\n </div>\n `,\n styles: `\n :host {\n --nt-accent: #f59e0b;\n --nt-ink: #1e293b;\n --nt-muted: #64748b;\n --nt-track: #e2e8f0;\n display: block;\n font-size: 12.5px;\n color: var(--nt-ink);\n }\n .nt-row { display: flex; align-items: center; gap: 8px; }\n .nt-dot {\n width: 8px; height: 8px; border-radius: 50%; flex: none;\n background: var(--nt-muted);\n }\n .nt-dot[data-status='loading'] { background: var(--nt-accent); animation: nt-blink 1s ease-in-out infinite; }\n .nt-dot[data-status='ready'] { background: #22c55e; }\n .nt-dot[data-status='busy'] { background: #3b82f6; animation: nt-blink 0.7s ease-in-out infinite; }\n .nt-dot[data-status='error'] { background: #dc2626; }\n .nt-label { font-weight: 600; }\n .nt-file { color: var(--nt-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 180px; }\n .nt-track {\n flex: 1; min-width: 60px; height: 6px; border-radius: 3px;\n background: var(--nt-track); overflow: hidden;\n }\n .nt-fill { height: 100%; background: var(--nt-accent); border-radius: 3px; transition: width 120ms linear; }\n .nt-pct { font-variant-numeric: tabular-nums; color: var(--nt-muted); }\n .nt-error .nt-label { color: #dc2626; }\n @keyframes nt-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }\n @media (prefers-reduced-motion: reduce) {\n .nt-dot { animation: none; }\n .nt-fill { transition: none; }\n }\n `,\n})\nexport class ModelProgressComponent {\n status = input.required<PipelineStatus>();\n progress = input<ModelProgress | null>(null);\n /** Labels per status; override to localize. */\n labels = input<Partial<Record<PipelineStatus, string>>>({});\n\n readonly label = computed(() => {\n const defaults: Record<PipelineStatus, string> = {\n idle: 'Model not loaded',\n loading: 'Downloading model',\n ready: 'Model ready',\n busy: 'Running',\n error: 'Failed to load model',\n };\n return this.labels()[this.status()] ?? defaults[this.status()];\n });\n}\n","/*\n * Public API Surface of ngx-transformers\n */\n\nexport * from './lib/transformers.models';\nexport * from './lib/transformers.providers';\nexport * from './lib/pipeline';\nexport * from './lib/text-classifier';\nexport * from './lib/text-embedder';\nexport * from './lib/model-progress.component';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "ngx-transformers",
3
+ "version": "0.1.0",
4
+ "description": "Run Hugging Face Transformers.js models in Angular - on-device ML with a signals API: text classification, embeddings, semantic similarity. No server, no API key.",
5
+ "keywords": [
6
+ "angular",
7
+ "transformers",
8
+ "transformers.js",
9
+ "machine-learning",
10
+ "onnx",
11
+ "webgpu",
12
+ "wasm",
13
+ "huggingface",
14
+ "embeddings",
15
+ "sentiment-analysis",
16
+ "on-device",
17
+ "signals"
18
+ ],
19
+ "license": "MIT",
20
+ "author": "Muzaffar Qosimov",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/qwertymuzaffar/ngx-transformers.git"
24
+ },
25
+ "peerDependencies": {
26
+ "@angular/common": "^22.1.0",
27
+ "@angular/core": "^22.1.0",
28
+ "@huggingface/transformers": "^4.0.0"
29
+ },
30
+ "dependencies": {
31
+ "tslib": "^2.3.0"
32
+ },
33
+ "sideEffects": false,
34
+ "module": "fesm2022/ngx-transformers.mjs",
35
+ "typings": "types/ngx-transformers.d.ts",
36
+ "exports": {
37
+ "./package.json": {
38
+ "default": "./package.json"
39
+ },
40
+ ".": {
41
+ "types": "./types/ngx-transformers.d.ts",
42
+ "default": "./fesm2022/ngx-transformers.mjs"
43
+ }
44
+ },
45
+ "type": "module"
46
+ }
@@ -0,0 +1,167 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { InjectionToken, EnvironmentProviders } from '@angular/core';
3
+
4
+ /** Lifecycle of a model pipeline. `error` is terminal until load() is retried. */
5
+ type PipelineStatus = 'idle' | 'loading' | 'ready' | 'busy' | 'error';
6
+ /** Download progress for one model file, mapped from Transformers.js callbacks. */
7
+ interface ModelProgress {
8
+ /** File currently downloading, e.g. "onnx/model_quantized.onnx". */
9
+ file: string;
10
+ /** 0-100 for the current file. */
11
+ progress: number;
12
+ loadedBytes: number;
13
+ totalBytes: number;
14
+ }
15
+ type TransformersDevice = 'wasm' | 'webgpu' | 'auto';
16
+ type TransformersDtype = 'fp32' | 'fp16' | 'q8' | 'q4';
17
+ /** Global defaults applied to every pipeline; see provideTransformers(). */
18
+ interface NgxTransformersConfig {
19
+ device?: TransformersDevice;
20
+ dtype?: TransformersDtype;
21
+ /** Extra options forwarded verbatim to every pipeline() call. */
22
+ pipelineOptions?: Record<string, unknown>;
23
+ }
24
+ /** Per-pipeline request; wins over the global config where both are set. */
25
+ interface PipelineRequest {
26
+ task: string;
27
+ /** Hugging Face model id; each task wrapper provides a sensible default. */
28
+ model?: string;
29
+ device?: TransformersDevice;
30
+ dtype?: TransformersDtype;
31
+ options?: Record<string, unknown>;
32
+ }
33
+ interface ClassificationResult {
34
+ label: string;
35
+ score: number;
36
+ }
37
+ /** One scored document from TextEmbedder.rank(). */
38
+ interface RankedResult {
39
+ text: string;
40
+ /** Cosine similarity in [-1, 1]; higher is more similar. */
41
+ score: number;
42
+ /** Index of the document in the input list. */
43
+ index: number;
44
+ }
45
+
46
+ /**
47
+ * The callable returned by Transformers.js pipeline(), reduced to the
48
+ * surface this library relies on.
49
+ */
50
+ type PipelineLike = ((input: unknown, options?: Record<string, unknown>) => Promise<unknown>) & {
51
+ dispose?: () => Promise<void>;
52
+ };
53
+ /**
54
+ * Creates a pipeline for a task. The default implementation lazy-imports
55
+ * @huggingface/transformers; tests and SSR shims can provide their own.
56
+ */
57
+ type PipelineFactory = (task: string, model: string | undefined, options: Record<string, unknown>) => Promise<PipelineLike>;
58
+ declare const PIPELINE_FACTORY: InjectionToken<PipelineFactory>;
59
+ declare const NGX_TRANSFORMERS_CONFIG: InjectionToken<NgxTransformersConfig>;
60
+ /**
61
+ * Sets workspace-wide defaults (device, dtype, extra pipeline options):
62
+ *
63
+ * ```ts
64
+ * bootstrapApplication(App, {
65
+ * providers: [provideTransformers({ device: 'webgpu', dtype: 'q8' })],
66
+ * });
67
+ * ```
68
+ */
69
+ declare function provideTransformers(config: NgxTransformersConfig): EnvironmentProviders;
70
+
71
+ /**
72
+ * A lazily-loaded Transformers.js pipeline wrapped in signals.
73
+ *
74
+ * The model is not downloaded until load() or the first run() call, so
75
+ * creating handles is free. All state is exposed as signals and safe to
76
+ * bind in zoneless apps.
77
+ */
78
+ declare class PipelineHandle<TIn = unknown, TOut = unknown> {
79
+ private readonly request;
80
+ private readonly factory;
81
+ private readonly config;
82
+ /** idle -> loading -> ready <-> busy; error only on load failure. */
83
+ readonly status: _angular_core.WritableSignal<PipelineStatus>;
84
+ /** Download progress for the file currently transferring, else null. */
85
+ readonly progress: _angular_core.WritableSignal<ModelProgress | null>;
86
+ readonly error: _angular_core.WritableSignal<unknown>;
87
+ /** True once the model is usable (including while a run is in flight). */
88
+ readonly ready: _angular_core.Signal<boolean>;
89
+ readonly busy: _angular_core.Signal<boolean>;
90
+ private pipe;
91
+ private loading;
92
+ constructor(request: PipelineRequest, factory: PipelineFactory, config: NgxTransformersConfig);
93
+ /** Downloads and initializes the model. Idempotent; retries after error. */
94
+ load(): Promise<void>;
95
+ /** Runs the pipeline, loading the model first if needed. */
96
+ run(input: TIn, runOptions?: Record<string, unknown>): Promise<TOut>;
97
+ /** Frees the model. The handle can be loaded again afterwards. */
98
+ dispose(): Promise<void>;
99
+ private doLoad;
100
+ private onProgress;
101
+ }
102
+ /**
103
+ * Creates a PipelineHandle in an injection context (constructor, field
104
+ * initializer, or runInInjectionContext). The handle is disposed with the
105
+ * surrounding component/injector.
106
+ */
107
+ declare function createPipeline<TIn = unknown, TOut = unknown>(request: PipelineRequest): PipelineHandle<TIn, TOut>;
108
+
109
+ declare const DEFAULT_TEXT_CLASSIFICATION_MODEL = "Xenova/distilbert-base-uncased-finetuned-sst-2-english";
110
+ /**
111
+ * Text classification (sentiment by default). The default model is
112
+ * DistilBERT SST-2 (~65 MB, q8) - swap `model` for any Transformers.js
113
+ * compatible text-classification checkpoint.
114
+ */
115
+ declare class TextClassifier extends PipelineHandle<string, ClassificationResult[] | ClassificationResult[][]> {
116
+ /** Classifies one text; resolves to labels sorted by score (top first). */
117
+ classify(text: string, topK?: number): Promise<ClassificationResult[]>;
118
+ }
119
+ /** Creates a TextClassifier in an injection context. */
120
+ declare function createTextClassifier(options?: Partial<Omit<PipelineRequest, 'task'>>): TextClassifier;
121
+
122
+ declare const DEFAULT_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2";
123
+ /** Tensor-ish output of feature-extraction: enough shape to read rows. */
124
+ interface TensorLike {
125
+ dims: number[];
126
+ data: ArrayLike<number>;
127
+ tolist?: () => number[][];
128
+ }
129
+ /** Cosine similarity of two vectors. For normalized vectors this is the dot product. */
130
+ declare function cosineSimilarity(a: readonly number[], b: readonly number[]): number;
131
+ /**
132
+ * Sentence embeddings via feature-extraction (all-MiniLM-L6-v2 by default,
133
+ * ~23 MB q8). Outputs mean-pooled, L2-normalized vectors ready for cosine
134
+ * similarity / semantic search.
135
+ */
136
+ declare class TextEmbedder extends PipelineHandle<string | string[], TensorLike> {
137
+ /** Embeds one or many texts; always resolves to one vector per text. */
138
+ embed(texts: string | string[]): Promise<number[][]>;
139
+ /** Cosine similarity of two texts in [-1, 1]. */
140
+ similarity(a: string, b: string): Promise<number>;
141
+ /** Ranks documents against a query, most similar first. */
142
+ rank(query: string, documents: string[]): Promise<RankedResult[]>;
143
+ }
144
+ /** Creates a TextEmbedder in an injection context. */
145
+ declare function createTextEmbedder(options?: Partial<Omit<PipelineRequest, 'task'>>): TextEmbedder;
146
+
147
+ /**
148
+ * Drop-in status line for a PipelineHandle: shows model download progress
149
+ * while loading, then the ready/busy/error state. Themeable via CSS custom
150
+ * properties (--nt-accent, --nt-ink, --nt-muted, --nt-track).
151
+ *
152
+ * ```html
153
+ * <ngx-model-progress [status]="classifier.status()" [progress]="classifier.progress()" />
154
+ * ```
155
+ */
156
+ declare class ModelProgressComponent {
157
+ status: _angular_core.InputSignal<PipelineStatus>;
158
+ progress: _angular_core.InputSignal<ModelProgress | null>;
159
+ /** Labels per status; override to localize. */
160
+ labels: _angular_core.InputSignal<Partial<Record<PipelineStatus, string>>>;
161
+ readonly label: _angular_core.Signal<string>;
162
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<ModelProgressComponent, never>;
163
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<ModelProgressComponent, "ngx-model-progress", never, { "status": { "alias": "status"; "required": true; "isSignal": true; }; "progress": { "alias": "progress"; "required": false; "isSignal": true; }; "labels": { "alias": "labels"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
164
+ }
165
+
166
+ export { DEFAULT_EMBEDDING_MODEL, DEFAULT_TEXT_CLASSIFICATION_MODEL, ModelProgressComponent, NGX_TRANSFORMERS_CONFIG, PIPELINE_FACTORY, PipelineHandle, TextClassifier, TextEmbedder, cosineSimilarity, createPipeline, createTextClassifier, createTextEmbedder, provideTransformers };
167
+ export type { ClassificationResult, ModelProgress, NgxTransformersConfig, PipelineFactory, PipelineLike, PipelineRequest, PipelineStatus, RankedResult, TransformersDevice, TransformersDtype };