ngx-transformers 0.3.0 → 0.4.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/LICENSE +21 -0
- package/README.md +142 -8
- package/fesm2022/ngx-transformers-worker.mjs +307 -0
- package/fesm2022/ngx-transformers-worker.mjs.map +1 -0
- package/fesm2022/ngx-transformers.mjs +443 -64
- package/fesm2022/ngx-transformers.mjs.map +1 -1
- package/package.json +11 -3
- package/types/ngx-transformers-worker.d.ts +165 -0
- package/types/ngx-transformers.d.ts +220 -18
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Muzaffar Qosimov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# ngx-transformers
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/ngx-transformers)
|
|
4
|
+
[](https://www.npmjs.com/package/ngx-transformers)
|
|
4
5
|
[](https://github.com/qwertymuzaffar/ngx-transformers/actions/workflows/ci.yml)
|
|
5
6
|
[](LICENSE)
|
|
6
7
|
|
|
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,
|
|
8
|
+
Run Hugging Face [Transformers.js](https://github.com/huggingface/transformers.js) models in Angular - **on-device ML with a signals API**. Text classification, zero-shot classification, sentence embeddings, semantic search, translation, Whisper speech-to-text and small LLMs with streaming, executed entirely in the browser (in a Web Worker if you like): no server, no API key, works offline once the model is cached.
|
|
8
9
|
|
|
9
|
-
**[
|
|
10
|
+
**[Documentation](https://qwertymuzaffar.github.io/ngx-transformers/)** · [Demo app](https://qwertymuzaffar.github.io/ngx-transformers/demo/) · [Storybook](https://qwertymuzaffar.github.io/ngx-transformers/storybook/) - the demos load real models in your browser.
|
|
10
11
|
|
|
11
12
|
## Why
|
|
12
13
|
|
|
@@ -90,6 +91,97 @@ async toggle() {
|
|
|
90
91
|
|
|
91
92
|
> 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.
|
|
92
93
|
|
|
94
|
+
## Zero-shot classification
|
|
95
|
+
|
|
96
|
+
Score labels you name against a text, no fine-tuning - an NLI model judges whether "This example is {label}." follows from the text:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { createZeroShotClassifier } from 'ngx-transformers';
|
|
100
|
+
|
|
101
|
+
readonly classifier = createZeroShotClassifier(); // mobilebert-uncased-mnli, ~26 MB q8
|
|
102
|
+
|
|
103
|
+
const scored = await this.classifier.classify(ticketText, ['billing', 'bug report', 'feature request']);
|
|
104
|
+
// [{ label: 'bug report', score: 0.91 }, { label: 'billing', score: 0.06 }, ...] - scores sum to 1
|
|
105
|
+
|
|
106
|
+
// several labels can apply at once - score each on its own:
|
|
107
|
+
const tags = await this.classifier.classify(text, ['food', 'repair', 'politics'], {
|
|
108
|
+
multiLabel: true,
|
|
109
|
+
hypothesisTemplate: 'This text is about {}.',
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`Xenova/distilbert-base-uncased-mnli` (~80 MB) is the more accurate drop-in: `createZeroShotClassifier({ model: '...' })`.
|
|
114
|
+
|
|
115
|
+
## Translation
|
|
116
|
+
|
|
117
|
+
Marian opus-mt checkpoints, one per language pair (~105 MB q8 each), resolved from the pair and loaded on demand:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { createTranslator } from 'ngx-transformers';
|
|
121
|
+
|
|
122
|
+
readonly translator = createTranslator({ from: 'en', to: 'ru' }); // Xenova/opus-mt-en-ru
|
|
123
|
+
|
|
124
|
+
const russian = await this.translator.translate('The model runs entirely in the browser.');
|
|
125
|
+
const german = await this.translator.translate('Good morning.', { to: 'de' }); // opus-mt-en-de, its own download
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The translator's `status` / `progress` signals follow the model used by the latest call, so one `<ngx-model-progress>` covers every pair; `handleFor({ to: 'de' })` returns the underlying `PipelineHandle` when you want one per pair. Override the checkpoint for a pair globally with `provideTransformers({ translationModels: { 'en-ru': 'my-org/en-ru-tiny' } })`.
|
|
129
|
+
|
|
130
|
+
Multilingual checkpoints (NLLB, M2M100) serve every pair from one model and take the codes per call:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
readonly translator = createTranslator({ model: 'Xenova/nllb-200-distilled-600M' });
|
|
134
|
+
await this.translator.translate(text, { from: 'eng_Latn', to: 'tgk_Cyrl' }); // src_lang / tgt_lang forwarded
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Text generation
|
|
138
|
+
|
|
139
|
+
A small language model, streamed token by token:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { createTextGenerator } from 'ngx-transformers';
|
|
143
|
+
|
|
144
|
+
readonly generator = createTextGenerator(); // SmolLM2-135M-Instruct, ~100 MB q4
|
|
145
|
+
|
|
146
|
+
const reply = await this.generator.generate(
|
|
147
|
+
[{ role: 'user', content: 'Explain signals in Angular in two sentences.' }],
|
|
148
|
+
{ maxNewTokens: 120, onToken: (piece) => console.log(piece) },
|
|
149
|
+
);
|
|
150
|
+
// generator.output() holds the text generated so far while the model runs
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Reactive inference
|
|
154
|
+
|
|
155
|
+
`inferenceResource()` runs a handle whenever an input signal changes, as an Angular resource with debounce and latest-wins:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
readonly text = signal('');
|
|
159
|
+
readonly sentiment = inferenceResource({
|
|
160
|
+
input: () => this.text().trim() || undefined, // undefined: idle
|
|
161
|
+
run: (text) => this.classifier.classify(text),
|
|
162
|
+
debounceMs: 300,
|
|
163
|
+
});
|
|
164
|
+
// template: sentiment.value(), sentiment.isLoading(), sentiment.error()
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Web Worker
|
|
168
|
+
|
|
169
|
+
One provider moves every pipeline off the main thread; handles and signals work unchanged:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
// transformers.worker.ts
|
|
173
|
+
/// <reference lib="webworker" />
|
|
174
|
+
import { runTransformersWorker } from 'ngx-transformers/worker';
|
|
175
|
+
runTransformersWorker();
|
|
176
|
+
|
|
177
|
+
// app.config.ts
|
|
178
|
+
provideTransformersWorker(
|
|
179
|
+
() => new Worker(new URL('./transformers.worker', import.meta.url), { type: 'module' }),
|
|
180
|
+
);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`ngx-transformers/worker` has no Angular dependency, so the worker chunk stays tiny; `@huggingface/transformers` loads inside the worker only.
|
|
184
|
+
|
|
93
185
|
## Any pipeline
|
|
94
186
|
|
|
95
187
|
`createPipeline()` exposes the full Transformers.js task surface with the same signal lifecycle:
|
|
@@ -115,7 +207,23 @@ bootstrapApplication(App, {
|
|
|
115
207
|
});
|
|
116
208
|
```
|
|
117
209
|
|
|
118
|
-
Per-pipeline `device`/`dtype`/`options` win over the global config.
|
|
210
|
+
Per-pipeline `device`/`dtype`/`options` win over the global config. `translationModels` maps a `"from-to"` pair to a checkpoint for `createTranslator()`.
|
|
211
|
+
|
|
212
|
+
Every handle gets its pipeline from the `PIPELINE_FACTORY` injection token. Provide your own to add options or logging, route work to a Web Worker, or stub models in tests. `createDefaultPipelineFactory()` is the default; it lazy-imports `@huggingface/transformers` on the first pipeline, so wrapping it keeps the initial bundle small:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { PIPELINE_FACTORY, createDefaultPipelineFactory, type PipelineFactory } from 'ngx-transformers';
|
|
216
|
+
|
|
217
|
+
const base = createDefaultPipelineFactory();
|
|
218
|
+
const logging: PipelineFactory = (task, model, options) => {
|
|
219
|
+
console.log('loading', task, model);
|
|
220
|
+
return base(task, model, options);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
bootstrapApplication(App, {
|
|
224
|
+
providers: [{ provide: PIPELINE_FACTORY, useValue: logging }],
|
|
225
|
+
});
|
|
226
|
+
```
|
|
119
227
|
|
|
120
228
|
## API
|
|
121
229
|
|
|
@@ -127,18 +235,25 @@ Per-pipeline `device`/`dtype`/`options` win over the global config.
|
|
|
127
235
|
| `createTextClassifier(options?)` | `TextClassifier` - sentiment/classification, `classify(text, topK?)` |
|
|
128
236
|
| `createTextEmbedder(options?)` | `TextEmbedder` - `embed()`, `similarity()`, `rank()` |
|
|
129
237
|
| `createSpeechRecognizer(options?)` | `SpeechRecognizer` - `transcribe(audio, options?)` with timestamps |
|
|
238
|
+
| `createZeroShotClassifier(options?)` | `ZeroShotClassifier` - `classify(text, labels, { multiLabel?, hypothesisTemplate? })` |
|
|
239
|
+
| `createTranslator(options?)` | `Translator` - `translate(text, { from?, to? })`, one model per pair, `handleFor(pair)` |
|
|
240
|
+
| `createTextGenerator(options?)` | `TextGenerator` - `generate(prompt, { maxNewTokens?, onToken?, ... })`, streamed into `output` |
|
|
241
|
+
| `inferenceResource({ input, run, debounceMs? })` | An Angular resource that re-runs a handle when an input signal changes |
|
|
242
|
+
| `provideTransformersWorker(createWorker)` | Run every pipeline in a Web Worker (`ngx-transformers/worker` runs the worker side) |
|
|
130
243
|
| `createMicRecorder(deps?)` | `MicRecorder` - mic capture with `recording`/`seconds`/`error` signals |
|
|
131
244
|
| `cosineSimilarity(a, b)` / `decodeAudio(blob)` | Standalone helpers |
|
|
245
|
+
| `PIPELINE_FACTORY` / `createDefaultPipelineFactory()` | Swap or wrap how pipelines are created |
|
|
132
246
|
|
|
133
|
-
All `create*` functions must run in an injection context (field initializer, constructor, or `runInInjectionContext`); handles are
|
|
247
|
+
All `create*` functions must run in an injection context (field initializer, constructor, or `runInInjectionContext`); handles are destroyed with the surrounding component (the model is released and later calls reject). Call `dispose()` yourself to free a model early and load it again later.
|
|
134
248
|
|
|
135
249
|
### PipelineHandle signals
|
|
136
250
|
|
|
137
251
|
| Signal | Type | Meaning |
|
|
138
252
|
|---|---|---|
|
|
139
253
|
| `status` | `'idle' \| 'loading' \| 'ready' \| 'busy' \| 'error'` | Lifecycle; `error` only from a failed load, retryable |
|
|
140
|
-
| `progress` | `ModelProgress \| null` | Download progress:
|
|
254
|
+
| `progress` | `ModelProgress \| null` | Download progress: the file reported last plus `overall` over every file of the model |
|
|
141
255
|
| `error` | `unknown` | The load error, if any |
|
|
256
|
+
| `runError` | `unknown` | The error of the most recent run, if it failed |
|
|
142
257
|
| `ready` / `busy` | `boolean` (computed) | Convenience for buttons and spinners |
|
|
143
258
|
|
|
144
259
|
### `<ngx-model-progress>`
|
|
@@ -152,6 +267,9 @@ Status line + download bar for any handle. Inputs: `status` (required), `progres
|
|
|
152
267
|
| `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
268
|
| `createTextEmbedder` | [Xenova/all-MiniLM-L6-v2](https://huggingface.co/Xenova/all-MiniLM-L6-v2) | ~23 MB | Apache-2.0 |
|
|
154
269
|
| `createSpeechRecognizer` | [onnx-community/whisper-tiny.en](https://huggingface.co/onnx-community/whisper-tiny.en) | ~41 MB (q4) | Apache-2.0 |
|
|
270
|
+
| `createZeroShotClassifier` | [Xenova/mobilebert-uncased-mnli](https://huggingface.co/Xenova/mobilebert-uncased-mnli) | ~26 MB | unlisted on the Hub (base MobileBERT: Apache-2.0) |
|
|
271
|
+
| `createTranslator` | [Xenova/opus-mt-{from}-{to}](https://huggingface.co/models?search=Xenova/opus-mt) | ~105 MB per pair | varies per pair (Apache-2.0 or CC-BY-4.0) |
|
|
272
|
+
| `createTextGenerator` | [HuggingFaceTB/SmolLM2-135M-Instruct](https://huggingface.co/HuggingFaceTB/SmolLM2-135M-Instruct) | ~100 MB (q4) | Apache-2.0 |
|
|
155
273
|
|
|
156
274
|
Swap any compatible checkpoint via `{ model: '...' }`. Check the license of the model you ship.
|
|
157
275
|
|
|
@@ -159,10 +277,26 @@ Swap any compatible checkpoint via `{ model: '...' }`. Check the license of the
|
|
|
159
277
|
|
|
160
278
|
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
279
|
|
|
162
|
-
##
|
|
280
|
+
## Device selection
|
|
281
|
+
|
|
282
|
+
WebGPU is several times faster than WASM where it works, but `navigator.gpu` existing is not enough: `requestAdapter()` can still hand back nothing on a machine without a usable GPU. `hasWebGpu()` runs that probe once (cached) and `detectDevice()` turns the answer into a device name; both resolve to the WASM answer on the server.
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import { createTextClassifier, detectDevice } from 'ngx-transformers';
|
|
286
|
+
|
|
287
|
+
const device = await detectDevice(); // 'webgpu' | 'wasm'
|
|
288
|
+
const classifier = createTextClassifier({ device });
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Or let every handle decide for itself:
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
bootstrapApplication(App, {
|
|
295
|
+
providers: [provideTransformers({ autoDevice: true, dtype: 'q8' })],
|
|
296
|
+
});
|
|
297
|
+
```
|
|
163
298
|
|
|
164
|
-
|
|
165
|
-
- WebGPU feature-detection helper
|
|
299
|
+
With `autoDevice`, a handle that sets no `device` (or `'auto'`) probes WebGPU on its first load and uses it when available, else WASM. An explicit `device` on the handle or in the global config still wins. `resetDeviceDetection()` clears the cached answer.
|
|
166
300
|
|
|
167
301
|
## License
|
|
168
302
|
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messages between the main thread (client.ts) and the worker (host.ts).
|
|
3
|
+
* Every request carries an id the response echoes; pipelines get a pipeId.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Helpers shared by the in-thread and worker pipeline factories for the run
|
|
8
|
+
* options ngx-transformers adds on top of the Transformers.js ones.
|
|
9
|
+
*/
|
|
10
|
+
const ON_TOKEN = 'onToken';
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Pulls the `onToken` callback out of a pipeline call's trailing options
|
|
16
|
+
* object. Returns the arguments without it, plus the callback if present.
|
|
17
|
+
*/
|
|
18
|
+
function takeOnToken(args) {
|
|
19
|
+
const last = args[args.length - 1];
|
|
20
|
+
if (!isPlainObject(last) || typeof last[ON_TOKEN] !== 'function')
|
|
21
|
+
return { args };
|
|
22
|
+
const { [ON_TOKEN]: onToken, ...rest } = last;
|
|
23
|
+
return { args: [...args.slice(0, -1), rest], onToken: onToken };
|
|
24
|
+
}
|
|
25
|
+
/** A shallow copy of an options object without its function-valued entries. */
|
|
26
|
+
function withoutFunctions(options) {
|
|
27
|
+
const out = {};
|
|
28
|
+
for (const [key, value] of Object.entries(options)) {
|
|
29
|
+
if (typeof value !== 'function')
|
|
30
|
+
out[key] = value;
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
/** Same as withoutFunctions() for the trailing options object of a call, if any. */
|
|
35
|
+
function withoutFunctionsInLast(args) {
|
|
36
|
+
const last = args[args.length - 1];
|
|
37
|
+
if (!isPlainObject(last))
|
|
38
|
+
return args;
|
|
39
|
+
return [...args.slice(0, -1), withoutFunctions(last)];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Tensor-like: the shape our TextEmbedder reads back on the main thread. */
|
|
43
|
+
function isTensorLike(value) {
|
|
44
|
+
return (typeof value === 'object' &&
|
|
45
|
+
value !== null &&
|
|
46
|
+
Array.isArray(value.dims) &&
|
|
47
|
+
ArrayBuffer.isView(value.data));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Turns a pipeline result into something structured cloning keeps intact:
|
|
51
|
+
* Tensors become { dims, data } with the buffer transferred, everything
|
|
52
|
+
* else (plain objects, arrays, strings) passes through unchanged.
|
|
53
|
+
*/
|
|
54
|
+
function toCloneable(result) {
|
|
55
|
+
const transfer = [];
|
|
56
|
+
const convert = (value) => {
|
|
57
|
+
if (isTensorLike(value)) {
|
|
58
|
+
const data = value.data.slice();
|
|
59
|
+
transfer.push(data.buffer);
|
|
60
|
+
return { dims: [...value.dims], data };
|
|
61
|
+
}
|
|
62
|
+
if (Array.isArray(value))
|
|
63
|
+
return value.map(convert);
|
|
64
|
+
return value;
|
|
65
|
+
};
|
|
66
|
+
return { value: convert(result), transfer };
|
|
67
|
+
}
|
|
68
|
+
function errorResponse(id, err) {
|
|
69
|
+
const error = err;
|
|
70
|
+
return {
|
|
71
|
+
type: 'error',
|
|
72
|
+
id,
|
|
73
|
+
name: typeof error?.name === 'string' ? error.name : 'Error',
|
|
74
|
+
message: typeof error?.message === 'string' ? error.message : String(err),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The worker side of ngx-transformers/worker: creates pipelines on request,
|
|
79
|
+
* runs them, streams progress and generated tokens back, and disposes them.
|
|
80
|
+
* runTransformersWorker() wires it to the worker's message port; tests call
|
|
81
|
+
* handle() directly.
|
|
82
|
+
*/
|
|
83
|
+
function createTransformersWorkerHost(options) {
|
|
84
|
+
const { post } = options;
|
|
85
|
+
const load = options.load ?? (() => import('@huggingface/transformers'));
|
|
86
|
+
const pipes = new Map();
|
|
87
|
+
let module = null;
|
|
88
|
+
let nextPipeId = 1;
|
|
89
|
+
// A failed import is not kept: the next create() tries again, so a load
|
|
90
|
+
// that failed on a network blip stays retryable, as PipelineHandle promises.
|
|
91
|
+
const loadModule = () => (module ??= load().catch((err) => {
|
|
92
|
+
module = null;
|
|
93
|
+
throw err;
|
|
94
|
+
}));
|
|
95
|
+
async function create(message) {
|
|
96
|
+
try {
|
|
97
|
+
const { pipeline } = await loadModule();
|
|
98
|
+
const pipe = (await pipeline(message.task, message.model, {
|
|
99
|
+
...message.options,
|
|
100
|
+
progress_callback: (event) => post({ type: 'progress', id: message.id, event }),
|
|
101
|
+
}));
|
|
102
|
+
const pipeId = nextPipeId++;
|
|
103
|
+
pipes.set(pipeId, pipe);
|
|
104
|
+
post({ type: 'created', id: message.id, pipeId });
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
post(errorResponse(message.id, err));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function run(message) {
|
|
111
|
+
const pipe = pipes.get(message.pipeId);
|
|
112
|
+
if (!pipe) {
|
|
113
|
+
post(errorResponse(message.id, new Error(`Unknown pipeline ${message.pipeId}.`)));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
let args = message.args;
|
|
118
|
+
if (message.stream) {
|
|
119
|
+
const { TextStreamer } = await loadModule();
|
|
120
|
+
if (TextStreamer) {
|
|
121
|
+
// The client already stripped onToken; the options object is last.
|
|
122
|
+
const options = { ...args[args.length - 1] };
|
|
123
|
+
options['streamer'] = new TextStreamer(pipe.tokenizer, {
|
|
124
|
+
skip_prompt: true,
|
|
125
|
+
skip_special_tokens: true,
|
|
126
|
+
callback_function: (text) => post({ type: 'token', id: message.id, text }),
|
|
127
|
+
});
|
|
128
|
+
args = [...args.slice(0, -1), options];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const { value, transfer } = toCloneable(await pipe(...args));
|
|
132
|
+
post({ type: 'result', id: message.id, result: value }, transfer);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
post(errorResponse(message.id, err));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
async handle(message) {
|
|
140
|
+
switch (message.type) {
|
|
141
|
+
case 'create':
|
|
142
|
+
return create(message);
|
|
143
|
+
case 'run':
|
|
144
|
+
return run(message);
|
|
145
|
+
case 'dispose': {
|
|
146
|
+
const pipe = pipes.get(message.pipeId);
|
|
147
|
+
pipes.delete(message.pipeId);
|
|
148
|
+
await pipe?.dispose?.();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Entry point for the worker file of an app:
|
|
157
|
+
*
|
|
158
|
+
* ```ts
|
|
159
|
+
* // transformers.worker.ts
|
|
160
|
+
* /// <reference lib="webworker" />
|
|
161
|
+
* import { runTransformersWorker } from 'ngx-transformers/worker';
|
|
162
|
+
* runTransformersWorker();
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* Pair it with provideTransformersWorker() on the main thread.
|
|
166
|
+
*/
|
|
167
|
+
function runTransformersWorker(scope = workerGlobalScope(), load) {
|
|
168
|
+
const host = createTransformersWorkerHost({
|
|
169
|
+
post: (message, transfer) => scope.postMessage(message, transfer),
|
|
170
|
+
load,
|
|
171
|
+
});
|
|
172
|
+
scope.onmessage = (event) => void host.handle(event.data);
|
|
173
|
+
return host;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* The current worker's global scope. On the main thread this would hook
|
|
177
|
+
* window.onmessage, which any origin can post to, so refuse instead.
|
|
178
|
+
*/
|
|
179
|
+
function workerGlobalScope() {
|
|
180
|
+
const global = globalThis;
|
|
181
|
+
if (global.WorkerGlobalScope === undefined) {
|
|
182
|
+
throw new Error('runTransformersWorker() must run inside a Web Worker file; on the main thread use provideTransformersWorker().');
|
|
183
|
+
}
|
|
184
|
+
return globalThis;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* A pipeline factory whose pipelines live in a Web Worker, so inference
|
|
189
|
+
* never blocks the UI thread. The worker runs runTransformersWorker() and
|
|
190
|
+
* is created lazily, on the first pipeline. Provide the factory through
|
|
191
|
+
* PIPELINE_FACTORY, or use provideTransformersWorker() from
|
|
192
|
+
* ngx-transformers which does exactly that.
|
|
193
|
+
*
|
|
194
|
+
* A worker that fails to start or crashes rejects every pending call and
|
|
195
|
+
* is dropped; the next pipeline creates a new one.
|
|
196
|
+
*/
|
|
197
|
+
function createWorkerPipelineFactory(worker) {
|
|
198
|
+
const pending = new Map();
|
|
199
|
+
let instance = null;
|
|
200
|
+
let nextId = 1;
|
|
201
|
+
function dispatch(message) {
|
|
202
|
+
const entry = pending.get(message.id);
|
|
203
|
+
if (!entry)
|
|
204
|
+
return;
|
|
205
|
+
switch (message.type) {
|
|
206
|
+
case 'created':
|
|
207
|
+
pending.delete(message.id);
|
|
208
|
+
entry.resolve(message.pipeId);
|
|
209
|
+
return;
|
|
210
|
+
case 'result':
|
|
211
|
+
pending.delete(message.id);
|
|
212
|
+
entry.resolve(message.result);
|
|
213
|
+
return;
|
|
214
|
+
case 'error': {
|
|
215
|
+
pending.delete(message.id);
|
|
216
|
+
const error = new Error(message.message);
|
|
217
|
+
error.name = message.name;
|
|
218
|
+
entry.reject(error);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
case 'progress':
|
|
222
|
+
entry.progress?.(message.event);
|
|
223
|
+
return;
|
|
224
|
+
case 'token':
|
|
225
|
+
entry.onToken?.(message.text);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/** The worker is gone: fail everything in flight and forget it. */
|
|
230
|
+
function fail(reason) {
|
|
231
|
+
const error = new Error(reason);
|
|
232
|
+
error.name = 'WorkerError';
|
|
233
|
+
const entries = [...pending.values()];
|
|
234
|
+
pending.clear();
|
|
235
|
+
instance = null;
|
|
236
|
+
for (const entry of entries)
|
|
237
|
+
entry.reject(error);
|
|
238
|
+
}
|
|
239
|
+
function getWorker() {
|
|
240
|
+
if (!instance) {
|
|
241
|
+
const created = typeof worker === 'function' ? worker() : worker;
|
|
242
|
+
created.addEventListener('message', (event) => dispatch(event.data));
|
|
243
|
+
created.addEventListener('error', (event) => fail(`Transformers worker failed: ${event.message ?? 'unknown error'}`));
|
|
244
|
+
created.addEventListener('messageerror', () => fail('Transformers worker sent a message that could not be deserialized.'));
|
|
245
|
+
instance = created;
|
|
246
|
+
}
|
|
247
|
+
return instance;
|
|
248
|
+
}
|
|
249
|
+
function request(message, extras = {}) {
|
|
250
|
+
return new Promise((resolve, reject) => {
|
|
251
|
+
pending.set(message.id, { resolve: resolve, reject, ...extras });
|
|
252
|
+
try {
|
|
253
|
+
getWorker().postMessage(message);
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
// Uncloneable input, or a worker that cannot be constructed.
|
|
257
|
+
pending.delete(message.id);
|
|
258
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
const factory = async (task, model, options) => {
|
|
263
|
+
const { progress_callback, ...rest } = options;
|
|
264
|
+
const progress = typeof progress_callback === 'function'
|
|
265
|
+
? progress_callback
|
|
266
|
+
: undefined;
|
|
267
|
+
const pipeId = await request({ type: 'create', id: nextId++, task, model, options: withoutFunctions(rest) }, { progress });
|
|
268
|
+
const pipe = (async (input, ...args) => {
|
|
269
|
+
const { args: cleanArgs, onToken } = takeOnToken(args);
|
|
270
|
+
return request({
|
|
271
|
+
type: 'run',
|
|
272
|
+
id: nextId++,
|
|
273
|
+
pipeId,
|
|
274
|
+
args: [input, ...withoutFunctionsInLast(cleanArgs)],
|
|
275
|
+
stream: onToken !== undefined,
|
|
276
|
+
}, { onToken });
|
|
277
|
+
});
|
|
278
|
+
pipe.dispose = async () => {
|
|
279
|
+
if (!instance)
|
|
280
|
+
return; // the worker is gone, and its pipelines with it
|
|
281
|
+
instance.postMessage({ type: 'dispose', pipeId });
|
|
282
|
+
};
|
|
283
|
+
return pipe;
|
|
284
|
+
};
|
|
285
|
+
return Object.assign(factory, {
|
|
286
|
+
terminate() {
|
|
287
|
+
const current = instance;
|
|
288
|
+
fail('Transformers worker terminated.');
|
|
289
|
+
current?.terminate?.();
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Run Transformers.js pipelines in a Web Worker. This entry point has no
|
|
296
|
+
* Angular dependency, so a worker file can import it; the main thread uses
|
|
297
|
+
* provideTransformersWorker() from ngx-transformers.
|
|
298
|
+
*
|
|
299
|
+
* @module ngx-transformers/worker
|
|
300
|
+
*/
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Generated bundle index. Do not edit.
|
|
304
|
+
*/
|
|
305
|
+
|
|
306
|
+
export { createTransformersWorkerHost, createWorkerPipelineFactory, runTransformersWorker, takeOnToken, withoutFunctions, withoutFunctionsInLast };
|
|
307
|
+
//# sourceMappingURL=ngx-transformers-worker.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngx-transformers-worker.mjs","sources":["../../../projects/ngx-transformers/worker/src/protocol.ts","../../../projects/ngx-transformers/worker/src/run-options.ts","../../../projects/ngx-transformers/worker/src/host.ts","../../../projects/ngx-transformers/worker/src/client.ts","../../../projects/ngx-transformers/worker/src/public-api.ts","../../../projects/ngx-transformers/worker/src/ngx-transformers-worker.ts"],"sourcesContent":["/**\n * Messages between the main thread (client.ts) and the worker (host.ts).\n * Every request carries an id the response echoes; pipelines get a pipeId.\n */\n\nexport interface CreateRequest {\n type: 'create';\n id: number;\n task: string;\n model?: string;\n /** Pipeline options without functions; progress is reported by message. */\n options: Record<string, unknown>;\n}\n\nexport interface RunRequest {\n type: 'run';\n id: number;\n pipeId: number;\n /** The input followed by the pipeline's positional arguments (options last). */\n args: unknown[];\n /** Stream generated text back as `token` messages (the onToken run option). */\n stream: boolean;\n}\n\nexport interface DisposeRequest {\n type: 'dispose';\n pipeId: number;\n}\n\nexport type WorkerRequest = CreateRequest | RunRequest | DisposeRequest;\n\nexport interface CreatedResponse {\n type: 'created';\n id: number;\n pipeId: number;\n}\n\nexport interface ResultResponse {\n type: 'result';\n id: number;\n result: unknown;\n}\n\nexport interface ErrorResponse {\n type: 'error';\n id: number;\n name: string;\n message: string;\n}\n\nexport interface ProgressResponse {\n type: 'progress';\n id: number;\n event: Record<string, unknown>;\n}\n\nexport interface TokenResponse {\n type: 'token';\n id: number;\n text: string;\n}\n\nexport type WorkerResponse =\n CreatedResponse | ResultResponse | ErrorResponse | ProgressResponse | TokenResponse;\n\n/**\n * A Transformers.js Tensor after crossing the worker boundary: the class is\n * lost to structured cloning, so the host sends its shape and buffer.\n */\nexport interface TensorData {\n dims: number[];\n data: ArrayLike<number>;\n}\n","/**\n * Helpers shared by the in-thread and worker pipeline factories for the run\n * options ngx-transformers adds on top of the Transformers.js ones.\n */\n\n/** Run option: called with each piece of generated text (text-generation). */\nexport type OnToken = (text: string) => void;\n\nconst ON_TOKEN = 'onToken';\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Pulls the `onToken` callback out of a pipeline call's trailing options\n * object. Returns the arguments without it, plus the callback if present.\n */\nexport function takeOnToken(args: unknown[]): { args: unknown[]; onToken?: OnToken } {\n const last = args[args.length - 1];\n if (!isPlainObject(last) || typeof last[ON_TOKEN] !== 'function') return { args };\n const { [ON_TOKEN]: onToken, ...rest } = last;\n return { args: [...args.slice(0, -1), rest], onToken: onToken as OnToken };\n}\n\n/** A shallow copy of an options object without its function-valued entries. */\nexport function withoutFunctions(options: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(options)) {\n if (typeof value !== 'function') out[key] = value;\n }\n return out;\n}\n\n/** Same as withoutFunctions() for the trailing options object of a call, if any. */\nexport function withoutFunctionsInLast(args: unknown[]): unknown[] {\n const last = args[args.length - 1];\n if (!isPlainObject(last)) return args;\n return [...args.slice(0, -1), withoutFunctions(last)];\n}\n","import type {\n CreateRequest,\n RunRequest,\n TensorData,\n WorkerRequest,\n WorkerResponse,\n} from './protocol';\n\n/** A pipeline callable inside the worker: the real Transformers.js pipeline. */\ntype Pipe = ((...args: unknown[]) => Promise<unknown>) & {\n tokenizer?: unknown;\n dispose?: () => Promise<void>;\n};\n\n/** The slice of @huggingface/transformers the host uses. */\nexport interface WorkerTransformersModule {\n pipeline(task: string, model?: string, options?: object): Promise<unknown>;\n TextStreamer?: new (\n // `never` keeps the real class (which wants a PreTrainedTokenizer) assignable.\n tokenizer: never,\n options: {\n skip_prompt?: boolean;\n skip_special_tokens?: boolean;\n callback_function?: (text: string) => void;\n },\n ) => unknown;\n}\n\nexport interface WorkerHostOptions {\n /** Sends a message to the main thread (postMessage in a worker). */\n post: (message: WorkerResponse, transfer?: Transferable[]) => void;\n /** Imports @huggingface/transformers; tests pass a stub. */\n load?: () => Promise<WorkerTransformersModule>;\n}\n\nexport interface WorkerHost {\n /** Handles one request from the main thread. Resolves once the reply is posted. */\n handle(message: WorkerRequest): Promise<void>;\n}\n\n/** Tensor-like: the shape our TextEmbedder reads back on the main thread. */\nfunction isTensorLike(value: unknown): value is TensorData & { tolist?: unknown } {\n return (\n typeof value === 'object' &&\n value !== null &&\n Array.isArray((value as TensorData).dims) &&\n ArrayBuffer.isView((value as TensorData).data)\n );\n}\n\n/**\n * Turns a pipeline result into something structured cloning keeps intact:\n * Tensors become { dims, data } with the buffer transferred, everything\n * else (plain objects, arrays, strings) passes through unchanged.\n */\nfunction toCloneable(result: unknown): { value: unknown; transfer: Transferable[] } {\n const transfer: Transferable[] = [];\n const convert = (value: unknown): unknown => {\n if (isTensorLike(value)) {\n const data = (value.data as Float32Array).slice();\n transfer.push(data.buffer);\n return { dims: [...value.dims], data } satisfies TensorData;\n }\n if (Array.isArray(value)) return value.map(convert);\n return value;\n };\n return { value: convert(result), transfer };\n}\n\nfunction errorResponse(id: number, err: unknown): WorkerResponse {\n const error = err as { name?: unknown; message?: unknown } | null;\n return {\n type: 'error',\n id,\n name: typeof error?.name === 'string' ? error.name : 'Error',\n message: typeof error?.message === 'string' ? error.message : String(err),\n };\n}\n\n/**\n * The worker side of ngx-transformers/worker: creates pipelines on request,\n * runs them, streams progress and generated tokens back, and disposes them.\n * runTransformersWorker() wires it to the worker's message port; tests call\n * handle() directly.\n */\nexport function createTransformersWorkerHost(options: WorkerHostOptions): WorkerHost {\n const { post } = options;\n const load: () => Promise<WorkerTransformersModule> =\n options.load ?? (() => import('@huggingface/transformers'));\n const pipes = new Map<number, Pipe>();\n let module: Promise<WorkerTransformersModule> | null = null;\n let nextPipeId = 1;\n\n // A failed import is not kept: the next create() tries again, so a load\n // that failed on a network blip stays retryable, as PipelineHandle promises.\n const loadModule = () =>\n (module ??= load().catch((err: unknown) => {\n module = null;\n throw err;\n }));\n\n async function create(message: CreateRequest): Promise<void> {\n try {\n const { pipeline } = await loadModule();\n const pipe = (await pipeline(message.task, message.model, {\n ...message.options,\n progress_callback: (event: Record<string, unknown>) =>\n post({ type: 'progress', id: message.id, event }),\n })) as Pipe;\n const pipeId = nextPipeId++;\n pipes.set(pipeId, pipe);\n post({ type: 'created', id: message.id, pipeId });\n } catch (err) {\n post(errorResponse(message.id, err));\n }\n }\n\n async function run(message: RunRequest): Promise<void> {\n const pipe = pipes.get(message.pipeId);\n if (!pipe) {\n post(errorResponse(message.id, new Error(`Unknown pipeline ${message.pipeId}.`)));\n return;\n }\n try {\n let args = message.args;\n if (message.stream) {\n const { TextStreamer } = await loadModule();\n if (TextStreamer) {\n // The client already stripped onToken; the options object is last.\n const options = { ...(args[args.length - 1] as Record<string, unknown>) };\n options['streamer'] = new TextStreamer(pipe.tokenizer as never, {\n skip_prompt: true,\n skip_special_tokens: true,\n callback_function: (text) => post({ type: 'token', id: message.id, text }),\n });\n args = [...args.slice(0, -1), options];\n }\n }\n const { value, transfer } = toCloneable(await pipe(...args));\n post({ type: 'result', id: message.id, result: value }, transfer);\n } catch (err) {\n post(errorResponse(message.id, err));\n }\n }\n\n return {\n async handle(message) {\n switch (message.type) {\n case 'create':\n return create(message);\n case 'run':\n return run(message);\n case 'dispose': {\n const pipe = pipes.get(message.pipeId);\n pipes.delete(message.pipeId);\n await pipe?.dispose?.();\n return;\n }\n }\n },\n };\n}\n\n/** The worker global scope surface the host needs. */\nexport interface WorkerScopeLike {\n postMessage(message: unknown, transfer?: Transferable[]): void;\n onmessage: ((event: { data: unknown }) => void) | null;\n}\n\n/**\n * Entry point for the worker file of an app:\n *\n * ```ts\n * // transformers.worker.ts\n * /// <reference lib=\"webworker\" />\n * import { runTransformersWorker } from 'ngx-transformers/worker';\n * runTransformersWorker();\n * ```\n *\n * Pair it with provideTransformersWorker() on the main thread.\n */\nexport function runTransformersWorker(\n scope: WorkerScopeLike = workerGlobalScope(),\n load?: () => Promise<WorkerTransformersModule>,\n): WorkerHost {\n const host = createTransformersWorkerHost({\n post: (message, transfer) => scope.postMessage(message, transfer),\n load,\n });\n scope.onmessage = (event) => void host.handle(event.data as WorkerRequest);\n return host;\n}\n\n/**\n * The current worker's global scope. On the main thread this would hook\n * window.onmessage, which any origin can post to, so refuse instead.\n */\nfunction workerGlobalScope(): WorkerScopeLike {\n const global = globalThis as { WorkerGlobalScope?: unknown };\n if (global.WorkerGlobalScope === undefined) {\n throw new Error(\n 'runTransformersWorker() must run inside a Web Worker file; on the main thread use provideTransformersWorker().',\n );\n }\n return globalThis as unknown as WorkerScopeLike;\n}\n","import type { WorkerRequest, WorkerResponse } from './protocol';\nimport { takeOnToken, withoutFunctions, withoutFunctionsInLast, type OnToken } from './run-options';\n\n/** What a worker's 'error' / 'messageerror' event carries, as far as the client reads it. */\nexport interface WorkerErrorEventLike {\n message?: string;\n data?: unknown;\n}\n\n/** The Worker surface the client needs; a real Worker satisfies it. */\nexport interface WorkerLike {\n postMessage(message: unknown, transfer?: Transferable[]): void;\n addEventListener(type: 'message', listener: (event: { data: unknown }) => void): void;\n addEventListener(\n type: 'error' | 'messageerror',\n listener: (event: WorkerErrorEventLike) => void,\n ): void;\n terminate?(): void;\n}\n\n/** A pipeline proxy on the main thread: calls run in the worker. */\nexport type WorkerPipeline = ((input: unknown, ...args: unknown[]) => Promise<unknown>) & {\n dispose: () => Promise<void>;\n};\n\n/** Structurally the same as ngx-transformers' PipelineFactory, plus terminate(). */\nexport type WorkerPipelineFactory = ((\n task: string,\n model: string | undefined,\n options: Record<string, unknown>,\n) => Promise<WorkerPipeline>) & {\n /** Stops the worker (if one was created); the next pipeline starts a fresh one. */\n terminate(): void;\n};\n\ninterface Pending {\n resolve: (value: unknown) => void;\n reject: (err: Error) => void;\n progress?: (event: Record<string, unknown>) => void;\n onToken?: OnToken;\n}\n\n/**\n * A pipeline factory whose pipelines live in a Web Worker, so inference\n * never blocks the UI thread. The worker runs runTransformersWorker() and\n * is created lazily, on the first pipeline. Provide the factory through\n * PIPELINE_FACTORY, or use provideTransformersWorker() from\n * ngx-transformers which does exactly that.\n *\n * A worker that fails to start or crashes rejects every pending call and\n * is dropped; the next pipeline creates a new one.\n */\nexport function createWorkerPipelineFactory(\n worker: WorkerLike | (() => WorkerLike),\n): WorkerPipelineFactory {\n const pending = new Map<number, Pending>();\n let instance: WorkerLike | null = null;\n let nextId = 1;\n\n function dispatch(message: WorkerResponse): void {\n const entry = pending.get(message.id);\n if (!entry) return;\n switch (message.type) {\n case 'created':\n pending.delete(message.id);\n entry.resolve(message.pipeId);\n return;\n case 'result':\n pending.delete(message.id);\n entry.resolve(message.result);\n return;\n case 'error': {\n pending.delete(message.id);\n const error = new Error(message.message);\n error.name = message.name;\n entry.reject(error);\n return;\n }\n case 'progress':\n entry.progress?.(message.event);\n return;\n case 'token':\n entry.onToken?.(message.text);\n return;\n }\n }\n\n /** The worker is gone: fail everything in flight and forget it. */\n function fail(reason: string): void {\n const error = new Error(reason);\n error.name = 'WorkerError';\n const entries = [...pending.values()];\n pending.clear();\n instance = null;\n for (const entry of entries) entry.reject(error);\n }\n\n function getWorker(): WorkerLike {\n if (!instance) {\n const created = typeof worker === 'function' ? worker() : worker;\n created.addEventListener('message', (event) => dispatch(event.data as WorkerResponse));\n created.addEventListener('error', (event) =>\n fail(`Transformers worker failed: ${event.message ?? 'unknown error'}`),\n );\n created.addEventListener('messageerror', () =>\n fail('Transformers worker sent a message that could not be deserialized.'),\n );\n instance = created;\n }\n return instance;\n }\n\n function request<T>(\n message: WorkerRequest & { id: number },\n extras: Omit<Pending, 'resolve' | 'reject'> = {},\n ): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n pending.set(message.id, { resolve: resolve as (value: unknown) => void, reject, ...extras });\n try {\n getWorker().postMessage(message);\n } catch (err) {\n // Uncloneable input, or a worker that cannot be constructed.\n pending.delete(message.id);\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n });\n }\n\n const factory = async (\n task: string,\n model: string | undefined,\n options: Record<string, unknown>,\n ) => {\n const { progress_callback, ...rest } = options;\n const progress =\n typeof progress_callback === 'function'\n ? (progress_callback as (event: Record<string, unknown>) => void)\n : undefined;\n const pipeId = await request<number>(\n { type: 'create', id: nextId++, task, model, options: withoutFunctions(rest) },\n { progress },\n );\n\n const pipe = (async (input: unknown, ...args: unknown[]) => {\n const { args: cleanArgs, onToken } = takeOnToken(args);\n return request(\n {\n type: 'run',\n id: nextId++,\n pipeId,\n args: [input, ...withoutFunctionsInLast(cleanArgs)],\n stream: onToken !== undefined,\n },\n { onToken },\n );\n }) as WorkerPipeline;\n pipe.dispose = async () => {\n if (!instance) return; // the worker is gone, and its pipelines with it\n instance.postMessage({ type: 'dispose', pipeId } satisfies WorkerRequest);\n };\n return pipe;\n };\n\n return Object.assign(factory, {\n terminate(): void {\n const current = instance;\n fail('Transformers worker terminated.');\n current?.terminate?.();\n },\n });\n}\n","/**\n * Run Transformers.js pipelines in a Web Worker. This entry point has no\n * Angular dependency, so a worker file can import it; the main thread uses\n * provideTransformersWorker() from ngx-transformers.\n *\n * @module ngx-transformers/worker\n */\n\nexport * from './protocol';\nexport * from './run-options';\nexport * from './host';\nexport * from './client';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|