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
|
@@ -1,14 +1,51 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, makeEnvironmentProviders, signal, computed,
|
|
2
|
+
import { InjectionToken, makeEnvironmentProviders, inject, DestroyRef, signal, computed, Injector, resource, untracked, effect, input, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { takeOnToken, createWorkerPipelineFactory } from 'ngx-transformers/worker';
|
|
3
4
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const importTransformers = () => import('@huggingface/transformers');
|
|
6
|
+
/**
|
|
7
|
+
* Wraps a pipeline so the `onToken` run option (TextGenerator streaming)
|
|
8
|
+
* becomes a Transformers.js TextStreamer on the pipeline's tokenizer.
|
|
9
|
+
*/
|
|
10
|
+
function withTokenStreaming(pipe, module) {
|
|
11
|
+
const streaming = (async (input, ...args) => {
|
|
12
|
+
const { args: rest, onToken } = takeOnToken(args);
|
|
13
|
+
if (onToken && module.TextStreamer) {
|
|
14
|
+
const options = { ...rest[rest.length - 1] };
|
|
15
|
+
options['streamer'] = new module.TextStreamer(pipe.tokenizer, { skip_prompt: true, skip_special_tokens: true, callback_function: onToken });
|
|
16
|
+
rest[rest.length - 1] = options;
|
|
17
|
+
}
|
|
18
|
+
return pipe(input, ...rest);
|
|
19
|
+
});
|
|
20
|
+
streaming.dispose = () => pipe.dispose?.() ?? Promise.resolve();
|
|
21
|
+
return streaming;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The factory behind PIPELINE_FACTORY. It imports @huggingface/transformers
|
|
25
|
+
* lazily, on the first pipeline, so the library adds nothing to the initial
|
|
26
|
+
* bundle. Wrap it to add options or logging while keeping the lazy import:
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* const base = createDefaultPipelineFactory();
|
|
30
|
+
* const factory: PipelineFactory = (task, model, options) =>
|
|
31
|
+
* base(task, model, { ...options, revision: 'v2' });
|
|
32
|
+
* providers: [{ provide: PIPELINE_FACTORY, useValue: factory }]
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* `load` is the importer. Tests pass a stub module; apps that need to
|
|
36
|
+
* configure Transformers.js (`env.allowRemoteModels`, `env.localModelPath`)
|
|
37
|
+
* do it there, before returning the module.
|
|
38
|
+
*/
|
|
39
|
+
function createDefaultPipelineFactory(load = importTransformers) {
|
|
40
|
+
return async (task, model, options) => {
|
|
41
|
+
const module = await load();
|
|
42
|
+
const pipe = (await module.pipeline(task, model, options));
|
|
43
|
+
return withTokenStreaming(pipe, module);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
9
46
|
const PIPELINE_FACTORY = new InjectionToken('ngx-transformers.pipeline-factory', {
|
|
10
47
|
providedIn: 'root',
|
|
11
|
-
factory: () =>
|
|
48
|
+
factory: () => createDefaultPipelineFactory(),
|
|
12
49
|
});
|
|
13
50
|
const NGX_TRANSFORMERS_CONFIG = new InjectionToken('ngx-transformers.config', {
|
|
14
51
|
providedIn: 'root',
|
|
@@ -26,6 +63,40 @@ const NGX_TRANSFORMERS_CONFIG = new InjectionToken('ngx-transformers.config', {
|
|
|
26
63
|
function provideTransformers(config) {
|
|
27
64
|
return makeEnvironmentProviders([{ provide: NGX_TRANSFORMERS_CONFIG, useValue: config }]);
|
|
28
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Runs every pipeline in a Web Worker so inference never blocks the UI.
|
|
68
|
+
* The worker file imports the worker entry point:
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* // transformers.worker.ts
|
|
72
|
+
* /// <reference lib="webworker" />
|
|
73
|
+
* import { runTransformersWorker } from 'ngx-transformers/worker';
|
|
74
|
+
* runTransformersWorker();
|
|
75
|
+
*
|
|
76
|
+
* // app.config.ts
|
|
77
|
+
* provideTransformersWorker(
|
|
78
|
+
* () => new Worker(new URL('./transformers.worker', import.meta.url), { type: 'module' }),
|
|
79
|
+
* )
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* The worker is created on the first pipeline and terminated with the
|
|
83
|
+
* injector. Handles, signals and the wrappers work unchanged; results must
|
|
84
|
+
* survive structured cloning (plain objects, arrays, typed arrays and
|
|
85
|
+
* tensors do). A worker that fails to start rejects the calls waiting on
|
|
86
|
+
* it, like a failed load in-thread.
|
|
87
|
+
*/
|
|
88
|
+
function provideTransformersWorker(createWorker) {
|
|
89
|
+
return makeEnvironmentProviders([
|
|
90
|
+
{
|
|
91
|
+
provide: PIPELINE_FACTORY,
|
|
92
|
+
useFactory: () => {
|
|
93
|
+
const factory = createWorkerPipelineFactory(createWorker);
|
|
94
|
+
inject(DestroyRef).onDestroy(() => factory.terminate());
|
|
95
|
+
return factory;
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
29
100
|
|
|
30
101
|
let webGpuProbe = null;
|
|
31
102
|
/**
|
|
@@ -58,6 +129,7 @@ async function probeWebGpu() {
|
|
|
58
129
|
}
|
|
59
130
|
}
|
|
60
131
|
|
|
132
|
+
const newSession = () => ({ pipe: null, loading: null, inFlight: 0, files: new Map() });
|
|
61
133
|
/**
|
|
62
134
|
* A lazily-loaded Transformers.js pipeline wrapped in signals.
|
|
63
135
|
*
|
|
@@ -72,18 +144,24 @@ class PipelineHandle {
|
|
|
72
144
|
/** idle -> loading -> ready <-> busy; error only on load failure. */
|
|
73
145
|
status = signal('idle', /* @ts-ignore */
|
|
74
146
|
...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
75
|
-
/** Download progress
|
|
147
|
+
/** Download progress: the file reported last, plus the total over all files. */
|
|
76
148
|
progress = signal(null, /* @ts-ignore */
|
|
77
149
|
...(ngDevMode ? [{ debugName: "progress" }] : /* istanbul ignore next */ []));
|
|
150
|
+
/** The last load error; cleared when a load starts. */
|
|
78
151
|
error = signal(null, /* @ts-ignore */
|
|
79
152
|
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
153
|
+
/** The error of the most recently started run, if it failed; cleared when a run starts. */
|
|
154
|
+
runError = signal(null, /* @ts-ignore */
|
|
155
|
+
...(ngDevMode ? [{ debugName: "runError" }] : /* istanbul ignore next */ []));
|
|
80
156
|
/** True once the model is usable (including while a run is in flight). */
|
|
81
157
|
ready = computed(() => this.status() === 'ready' || this.status() === 'busy', /* @ts-ignore */
|
|
82
158
|
...(ngDevMode ? [{ debugName: "ready" }] : /* istanbul ignore next */ []));
|
|
83
159
|
busy = computed(() => this.status() === 'busy' || this.status() === 'loading', /* @ts-ignore */
|
|
84
160
|
...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
|
|
85
|
-
|
|
86
|
-
|
|
161
|
+
session = newSession();
|
|
162
|
+
destroyed = false;
|
|
163
|
+
/** Counts runs so only the most recently started one writes runError. */
|
|
164
|
+
runSequence = 0;
|
|
87
165
|
constructor(request, factory, config) {
|
|
88
166
|
this.request = request;
|
|
89
167
|
this.factory = factory;
|
|
@@ -91,12 +169,22 @@ class PipelineHandle {
|
|
|
91
169
|
}
|
|
92
170
|
/** Downloads and initializes the model. Idempotent; retries after error. */
|
|
93
171
|
load() {
|
|
94
|
-
if (this.
|
|
172
|
+
if (this.destroyed)
|
|
173
|
+
return Promise.reject(destroyedError());
|
|
174
|
+
const session = this.session;
|
|
175
|
+
if (session.pipe)
|
|
95
176
|
return Promise.resolve();
|
|
96
|
-
|
|
97
|
-
|
|
177
|
+
session.loading ??= this.doLoad(session).finally(() => {
|
|
178
|
+
session.loading = null;
|
|
179
|
+
});
|
|
180
|
+
return session.loading;
|
|
98
181
|
}
|
|
99
|
-
/**
|
|
182
|
+
/**
|
|
183
|
+
* Runs the pipeline, loading the model first if needed. Besides the
|
|
184
|
+
* pipeline's own options, `runOptions.signal` (an AbortSignal) makes the
|
|
185
|
+
* run reject before it starts when the signal has already fired, which
|
|
186
|
+
* spares superseded runs the inference (see inferenceResource()).
|
|
187
|
+
*/
|
|
100
188
|
run(input, runOptions) {
|
|
101
189
|
return this.runWith(input, runOptions);
|
|
102
190
|
}
|
|
@@ -106,45 +194,96 @@ class PipelineHandle {
|
|
|
106
194
|
* classification takes (text, candidateLabels, options).
|
|
107
195
|
*/
|
|
108
196
|
async runWith(input, ...extraArgs) {
|
|
197
|
+
const { args, signal } = takeSignal(extraArgs);
|
|
109
198
|
await this.load();
|
|
199
|
+
// The model cannot be interrupted, but a run nobody wants any more
|
|
200
|
+
// (a newer input superseded it while the model loaded) need not start.
|
|
201
|
+
if (signal?.aborted)
|
|
202
|
+
throw abortError();
|
|
203
|
+
const session = this.session;
|
|
204
|
+
const pipe = session.pipe;
|
|
205
|
+
if (!pipe) {
|
|
206
|
+
throw new Error('PipelineHandle: disposed before the model finished loading.');
|
|
207
|
+
}
|
|
208
|
+
const sequence = ++this.runSequence;
|
|
209
|
+
session.inFlight++;
|
|
110
210
|
this.status.set('busy');
|
|
211
|
+
this.runError.set(null);
|
|
111
212
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
213
|
+
return (await pipe(input, ...args));
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
if (this.session === session && sequence === this.runSequence)
|
|
217
|
+
this.runError.set(err);
|
|
218
|
+
throw err;
|
|
114
219
|
}
|
|
115
220
|
finally {
|
|
116
|
-
//
|
|
117
|
-
|
|
221
|
+
// Back to ready once the last overlapping run finishes - a failed run
|
|
222
|
+
// leaves the model intact. A run that outlived dispose() settles its
|
|
223
|
+
// own, retired session and leaves the status alone.
|
|
224
|
+
session.inFlight--;
|
|
225
|
+
if (session.inFlight === 0 && this.session === session)
|
|
226
|
+
this.status.set('ready');
|
|
118
227
|
}
|
|
119
228
|
}
|
|
120
|
-
/**
|
|
229
|
+
/**
|
|
230
|
+
* Frees the model. A download still in flight is cancelled: its model is
|
|
231
|
+
* released on arrival and the handle stays idle. The handle can be loaded
|
|
232
|
+
* again afterwards; destroy() is the terminal variant.
|
|
233
|
+
*/
|
|
121
234
|
async dispose() {
|
|
122
|
-
const
|
|
123
|
-
this.
|
|
235
|
+
const session = this.session;
|
|
236
|
+
this.session = newSession();
|
|
124
237
|
this.status.set('idle');
|
|
125
238
|
this.progress.set(null);
|
|
126
|
-
await pipe?.dispose?.();
|
|
239
|
+
await session.pipe?.dispose?.();
|
|
127
240
|
}
|
|
128
|
-
|
|
241
|
+
/**
|
|
242
|
+
* dispose() for good: later load() and run() calls reject instead of
|
|
243
|
+
* downloading a model nobody would release. create*() registers this with
|
|
244
|
+
* the DestroyRef of the injection context, so a handle declared in a
|
|
245
|
+
* component ends with the component.
|
|
246
|
+
*/
|
|
247
|
+
destroy() {
|
|
248
|
+
this.destroyed = true;
|
|
249
|
+
return this.dispose();
|
|
250
|
+
}
|
|
251
|
+
async doLoad(session) {
|
|
129
252
|
this.status.set('loading');
|
|
130
253
|
this.error.set(null);
|
|
254
|
+
// A retry after a failed attempt starts its bookkeeping from scratch.
|
|
255
|
+
session.files = new Map();
|
|
256
|
+
this.progress.set(null);
|
|
131
257
|
const options = {
|
|
132
258
|
...this.config.pipelineOptions,
|
|
133
259
|
...this.request.options,
|
|
134
|
-
progress_callback: (event) => this.onProgress(event),
|
|
260
|
+
progress_callback: (event) => this.onProgress(event, session),
|
|
135
261
|
};
|
|
136
262
|
const device = await this.resolveDevice();
|
|
263
|
+
// Disposed while probing the device: do not start the download at all.
|
|
264
|
+
if (this.session !== session)
|
|
265
|
+
return;
|
|
137
266
|
const dtype = this.request.dtype ?? this.config.dtype;
|
|
138
267
|
if (device && device !== 'auto')
|
|
139
268
|
options['device'] = device;
|
|
140
269
|
if (dtype)
|
|
141
270
|
options['dtype'] = dtype;
|
|
142
271
|
try {
|
|
143
|
-
|
|
272
|
+
const pipe = await this.factory(this.request.task, this.request.model, options);
|
|
273
|
+
if (this.session !== session) {
|
|
274
|
+
// Disposed while downloading: nobody wants this model any more.
|
|
275
|
+
await pipe.dispose?.();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
session.pipe = pipe;
|
|
144
279
|
this.progress.set(null);
|
|
145
280
|
this.status.set('ready');
|
|
146
281
|
}
|
|
147
282
|
catch (err) {
|
|
283
|
+
// A cancelled load is not an error - dispose() already reset the handle.
|
|
284
|
+
if (this.session !== session)
|
|
285
|
+
return;
|
|
286
|
+
this.progress.set(null);
|
|
148
287
|
this.error.set(err);
|
|
149
288
|
this.status.set('error');
|
|
150
289
|
throw err;
|
|
@@ -160,25 +299,97 @@ class PipelineHandle {
|
|
|
160
299
|
return configured;
|
|
161
300
|
return this.config.autoDevice ? detectDevice() : configured;
|
|
162
301
|
}
|
|
163
|
-
|
|
164
|
-
|
|
302
|
+
/**
|
|
303
|
+
* Transformers.js reports each file on its own (initiate, download,
|
|
304
|
+
* progress, done). The session records every file it hears about; the
|
|
305
|
+
* signal is published on progress and done events only, so a file that
|
|
306
|
+
* has not transferred a byte never replaces the one that is moving.
|
|
307
|
+
*/
|
|
308
|
+
onProgress(event, session) {
|
|
309
|
+
// Events from a load that dispose() cancelled must not resurrect the bar.
|
|
310
|
+
if (this.session !== session || !event.file)
|
|
165
311
|
return;
|
|
312
|
+
const file = session.files.get(event.file) ?? { loaded: 0, total: 0, done: false };
|
|
313
|
+
session.files.set(event.file, file);
|
|
314
|
+
switch (event.status) {
|
|
315
|
+
case 'progress':
|
|
316
|
+
file.loaded = event.loaded ?? file.loaded;
|
|
317
|
+
file.total = event.total ?? file.total;
|
|
318
|
+
break;
|
|
319
|
+
case 'done':
|
|
320
|
+
file.done = true;
|
|
321
|
+
if (file.total > 0)
|
|
322
|
+
file.loaded = file.total;
|
|
323
|
+
break;
|
|
324
|
+
default:
|
|
325
|
+
return; // initiate / download: recorded, not published
|
|
326
|
+
}
|
|
327
|
+
const percent = event.status === 'done' ? 100 : Math.round(event.progress ?? 0);
|
|
328
|
+
const overall = overallOf(session.files);
|
|
166
329
|
this.progress.set({
|
|
167
330
|
file: event.file,
|
|
168
|
-
progress:
|
|
169
|
-
loadedBytes:
|
|
170
|
-
totalBytes:
|
|
331
|
+
progress: percent,
|
|
332
|
+
loadedBytes: file.loaded,
|
|
333
|
+
totalBytes: file.total,
|
|
334
|
+
...(overall ? { overall } : {}),
|
|
171
335
|
});
|
|
172
336
|
}
|
|
173
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* The total over the files seen so far. Transformers.js fetches the small
|
|
340
|
+
* files (config, tokenizer) before it starts the weights, so a total
|
|
341
|
+
* computed before the weights are known would read 100% and then collapse;
|
|
342
|
+
* it is therefore withheld until a weights file has been seen.
|
|
343
|
+
*/
|
|
344
|
+
function overallOf(files) {
|
|
345
|
+
let weightsSeen = false;
|
|
346
|
+
let loadedBytes = 0;
|
|
347
|
+
let totalBytes = 0;
|
|
348
|
+
let filesDone = 0;
|
|
349
|
+
for (const [name, file] of files) {
|
|
350
|
+
if (name.includes('.onnx'))
|
|
351
|
+
weightsSeen = true;
|
|
352
|
+
if (file.total > 0) {
|
|
353
|
+
loadedBytes += Math.min(file.loaded, file.total);
|
|
354
|
+
totalBytes += file.total;
|
|
355
|
+
}
|
|
356
|
+
if (file.done)
|
|
357
|
+
filesDone++;
|
|
358
|
+
}
|
|
359
|
+
if (!weightsSeen)
|
|
360
|
+
return undefined;
|
|
361
|
+
return {
|
|
362
|
+
progress: totalBytes > 0 ? Math.round((loadedBytes / totalBytes) * 100) : 0,
|
|
363
|
+
loadedBytes,
|
|
364
|
+
totalBytes,
|
|
365
|
+
files: files.size,
|
|
366
|
+
filesDone,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/** Pulls `signal` out of a call's trailing options object. */
|
|
370
|
+
function takeSignal(args) {
|
|
371
|
+
const last = args[args.length - 1];
|
|
372
|
+
if (typeof last !== 'object' || last === null || Array.isArray(last))
|
|
373
|
+
return { args };
|
|
374
|
+
const { signal, ...rest } = last;
|
|
375
|
+
if (!(signal instanceof AbortSignal))
|
|
376
|
+
return { args };
|
|
377
|
+
return { args: [...args.slice(0, -1), rest], signal };
|
|
378
|
+
}
|
|
379
|
+
function abortError() {
|
|
380
|
+
return new DOMException('PipelineHandle: the run was aborted before it started.', 'AbortError');
|
|
381
|
+
}
|
|
382
|
+
function destroyedError() {
|
|
383
|
+
return new Error('PipelineHandle: destroyed with its component; create a new handle.');
|
|
384
|
+
}
|
|
174
385
|
/**
|
|
175
386
|
* Creates a PipelineHandle in an injection context (constructor, field
|
|
176
|
-
* initializer, or runInInjectionContext). The handle is
|
|
387
|
+
* initializer, or runInInjectionContext). The handle is destroyed with the
|
|
177
388
|
* surrounding component/injector.
|
|
178
389
|
*/
|
|
179
390
|
function createPipeline(request) {
|
|
180
391
|
const handle = new PipelineHandle(request, inject(PIPELINE_FACTORY), inject(NGX_TRANSFORMERS_CONFIG));
|
|
181
|
-
inject(DestroyRef, { optional: true })?.onDestroy(() => void handle.
|
|
392
|
+
inject(DestroyRef, { optional: true })?.onDestroy(() => void handle.destroy());
|
|
182
393
|
return handle;
|
|
183
394
|
}
|
|
184
395
|
|
|
@@ -190,8 +401,8 @@ const DEFAULT_TEXT_CLASSIFICATION_MODEL = 'Xenova/distilbert-base-uncased-finetu
|
|
|
190
401
|
*/
|
|
191
402
|
class TextClassifier extends PipelineHandle {
|
|
192
403
|
/** Classifies one text; resolves to labels sorted by score (top first). */
|
|
193
|
-
async classify(text, topK = 1) {
|
|
194
|
-
const out = await this.run(text, { top_k: topK });
|
|
404
|
+
async classify(text, topK = 1, options = {}) {
|
|
405
|
+
const out = await this.run(text, { top_k: topK, signal: options.signal });
|
|
195
406
|
// Single input: transformers.js returns a flat array of {label, score}.
|
|
196
407
|
const flat = (Array.isArray(out[0]) ? out[0] : out);
|
|
197
408
|
return [...flat].sort((a, b) => b.score - a.score);
|
|
@@ -200,7 +411,7 @@ class TextClassifier extends PipelineHandle {
|
|
|
200
411
|
/** Creates a TextClassifier in an injection context. */
|
|
201
412
|
function createTextClassifier(options = {}) {
|
|
202
413
|
const classifier = new TextClassifier({ task: 'text-classification', model: DEFAULT_TEXT_CLASSIFICATION_MODEL, ...options }, inject(PIPELINE_FACTORY), inject(NGX_TRANSFORMERS_CONFIG));
|
|
203
|
-
inject(DestroyRef, { optional: true })?.onDestroy(() => void classifier.
|
|
414
|
+
inject(DestroyRef, { optional: true })?.onDestroy(() => void classifier.destroy());
|
|
204
415
|
return classifier;
|
|
205
416
|
}
|
|
206
417
|
|
|
@@ -228,25 +439,29 @@ function cosineSimilarity(a, b) {
|
|
|
228
439
|
*/
|
|
229
440
|
class TextEmbedder extends PipelineHandle {
|
|
230
441
|
/** Embeds one or many texts; always resolves to one vector per text. */
|
|
231
|
-
async embed(texts) {
|
|
442
|
+
async embed(texts, options = {}) {
|
|
232
443
|
const input = Array.isArray(texts) ? texts : [texts];
|
|
233
444
|
if (input.length === 0)
|
|
234
445
|
return [];
|
|
235
|
-
const out = await this.run(input, { pooling: 'mean', normalize: true });
|
|
446
|
+
const out = await this.run(input, { pooling: 'mean', normalize: true, signal: options.signal });
|
|
236
447
|
return toRows(out);
|
|
237
448
|
}
|
|
238
449
|
/** Cosine similarity of two texts in [-1, 1]. */
|
|
239
|
-
async similarity(a, b) {
|
|
240
|
-
const [va, vb] = await this.embed([a, b]);
|
|
450
|
+
async similarity(a, b, options = {}) {
|
|
451
|
+
const [va, vb] = await this.embed([a, b], options);
|
|
241
452
|
return cosineSimilarity(va, vb);
|
|
242
453
|
}
|
|
243
454
|
/** Ranks documents against a query, most similar first. */
|
|
244
|
-
async rank(query, documents) {
|
|
455
|
+
async rank(query, documents, options = {}) {
|
|
245
456
|
if (documents.length === 0)
|
|
246
457
|
return [];
|
|
247
|
-
const [queryVec, ...docVecs] = await this.embed([query, ...documents]);
|
|
458
|
+
const [queryVec, ...docVecs] = await this.embed([query, ...documents], options);
|
|
248
459
|
return docVecs
|
|
249
|
-
.map((vec, index) => ({
|
|
460
|
+
.map((vec, index) => ({
|
|
461
|
+
text: documents[index],
|
|
462
|
+
score: cosineSimilarity(queryVec, vec),
|
|
463
|
+
index,
|
|
464
|
+
}))
|
|
250
465
|
.sort((a, b) => b.score - a.score);
|
|
251
466
|
}
|
|
252
467
|
}
|
|
@@ -264,7 +479,7 @@ function toRows(out) {
|
|
|
264
479
|
/** Creates a TextEmbedder in an injection context. */
|
|
265
480
|
function createTextEmbedder(options = {}) {
|
|
266
481
|
const embedder = new TextEmbedder({ task: 'feature-extraction', model: DEFAULT_EMBEDDING_MODEL, ...options }, inject(PIPELINE_FACTORY), inject(NGX_TRANSFORMERS_CONFIG));
|
|
267
|
-
inject(DestroyRef, { optional: true })?.onDestroy(() => void embedder.
|
|
482
|
+
inject(DestroyRef, { optional: true })?.onDestroy(() => void embedder.destroy());
|
|
268
483
|
return embedder;
|
|
269
484
|
}
|
|
270
485
|
|
|
@@ -324,6 +539,8 @@ class SpeechRecognizer extends PipelineHandle {
|
|
|
324
539
|
runOptions['language'] = options.language;
|
|
325
540
|
if (options.task !== undefined)
|
|
326
541
|
runOptions['task'] = options.task;
|
|
542
|
+
if (options.signal !== undefined)
|
|
543
|
+
runOptions['signal'] = options.signal;
|
|
327
544
|
const raw = await this.run(input, runOptions);
|
|
328
545
|
const first = (Array.isArray(raw) ? raw[0] : raw) ?? {};
|
|
329
546
|
const chunks = first.chunks?.map((c) => ({
|
|
@@ -343,7 +560,7 @@ class SpeechRecognizer extends PipelineHandle {
|
|
|
343
560
|
*/
|
|
344
561
|
function createSpeechRecognizer(options = {}) {
|
|
345
562
|
const recognizer = new SpeechRecognizer({ task: 'automatic-speech-recognition', model: DEFAULT_ASR_MODEL, dtype: 'q4', ...options }, inject(PIPELINE_FACTORY), inject(NGX_TRANSFORMERS_CONFIG));
|
|
346
|
-
inject(DestroyRef, { optional: true })?.onDestroy(() => void recognizer.
|
|
563
|
+
inject(DestroyRef, { optional: true })?.onDestroy(() => void recognizer.destroy());
|
|
347
564
|
return recognizer;
|
|
348
565
|
}
|
|
349
566
|
|
|
@@ -370,6 +587,8 @@ class ZeroShotClassifier extends PipelineHandle {
|
|
|
370
587
|
runOptions['multi_label'] = options.multiLabel;
|
|
371
588
|
if (options.hypothesisTemplate !== undefined)
|
|
372
589
|
runOptions['hypothesis_template'] = options.hypothesisTemplate;
|
|
590
|
+
if (options.signal !== undefined)
|
|
591
|
+
runOptions['signal'] = options.signal;
|
|
373
592
|
const raw = await this.runWith(text, [...labels], runOptions);
|
|
374
593
|
const first = Array.isArray(raw) ? raw[0] : raw;
|
|
375
594
|
const scoredLabels = first?.labels ?? [];
|
|
@@ -382,7 +601,7 @@ class ZeroShotClassifier extends PipelineHandle {
|
|
|
382
601
|
/** Creates a ZeroShotClassifier in an injection context. */
|
|
383
602
|
function createZeroShotClassifier(options = {}) {
|
|
384
603
|
const classifier = new ZeroShotClassifier({ task: 'zero-shot-classification', model: DEFAULT_ZERO_SHOT_MODEL, ...options }, inject(PIPELINE_FACTORY), inject(NGX_TRANSFORMERS_CONFIG));
|
|
385
|
-
inject(DestroyRef, { optional: true })?.onDestroy(() => void classifier.
|
|
604
|
+
inject(DestroyRef, { optional: true })?.onDestroy(() => void classifier.destroy());
|
|
386
605
|
return classifier;
|
|
387
606
|
}
|
|
388
607
|
|
|
@@ -418,12 +637,15 @@ class Translator {
|
|
|
418
637
|
active = signal(null, /* @ts-ignore */
|
|
419
638
|
...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
420
639
|
handles = new Map();
|
|
640
|
+
destroyed = false;
|
|
421
641
|
status = computed(() => this.active()?.status() ?? 'idle', /* @ts-ignore */
|
|
422
642
|
...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
423
643
|
progress = computed(() => this.active()?.progress() ?? null, /* @ts-ignore */
|
|
424
644
|
...(ngDevMode ? [{ debugName: "progress" }] : /* istanbul ignore next */ []));
|
|
425
645
|
error = computed(() => this.active()?.error() ?? null, /* @ts-ignore */
|
|
426
646
|
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
647
|
+
runError = computed(() => this.active()?.runError() ?? null, /* @ts-ignore */
|
|
648
|
+
...(ngDevMode ? [{ debugName: "runError" }] : /* istanbul ignore next */ []));
|
|
427
649
|
ready = computed(() => this.active()?.ready() ?? false, /* @ts-ignore */
|
|
428
650
|
...(ngDevMode ? [{ debugName: "ready" }] : /* istanbul ignore next */ []));
|
|
429
651
|
busy = computed(() => this.active()?.busy() ?? false, /* @ts-ignore */
|
|
@@ -434,13 +656,13 @@ class Translator {
|
|
|
434
656
|
this.config = config;
|
|
435
657
|
}
|
|
436
658
|
/** Downloads the model for a pair ahead of the first translate() call. */
|
|
437
|
-
load(pair = {}) {
|
|
659
|
+
async load(pair = {}) {
|
|
438
660
|
return this.handleFor(pair).load();
|
|
439
661
|
}
|
|
440
662
|
/** Translates one text; the pair defaults to the one given at creation. */
|
|
441
663
|
async translate(text, pair = {}) {
|
|
442
664
|
const handle = this.handleFor(pair);
|
|
443
|
-
const raw = await handle.run(text, this.languageOptions(pair));
|
|
665
|
+
const raw = await handle.run(text, { ...this.languageOptions(pair), signal: pair.signal });
|
|
444
666
|
const first = Array.isArray(raw) ? raw[0] : raw;
|
|
445
667
|
return (first?.translation_text ?? '').trim();
|
|
446
668
|
}
|
|
@@ -460,15 +682,32 @@ class Translator {
|
|
|
460
682
|
* keep several pairs warm and want a progress line per model.
|
|
461
683
|
*/
|
|
462
684
|
handleFor(pair = {}) {
|
|
685
|
+
if (this.destroyed) {
|
|
686
|
+
throw new Error('Translator: destroyed with its component; create a new translator.');
|
|
687
|
+
}
|
|
463
688
|
const model = this.modelFor(pair);
|
|
464
689
|
let handle = this.handles.get(model);
|
|
465
690
|
if (!handle) {
|
|
466
|
-
handle = new PipelineHandle({
|
|
691
|
+
handle = new PipelineHandle({
|
|
692
|
+
task: 'translation',
|
|
693
|
+
model,
|
|
694
|
+
device: this.options.device,
|
|
695
|
+
dtype: this.options.dtype,
|
|
696
|
+
options: this.options.options,
|
|
697
|
+
}, this.factory, this.config);
|
|
467
698
|
this.handles.set(model, handle);
|
|
468
699
|
}
|
|
469
700
|
this.active.set(handle);
|
|
470
701
|
return handle;
|
|
471
702
|
}
|
|
703
|
+
/**
|
|
704
|
+
* dispose() for good: later calls reject instead of loading a model nobody
|
|
705
|
+
* would release. createTranslator() registers this with the DestroyRef.
|
|
706
|
+
*/
|
|
707
|
+
destroy() {
|
|
708
|
+
this.destroyed = true;
|
|
709
|
+
return this.dispose();
|
|
710
|
+
}
|
|
472
711
|
/** Frees every model. The translator can be used again afterwards. */
|
|
473
712
|
async dispose() {
|
|
474
713
|
const handles = [...this.handles.values()];
|
|
@@ -493,10 +732,124 @@ class Translator {
|
|
|
493
732
|
/** Creates a Translator in an injection context. */
|
|
494
733
|
function createTranslator(options = {}) {
|
|
495
734
|
const translator = new Translator(options, inject(PIPELINE_FACTORY), inject(NGX_TRANSFORMERS_CONFIG));
|
|
496
|
-
inject(DestroyRef, { optional: true })?.onDestroy(() => void translator.
|
|
735
|
+
inject(DestroyRef, { optional: true })?.onDestroy(() => void translator.destroy());
|
|
497
736
|
return translator;
|
|
498
737
|
}
|
|
499
738
|
|
|
739
|
+
// The smallest instruct model that holds a conversation; its repository
|
|
740
|
+
// ships the ONNX variants Transformers.js loads (~100 MB at q4).
|
|
741
|
+
const DEFAULT_TEXT_GENERATION_MODEL = 'HuggingFaceTB/SmolLM2-135M-Instruct';
|
|
742
|
+
/**
|
|
743
|
+
* Text generation with a small language model, streamed token by token.
|
|
744
|
+
* The default checkpoint is SmolLM2-135M-Instruct; swap `model` for any
|
|
745
|
+
* Transformers.js text-generation checkpoint (Qwen2.5, Llama, Phi...).
|
|
746
|
+
*/
|
|
747
|
+
class TextGenerator extends PipelineHandle {
|
|
748
|
+
/** The text generated so far by the latest generate() call; reset when a call starts. */
|
|
749
|
+
output = signal('', /* @ts-ignore */
|
|
750
|
+
...(ngDevMode ? [{ debugName: "output" }] : /* istanbul ignore next */ []));
|
|
751
|
+
/** Only the most recently started call may write output. */
|
|
752
|
+
generation = 0;
|
|
753
|
+
warnedNoStreaming = false;
|
|
754
|
+
/**
|
|
755
|
+
* Generates a reply to a prompt or a chat. Tokens stream into `output`
|
|
756
|
+
* (and `options.onToken`) while the model runs; resolves with the full,
|
|
757
|
+
* trimmed reply.
|
|
758
|
+
*/
|
|
759
|
+
async generate(prompt, options = {}) {
|
|
760
|
+
const runOptions = { max_new_tokens: options.maxNewTokens ?? 256 };
|
|
761
|
+
if (options.doSample !== undefined)
|
|
762
|
+
runOptions['do_sample'] = options.doSample;
|
|
763
|
+
if (options.temperature !== undefined)
|
|
764
|
+
runOptions['temperature'] = options.temperature;
|
|
765
|
+
if (options.topP !== undefined)
|
|
766
|
+
runOptions['top_p'] = options.topP;
|
|
767
|
+
if (options.topK !== undefined)
|
|
768
|
+
runOptions['top_k'] = options.topK;
|
|
769
|
+
if (options.repetitionPenalty !== undefined) {
|
|
770
|
+
runOptions['repetition_penalty'] = options.repetitionPenalty;
|
|
771
|
+
}
|
|
772
|
+
// A plain prompt would otherwise come back with the prompt in front.
|
|
773
|
+
if (typeof prompt === 'string')
|
|
774
|
+
runOptions['return_full_text'] = false;
|
|
775
|
+
if (options.signal !== undefined)
|
|
776
|
+
runOptions['signal'] = options.signal;
|
|
777
|
+
const generation = ++this.generation;
|
|
778
|
+
let streamed = 0;
|
|
779
|
+
this.output.set('');
|
|
780
|
+
runOptions['onToken'] = (text) => {
|
|
781
|
+
streamed++;
|
|
782
|
+
if (generation === this.generation)
|
|
783
|
+
this.output.update((current) => current + text);
|
|
784
|
+
options.onToken?.(text);
|
|
785
|
+
};
|
|
786
|
+
const raw = await this.run(prompt, runOptions);
|
|
787
|
+
const first = Array.isArray(raw) ? raw[0] : raw;
|
|
788
|
+
const generated = first?.generated_text;
|
|
789
|
+
const text = Array.isArray(generated) ? (generated.at(-1)?.content ?? '') : (generated ?? '');
|
|
790
|
+
const reply = text.trim();
|
|
791
|
+
if (generation === this.generation)
|
|
792
|
+
this.output.set(reply);
|
|
793
|
+
if (streamed === 0 && reply && !this.warnedNoStreaming) {
|
|
794
|
+
// Streaming relies on the pipeline factory turning the onToken run
|
|
795
|
+
// option into a TextStreamer, which the built-in factories do. A
|
|
796
|
+
// custom factory that returns a raw pipeline drops it silently.
|
|
797
|
+
this.warnedNoStreaming = true;
|
|
798
|
+
console.warn('ngx-transformers: the reply arrived without streamed tokens. A custom PIPELINE_FACTORY must wrap createDefaultPipelineFactory() (or handle the onToken run option) for TextGenerator.output to stream.');
|
|
799
|
+
}
|
|
800
|
+
return reply;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Creates a TextGenerator in an injection context; destroyed with the
|
|
805
|
+
* component. dtype defaults to 'q4', the size/quality sweet spot for small
|
|
806
|
+
* decoders on the WebAssembly runtime.
|
|
807
|
+
*/
|
|
808
|
+
function createTextGenerator(options = {}) {
|
|
809
|
+
const generator = new TextGenerator({ task: 'text-generation', model: DEFAULT_TEXT_GENERATION_MODEL, dtype: 'q4', ...options }, inject(PIPELINE_FACTORY), inject(NGX_TRANSFORMERS_CONFIG));
|
|
810
|
+
inject(DestroyRef, { optional: true })?.onDestroy(() => void generator.destroy());
|
|
811
|
+
return generator;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Runs inference whenever an input signal changes, as an Angular resource:
|
|
816
|
+
* `value()`, `isLoading()`, `error()` and `status()` are signals, and only
|
|
817
|
+
* the result for the latest input is kept.
|
|
818
|
+
*
|
|
819
|
+
* ```ts
|
|
820
|
+
* readonly text = signal('');
|
|
821
|
+
* readonly classifier = createTextClassifier();
|
|
822
|
+
* readonly sentiment = inferenceResource({
|
|
823
|
+
* input: () => this.text().trim() || undefined,
|
|
824
|
+
* run: (text, signal) => this.classifier.classify(text, 1, { signal }),
|
|
825
|
+
* debounceMs: 300,
|
|
826
|
+
* });
|
|
827
|
+
* // template: @if (sentiment.value(); as result) { {{ result[0].label }} }
|
|
828
|
+
* ```
|
|
829
|
+
*/
|
|
830
|
+
function inferenceResource(options) {
|
|
831
|
+
const injector = options.injector ?? inject(Injector);
|
|
832
|
+
const input = options.debounceMs
|
|
833
|
+
? debounced(options.input, options.debounceMs, injector)
|
|
834
|
+
: options.input;
|
|
835
|
+
return resource({
|
|
836
|
+
params: () => input(),
|
|
837
|
+
loader: ({ params, abortSignal }) => options.run(params, abortSignal),
|
|
838
|
+
injector,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
/** A signal that follows `source` once it has been stable for `ms`. */
|
|
842
|
+
function debounced(source, ms, injector) {
|
|
843
|
+
const out = signal(untracked(source), /* @ts-ignore */
|
|
844
|
+
...(ngDevMode ? [{ debugName: "out" }] : /* istanbul ignore next */ []));
|
|
845
|
+
effect((onCleanup) => {
|
|
846
|
+
const value = source();
|
|
847
|
+
const timer = setTimeout(() => out.set(value), ms);
|
|
848
|
+
onCleanup(() => clearTimeout(timer));
|
|
849
|
+
}, { injector });
|
|
850
|
+
return out.asReadonly();
|
|
851
|
+
}
|
|
852
|
+
|
|
500
853
|
/**
|
|
501
854
|
* Microphone capture with a signals API. start() asks for mic permission
|
|
502
855
|
* and records; stop() resolves with the encoded audio Blob - pass it to
|
|
@@ -598,7 +951,8 @@ function createMicRecorder(deps = {}) {
|
|
|
598
951
|
|
|
599
952
|
/**
|
|
600
953
|
* Drop-in status line for a PipelineHandle: shows model download progress
|
|
601
|
-
* while loading, then the ready/busy/error
|
|
954
|
+
* (over every file of the model) while loading, then the ready/busy/error
|
|
955
|
+
* state. Themeable via CSS custom
|
|
602
956
|
* properties (--nt-accent, --nt-ink, --nt-muted, --nt-track).
|
|
603
957
|
*
|
|
604
958
|
* ```html
|
|
@@ -613,6 +967,10 @@ class ModelProgressComponent {
|
|
|
613
967
|
/** Labels per status; override to localize. */
|
|
614
968
|
labels = input({}, /* @ts-ignore */
|
|
615
969
|
...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
|
|
970
|
+
/** The bar follows the whole download when the handle reports it, else the current file. */
|
|
971
|
+
percent(progress) {
|
|
972
|
+
return progress.overall?.progress ?? progress.progress;
|
|
973
|
+
}
|
|
616
974
|
label = computed(() => {
|
|
617
975
|
const defaults = {
|
|
618
976
|
idle: 'Model not loaded',
|
|
@@ -624,22 +982,31 @@ class ModelProgressComponent {
|
|
|
624
982
|
return this.labels()[this.status()] ?? defaults[this.status()];
|
|
625
983
|
}, /* @ts-ignore */
|
|
626
984
|
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
627
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
628
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
985
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ModelProgressComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
986
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ModelProgressComponent, isStandalone: true, selector: "ngx-model-progress", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null }, progress: { classPropertyName: "progress", publicName: "progress", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
629
987
|
<div class="nt-row" [class.nt-error]="status() === 'error'" role="status">
|
|
630
988
|
<span class="nt-dot" [attr.data-status]="status()"></span>
|
|
631
989
|
<span class="nt-label">{{ label() }}</span>
|
|
632
990
|
@if (status() === 'loading' && progress(); as p) {
|
|
633
991
|
<span class="nt-file">{{ p.file }}</span>
|
|
634
|
-
|
|
635
|
-
<
|
|
992
|
+
@if (p.overall && p.overall.files > 1) {
|
|
993
|
+
<span class="nt-files">{{ p.overall.filesDone }}/{{ p.overall.files }} files</span>
|
|
994
|
+
}
|
|
995
|
+
<div
|
|
996
|
+
class="nt-track"
|
|
997
|
+
role="progressbar"
|
|
998
|
+
[attr.aria-valuenow]="percent(p)"
|
|
999
|
+
aria-valuemin="0"
|
|
1000
|
+
aria-valuemax="100"
|
|
1001
|
+
>
|
|
1002
|
+
<div class="nt-fill" [style.width.%]="percent(p)"></div>
|
|
636
1003
|
</div>
|
|
637
|
-
<span class="nt-pct">{{ p
|
|
1004
|
+
<span class="nt-pct">{{ percent(p) }}%</span>
|
|
638
1005
|
}
|
|
639
1006
|
</div>
|
|
640
|
-
`, isInline: true, styles: [":host{--nt-accent: #f59e0b;--nt-ink: #1e293b;--nt-muted: #64748b;--nt-track: #e2e8f0;display:block;font-size:12.5px;color:var(--nt-ink)}.nt-row{display:flex;align-items:center;gap:8px}.nt-dot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--nt-muted)}.nt-dot[data-status=loading]{background:var(--nt-accent);animation:nt-blink 1s ease-in-out infinite}.nt-dot[data-status=ready]{background:#22c55e}.nt-dot[data-status=busy]{background:#3b82f6;animation:nt-blink .7s ease-in-out infinite}.nt-dot[data-status=error]{background:#dc2626}.nt-label{font-weight:600}.nt-file{color:var(--nt-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px}.nt-track{flex:1;min-width:60px;height:6px;border-radius:3px;background:var(--nt-track);overflow:hidden}.nt-fill{height:100%;background:var(--nt-accent);border-radius:3px;transition:width .12s linear}.nt-pct{font-variant-numeric:tabular-nums;color:var(--nt-muted)}.nt-error .nt-label{color:#dc2626}@keyframes nt-blink{0%,to{opacity:1}50%{opacity:.35}}@media(prefers-reduced-motion:reduce){.nt-dot{animation:none}.nt-fill{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1007
|
+
`, isInline: true, styles: [":host{--nt-accent: #f59e0b;--nt-ink: #1e293b;--nt-muted: #64748b;--nt-track: #e2e8f0;display:block;font-size:12.5px;color:var(--nt-ink)}.nt-row{display:flex;align-items:center;gap:8px}.nt-dot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--nt-muted)}.nt-dot[data-status=loading]{background:var(--nt-accent);animation:nt-blink 1s ease-in-out infinite}.nt-dot[data-status=ready]{background:#22c55e}.nt-dot[data-status=busy]{background:#3b82f6;animation:nt-blink .7s ease-in-out infinite}.nt-dot[data-status=error]{background:#dc2626}.nt-label{font-weight:600}.nt-file{color:var(--nt-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px}.nt-track{flex:1;min-width:60px;height:6px;border-radius:3px;background:var(--nt-track);overflow:hidden}.nt-fill{height:100%;background:var(--nt-accent);border-radius:3px;transition:width .12s linear}.nt-files{color:var(--nt-muted);white-space:nowrap}.nt-pct{font-variant-numeric:tabular-nums;color:var(--nt-muted)}.nt-error .nt-label{color:#dc2626}@keyframes nt-blink{0%,to{opacity:1}50%{opacity:.35}}@media(prefers-reduced-motion:reduce){.nt-dot{animation:none}.nt-fill{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
641
1008
|
}
|
|
642
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
1009
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ModelProgressComponent, decorators: [{
|
|
643
1010
|
type: Component,
|
|
644
1011
|
args: [{ selector: 'ngx-model-progress', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
645
1012
|
<div class="nt-row" [class.nt-error]="status() === 'error'" role="status">
|
|
@@ -647,22 +1014,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
647
1014
|
<span class="nt-label">{{ label() }}</span>
|
|
648
1015
|
@if (status() === 'loading' && progress(); as p) {
|
|
649
1016
|
<span class="nt-file">{{ p.file }}</span>
|
|
650
|
-
|
|
651
|
-
<
|
|
1017
|
+
@if (p.overall && p.overall.files > 1) {
|
|
1018
|
+
<span class="nt-files">{{ p.overall.filesDone }}/{{ p.overall.files }} files</span>
|
|
1019
|
+
}
|
|
1020
|
+
<div
|
|
1021
|
+
class="nt-track"
|
|
1022
|
+
role="progressbar"
|
|
1023
|
+
[attr.aria-valuenow]="percent(p)"
|
|
1024
|
+
aria-valuemin="0"
|
|
1025
|
+
aria-valuemax="100"
|
|
1026
|
+
>
|
|
1027
|
+
<div class="nt-fill" [style.width.%]="percent(p)"></div>
|
|
652
1028
|
</div>
|
|
653
|
-
<span class="nt-pct">{{ p
|
|
1029
|
+
<span class="nt-pct">{{ percent(p) }}%</span>
|
|
654
1030
|
}
|
|
655
1031
|
</div>
|
|
656
|
-
`, styles: [":host{--nt-accent: #f59e0b;--nt-ink: #1e293b;--nt-muted: #64748b;--nt-track: #e2e8f0;display:block;font-size:12.5px;color:var(--nt-ink)}.nt-row{display:flex;align-items:center;gap:8px}.nt-dot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--nt-muted)}.nt-dot[data-status=loading]{background:var(--nt-accent);animation:nt-blink 1s ease-in-out infinite}.nt-dot[data-status=ready]{background:#22c55e}.nt-dot[data-status=busy]{background:#3b82f6;animation:nt-blink .7s ease-in-out infinite}.nt-dot[data-status=error]{background:#dc2626}.nt-label{font-weight:600}.nt-file{color:var(--nt-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px}.nt-track{flex:1;min-width:60px;height:6px;border-radius:3px;background:var(--nt-track);overflow:hidden}.nt-fill{height:100%;background:var(--nt-accent);border-radius:3px;transition:width .12s linear}.nt-pct{font-variant-numeric:tabular-nums;color:var(--nt-muted)}.nt-error .nt-label{color:#dc2626}@keyframes nt-blink{0%,to{opacity:1}50%{opacity:.35}}@media(prefers-reduced-motion:reduce){.nt-dot{animation:none}.nt-fill{transition:none}}\n"] }]
|
|
1032
|
+
`, styles: [":host{--nt-accent: #f59e0b;--nt-ink: #1e293b;--nt-muted: #64748b;--nt-track: #e2e8f0;display:block;font-size:12.5px;color:var(--nt-ink)}.nt-row{display:flex;align-items:center;gap:8px}.nt-dot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--nt-muted)}.nt-dot[data-status=loading]{background:var(--nt-accent);animation:nt-blink 1s ease-in-out infinite}.nt-dot[data-status=ready]{background:#22c55e}.nt-dot[data-status=busy]{background:#3b82f6;animation:nt-blink .7s ease-in-out infinite}.nt-dot[data-status=error]{background:#dc2626}.nt-label{font-weight:600}.nt-file{color:var(--nt-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px}.nt-track{flex:1;min-width:60px;height:6px;border-radius:3px;background:var(--nt-track);overflow:hidden}.nt-fill{height:100%;background:var(--nt-accent);border-radius:3px;transition:width .12s linear}.nt-files{color:var(--nt-muted);white-space:nowrap}.nt-pct{font-variant-numeric:tabular-nums;color:var(--nt-muted)}.nt-error .nt-label{color:#dc2626}@keyframes nt-blink{0%,to{opacity:1}50%{opacity:.35}}@media(prefers-reduced-motion:reduce){.nt-dot{animation:none}.nt-fill{transition:none}}\n"] }]
|
|
657
1033
|
}], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], progress: [{ type: i0.Input, args: [{ isSignal: true, alias: "progress", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }] } });
|
|
658
1034
|
|
|
659
|
-
|
|
660
|
-
* Public API
|
|
1035
|
+
/**
|
|
1036
|
+
* Public API surface of ngx-transformers: handles, wrappers, configuration
|
|
1037
|
+
* and the progress component.
|
|
1038
|
+
*
|
|
1039
|
+
* @module ngx-transformers
|
|
661
1040
|
*/
|
|
662
1041
|
|
|
663
1042
|
/**
|
|
664
1043
|
* Generated bundle index. Do not edit.
|
|
665
1044
|
*/
|
|
666
1045
|
|
|
667
|
-
export { DEFAULT_ASR_MODEL, DEFAULT_EMBEDDING_MODEL, DEFAULT_TEXT_CLASSIFICATION_MODEL, DEFAULT_ZERO_SHOT_MODEL, MicRecorder, ModelProgressComponent, NGX_TRANSFORMERS_CONFIG, PIPELINE_FACTORY, PipelineHandle, SpeechRecognizer, TextClassifier, TextEmbedder, Translator, WHISPER_SAMPLE_RATE, ZeroShotClassifier, cosineSimilarity, createMicRecorder, createPipeline, createSpeechRecognizer, createTextClassifier, createTextEmbedder, createTranslator, createZeroShotClassifier, decodeAudio, defaultTranslationModel, detectDevice, hasWebGpu, provideTransformers, resetDeviceDetection, resolveTranslationModel };
|
|
1046
|
+
export { DEFAULT_ASR_MODEL, DEFAULT_EMBEDDING_MODEL, DEFAULT_TEXT_CLASSIFICATION_MODEL, DEFAULT_TEXT_GENERATION_MODEL, DEFAULT_ZERO_SHOT_MODEL, MicRecorder, ModelProgressComponent, NGX_TRANSFORMERS_CONFIG, PIPELINE_FACTORY, PipelineHandle, SpeechRecognizer, TextClassifier, TextEmbedder, TextGenerator, Translator, WHISPER_SAMPLE_RATE, ZeroShotClassifier, cosineSimilarity, createDefaultPipelineFactory, createMicRecorder, createPipeline, createSpeechRecognizer, createTextClassifier, createTextEmbedder, createTextGenerator, createTranslator, createZeroShotClassifier, decodeAudio, defaultTranslationModel, detectDevice, hasWebGpu, inferenceResource, provideTransformers, provideTransformersWorker, resetDeviceDetection, resolveTranslationModel };
|
|
668
1047
|
//# sourceMappingURL=ngx-transformers.mjs.map
|