ngx-transformers 0.1.0 → 0.2.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 CHANGED
@@ -1,64 +1,169 @@
1
- # NgxTransformers
1
+ # ngx-transformers
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 22.1.0.
3
+ [![npm version](https://img.shields.io/npm/v/ngx-transformers)](https://www.npmjs.com/package/ngx-transformers)
4
+ [![CI](https://github.com/qwertymuzaffar/ngx-transformers/actions/workflows/ci.yml/badge.svg)](https://github.com/qwertymuzaffar/ngx-transformers/actions/workflows/ci.yml)
5
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
6
 
5
- ## Code scaffolding
7
+ Run Hugging Face [Transformers.js](https://github.com/huggingface/transformers.js) models in Angular - **on-device ML with a signals API**. Text classification, sentence embeddings, semantic search, and Whisper speech-to-text that execute entirely in the browser: no server, no API key, works offline once the model is cached.
6
8
 
7
- Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
9
+ **[Live demo (Storybook)](https://qwertymuzaffar.github.io/ngx-transformers/)** - loads real models in your browser.
8
10
 
9
- ```bash
10
- ng generate component component-name
11
- ```
11
+ ## Why
12
12
 
13
- For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
13
+ Transformers.js has a React tutorial and hooks ecosystem - Angular has nothing. This library closes that gap with idiomatic Angular: lazily-loaded pipelines wrapped in signals, DI-friendly configuration, automatic cleanup with the owning component, and a drop-in progress component for the model download.
14
+
15
+ ## Install
14
16
 
15
17
  ```bash
16
- ng generate --help
18
+ npm i ngx-transformers @huggingface/transformers
19
+ ```
20
+
21
+ `@huggingface/transformers` (v4) is a peer dependency. Angular >= 22.
22
+
23
+ ## Quick start
24
+
25
+ ```ts
26
+ import { Component, signal } from '@angular/core';
27
+ import { createTextClassifier, ModelProgressComponent } from 'ngx-transformers';
28
+
29
+ @Component({
30
+ imports: [ModelProgressComponent],
31
+ template: `
32
+ <textarea #box></textarea>
33
+ <button (click)="analyze(box.value)" [disabled]="classifier.busy()">Analyze</button>
34
+ <ngx-model-progress [status]="classifier.status()" [progress]="classifier.progress()" />
35
+ @if (label(); as l) { <strong>{{ l }}</strong> }
36
+ `,
37
+ })
38
+ export class SentimentComponent {
39
+ readonly classifier = createTextClassifier(); // no download yet - lazy
40
+ readonly label = signal<string | null>(null);
41
+
42
+ async analyze(text: string) {
43
+ const [top] = await this.classifier.classify(text); // downloads model on first call
44
+ this.label.set(`${top.label} ${(top.score * 100).toFixed(1)}%`);
45
+ }
46
+ }
17
47
  ```
18
48
 
19
- ## Building
49
+ The model downloads on the first `classify()` call (with progress reported through the `progress` signal), is cached by the browser, and is disposed automatically when the component is destroyed.
20
50
 
21
- To build the library, run:
51
+ ## Semantic search
22
52
 
23
- ```bash
24
- ng build ngx-transformers
53
+ ```ts
54
+ import { createTextEmbedder } from 'ngx-transformers';
55
+
56
+ readonly embedder = createTextEmbedder(); // all-MiniLM-L6-v2, ~23 MB q8
57
+
58
+ const ranked = await this.embedder.rank('how do I make my app faster?', docs);
59
+ // [{ text: 'Use trackBy and virtual scrolling...', score: 0.28, index: 2 }, ...]
60
+
61
+ const score = await this.embedder.similarity('car', 'automobile'); // ~0.8
62
+ const vectors = await this.embedder.embed(['one', 'two']); // number[][]
25
63
  ```
26
64
 
27
- This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
65
+ ## Speech to text (v0.2)
66
+
67
+ Whisper, fully in the browser - the audio never leaves the device:
28
68
 
29
- ### Publishing the Library
69
+ ```ts
70
+ import { createMicRecorder, createSpeechRecognizer } from 'ngx-transformers';
30
71
 
31
- Once the project is built, you can publish your library by following these steps:
72
+ readonly whisper = createSpeechRecognizer(); // whisper-tiny.en, ~41 MB q4
73
+ readonly mic = createMicRecorder();
32
74
 
33
- 1. Navigate to the `dist` directory:
75
+ // a URL, File/Blob, ArrayBuffer, or 16 kHz Float32Array:
76
+ const { text, chunks } = await this.whisper.transcribe(fileOrUrl, { returnTimestamps: true });
34
77
 
35
- ```bash
36
- cd dist/ngx-transformers
37
- ```
78
+ // dictation:
79
+ async toggle() {
80
+ if (this.mic.recording()) {
81
+ const audio = await this.mic.stop(); // encoded Blob
82
+ const { text } = await this.whisper.transcribe(audio); // decoded + resampled for you
83
+ } else {
84
+ await this.mic.start(); // asks for mic permission
85
+ }
86
+ }
87
+ ```
38
88
 
39
- 2. Run the `npm publish` command to publish your library to the npm registry:
40
- ```bash
41
- npm publish
42
- ```
89
+ `MicRecorder` exposes `recording`, `seconds`, and `error` signals for the UI. `decodeAudio(blob)` is exported separately if you want the 16 kHz mono `Float32Array` yourself.
43
90
 
44
- ## Running unit tests
91
+ > Note: the recognizer defaults to `dtype: 'q4'` - q8 Whisper decoders currently fail on the v4 WASM runtime ([transformers.js#1707](https://github.com/huggingface/transformers.js/issues/1707)). Multilingual checkpoints (e.g. `onnx-community/whisper-tiny`) accept `language` and `task: 'translate'` options.
45
92
 
46
- To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command:
93
+ ## Any pipeline
47
94
 
48
- ```bash
49
- ng test
95
+ `createPipeline()` exposes the full Transformers.js task surface with the same signal lifecycle:
96
+
97
+ ```ts
98
+ import { createPipeline } from 'ngx-transformers';
99
+
100
+ readonly summarizer = createPipeline<string, { summary_text: string }[]>({
101
+ task: 'summarization',
102
+ model: 'Xenova/distilbart-cnn-6-6',
103
+ });
104
+
105
+ const [out] = await this.summarizer.run(longText);
50
106
  ```
51
107
 
52
- ## Running end-to-end tests
108
+ ## Global configuration
53
109
 
54
- For end-to-end (e2e) testing, run:
110
+ ```ts
111
+ import { provideTransformers } from 'ngx-transformers';
55
112
 
56
- ```bash
57
- ng e2e
113
+ bootstrapApplication(App, {
114
+ providers: [provideTransformers({ device: 'webgpu', dtype: 'q8' })],
115
+ });
58
116
  ```
59
117
 
60
- Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
118
+ Per-pipeline `device`/`dtype`/`options` win over the global config.
119
+
120
+ ## API
121
+
122
+ ### Handles
123
+
124
+ | Export | What it is |
125
+ |---|---|
126
+ | `createPipeline(request)` | Generic `PipelineHandle` for any Transformers.js task |
127
+ | `createTextClassifier(options?)` | `TextClassifier` - sentiment/classification, `classify(text, topK?)` |
128
+ | `createTextEmbedder(options?)` | `TextEmbedder` - `embed()`, `similarity()`, `rank()` |
129
+ | `createSpeechRecognizer(options?)` | `SpeechRecognizer` - `transcribe(audio, options?)` with timestamps |
130
+ | `createMicRecorder(deps?)` | `MicRecorder` - mic capture with `recording`/`seconds`/`error` signals |
131
+ | `cosineSimilarity(a, b)` / `decodeAudio(blob)` | Standalone helpers |
132
+
133
+ All `create*` functions must run in an injection context (field initializer, constructor, or `runInInjectionContext`); handles are disposed with the surrounding component.
134
+
135
+ ### PipelineHandle signals
136
+
137
+ | Signal | Type | Meaning |
138
+ |---|---|---|
139
+ | `status` | `'idle' \| 'loading' \| 'ready' \| 'busy' \| 'error'` | Lifecycle; `error` only from a failed load, retryable |
140
+ | `progress` | `ModelProgress \| null` | Download progress: `file`, `progress` (0-100), bytes |
141
+ | `error` | `unknown` | The load error, if any |
142
+ | `ready` / `busy` | `boolean` (computed) | Convenience for buttons and spinners |
143
+
144
+ ### `<ngx-model-progress>`
145
+
146
+ Status line + download bar for any handle. Inputs: `status` (required), `progress`, `labels` (override per-status text). Themeable via `--nt-accent`, `--nt-ink`, `--nt-muted`, `--nt-track`.
147
+
148
+ ## Default models
149
+
150
+ | Wrapper | Model | Size (q8) | License |
151
+ |---|---|---|---|
152
+ | `createTextClassifier` | [Xenova/distilbert-base-uncased-finetuned-sst-2-english](https://huggingface.co/Xenova/distilbert-base-uncased-finetuned-sst-2-english) | ~65 MB | Apache-2.0 |
153
+ | `createTextEmbedder` | [Xenova/all-MiniLM-L6-v2](https://huggingface.co/Xenova/all-MiniLM-L6-v2) | ~23 MB | Apache-2.0 |
154
+ | `createSpeechRecognizer` | [onnx-community/whisper-tiny.en](https://huggingface.co/onnx-community/whisper-tiny.en) | ~41 MB (q4) | Apache-2.0 |
155
+
156
+ Swap any compatible checkpoint via `{ model: '...' }`. Check the license of the model you ship.
157
+
158
+ ## SSR
159
+
160
+ Model loading is browser-only (WASM/WebGPU). Creating handles is safe on the server - nothing downloads until `load()`/`run()` - but call those only in browser code paths.
161
+
162
+ ## Roadmap
163
+
164
+ - Zero-shot classification and translation wrappers
165
+ - WebGPU feature-detection helper
61
166
 
62
- ## Additional Resources
167
+ ## License
63
168
 
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.
169
+ MIT (c) Muzaffar Qosimov
@@ -218,6 +218,184 @@ function createTextEmbedder(options = {}) {
218
218
  return embedder;
219
219
  }
220
220
 
221
+ // onnx-community, not the legacy Xenova export: the old ONNX decoder lacks
222
+ // the quantization scales transformers.js v4's runtime requires.
223
+ const DEFAULT_ASR_MODEL = 'onnx-community/whisper-tiny.en';
224
+ /** Whisper expects 16 kHz mono float PCM. */
225
+ const WHISPER_SAMPLE_RATE = 16000;
226
+ /**
227
+ * Decodes an encoded audio file (wav/mp3/webm/ogg - anything the browser
228
+ * can decode) to 16 kHz mono Float32Array, ready for Whisper. Browser-only:
229
+ * uses AudioContext with a target sample rate, which resamples on decode.
230
+ */
231
+ async function decodeAudio(source, targetSampleRate = WHISPER_SAMPLE_RATE) {
232
+ const AudioContextCtor = globalThis.AudioContext;
233
+ if (!AudioContextCtor) {
234
+ throw new Error('decodeAudio requires a browser AudioContext (not available in this environment).');
235
+ }
236
+ const buffer = source instanceof Blob ? await source.arrayBuffer() : source;
237
+ const ctx = new AudioContextCtor({ sampleRate: targetSampleRate });
238
+ try {
239
+ const decoded = await ctx.decodeAudioData(buffer);
240
+ if (decoded.numberOfChannels === 1)
241
+ return decoded.getChannelData(0);
242
+ // Mix down to mono by averaging channels.
243
+ const left = decoded.getChannelData(0);
244
+ const right = decoded.getChannelData(1);
245
+ const mono = new Float32Array(left.length);
246
+ for (let i = 0; i < left.length; i++)
247
+ mono[i] = (left[i] + right[i]) / 2;
248
+ return mono;
249
+ }
250
+ finally {
251
+ await ctx.close();
252
+ }
253
+ }
254
+ /**
255
+ * Speech-to-text via Whisper. The default checkpoint is whisper-tiny.en
256
+ * (~41 MB q8, English-only); swap `model` for a multilingual or larger one
257
+ * (e.g. Xenova/whisper-small).
258
+ */
259
+ class SpeechRecognizer extends PipelineHandle {
260
+ /**
261
+ * Transcribes audio: a URL, a decoded 16 kHz Float32Array, or an encoded
262
+ * Blob/ArrayBuffer (decoded via decodeAudio first - browser-only).
263
+ */
264
+ async transcribe(audio, options = {}) {
265
+ const input = audio instanceof Blob || audio instanceof ArrayBuffer ? await decodeAudio(audio) : audio;
266
+ const runOptions = {};
267
+ if (options.returnTimestamps !== undefined)
268
+ runOptions['return_timestamps'] = options.returnTimestamps;
269
+ if (options.chunkLengthS !== undefined)
270
+ runOptions['chunk_length_s'] = options.chunkLengthS;
271
+ if (options.strideLengthS !== undefined)
272
+ runOptions['stride_length_s'] = options.strideLengthS;
273
+ if (options.language !== undefined)
274
+ runOptions['language'] = options.language;
275
+ if (options.task !== undefined)
276
+ runOptions['task'] = options.task;
277
+ const raw = await this.run(input, runOptions);
278
+ const first = (Array.isArray(raw) ? raw[0] : raw) ?? {};
279
+ const chunks = first.chunks?.map((c) => ({
280
+ text: c.text,
281
+ start: c.timestamp?.[0] ?? null,
282
+ end: c.timestamp?.[1] ?? null,
283
+ }));
284
+ return { text: (first.text ?? '').trim(), ...(chunks ? { chunks } : {}) };
285
+ }
286
+ }
287
+ /**
288
+ * Creates a SpeechRecognizer in an injection context.
289
+ *
290
+ * dtype defaults to 'q4' (not the library-wide default): q8 Whisper
291
+ * decoders fail session creation on the v4 WASM runtime
292
+ * (huggingface/transformers.js#1707), while q4 loads and runs everywhere.
293
+ */
294
+ function createSpeechRecognizer(options = {}) {
295
+ const recognizer = new SpeechRecognizer({ task: 'automatic-speech-recognition', model: DEFAULT_ASR_MODEL, dtype: 'q4', ...options }, inject(PIPELINE_FACTORY), inject(NGX_TRANSFORMERS_CONFIG));
296
+ inject(DestroyRef, { optional: true })?.onDestroy(() => void recognizer.dispose());
297
+ return recognizer;
298
+ }
299
+
300
+ /**
301
+ * Microphone capture with a signals API. start() asks for mic permission
302
+ * and records; stop() resolves with the encoded audio Blob - pass it to
303
+ * SpeechRecognizer.transcribe(). Browser-only.
304
+ *
305
+ * ```ts
306
+ * readonly mic = createMicRecorder();
307
+ * readonly whisper = createSpeechRecognizer();
308
+ *
309
+ * async toggle() {
310
+ * if (this.mic.recording()) {
311
+ * const audio = await this.mic.stop();
312
+ * const { text } = await this.whisper.transcribe(audio);
313
+ * } else {
314
+ * await this.mic.start();
315
+ * }
316
+ * }
317
+ * ```
318
+ */
319
+ class MicRecorder {
320
+ deps;
321
+ recording = signal(false, /* @ts-ignore */
322
+ ...(ngDevMode ? [{ debugName: "recording" }] : /* istanbul ignore next */ []));
323
+ /** Elapsed recording time in whole seconds. */
324
+ seconds = signal(0, /* @ts-ignore */
325
+ ...(ngDevMode ? [{ debugName: "seconds" }] : /* istanbul ignore next */ []));
326
+ error = signal(null, /* @ts-ignore */
327
+ ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
328
+ stream = null;
329
+ recorder = null;
330
+ chunks = [];
331
+ timer = null;
332
+ constructor(deps = {}) {
333
+ this.deps = deps;
334
+ }
335
+ /** Requests the microphone and starts recording. No-op while recording. */
336
+ async start() {
337
+ if (this.recording())
338
+ return;
339
+ this.error.set(null);
340
+ try {
341
+ this.stream = await (this.deps.getUserMedia
342
+ ? this.deps.getUserMedia()
343
+ : navigator.mediaDevices.getUserMedia({ audio: true }));
344
+ this.recorder = this.deps.createRecorder
345
+ ? this.deps.createRecorder(this.stream)
346
+ : new MediaRecorder(this.stream);
347
+ this.chunks = [];
348
+ this.recorder.ondataavailable = (event) => {
349
+ if (event.data.size > 0)
350
+ this.chunks.push(event.data);
351
+ };
352
+ this.recorder.start();
353
+ this.recording.set(true);
354
+ this.seconds.set(0);
355
+ this.timer = setInterval(() => this.seconds.set(this.seconds() + 1), 1000);
356
+ }
357
+ catch (err) {
358
+ this.error.set(err);
359
+ this.cleanup();
360
+ throw err;
361
+ }
362
+ }
363
+ /** Stops recording and resolves with the captured audio. */
364
+ stop() {
365
+ const recorder = this.recorder;
366
+ if (!recorder || !this.recording()) {
367
+ return Promise.reject(new Error('MicRecorder.stop() called while not recording.'));
368
+ }
369
+ return new Promise((resolve) => {
370
+ recorder.onstop = () => {
371
+ const blob = new Blob(this.chunks, { type: recorder.mimeType || 'audio/webm' });
372
+ this.cleanup();
373
+ resolve(blob);
374
+ };
375
+ recorder.stop();
376
+ });
377
+ }
378
+ /** Stops tracks and timers without resolving audio (e.g. on destroy). */
379
+ dispose() {
380
+ this.cleanup();
381
+ }
382
+ cleanup() {
383
+ if (this.timer !== null)
384
+ clearInterval(this.timer);
385
+ this.timer = null;
386
+ this.stream?.getTracks().forEach((track) => track.stop());
387
+ this.stream = null;
388
+ this.recorder = null;
389
+ this.recording.set(false);
390
+ }
391
+ }
392
+ /** Creates a MicRecorder in an injection context; disposed with the component. */
393
+ function createMicRecorder(deps = {}) {
394
+ const recorder = new MicRecorder(deps);
395
+ inject(DestroyRef, { optional: true })?.onDestroy(() => recorder.dispose());
396
+ return recorder;
397
+ }
398
+
221
399
  /**
222
400
  * Drop-in status line for a PipelineHandle: shows model download progress
223
401
  * while loading, then the ready/busy/error state. Themeable via CSS custom
@@ -286,5 +464,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
286
464
  * Generated bundle index. Do not edit.
287
465
  */
288
466
 
289
- export { DEFAULT_EMBEDDING_MODEL, DEFAULT_TEXT_CLASSIFICATION_MODEL, ModelProgressComponent, NGX_TRANSFORMERS_CONFIG, PIPELINE_FACTORY, PipelineHandle, TextClassifier, TextEmbedder, cosineSimilarity, createPipeline, createTextClassifier, createTextEmbedder, provideTransformers };
467
+ export { DEFAULT_ASR_MODEL, DEFAULT_EMBEDDING_MODEL, DEFAULT_TEXT_CLASSIFICATION_MODEL, MicRecorder, ModelProgressComponent, NGX_TRANSFORMERS_CONFIG, PIPELINE_FACTORY, PipelineHandle, SpeechRecognizer, TextClassifier, TextEmbedder, WHISPER_SAMPLE_RATE, cosineSimilarity, createMicRecorder, createPipeline, createSpeechRecognizer, createTextClassifier, createTextEmbedder, decodeAudio, provideTransformers };
290
468
  //# sourceMappingURL=ngx-transformers.mjs.map
@@ -1 +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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"}
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/speech-recognizer.ts","../../../projects/ngx-transformers/src/lib/mic-recorder.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 { DestroyRef, inject } from '@angular/core';\nimport { PipelineHandle } from './pipeline';\nimport type { PipelineRequest, TranscribeOptions, Transcription, TranscriptionChunk } from './transformers.models';\nimport { NGX_TRANSFORMERS_CONFIG, PIPELINE_FACTORY } from './transformers.providers';\n\n// onnx-community, not the legacy Xenova export: the old ONNX decoder lacks\n// the quantization scales transformers.js v4's runtime requires.\nexport const DEFAULT_ASR_MODEL = 'onnx-community/whisper-tiny.en';\n\n/** Whisper expects 16 kHz mono float PCM. */\nexport const WHISPER_SAMPLE_RATE = 16000;\n\n/** Raw ASR pipeline output (subset we consume). */\ninterface RawTranscription {\n text?: string;\n chunks?: { text: string; timestamp?: [number | null, number | null] }[];\n}\n\n/**\n * Decodes an encoded audio file (wav/mp3/webm/ogg - anything the browser\n * can decode) to 16 kHz mono Float32Array, ready for Whisper. Browser-only:\n * uses AudioContext with a target sample rate, which resamples on decode.\n */\nexport async function decodeAudio(\n source: Blob | ArrayBuffer,\n targetSampleRate = WHISPER_SAMPLE_RATE,\n): Promise<Float32Array> {\n const AudioContextCtor = (globalThis as { AudioContext?: typeof AudioContext }).AudioContext;\n if (!AudioContextCtor) {\n throw new Error('decodeAudio requires a browser AudioContext (not available in this environment).');\n }\n const buffer = source instanceof Blob ? await source.arrayBuffer() : source;\n const ctx = new AudioContextCtor({ sampleRate: targetSampleRate });\n try {\n const decoded = await ctx.decodeAudioData(buffer);\n if (decoded.numberOfChannels === 1) return decoded.getChannelData(0);\n // Mix down to mono by averaging channels.\n const left = decoded.getChannelData(0);\n const right = decoded.getChannelData(1);\n const mono = new Float32Array(left.length);\n for (let i = 0; i < left.length; i++) mono[i] = (left[i] + right[i]) / 2;\n return mono;\n } finally {\n await ctx.close();\n }\n}\n\n/**\n * Speech-to-text via Whisper. The default checkpoint is whisper-tiny.en\n * (~41 MB q8, English-only); swap `model` for a multilingual or larger one\n * (e.g. Xenova/whisper-small).\n */\nexport class SpeechRecognizer extends PipelineHandle<string | Float32Array, RawTranscription | RawTranscription[]> {\n /**\n * Transcribes audio: a URL, a decoded 16 kHz Float32Array, or an encoded\n * Blob/ArrayBuffer (decoded via decodeAudio first - browser-only).\n */\n async transcribe(\n audio: string | Float32Array | Blob | ArrayBuffer,\n options: TranscribeOptions = {},\n ): Promise<Transcription> {\n const input =\n audio instanceof Blob || audio instanceof ArrayBuffer ? await decodeAudio(audio) : audio;\n\n const runOptions: Record<string, unknown> = {};\n if (options.returnTimestamps !== undefined) runOptions['return_timestamps'] = options.returnTimestamps;\n if (options.chunkLengthS !== undefined) runOptions['chunk_length_s'] = options.chunkLengthS;\n if (options.strideLengthS !== undefined) runOptions['stride_length_s'] = options.strideLengthS;\n if (options.language !== undefined) runOptions['language'] = options.language;\n if (options.task !== undefined) runOptions['task'] = options.task;\n\n const raw = await this.run(input, runOptions);\n const first = (Array.isArray(raw) ? raw[0] : raw) ?? {};\n const chunks: TranscriptionChunk[] | undefined = first.chunks?.map((c) => ({\n text: c.text,\n start: c.timestamp?.[0] ?? null,\n end: c.timestamp?.[1] ?? null,\n }));\n return { text: (first.text ?? '').trim(), ...(chunks ? { chunks } : {}) };\n }\n}\n\n/**\n * Creates a SpeechRecognizer in an injection context.\n *\n * dtype defaults to 'q4' (not the library-wide default): q8 Whisper\n * decoders fail session creation on the v4 WASM runtime\n * (huggingface/transformers.js#1707), while q4 loads and runs everywhere.\n */\nexport function createSpeechRecognizer(options: Partial<Omit<PipelineRequest, 'task'>> = {}): SpeechRecognizer {\n const recognizer = new SpeechRecognizer(\n { task: 'automatic-speech-recognition', model: DEFAULT_ASR_MODEL, dtype: 'q4', ...options },\n inject(PIPELINE_FACTORY),\n inject(NGX_TRANSFORMERS_CONFIG),\n );\n inject(DestroyRef, { optional: true })?.onDestroy(() => void recognizer.dispose());\n return recognizer;\n}\n","import { DestroyRef, inject, signal } from '@angular/core';\n\n/** Minimal recorder surface - lets tests (and exotic runtimes) inject fakes. */\nexport interface RecorderLike {\n start(): void;\n stop(): void;\n ondataavailable: ((event: { data: Blob }) => void) | null;\n onstop: (() => void) | null;\n mimeType?: string;\n}\n\nexport interface MicRecorderDeps {\n /** Defaults to navigator.mediaDevices.getUserMedia({ audio: true }). */\n getUserMedia?: () => Promise<MediaStream>;\n /** Defaults to new MediaRecorder(stream). */\n createRecorder?: (stream: MediaStream) => RecorderLike;\n}\n\n/**\n * Microphone capture with a signals API. start() asks for mic permission\n * and records; stop() resolves with the encoded audio Blob - pass it to\n * SpeechRecognizer.transcribe(). Browser-only.\n *\n * ```ts\n * readonly mic = createMicRecorder();\n * readonly whisper = createSpeechRecognizer();\n *\n * async toggle() {\n * if (this.mic.recording()) {\n * const audio = await this.mic.stop();\n * const { text } = await this.whisper.transcribe(audio);\n * } else {\n * await this.mic.start();\n * }\n * }\n * ```\n */\nexport class MicRecorder {\n readonly recording = signal(false);\n /** Elapsed recording time in whole seconds. */\n readonly seconds = signal(0);\n readonly error = signal<unknown>(null);\n\n private stream: MediaStream | null = null;\n private recorder: RecorderLike | null = null;\n private chunks: Blob[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(private readonly deps: MicRecorderDeps = {}) {}\n\n /** Requests the microphone and starts recording. No-op while recording. */\n async start(): Promise<void> {\n if (this.recording()) return;\n this.error.set(null);\n try {\n this.stream = await (this.deps.getUserMedia\n ? this.deps.getUserMedia()\n : navigator.mediaDevices.getUserMedia({ audio: true }));\n this.recorder = this.deps.createRecorder\n ? this.deps.createRecorder(this.stream)\n : (new MediaRecorder(this.stream) as unknown as RecorderLike);\n this.chunks = [];\n this.recorder.ondataavailable = (event) => {\n if (event.data.size > 0) this.chunks.push(event.data);\n };\n this.recorder.start();\n this.recording.set(true);\n this.seconds.set(0);\n this.timer = setInterval(() => this.seconds.set(this.seconds() + 1), 1000);\n } catch (err) {\n this.error.set(err);\n this.cleanup();\n throw err;\n }\n }\n\n /** Stops recording and resolves with the captured audio. */\n stop(): Promise<Blob> {\n const recorder = this.recorder;\n if (!recorder || !this.recording()) {\n return Promise.reject(new Error('MicRecorder.stop() called while not recording.'));\n }\n return new Promise<Blob>((resolve) => {\n recorder.onstop = () => {\n const blob = new Blob(this.chunks, { type: recorder.mimeType || 'audio/webm' });\n this.cleanup();\n resolve(blob);\n };\n recorder.stop();\n });\n }\n\n /** Stops tracks and timers without resolving audio (e.g. on destroy). */\n dispose(): void {\n this.cleanup();\n }\n\n private cleanup(): void {\n if (this.timer !== null) clearInterval(this.timer);\n this.timer = null;\n this.stream?.getTracks().forEach((track) => track.stop());\n this.stream = null;\n this.recorder = null;\n this.recording.set(false);\n }\n}\n\n/** Creates a MicRecorder in an injection context; disposed with the component. */\nexport function createMicRecorder(deps: MicRecorderDeps = {}): MicRecorder {\n const recorder = new MicRecorder(deps);\n inject(DestroyRef, { optional: true })?.onDestroy(() => recorder.dispose());\n return recorder;\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/speech-recognizer';\nexport * from './lib/mic-recorder';\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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
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.",
3
+ "version": "0.2.0",
4
+ "description": "Run Hugging Face Transformers.js models in Angular - on-device ML with a signals API: text classification, embeddings, semantic search, Whisper speech-to-text. No server, no API key.",
5
5
  "keywords": [
6
6
  "angular",
7
7
  "transformers",
@@ -14,7 +14,10 @@
14
14
  "embeddings",
15
15
  "sentiment-analysis",
16
16
  "on-device",
17
- "signals"
17
+ "signals",
18
+ "whisper",
19
+ "speech-to-text",
20
+ "speech-recognition"
18
21
  ],
19
22
  "license": "MIT",
20
23
  "author": "Muzaffar Qosimov",
@@ -42,6 +42,30 @@ interface RankedResult {
42
42
  /** Index of the document in the input list. */
43
43
  index: number;
44
44
  }
45
+ /** One timestamped segment of a transcription. */
46
+ interface TranscriptionChunk {
47
+ text: string;
48
+ /** Seconds from the start of the audio; null when the model omits it. */
49
+ start: number | null;
50
+ end: number | null;
51
+ }
52
+ interface Transcription {
53
+ text: string;
54
+ /** Present when transcribe() was called with returnTimestamps. */
55
+ chunks?: TranscriptionChunk[];
56
+ }
57
+ interface TranscribeOptions {
58
+ /** true for segment timestamps, 'word' for word-level. */
59
+ returnTimestamps?: boolean | 'word';
60
+ /** Split audio longer than ~30 s into chunks of this many seconds. */
61
+ chunkLengthS?: number;
62
+ /** Overlap between chunks, in seconds. */
63
+ strideLengthS?: number;
64
+ /** Source language (multilingual Whisper checkpoints only). */
65
+ language?: string;
66
+ /** 'transcribe' (default) or 'translate' (multilingual checkpoints only). */
67
+ task?: 'transcribe' | 'translate';
68
+ }
45
69
 
46
70
  /**
47
71
  * The callable returned by Transformers.js pipeline(), reduced to the
@@ -144,6 +168,101 @@ declare class TextEmbedder extends PipelineHandle<string | string[], TensorLike>
144
168
  /** Creates a TextEmbedder in an injection context. */
145
169
  declare function createTextEmbedder(options?: Partial<Omit<PipelineRequest, 'task'>>): TextEmbedder;
146
170
 
171
+ declare const DEFAULT_ASR_MODEL = "onnx-community/whisper-tiny.en";
172
+ /** Whisper expects 16 kHz mono float PCM. */
173
+ declare const WHISPER_SAMPLE_RATE = 16000;
174
+ /** Raw ASR pipeline output (subset we consume). */
175
+ interface RawTranscription {
176
+ text?: string;
177
+ chunks?: {
178
+ text: string;
179
+ timestamp?: [number | null, number | null];
180
+ }[];
181
+ }
182
+ /**
183
+ * Decodes an encoded audio file (wav/mp3/webm/ogg - anything the browser
184
+ * can decode) to 16 kHz mono Float32Array, ready for Whisper. Browser-only:
185
+ * uses AudioContext with a target sample rate, which resamples on decode.
186
+ */
187
+ declare function decodeAudio(source: Blob | ArrayBuffer, targetSampleRate?: number): Promise<Float32Array>;
188
+ /**
189
+ * Speech-to-text via Whisper. The default checkpoint is whisper-tiny.en
190
+ * (~41 MB q8, English-only); swap `model` for a multilingual or larger one
191
+ * (e.g. Xenova/whisper-small).
192
+ */
193
+ declare class SpeechRecognizer extends PipelineHandle<string | Float32Array, RawTranscription | RawTranscription[]> {
194
+ /**
195
+ * Transcribes audio: a URL, a decoded 16 kHz Float32Array, or an encoded
196
+ * Blob/ArrayBuffer (decoded via decodeAudio first - browser-only).
197
+ */
198
+ transcribe(audio: string | Float32Array | Blob | ArrayBuffer, options?: TranscribeOptions): Promise<Transcription>;
199
+ }
200
+ /**
201
+ * Creates a SpeechRecognizer in an injection context.
202
+ *
203
+ * dtype defaults to 'q4' (not the library-wide default): q8 Whisper
204
+ * decoders fail session creation on the v4 WASM runtime
205
+ * (huggingface/transformers.js#1707), while q4 loads and runs everywhere.
206
+ */
207
+ declare function createSpeechRecognizer(options?: Partial<Omit<PipelineRequest, 'task'>>): SpeechRecognizer;
208
+
209
+ /** Minimal recorder surface - lets tests (and exotic runtimes) inject fakes. */
210
+ interface RecorderLike {
211
+ start(): void;
212
+ stop(): void;
213
+ ondataavailable: ((event: {
214
+ data: Blob;
215
+ }) => void) | null;
216
+ onstop: (() => void) | null;
217
+ mimeType?: string;
218
+ }
219
+ interface MicRecorderDeps {
220
+ /** Defaults to navigator.mediaDevices.getUserMedia({ audio: true }). */
221
+ getUserMedia?: () => Promise<MediaStream>;
222
+ /** Defaults to new MediaRecorder(stream). */
223
+ createRecorder?: (stream: MediaStream) => RecorderLike;
224
+ }
225
+ /**
226
+ * Microphone capture with a signals API. start() asks for mic permission
227
+ * and records; stop() resolves with the encoded audio Blob - pass it to
228
+ * SpeechRecognizer.transcribe(). Browser-only.
229
+ *
230
+ * ```ts
231
+ * readonly mic = createMicRecorder();
232
+ * readonly whisper = createSpeechRecognizer();
233
+ *
234
+ * async toggle() {
235
+ * if (this.mic.recording()) {
236
+ * const audio = await this.mic.stop();
237
+ * const { text } = await this.whisper.transcribe(audio);
238
+ * } else {
239
+ * await this.mic.start();
240
+ * }
241
+ * }
242
+ * ```
243
+ */
244
+ declare class MicRecorder {
245
+ private readonly deps;
246
+ readonly recording: _angular_core.WritableSignal<boolean>;
247
+ /** Elapsed recording time in whole seconds. */
248
+ readonly seconds: _angular_core.WritableSignal<number>;
249
+ readonly error: _angular_core.WritableSignal<unknown>;
250
+ private stream;
251
+ private recorder;
252
+ private chunks;
253
+ private timer;
254
+ constructor(deps?: MicRecorderDeps);
255
+ /** Requests the microphone and starts recording. No-op while recording. */
256
+ start(): Promise<void>;
257
+ /** Stops recording and resolves with the captured audio. */
258
+ stop(): Promise<Blob>;
259
+ /** Stops tracks and timers without resolving audio (e.g. on destroy). */
260
+ dispose(): void;
261
+ private cleanup;
262
+ }
263
+ /** Creates a MicRecorder in an injection context; disposed with the component. */
264
+ declare function createMicRecorder(deps?: MicRecorderDeps): MicRecorder;
265
+
147
266
  /**
148
267
  * Drop-in status line for a PipelineHandle: shows model download progress
149
268
  * while loading, then the ready/busy/error state. Themeable via CSS custom
@@ -163,5 +282,5 @@ declare class ModelProgressComponent {
163
282
  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
283
  }
165
284
 
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 };
285
+ export { DEFAULT_ASR_MODEL, DEFAULT_EMBEDDING_MODEL, DEFAULT_TEXT_CLASSIFICATION_MODEL, MicRecorder, ModelProgressComponent, NGX_TRANSFORMERS_CONFIG, PIPELINE_FACTORY, PipelineHandle, SpeechRecognizer, TextClassifier, TextEmbedder, WHISPER_SAMPLE_RATE, cosineSimilarity, createMicRecorder, createPipeline, createSpeechRecognizer, createTextClassifier, createTextEmbedder, decodeAudio, provideTransformers };
286
+ export type { ClassificationResult, MicRecorderDeps, ModelProgress, NgxTransformersConfig, PipelineFactory, PipelineLike, PipelineRequest, PipelineStatus, RankedResult, RecorderLike, TranscribeOptions, Transcription, TranscriptionChunk, TransformersDevice, TransformersDtype };