Vokturz commited on
Commit
9283c8b
·
1 Parent(s): 08476ef

Refactor code for consistency and readability

Browse files

- Standardized string quotes from double to single in various components and files.
- Improved formatting and indentation in App.tsx, Footer.tsx, Header.tsx, and other components for better readability.
- Added a new worker for text classification and updated the existing zero-shot classification worker.
- Cleaned up unused lines and comments in types.ts and other files.
- Updated tailwind.config.js and tsconfig.json for consistency in formatting.
- Ensured all components and files follow a consistent coding style.

.prettierrc ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "tabWidth": 2,
3
+ "useTabs": false,
4
+ "trailingComma": "none",
5
+ "singleQuote": true,
6
+ "semi": false
7
+ }
README.md CHANGED
@@ -9,7 +9,6 @@ app_build_command: npm run build
9
  app_file: build/index.html
10
  ---
11
 
12
-
13
  # Getting Started with Create React App
14
 
15
  This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
 
9
  app_file: build/index.html
10
  ---
11
 
 
12
  # Getting Started with Create React App
13
 
14
  This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
pnpm-lock.yaml CHANGED
The diff for this file is too large to render. See raw diff
 
public/index.html CHANGED
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
  <meta charset="utf-8" />
 
1
+ <!doctype html>
2
  <html lang="en">
3
  <head>
4
  <meta charset="utf-8" />
react.md CHANGED
@@ -1,4 +1,3 @@
1
-
2
  # Building a React application
3
 
4
  In this tutorial, we'll be building a simple React application that performs multilingual translation using Transformers.js! The final product will look something like this:
@@ -6,16 +5,15 @@ In this tutorial, we'll be building a simple React application that performs mul
6
  ![Demo](https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/react-translator-demo.gif)
7
 
8
  Useful links:
 
9
  - [Demo site](https://huggingface.co/spaces/Xenova/react-translator)
10
  - [Source code](https://github.com/huggingface/transformers.js-examples/tree/main/react-translator)
11
 
12
-
13
  ## Prerequisites
14
 
15
  - [Node.js](https://nodejs.org/en/) version 18+
16
  - [npm](https://www.npmjs.com/) version 9+
17
 
18
-
19
  ## Step 1: Initialise the project
20
 
21
  For this tutorial, we will use [Vite](https://vitejs.dev/) to initialise our project. Vite is a build tool that allows us to quickly set up a React application with minimal configuration. Run the following command in your terminal:
@@ -51,65 +49,67 @@ npm install @huggingface/transformers
51
  ```
52
 
53
  For this application, we will use the [Xenova/nllb-200-distilled-600M](https://huggingface.co/Xenova/nllb-200-distilled-600M) model, which can perform multilingual translation among 200 languages. Before we start, there are 2 things we need to take note of:
 
54
  1. ML inference can be quite computationally intensive, so it's better to load and run the models in a separate thread from the main (UI) thread.
55
  2. Since the model is quite large (>1 GB), we don't want to download it until the user clicks the "Translate" button.
56
 
57
  We can achieve both of these goals by using a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) and some [React hooks](https://react.dev/reference/react).
58
 
59
  1. Create a file called `worker.js` in the `src` directory. This script will do all the heavy-lifing for us, including loading and running of the translation pipeline. To ensure the model is only loaded once, we will create the `MyTranslationPipeline` class which use the [singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) to lazily create a single instance of the pipeline when `getInstance` is first called, and use this pipeline for all subsequent calls:
60
- ```javascript
61
- import { pipeline, TextStreamer } from '@huggingface/transformers';
62
-
63
- class MyTranslationPipeline {
64
- static task = 'translation';
65
- static model = 'Xenova/nllb-200-distilled-600M';
66
- static instance = null;
67
-
68
- static async getInstance(progress_callback = null) {
69
- this.instance ??= pipeline(this.task, this.model, { progress_callback });
70
- return this.instance;
71
- }
72
- }
73
- ```
 
74
 
75
  2. Modify `App.jsx` in the `src` directory. This file is automatically created when initializing our React project, and will contain some boilerplate code. Inside the `App` function, let's create the web worker and store a reference to it using the `useRef` hook:
76
- ```jsx
77
- // Remember to import the relevant hooks
78
- import { useEffect, useRef, useState } from 'react'
79
- import './App.css'
80
-
81
- function App() {
82
- // Create a reference to the worker object.
83
- const worker = useRef(null);
84
-
85
- // We use the `useEffect` hook to setup the worker as soon as the `App` component is mounted.
86
- useEffect(() => {
87
- // Create the worker if it does not yet exist.
88
- worker.current ??= new Worker(new URL('./worker.js', import.meta.url), {
89
- type: 'module'
90
- });
91
-
92
- // Create a callback function for messages from the worker thread.
93
- const onMessageReceived = (e) => {
94
- // TODO: Will fill in later
95
- };
96
-
97
- // Attach the callback function as an event listener.
98
- worker.current.addEventListener('message', onMessageReceived);
99
-
100
- // Define a cleanup function for when the component is unmounted.
101
- return () => worker.current.removeEventListener('message', onMessageReceived);
102
- });
103
-
104
- return (
105
- // TODO: Rest of our app goes here...
106
- )
107
- }
108
 
109
- export default App
 
 
 
110
 
111
- ```
 
 
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  ## Step 3: Design the user interface
115
 
@@ -120,58 +120,58 @@ We recommend starting the development server again with `npm run dev`
120
 
121
  </Tip>
122
 
123
-
124
-
125
  First, let's define our components. Create a folder called `components` in the `src` directory, and create the following files:
126
- 1. `LanguageSelector.jsx`: This component will allow the user to select the input and output languages. Check out the full list of languages [here](https://github.com/huggingface/transformers.js-examples/tree/main/react-translator/src/components/LanguageSelector.jsx).
127
- ```jsx
128
- const LANGUAGES = {
129
- "Acehnese (Arabic script)": "ace_Arab",
130
- "Acehnese (Latin script)": "ace_Latn",
131
- "Afrikaans": "afr_Latn",
132
- ...
133
- "Zulu": "zul_Latn",
134
- }
135
 
136
- export default function LanguageSelector({ type, onChange, defaultLanguage }) {
137
- return (
138
- <div className='language-selector'>
139
- <label>{type}: </label>
140
- <select onChange={onChange} defaultValue={defaultLanguage}>
141
- {Object.entries(LANGUAGES).map(([key, value]) => {
142
- return <option key={key} value={value}>{key}</option>
143
- })}
144
- </select>
145
- </div>
146
- )
147
- }
148
- ```
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
  2. `Progress.jsx`: This component will display the progress for downloading each model file.
152
- ```jsx
153
- export default function Progress({ text, percentage }) {
154
- percentage = percentage ?? 0;
155
- return (
156
- <div className="progress-container">
157
- <div className='progress-bar' style={{ 'width': `${percentage}%` }}>
158
- {text} ({`${percentage.toFixed(2)}%`})
159
- </div>
160
- </div>
161
- );
162
- }
163
- ```
164
 
165
  We can now use these components in `App.jsx` by adding these imports to the top of the file:
 
166
  ```jsx
167
  import LanguageSelector from './components/LanguageSelector';
168
  import Progress from './components/Progress';
169
  ```
170
 
171
  Let's also add some state variables to keep track of a few things in our application, like model loading, languages, input text, and output text. Add the following code to the beginning of the `App` function in `src/App.jsx`:
 
172
  ```jsx
173
  function App() {
174
-
175
  // Model loading
176
  const [ready, setReady] = useState(null);
177
  const [disabled, setDisabled] = useState(false);
@@ -187,8 +187,7 @@ function App() {
187
  }
188
  ```
189
 
190
-
191
- Next, we can add our custom components to the main `App` component. We will also add two `textarea` elements for input and output text, and a `button` to trigger the translation. Modify the `return` statement to look like this:
192
 
193
  ```jsx
194
  return (
@@ -196,200 +195,215 @@ return (
196
  <h1>Transformers.js</h1>
197
  <h2>ML-powered multilingual translation in React!</h2>
198
 
199
- <div className='container'>
200
- <div className='language-container'>
201
- <LanguageSelector type={"Source"} defaultLanguage={"eng_Latn"} onChange={x => setSourceLanguage(x.target.value)} />
202
- <LanguageSelector type={"Target"} defaultLanguage={"fra_Latn"} onChange={x => setTargetLanguage(x.target.value)} />
 
 
 
 
 
 
 
 
203
  </div>
204
 
205
- <div className='textbox-container'>
206
- <textarea value={input} rows={3} onChange={e => setInput(e.target.value)}></textarea>
 
 
 
 
207
  <textarea value={output} rows={3} readOnly></textarea>
208
  </div>
209
  </div>
210
 
211
- <button disabled={disabled} onClick={translate}>Translate</button>
 
 
212
 
213
- <div className='progress-bars-container'>
214
- {ready === false && (
215
- <label>Loading models... (only run once)</label>
216
- )}
217
- {progressItems.map(data => (
218
  <div key={data.file}>
219
  <Progress text={data.file} percentage={data.progress} />
220
  </div>
221
  ))}
222
  </div>
223
  </>
224
- )
225
  ```
226
 
227
  Don't worry about the `translate` function for now. We will define it in the next section.
228
 
229
  Finally, we can add some CSS to make our app look a little nicer. Modify the following files in the `src` directory:
230
- 1. `index.css`:
231
- <details>
232
- <summary>View code</summary>
233
-
234
- ```css
235
- :root {
236
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
237
- line-height: 1.5;
238
- font-weight: 400;
239
- color: #213547;
240
- background-color: #ffffff;
241
-
242
- font-synthesis: none;
243
- text-rendering: optimizeLegibility;
244
- -webkit-font-smoothing: antialiased;
245
- -moz-osx-font-smoothing: grayscale;
246
- -webkit-text-size-adjust: 100%;
247
- }
248
-
249
- body {
250
- margin: 0;
251
- display: flex;
252
- place-items: center;
253
- min-width: 320px;
254
- min-height: 100vh;
255
- }
256
-
257
- h1 {
258
- font-size: 3.2em;
259
- line-height: 1;
260
- }
261
-
262
- h1,
263
- h2 {
264
- margin: 8px;
265
- }
266
-
267
- select {
268
- padding: 0.3em;
269
- cursor: pointer;
270
- }
271
 
272
- textarea {
273
- padding: 0.6em;
274
- }
275
-
276
- button {
277
- padding: 0.6em 1.2em;
278
- cursor: pointer;
279
- font-weight: 500;
280
- }
281
-
282
- button[disabled] {
283
- cursor: not-allowed;
284
- }
285
-
286
- select,
287
- textarea,
288
- button {
289
- border-radius: 8px;
290
- border: 1px solid transparent;
291
- font-size: 1em;
292
- font-family: inherit;
293
- background-color: #f9f9f9;
294
- transition: border-color 0.25s;
295
- }
296
-
297
- select:hover,
298
- textarea:hover,
299
- button:not([disabled]):hover {
300
- border-color: #646cff;
301
- }
302
-
303
- select:focus,
304
- select:focus-visible,
305
- textarea:focus,
306
- textarea:focus-visible,
307
- button:focus,
308
- button:focus-visible {
309
- outline: 4px auto -webkit-focus-ring-color;
310
- }
311
- ```
312
- </details>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
 
314
  1. `App.css`
315
- <details>
316
- <summary>View code</summary>
317
-
318
- ```css
319
- #root {
320
- max-width: 1280px;
321
- margin: 0 auto;
322
- padding: 2rem;
323
- text-align: center;
324
- }
325
-
326
- .language-container {
327
- display: flex;
328
- gap: 20px;
329
- }
330
-
331
- .textbox-container {
332
- display: flex;
333
- justify-content: center;
334
- gap: 20px;
335
- width: 800px;
336
- }
337
-
338
- .textbox-container>textarea, .language-selector {
339
- width: 50%;
340
- }
341
-
342
- .language-selector>select {
343
- width: 150px;
344
- }
345
-
346
- .progress-container {
347
- position: relative;
348
- font-size: 14px;
349
- color: white;
350
- background-color: #e9ecef;
351
- border: solid 1px;
352
- border-radius: 8px;
353
- text-align: left;
354
- overflow: hidden;
355
- }
356
-
357
- .progress-bar {
358
- padding: 0 4px;
359
- z-index: 0;
360
- top: 0;
361
- width: 1%;
362
- overflow: hidden;
363
- background-color: #007bff;
364
- white-space: nowrap;
365
- }
366
-
367
- .progress-text {
368
- z-index: 2;
369
- }
370
-
371
- .selector-container {
372
- display: flex;
373
- gap: 20px;
374
- }
375
-
376
- .progress-bars-container {
377
- padding: 8px;
378
- height: 140px;
379
- }
380
-
381
- .container {
382
- margin: 25px;
383
- display: flex;
384
- flex-direction: column;
385
- gap: 10px;
386
- }
387
- ```
388
- </details>
 
 
389
 
390
  ## Step 4: Connecting everything together
391
 
392
-
393
  Now that we have a basic user interface set up, we can finally connect everything together.
394
 
395
  First, let's define the `translate` function, which will be called when the user clicks the `Translate` button. This sends a message (containing the input text, source language, and target language) to the worker thread for processing. We will also disable the button so the user doesn't click it multiple times. Add the following code just before the `return` statement in the `App` function:
@@ -401,9 +415,9 @@ const translate = () => {
401
  worker.current.postMessage({
402
  text: input,
403
  src_lang: sourceLanguage,
404
- tgt_lang: targetLanguage,
405
  });
406
- }
407
  ```
408
 
409
  Now, let's add an event listener in `src/worker.js` to listen for messages from the main thread. We will send back messages (e.g., for model loading progress and text streaming) to the main thread with `self.postMessage`.
@@ -413,37 +427,37 @@ Now, let's add an event listener in `src/worker.js` to listen for messages from
413
  self.addEventListener('message', async (event) => {
414
  // Retrieve the translation pipeline. When called for the first time,
415
  // this will load the pipeline and save it for future use.
416
- const translator = await MyTranslationPipeline.getInstance(x => {
417
- // We also add a progress callback to the pipeline so that we can
418
- // track model loading.
419
- self.postMessage(x);
420
  });
421
 
422
  // Capture partial output as it streams from the pipeline
423
  const streamer = new TextStreamer(translator.tokenizer, {
424
- skip_prompt: true,
425
- skip_special_tokens: true,
426
- callback_function: function (text) {
427
- self.postMessage({
428
- status: 'update',
429
- output: text
430
- });
431
- }
432
  });
433
 
434
  // Actually perform the translation
435
  const output = await translator(event.data.text, {
436
- tgt_lang: event.data.tgt_lang,
437
- src_lang: event.data.src_lang,
438
 
439
- // Allows for partial output to be captured
440
- streamer,
441
  });
442
 
443
  // Send the output back to the main thread
444
  self.postMessage({
445
- status: 'complete',
446
- output,
447
  });
448
  });
449
  ```
@@ -456,15 +470,15 @@ const onMessageReceived = (e) => {
456
  case 'initiate':
457
  // Model file start load: add a new progress item to the list.
458
  setReady(false);
459
- setProgressItems(prev => [...prev, e.data]);
460
  break;
461
 
462
  case 'progress':
463
  // Model file progress: update one of the progress items.
464
- setProgressItems(
465
- prev => prev.map(item => {
466
  if (item.file === e.data.file) {
467
- return { ...item, progress: e.data.progress }
468
  }
469
  return item;
470
  })
@@ -473,8 +487,8 @@ const onMessageReceived = (e) => {
473
 
474
  case 'done':
475
  // Model file loaded: remove the progress item from the list.
476
- setProgressItems(
477
- prev => prev.filter(item => item.file !== e.data.file)
478
  );
479
  break;
480
 
@@ -485,7 +499,7 @@ const onMessageReceived = (e) => {
485
 
486
  case 'update':
487
  // Generation update: update the output text.
488
- setOutput(o => o + e.data.output);
489
  break;
490
 
491
  case 'complete':
@@ -498,8 +512,6 @@ const onMessageReceived = (e) => {
498
 
499
  You can now run the application with `npm run dev` and perform multilingual translation directly in your browser!
500
 
501
-
502
-
503
  ## (Optional) Step 5: Build and deploy
504
 
505
  To build your application, simply run `npm run build`. This will bundle your application and output the static files to the `dist` folder.
@@ -509,4 +521,4 @@ For this demo, we will deploy our application as a static [Hugging Face Space](h
509
  1. Visit [https://huggingface.co/new-space](https://huggingface.co/new-space) and fill in the form. Remember to select "Static" as the space type.
510
  2. Go to "Files" &rarr; "Add file" &rarr; "Upload files". Drag the `index.html` file and `public/` folder from the `dist` folder into the upload box and click "Upload". After they have uploaded, scroll down to the button and click "Commit changes to main".
511
 
512
- **That's it!** Your application should now be live at `https://huggingface.co/spaces/<your-username>/<your-space-name>`!
 
 
1
  # Building a React application
2
 
3
  In this tutorial, we'll be building a simple React application that performs multilingual translation using Transformers.js! The final product will look something like this:
 
5
  ![Demo](https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/react-translator-demo.gif)
6
 
7
  Useful links:
8
+
9
  - [Demo site](https://huggingface.co/spaces/Xenova/react-translator)
10
  - [Source code](https://github.com/huggingface/transformers.js-examples/tree/main/react-translator)
11
 
 
12
  ## Prerequisites
13
 
14
  - [Node.js](https://nodejs.org/en/) version 18+
15
  - [npm](https://www.npmjs.com/) version 9+
16
 
 
17
  ## Step 1: Initialise the project
18
 
19
  For this tutorial, we will use [Vite](https://vitejs.dev/) to initialise our project. Vite is a build tool that allows us to quickly set up a React application with minimal configuration. Run the following command in your terminal:
 
49
  ```
50
 
51
  For this application, we will use the [Xenova/nllb-200-distilled-600M](https://huggingface.co/Xenova/nllb-200-distilled-600M) model, which can perform multilingual translation among 200 languages. Before we start, there are 2 things we need to take note of:
52
+
53
  1. ML inference can be quite computationally intensive, so it's better to load and run the models in a separate thread from the main (UI) thread.
54
  2. Since the model is quite large (>1 GB), we don't want to download it until the user clicks the "Translate" button.
55
 
56
  We can achieve both of these goals by using a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) and some [React hooks](https://react.dev/reference/react).
57
 
58
  1. Create a file called `worker.js` in the `src` directory. This script will do all the heavy-lifing for us, including loading and running of the translation pipeline. To ensure the model is only loaded once, we will create the `MyTranslationPipeline` class which use the [singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) to lazily create a single instance of the pipeline when `getInstance` is first called, and use this pipeline for all subsequent calls:
59
+
60
+ ```javascript
61
+ import { pipeline, TextStreamer } from '@huggingface/transformers';
62
+
63
+ class MyTranslationPipeline {
64
+ static task = 'translation';
65
+ static model = 'Xenova/nllb-200-distilled-600M';
66
+ static instance = null;
67
+
68
+ static async getInstance(progress_callback = null) {
69
+ this.instance ??= pipeline(this.task, this.model, { progress_callback });
70
+ return this.instance;
71
+ }
72
+ }
73
+ ```
74
 
75
  2. Modify `App.jsx` in the `src` directory. This file is automatically created when initializing our React project, and will contain some boilerplate code. Inside the `App` function, let's create the web worker and store a reference to it using the `useRef` hook:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
+ ```jsx
78
+ // Remember to import the relevant hooks
79
+ import { useEffect, useRef, useState } from 'react'
80
+ import './App.css'
81
 
82
+ function App() {
83
+ // Create a reference to the worker object.
84
+ const worker = useRef(null);
85
 
86
+ // We use the `useEffect` hook to setup the worker as soon as the `App` component is mounted.
87
+ useEffect(() => {
88
+ // Create the worker if it does not yet exist.
89
+ worker.current ??= new Worker(new URL('./worker.js', import.meta.url), {
90
+ type: 'module'
91
+ });
92
+
93
+ // Create a callback function for messages from the worker thread.
94
+ const onMessageReceived = (e) => {
95
+ // TODO: Will fill in later
96
+ };
97
+
98
+ // Attach the callback function as an event listener.
99
+ worker.current.addEventListener('message', onMessageReceived);
100
+
101
+ // Define a cleanup function for when the component is unmounted.
102
+ return () => worker.current.removeEventListener('message', onMessageReceived);
103
+ });
104
+
105
+ return (
106
+ // TODO: Rest of our app goes here...
107
+ )
108
+ }
109
+
110
+ export default App
111
+
112
+ ```
113
 
114
  ## Step 3: Design the user interface
115
 
 
120
 
121
  </Tip>
122
 
 
 
123
  First, let's define our components. Create a folder called `components` in the `src` directory, and create the following files:
 
 
 
 
 
 
 
 
 
124
 
125
+ 1. `LanguageSelector.jsx`: This component will allow the user to select the input and output languages. Check out the full list of languages [here](https://github.com/huggingface/transformers.js-examples/tree/main/react-translator/src/components/LanguageSelector.jsx).
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
+ ```jsx
128
+ const LANGUAGES = {
129
+ "Acehnese (Arabic script)": "ace_Arab",
130
+ "Acehnese (Latin script)": "ace_Latn",
131
+ "Afrikaans": "afr_Latn",
132
+ ...
133
+ "Zulu": "zul_Latn",
134
+ }
135
+
136
+ export default function LanguageSelector({ type, onChange, defaultLanguage }) {
137
+ return (
138
+ <div className='language-selector'>
139
+ <label>{type}: </label>
140
+ <select onChange={onChange} defaultValue={defaultLanguage}>
141
+ {Object.entries(LANGUAGES).map(([key, value]) => {
142
+ return <option key={key} value={value}>{key}</option>
143
+ })}
144
+ </select>
145
+ </div>
146
+ )
147
+ }
148
+ ```
149
 
150
  2. `Progress.jsx`: This component will display the progress for downloading each model file.
151
+ ```jsx
152
+ export default function Progress({ text, percentage }) {
153
+ percentage = percentage ?? 0;
154
+ return (
155
+ <div className="progress-container">
156
+ <div className="progress-bar" style={{ width: `${percentage}%` }}>
157
+ {text} ({`${percentage.toFixed(2)}%`})
158
+ </div>
159
+ </div>
160
+ );
161
+ }
162
+ ```
163
 
164
  We can now use these components in `App.jsx` by adding these imports to the top of the file:
165
+
166
  ```jsx
167
  import LanguageSelector from './components/LanguageSelector';
168
  import Progress from './components/Progress';
169
  ```
170
 
171
  Let's also add some state variables to keep track of a few things in our application, like model loading, languages, input text, and output text. Add the following code to the beginning of the `App` function in `src/App.jsx`:
172
+
173
  ```jsx
174
  function App() {
 
175
  // Model loading
176
  const [ready, setReady] = useState(null);
177
  const [disabled, setDisabled] = useState(false);
 
187
  }
188
  ```
189
 
190
+ Next, we can add our custom components to the main `App` component. We will also add two `textarea` elements for input and output text, and a `button` to trigger the translation. Modify the `return` statement to look like this:
 
191
 
192
  ```jsx
193
  return (
 
195
  <h1>Transformers.js</h1>
196
  <h2>ML-powered multilingual translation in React!</h2>
197
 
198
+ <div className="container">
199
+ <div className="language-container">
200
+ <LanguageSelector
201
+ type={'Source'}
202
+ defaultLanguage={'eng_Latn'}
203
+ onChange={(x) => setSourceLanguage(x.target.value)}
204
+ />
205
+ <LanguageSelector
206
+ type={'Target'}
207
+ defaultLanguage={'fra_Latn'}
208
+ onChange={(x) => setTargetLanguage(x.target.value)}
209
+ />
210
  </div>
211
 
212
+ <div className="textbox-container">
213
+ <textarea
214
+ value={input}
215
+ rows={3}
216
+ onChange={(e) => setInput(e.target.value)}
217
+ ></textarea>
218
  <textarea value={output} rows={3} readOnly></textarea>
219
  </div>
220
  </div>
221
 
222
+ <button disabled={disabled} onClick={translate}>
223
+ Translate
224
+ </button>
225
 
226
+ <div className="progress-bars-container">
227
+ {ready === false && <label>Loading models... (only run once)</label>}
228
+ {progressItems.map((data) => (
 
 
229
  <div key={data.file}>
230
  <Progress text={data.file} percentage={data.progress} />
231
  </div>
232
  ))}
233
  </div>
234
  </>
235
+ );
236
  ```
237
 
238
  Don't worry about the `translate` function for now. We will define it in the next section.
239
 
240
  Finally, we can add some CSS to make our app look a little nicer. Modify the following files in the `src` directory:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
+ 1. `index.css`:
243
+ <details>
244
+ <summary>View code</summary>
245
+
246
+ ```css
247
+ :root {
248
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
249
+ line-height: 1.5;
250
+ font-weight: 400;
251
+ color: #213547;
252
+ background-color: #ffffff;
253
+
254
+ font-synthesis: none;
255
+ text-rendering: optimizeLegibility;
256
+ -webkit-font-smoothing: antialiased;
257
+ -moz-osx-font-smoothing: grayscale;
258
+ -webkit-text-size-adjust: 100%;
259
+ }
260
+
261
+ body {
262
+ margin: 0;
263
+ display: flex;
264
+ place-items: center;
265
+ min-width: 320px;
266
+ min-height: 100vh;
267
+ }
268
+
269
+ h1 {
270
+ font-size: 3.2em;
271
+ line-height: 1;
272
+ }
273
+
274
+ h1,
275
+ h2 {
276
+ margin: 8px;
277
+ }
278
+
279
+ select {
280
+ padding: 0.3em;
281
+ cursor: pointer;
282
+ }
283
+
284
+ textarea {
285
+ padding: 0.6em;
286
+ }
287
+
288
+ button {
289
+ padding: 0.6em 1.2em;
290
+ cursor: pointer;
291
+ font-weight: 500;
292
+ }
293
+
294
+ button[disabled] {
295
+ cursor: not-allowed;
296
+ }
297
+
298
+ select,
299
+ textarea,
300
+ button {
301
+ border-radius: 8px;
302
+ border: 1px solid transparent;
303
+ font-size: 1em;
304
+ font-family: inherit;
305
+ background-color: #f9f9f9;
306
+ transition: border-color 0.25s;
307
+ }
308
+
309
+ select:hover,
310
+ textarea:hover,
311
+ button:not([disabled]):hover {
312
+ border-color: #646cff;
313
+ }
314
+
315
+ select:focus,
316
+ select:focus-visible,
317
+ textarea:focus,
318
+ textarea:focus-visible,
319
+ button:focus,
320
+ button:focus-visible {
321
+ outline: 4px auto -webkit-focus-ring-color;
322
+ }
323
+ ```
324
+
325
+ </details>
326
 
327
  1. `App.css`
328
+ <details>
329
+ <summary>View code</summary>
330
+
331
+ ```css
332
+ #root {
333
+ max-width: 1280px;
334
+ margin: 0 auto;
335
+ padding: 2rem;
336
+ text-align: center;
337
+ }
338
+
339
+ .language-container {
340
+ display: flex;
341
+ gap: 20px;
342
+ }
343
+
344
+ .textbox-container {
345
+ display: flex;
346
+ justify-content: center;
347
+ gap: 20px;
348
+ width: 800px;
349
+ }
350
+
351
+ .textbox-container > textarea,
352
+ .language-selector {
353
+ width: 50%;
354
+ }
355
+
356
+ .language-selector > select {
357
+ width: 150px;
358
+ }
359
+
360
+ .progress-container {
361
+ position: relative;
362
+ font-size: 14px;
363
+ color: white;
364
+ background-color: #e9ecef;
365
+ border: solid 1px;
366
+ border-radius: 8px;
367
+ text-align: left;
368
+ overflow: hidden;
369
+ }
370
+
371
+ .progress-bar {
372
+ padding: 0 4px;
373
+ z-index: 0;
374
+ top: 0;
375
+ width: 1%;
376
+ overflow: hidden;
377
+ background-color: #007bff;
378
+ white-space: nowrap;
379
+ }
380
+
381
+ .progress-text {
382
+ z-index: 2;
383
+ }
384
+
385
+ .selector-container {
386
+ display: flex;
387
+ gap: 20px;
388
+ }
389
+
390
+ .progress-bars-container {
391
+ padding: 8px;
392
+ height: 140px;
393
+ }
394
+
395
+ .container {
396
+ margin: 25px;
397
+ display: flex;
398
+ flex-direction: column;
399
+ gap: 10px;
400
+ }
401
+ ```
402
+
403
+ </details>
404
 
405
  ## Step 4: Connecting everything together
406
 
 
407
  Now that we have a basic user interface set up, we can finally connect everything together.
408
 
409
  First, let's define the `translate` function, which will be called when the user clicks the `Translate` button. This sends a message (containing the input text, source language, and target language) to the worker thread for processing. We will also disable the button so the user doesn't click it multiple times. Add the following code just before the `return` statement in the `App` function:
 
415
  worker.current.postMessage({
416
  text: input,
417
  src_lang: sourceLanguage,
418
+ tgt_lang: targetLanguage
419
  });
420
+ };
421
  ```
422
 
423
  Now, let's add an event listener in `src/worker.js` to listen for messages from the main thread. We will send back messages (e.g., for model loading progress and text streaming) to the main thread with `self.postMessage`.
 
427
  self.addEventListener('message', async (event) => {
428
  // Retrieve the translation pipeline. When called for the first time,
429
  // this will load the pipeline and save it for future use.
430
+ const translator = await MyTranslationPipeline.getInstance((x) => {
431
+ // We also add a progress callback to the pipeline so that we can
432
+ // track model loading.
433
+ self.postMessage(x);
434
  });
435
 
436
  // Capture partial output as it streams from the pipeline
437
  const streamer = new TextStreamer(translator.tokenizer, {
438
+ skip_prompt: true,
439
+ skip_special_tokens: true,
440
+ callback_function: function (text) {
441
+ self.postMessage({
442
+ status: 'update',
443
+ output: text
444
+ });
445
+ }
446
  });
447
 
448
  // Actually perform the translation
449
  const output = await translator(event.data.text, {
450
+ tgt_lang: event.data.tgt_lang,
451
+ src_lang: event.data.src_lang,
452
 
453
+ // Allows for partial output to be captured
454
+ streamer
455
  });
456
 
457
  // Send the output back to the main thread
458
  self.postMessage({
459
+ status: 'complete',
460
+ output
461
  });
462
  });
463
  ```
 
470
  case 'initiate':
471
  // Model file start load: add a new progress item to the list.
472
  setReady(false);
473
+ setProgressItems((prev) => [...prev, e.data]);
474
  break;
475
 
476
  case 'progress':
477
  // Model file progress: update one of the progress items.
478
+ setProgressItems((prev) =>
479
+ prev.map((item) => {
480
  if (item.file === e.data.file) {
481
+ return { ...item, progress: e.data.progress };
482
  }
483
  return item;
484
  })
 
487
 
488
  case 'done':
489
  // Model file loaded: remove the progress item from the list.
490
+ setProgressItems((prev) =>
491
+ prev.filter((item) => item.file !== e.data.file)
492
  );
493
  break;
494
 
 
499
 
500
  case 'update':
501
  // Generation update: update the output text.
502
+ setOutput((o) => o + e.data.output);
503
  break;
504
 
505
  case 'complete':
 
512
 
513
  You can now run the application with `npm run dev` and perform multilingual translation directly in your browser!
514
 
 
 
515
  ## (Optional) Step 5: Build and deploy
516
 
517
  To build your application, simply run `npm run build`. This will bundle your application and output the static files to the `dist` folder.
 
521
  1. Visit [https://huggingface.co/new-space](https://huggingface.co/new-space) and fill in the form. Remember to select "Static" as the space type.
522
  2. Go to "Files" &rarr; "Add file" &rarr; "Upload files". Drag the `index.html` file and `public/` folder from the `dist` folder into the upload box and click "Upload". After they have uploaded, scroll down to the button and click "Commit changes to main".
523
 
524
+ **That's it!** Your application should now be live at `https://huggingface.co/spaces/<your-username>/<your-space-name>`!
src/App.tsx CHANGED
@@ -1,12 +1,12 @@
1
- import { useState } from "react";
2
- import PipelineSelector from "./components/PipelineSelector";
3
- import ZeroShotClassification from "./components/ZeroShotClassification";
4
- import TextClassification from "./components/TextClassification";
5
- import Header from "./Header";
6
- import Footer from "./Footer";
7
 
8
  function App() {
9
- const [pipeline, setPipeline] = useState("zero-shot-classification");
10
 
11
  return (
12
  <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
@@ -39,14 +39,14 @@ function App() {
39
  </div>
40
  <div>
41
  <h3 className="text-sm font-medium text-gray-900">
42
- {pipeline === "zero-shot-classification"
43
- ? "Zero-Shot Classification"
44
- : "Text-Classification"}
45
  </h3>
46
  <p className="text-sm text-gray-600 mt-1">
47
- {pipeline === "zero-shot-classification"
48
- ? "Classify text into custom categories without training data. Perfect for organizing content, routing messages, or analyzing feedback."
49
- : "Classify text into predefined categories. Ideal for sentiment analysis, spam detection, or topic categorization."}
50
  </p>
51
  </div>
52
  </div>
@@ -56,10 +56,10 @@ function App() {
56
 
57
  {/* Pipeline Component */}
58
  <div className="bg-white rounded-lg shadow-sm border overflow-hidden">
59
- {pipeline === "zero-shot-classification" && (
60
  <ZeroShotClassification />
61
  )}
62
- {pipeline === "text-classification" && <TextClassification />}
63
  </div>
64
  </main>
65
 
 
1
+ import { useState } from 'react';
2
+ import PipelineSelector from './components/PipelineSelector';
3
+ import ZeroShotClassification from './components/ZeroShotClassification';
4
+ import TextClassification from './components/TextClassification';
5
+ import Header from './Header';
6
+ import Footer from './Footer';
7
 
8
  function App() {
9
+ const [pipeline, setPipeline] = useState('zero-shot-classification');
10
 
11
  return (
12
  <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
 
39
  </div>
40
  <div>
41
  <h3 className="text-sm font-medium text-gray-900">
42
+ {pipeline === 'zero-shot-classification'
43
+ ? 'Zero-Shot Classification'
44
+ : 'Text-Classification'}
45
  </h3>
46
  <p className="text-sm text-gray-600 mt-1">
47
+ {pipeline === 'zero-shot-classification'
48
+ ? 'Classify text into custom categories without training data. Perfect for organizing content, routing messages, or analyzing feedback.'
49
+ : 'Classify text into predefined categories. Ideal for sentiment analysis, spam detection, or topic categorization.'}
50
  </p>
51
  </div>
52
  </div>
 
56
 
57
  {/* Pipeline Component */}
58
  <div className="bg-white rounded-lg shadow-sm border overflow-hidden">
59
+ {pipeline === 'zero-shot-classification' && (
60
  <ZeroShotClassification />
61
  )}
62
+ {pipeline === 'text-classification' && <TextClassification />}
63
  </div>
64
  </main>
65
 
src/Footer.tsx CHANGED
@@ -1,26 +1,26 @@
1
  function Footer() {
2
  return (
3
  <footer className="bg-white border-t mt-12">
4
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
5
- <div className="flex items-center justify-between">
6
- <div className="flex items-center space-x-4 text-sm text-gray-500">
7
- <span>Powered by</span>
8
- <a
9
- href="https://huggingface.co/docs/transformers.js"
10
- target="_blank"
11
- rel="noopener noreferrer"
12
- className="inline-flex items-center space-x-1 text-blue-600 hover:text-blue-800 font-medium"
13
- >
14
- <span>🤗 Transformers.js</span>
15
- </a>
16
- </div>
17
- <div className="text-sm text-gray-500">
18
- All processing happens in your browser - your data stays private
19
- </div>
20
  </div>
21
  </div>
22
- </footer>
 
23
  );
24
  }
25
 
26
- export default Footer;
 
1
  function Footer() {
2
  return (
3
  <footer className="bg-white border-t mt-12">
4
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
5
+ <div className="flex items-center justify-between">
6
+ <div className="flex items-center space-x-4 text-sm text-gray-500">
7
+ <span>Powered by</span>
8
+ <a
9
+ href="https://huggingface.co/docs/transformers.js"
10
+ target="_blank"
11
+ rel="noopener noreferrer"
12
+ className="inline-flex items-center space-x-1 text-blue-600 hover:text-blue-800 font-medium"
13
+ >
14
+ <span>🤗 Transformers.js</span>
15
+ </a>
16
+ </div>
17
+ <div className="text-sm text-gray-500">
18
+ All processing happens in your browser - your data stays private
 
19
  </div>
20
  </div>
21
+ </div>
22
+ </footer>
23
  );
24
  }
25
 
26
+ export default Footer;
src/Header.tsx CHANGED
@@ -1,35 +1,35 @@
1
  function Header() {
2
- return (
3
  <header className="bg-white shadow-sm border-b">
4
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
5
- <div className="flex items-center justify-between h-16">
6
- <div className="flex items-center space-x-3">
7
- <div className="w-8 h-8 bg-gradient-to-r from-blue-500 to-indigo-600 rounded-lg flex items-center justify-center">
8
- <svg
9
- className="w-5 h-5 text-white"
10
- fill="currentColor"
11
- viewBox="0 0 20 20"
12
- >
13
- <path
14
- fillRule="evenodd"
15
- d="M12.316 3.051a1 1 0 01.633 1.265l-4 12a1 1 0 11-1.898-.632l4-12a1 1 0 011.265-.633zM5.707 6.293a1 1 0 010 1.414L3.414 10l2.293 2.293a1 1 0 11-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0zm8.586 0a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 11-1.414-1.414L16.586 10l-2.293-2.293a1 1 0 010-1.414z"
16
- clipRule="evenodd"
17
- />
18
- </svg>
19
- </div>
20
- <div>
21
- <h1 className="text-xl font-bold text-gray-900">
22
- Transformers.js Playground
23
- </h1>
24
- <p className="text-sm text-gray-500">
25
- Run Hugging Face models in your browser
26
- </p>
27
- </div>
28
  </div>
29
  </div>
30
  </div>
31
- </header>
32
- )
 
33
  }
34
 
35
- export default Header;
 
1
  function Header() {
2
+ return (
3
  <header className="bg-white shadow-sm border-b">
4
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
5
+ <div className="flex items-center justify-center h-16">
6
+ <div className="flex items-center space-x-3">
7
+ <div className="w-8 h-8 bg-gradient-to-r from-blue-500 to-indigo-600 rounded-lg flex items-center justify-center">
8
+ <svg
9
+ className="w-5 h-5 text-white"
10
+ fill="currentColor"
11
+ viewBox="0 0 20 20"
12
+ >
13
+ <path
14
+ fillRule="evenodd"
15
+ d="M12.316 3.051a1 1 0 01.633 1.265l-4 12a1 1 0 11-1.898-.632l4-12a1 1 0 011.265-.633zM5.707 6.293a1 1 0 010 1.414L3.414 10l2.293 2.293a1 1 0 11-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0zm8.586 0a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 11-1.414-1.414L16.586 10l-2.293-2.293a1 1 0 010-1.414z"
16
+ clipRule="evenodd"
17
+ />
18
+ </svg>
19
+ </div>
20
+ <div>
21
+ <h1 className="text-xl font-bold text-gray-900">
22
+ Transformers.js Playground
23
+ </h1>
24
+ <p className="text-sm text-gray-500">
25
+ Run Hugging Face models in your browser
26
+ </p>
 
27
  </div>
28
  </div>
29
  </div>
30
+ </div>
31
+ </header>
32
+ );
33
  }
34
 
35
+ export default Header;
src/components/ModelSelector.tsx CHANGED
@@ -6,7 +6,11 @@ interface ModelSelectorProps {
6
  models: string[];
7
  }
8
 
9
- const ModelSelector: React.FC<ModelSelectorProps> = ({ model, setModel, models }) => {
 
 
 
 
10
  return (
11
  <select value={model} onChange={(e) => setModel(e.target.value)}>
12
  {models.map((m) => (
 
6
  models: string[];
7
  }
8
 
9
+ const ModelSelector: React.FC<ModelSelectorProps> = ({
10
+ model,
11
+ setModel,
12
+ models
13
+ }) => {
14
  return (
15
  <select value={model} onChange={(e) => setModel(e.target.value)}>
16
  {models.map((m) => (
src/components/PipelineSelector.tsx CHANGED
@@ -5,7 +5,7 @@ const pipelines = [
5
  'text-classification',
6
  'image-classification',
7
  'question-answering',
8
- 'translation',
9
  ];
10
 
11
  interface PipelineSelectorProps {
@@ -13,7 +13,10 @@ interface PipelineSelectorProps {
13
  setPipeline: (pipeline: string) => void;
14
  }
15
 
16
- const PipelineSelector: React.FC<PipelineSelectorProps> = ({ pipeline, setPipeline }) => {
 
 
 
17
  return (
18
  <select value={pipeline} onChange={(e) => setPipeline(e.target.value)}>
19
  {pipelines.map((p) => (
 
5
  'text-classification',
6
  'image-classification',
7
  'question-answering',
8
+ 'translation'
9
  ];
10
 
11
  interface PipelineSelectorProps {
 
13
  setPipeline: (pipeline: string) => void;
14
  }
15
 
16
+ const PipelineSelector: React.FC<PipelineSelectorProps> = ({
17
+ pipeline,
18
+ setPipeline
19
+ }) => {
20
  return (
21
  <select value={pipeline} onChange={(e) => setPipeline(e.target.value)}>
22
  {pipelines.map((p) => (
src/components/TextClassification.tsx CHANGED
@@ -1,23 +1,27 @@
1
- import { useState, useRef, useEffect, useCallback } from "react";
2
- import { ClassificationOutput, TextClassificationWorkerInput, WorkerMessage } from "../types";
 
 
 
 
3
 
4
  const PLACEHOLDER_TEXTS: string[] = [
5
- "I absolutely love this product! It exceeded all my expectations.",
6
  "This is the worst purchase I've ever made. Complete waste of money.",
7
- "The service was okay, nothing special but not terrible either.",
8
- "Amazing quality and fast delivery. Highly recommended!",
9
  "I'm not sure how I feel about this. It's decent but could be better.",
10
- "Terrible customer service. They were rude and unhelpful.",
11
  "Great value for money. I'm very satisfied with my purchase.",
12
- "The product arrived damaged and the return process was a nightmare.",
13
- "Pretty good overall. A few minor issues but mostly positive experience.",
14
- "Outstanding! This company really knows how to treat their customers.",
15
  ].sort(() => Math.random() - 0.5);
16
 
17
  function TextClassification() {
18
- const [text, setText] = useState<string>(PLACEHOLDER_TEXTS.join("\n"));
19
  const [results, setResults] = useState<ClassificationOutput[]>([]);
20
- const [status, setStatus] = useState<string>("idle");
21
  const [progress, setProgress] = useState<number>(0);
22
 
23
  // Create a reference to the worker object.
@@ -28,9 +32,9 @@ function TextClassification() {
28
  if (!worker.current) {
29
  // Create the worker if it does not yet exist.
30
  worker.current = new Worker(
31
- new URL("../workers/sentiment-analysis.js", import.meta.url),
32
  {
33
- type: "module",
34
  }
35
  );
36
  }
@@ -38,49 +42,48 @@ function TextClassification() {
38
  // Create a callback function for messages from the worker thread.
39
  const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
40
  const status = e.data.status;
41
- if (status === "initiate") {
42
- setStatus("loading");
43
- } else if (status === "ready") {
44
- setStatus("ready");
45
- } else if (status === "progress") {
46
- setStatus("progress");
47
  if (
48
  e.data.output.progress &&
49
- (e.data.output.file as string).startsWith("onnx")
50
  )
51
  setProgress(e.data.output.progress);
52
- } else if (status === "output") {
53
  const result = e.data.output!;
54
  setResults((prevResults) => [...prevResults, result]);
55
  console.log(result);
56
- } else if (status === "complete") {
57
- setStatus("idle");
58
  setProgress(100);
59
  }
60
  };
61
 
62
  // Attach the callback function as an event listener.
63
- worker.current.addEventListener("message", onMessageReceived);
64
 
65
  // Define a cleanup function for when the component is unmounted.
66
  return () =>
67
- worker.current?.removeEventListener("message", onMessageReceived);
68
  }, []);
69
 
70
  const classify = useCallback(() => {
71
- setStatus("processing");
72
  setResults([]); // Clear previous results
73
  const message: TextClassificationWorkerInput = { text };
74
  worker.current?.postMessage(message);
75
  }, [text]);
76
 
77
- const busy: boolean = status !== "idle";
78
 
79
  const handleClear = (): void => {
80
  setResults([]);
81
  };
82
 
83
-
84
  return (
85
  <div className="flex flex-col h-[40vh] max-h-[80vh] w-full p-4">
86
  <h1 className="text-2xl font-bold mb-4">Text Classification</h1>
@@ -103,12 +106,12 @@ function TextClassification() {
103
  onClick={classify}
104
  >
105
  {!busy
106
- ? "Classify Text"
107
- : status === "loading"
108
- ? "Model loading..."
109
- : "Processing..."}
110
  </button>
111
- {status === "progress" && (
112
  <div className="text-sm font-medium">{progress}%</div>
113
  )}
114
  <button
@@ -134,10 +137,7 @@ function TextClassification() {
134
  ) : (
135
  <div className="space-y-3">
136
  {results.map((result, index) => (
137
- <div
138
- key={index}
139
- className="p-3 rounded border-2"
140
- >
141
  <div className="flex justify-between items-start mb-2">
142
  <span className="font-semibold text-sm">
143
  {result.labels[0]}
 
1
+ import { useState, useRef, useEffect, useCallback } from 'react';
2
+ import {
3
+ ClassificationOutput,
4
+ TextClassificationWorkerInput,
5
+ WorkerMessage
6
+ } from '../types';
7
 
8
  const PLACEHOLDER_TEXTS: string[] = [
9
+ 'I absolutely love this product! It exceeded all my expectations.',
10
  "This is the worst purchase I've ever made. Complete waste of money.",
11
+ 'The service was okay, nothing special but not terrible either.',
12
+ 'Amazing quality and fast delivery. Highly recommended!',
13
  "I'm not sure how I feel about this. It's decent but could be better.",
14
+ 'Terrible customer service. They were rude and unhelpful.',
15
  "Great value for money. I'm very satisfied with my purchase.",
16
+ 'The product arrived damaged and the return process was a nightmare.',
17
+ 'Pretty good overall. A few minor issues but mostly positive experience.',
18
+ 'Outstanding! This company really knows how to treat their customers.'
19
  ].sort(() => Math.random() - 0.5);
20
 
21
  function TextClassification() {
22
+ const [text, setText] = useState<string>(PLACEHOLDER_TEXTS.join('\n'));
23
  const [results, setResults] = useState<ClassificationOutput[]>([]);
24
+ const [status, setStatus] = useState<string>('idle');
25
  const [progress, setProgress] = useState<number>(0);
26
 
27
  // Create a reference to the worker object.
 
32
  if (!worker.current) {
33
  // Create the worker if it does not yet exist.
34
  worker.current = new Worker(
35
+ new URL('../workers/text-classification.js', import.meta.url),
36
  {
37
+ type: 'module'
38
  }
39
  );
40
  }
 
42
  // Create a callback function for messages from the worker thread.
43
  const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
44
  const status = e.data.status;
45
+ if (status === 'initiate') {
46
+ setStatus('loading');
47
+ } else if (status === 'ready') {
48
+ setStatus('ready');
49
+ } else if (status === 'progress') {
50
+ setStatus('progress');
51
  if (
52
  e.data.output.progress &&
53
+ (e.data.output.file as string).startsWith('onnx')
54
  )
55
  setProgress(e.data.output.progress);
56
+ } else if (status === 'output') {
57
  const result = e.data.output!;
58
  setResults((prevResults) => [...prevResults, result]);
59
  console.log(result);
60
+ } else if (status === 'complete') {
61
+ setStatus('idle');
62
  setProgress(100);
63
  }
64
  };
65
 
66
  // Attach the callback function as an event listener.
67
+ worker.current.addEventListener('message', onMessageReceived);
68
 
69
  // Define a cleanup function for when the component is unmounted.
70
  return () =>
71
+ worker.current?.removeEventListener('message', onMessageReceived);
72
  }, []);
73
 
74
  const classify = useCallback(() => {
75
+ setStatus('processing');
76
  setResults([]); // Clear previous results
77
  const message: TextClassificationWorkerInput = { text };
78
  worker.current?.postMessage(message);
79
  }, [text]);
80
 
81
+ const busy: boolean = status !== 'idle';
82
 
83
  const handleClear = (): void => {
84
  setResults([]);
85
  };
86
 
 
87
  return (
88
  <div className="flex flex-col h-[40vh] max-h-[80vh] w-full p-4">
89
  <h1 className="text-2xl font-bold mb-4">Text Classification</h1>
 
106
  onClick={classify}
107
  >
108
  {!busy
109
+ ? 'Classify Text'
110
+ : status === 'loading'
111
+ ? 'Model loading...'
112
+ : 'Processing...'}
113
  </button>
114
+ {status === 'progress' && (
115
  <div className="text-sm font-medium">{progress}%</div>
116
  )}
117
  <button
 
137
  ) : (
138
  <div className="space-y-3">
139
  {results.map((result, index) => (
140
+ <div key={index} className="p-3 rounded border-2">
 
 
 
141
  <div className="flex justify-between items-start mb-2">
142
  <span className="font-semibold text-sm">
143
  {result.labels[0]}
src/components/ZeroShotClassification.tsx CHANGED
@@ -1,10 +1,10 @@
1
  // src/App.tsx
2
- import { useState, useRef, useEffect, useCallback } from "react";
3
- import { Section, WorkerMessage, ZeroShotWorkerInput } from "../types";
4
 
5
  const PLACEHOLDER_REVIEWS: string[] = [
6
  // battery/charging problems
7
- "Disappointed with the battery life! The phone barely lasts half a day with regular use. Considering how much I paid for it, I expected better performance in this department.",
8
  "I bought this phone a week ago, and I'm already frustrated with the battery life. It barely lasts half a day with normal usage. I expected more from a supposedly high-end device",
9
  "The charging port is so finicky. Sometimes it takes forever to charge, and other times it doesn't even recognize the charger. Frustrating experience!",
10
 
@@ -14,37 +14,37 @@ const PLACEHOLDER_REVIEWS: string[] = [
14
  "Forget about a heatwave outside; my phone's got its own. It's like a little portable heater. Not what I signed up for.",
15
 
16
  // poor build quality
17
- "I dropped the phone from a short distance, and the screen cracked easily. Not as durable as I expected from a flagship device.",
18
- "Took a slight bump in my bag, and the frame got dinged. Are we back in the flip phone era?",
19
  "So, my phone's been in my pocket with just keys – no ninja moves or anything. Still, it managed to get some scratches. Disappointed with the build quality.",
20
 
21
  // software
22
- "The software updates are a nightmare. Each update seems to introduce new bugs, and it takes forever for them to be fixed.",
23
- "Constant crashes and freezes make me want to throw it into a black hole.",
24
  "Every time I open Instagram, my phone freezes and crashes. It's so frustrating!",
25
 
26
  // other
27
  "I'm not sure what to make of this phone. It's not bad, but it's not great either. I'm on the fence about it.",
28
  "I hate the color of this phone. It's so ugly!",
29
- "This phone sucks! I'm returning it.",
30
  ].sort(() => Math.random() - 0.5);
31
 
32
  const PLACEHOLDER_SECTIONS: string[] = [
33
- "Battery and charging problems",
34
- "Overheating",
35
- "Poor build quality",
36
- "Software issues",
37
- "Other",
38
  ];
39
 
40
  function ZeroShotClassification() {
41
- const [text, setText] = useState<string>(PLACEHOLDER_REVIEWS.join("\n"));
42
 
43
  const [sections, setSections] = useState<Section[]>(
44
- PLACEHOLDER_SECTIONS.map((title) => ({ title, items: [] })),
45
  );
46
 
47
- const [status, setStatus] = useState<string>("idle");
48
  const [progress, setProgress] = useState<number>(0);
49
 
50
  // Create a reference to the worker object.
@@ -55,9 +55,9 @@ function ZeroShotClassification() {
55
  if (!worker.current) {
56
  // Create the worker if it does not yet exist.
57
  worker.current = new Worker(
58
- new URL("../workers/zero-shot.js", import.meta.url),
59
  {
60
- type: "module",
61
  }
62
  );
63
  }
@@ -65,19 +65,22 @@ function ZeroShotClassification() {
65
  // Create a callback function for messages from the worker thread.
66
  const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
67
  const status = e.data.status;
68
- if (status === "initiate") {
69
- setStatus("loading");
70
- } else if (status === "ready") {
71
- setStatus("ready");
72
- } else if (status === "progress") {
73
- setStatus("progress");
74
- if (e.data.output.progress && (e.data.output.file as string).startsWith('onnx'))
75
- setProgress(e.data.output.progress)
76
- } else if (status === "output") {
 
 
 
77
  const { sequence, labels, scores } = e.data.output!;
78
 
79
  // Threshold for classification
80
- const label = scores[0] > 0.5 ? labels[0] : "Other";
81
 
82
  const sectionID =
83
  sections.map((x) => x.title).indexOf(label) ?? sections.length - 1;
@@ -85,44 +88,44 @@ function ZeroShotClassification() {
85
  const newSections = [...sections];
86
  newSections[sectionID] = {
87
  ...newSections[sectionID],
88
- items: [...newSections[sectionID].items, sequence],
89
  };
90
  return newSections;
91
  });
92
- } else if (status === "complete") {
93
- setStatus("idle");
94
- setProgress(100)
95
  }
96
  };
97
 
98
  // Attach the callback function as an event listener.
99
- worker.current.addEventListener("message", onMessageReceived);
100
 
101
  // Define a cleanup function for when the component is unmounted.
102
  return () =>
103
- worker.current?.removeEventListener("message", onMessageReceived);
104
  }, [sections]);
105
 
106
  const classify = useCallback(() => {
107
- setStatus("processing");
108
  const message: ZeroShotWorkerInput = {
109
  text,
110
  labels: sections
111
  .slice(0, sections.length - 1)
112
- .map((section) => section.title),
113
  };
114
  worker.current?.postMessage(message);
115
  }, [text, sections]);
116
 
117
- const busy: boolean = status !== "idle";
118
 
119
  const handleAddCategory = (): void => {
120
  setSections((sections) => {
121
  const newSections = [...sections];
122
  // add at position 2 from the end
123
  newSections.splice(newSections.length - 1, 0, {
124
- title: "New Category",
125
- items: [],
126
  });
127
  return newSections;
128
  });
@@ -140,8 +143,8 @@ function ZeroShotClassification() {
140
  setSections((sections) =>
141
  sections.map((section) => ({
142
  ...section,
143
- items: [],
144
- })),
145
  );
146
  };
147
 
@@ -167,16 +170,14 @@ function ZeroShotClassification() {
167
  onClick={classify}
168
  >
169
  {!busy
170
- ? "Categorize"
171
- : status === "loading"
172
- ? "Model loading..."
173
- : "Processing"}
174
  </button>
175
- { status === "progress" &&
176
- <div className="text-sm font-medium">
177
- {progress}%
178
- </div>
179
- }
180
  <div className="flex gap-1">
181
  <button
182
  className="border py-1 px-2 bg-green-400 rounded text-white text-sm font-medium cursor-pointer"
@@ -204,7 +205,7 @@ function ZeroShotClassification() {
204
  {sections.map((section, index) => (
205
  <div key={index} className="flex flex-col w-full">
206
  <input
207
- disabled={section.title === "Other"}
208
  className="w-full border px-1 text-center"
209
  value={section.title}
210
  onChange={(e) => handleSectionTitleChange(index, e.target.value)}
 
1
  // src/App.tsx
2
+ import { useState, useRef, useEffect, useCallback } from 'react';
3
+ import { Section, WorkerMessage, ZeroShotWorkerInput } from '../types';
4
 
5
  const PLACEHOLDER_REVIEWS: string[] = [
6
  // battery/charging problems
7
+ 'Disappointed with the battery life! The phone barely lasts half a day with regular use. Considering how much I paid for it, I expected better performance in this department.',
8
  "I bought this phone a week ago, and I'm already frustrated with the battery life. It barely lasts half a day with normal usage. I expected more from a supposedly high-end device",
9
  "The charging port is so finicky. Sometimes it takes forever to charge, and other times it doesn't even recognize the charger. Frustrating experience!",
10
 
 
14
  "Forget about a heatwave outside; my phone's got its own. It's like a little portable heater. Not what I signed up for.",
15
 
16
  // poor build quality
17
+ 'I dropped the phone from a short distance, and the screen cracked easily. Not as durable as I expected from a flagship device.',
18
+ 'Took a slight bump in my bag, and the frame got dinged. Are we back in the flip phone era?',
19
  "So, my phone's been in my pocket with just keys – no ninja moves or anything. Still, it managed to get some scratches. Disappointed with the build quality.",
20
 
21
  // software
22
+ 'The software updates are a nightmare. Each update seems to introduce new bugs, and it takes forever for them to be fixed.',
23
+ 'Constant crashes and freezes make me want to throw it into a black hole.',
24
  "Every time I open Instagram, my phone freezes and crashes. It's so frustrating!",
25
 
26
  // other
27
  "I'm not sure what to make of this phone. It's not bad, but it's not great either. I'm on the fence about it.",
28
  "I hate the color of this phone. It's so ugly!",
29
+ "This phone sucks! I'm returning it."
30
  ].sort(() => Math.random() - 0.5);
31
 
32
  const PLACEHOLDER_SECTIONS: string[] = [
33
+ 'Battery and charging problems',
34
+ 'Overheating',
35
+ 'Poor build quality',
36
+ 'Software issues',
37
+ 'Other'
38
  ];
39
 
40
  function ZeroShotClassification() {
41
+ const [text, setText] = useState<string>(PLACEHOLDER_REVIEWS.join('\n'));
42
 
43
  const [sections, setSections] = useState<Section[]>(
44
+ PLACEHOLDER_SECTIONS.map((title) => ({ title, items: [] }))
45
  );
46
 
47
+ const [status, setStatus] = useState<string>('idle');
48
  const [progress, setProgress] = useState<number>(0);
49
 
50
  // Create a reference to the worker object.
 
55
  if (!worker.current) {
56
  // Create the worker if it does not yet exist.
57
  worker.current = new Worker(
58
+ new URL('../workers/zero-shot.js', import.meta.url),
59
  {
60
+ type: 'module'
61
  }
62
  );
63
  }
 
65
  // Create a callback function for messages from the worker thread.
66
  const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
67
  const status = e.data.status;
68
+ if (status === 'initiate') {
69
+ setStatus('loading');
70
+ } else if (status === 'ready') {
71
+ setStatus('ready');
72
+ } else if (status === 'progress') {
73
+ setStatus('progress');
74
+ if (
75
+ e.data.output.progress &&
76
+ (e.data.output.file as string).startsWith('onnx')
77
+ )
78
+ setProgress(e.data.output.progress);
79
+ } else if (status === 'output') {
80
  const { sequence, labels, scores } = e.data.output!;
81
 
82
  // Threshold for classification
83
+ const label = scores[0] > 0.5 ? labels[0] : 'Other';
84
 
85
  const sectionID =
86
  sections.map((x) => x.title).indexOf(label) ?? sections.length - 1;
 
88
  const newSections = [...sections];
89
  newSections[sectionID] = {
90
  ...newSections[sectionID],
91
+ items: [...newSections[sectionID].items, sequence]
92
  };
93
  return newSections;
94
  });
95
+ } else if (status === 'complete') {
96
+ setStatus('idle');
97
+ setProgress(100);
98
  }
99
  };
100
 
101
  // Attach the callback function as an event listener.
102
+ worker.current.addEventListener('message', onMessageReceived);
103
 
104
  // Define a cleanup function for when the component is unmounted.
105
  return () =>
106
+ worker.current?.removeEventListener('message', onMessageReceived);
107
  }, [sections]);
108
 
109
  const classify = useCallback(() => {
110
+ setStatus('processing');
111
  const message: ZeroShotWorkerInput = {
112
  text,
113
  labels: sections
114
  .slice(0, sections.length - 1)
115
+ .map((section) => section.title)
116
  };
117
  worker.current?.postMessage(message);
118
  }, [text, sections]);
119
 
120
+ const busy: boolean = status !== 'idle';
121
 
122
  const handleAddCategory = (): void => {
123
  setSections((sections) => {
124
  const newSections = [...sections];
125
  // add at position 2 from the end
126
  newSections.splice(newSections.length - 1, 0, {
127
+ title: 'New Category',
128
+ items: []
129
  });
130
  return newSections;
131
  });
 
143
  setSections((sections) =>
144
  sections.map((section) => ({
145
  ...section,
146
+ items: []
147
+ }))
148
  );
149
  };
150
 
 
170
  onClick={classify}
171
  >
172
  {!busy
173
+ ? 'Categorize'
174
+ : status === 'loading'
175
+ ? 'Model loading...'
176
+ : 'Processing'}
177
  </button>
178
+ {status === 'progress' && (
179
+ <div className="text-sm font-medium">{progress}%</div>
180
+ )}
 
 
181
  <div className="flex gap-1">
182
  <button
183
  className="border py-1 px-2 bg-green-400 rounded text-white text-sm font-medium cursor-pointer"
 
205
  {sections.map((section, index) => (
206
  <div key={index} className="flex flex-col w-full">
207
  <input
208
+ disabled={section.title === 'Other'}
209
  className="w-full border px-1 text-center"
210
  value={section.title}
211
  onChange={(e) => handleSectionTitleChange(index, e.target.value)}
src/index.css CHANGED
@@ -4,14 +4,14 @@
4
 
5
  body {
6
  margin: 0;
7
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
8
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
9
- sans-serif;
10
  -webkit-font-smoothing: antialiased;
11
  -moz-osx-font-smoothing: grayscale;
12
  }
13
 
14
  code {
15
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
16
- monospace;
17
  }
 
4
 
5
  body {
6
  margin: 0;
7
+ font-family:
8
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
9
+ 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
10
  -webkit-font-smoothing: antialiased;
11
  -moz-osx-font-smoothing: grayscale;
12
  }
13
 
14
  code {
15
+ font-family:
16
+ source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
17
  }
src/types.ts CHANGED
@@ -9,8 +9,6 @@ export interface ClassificationOutput {
9
  scores: number[];
10
  }
11
 
12
-
13
-
14
  export interface WorkerMessage {
15
  status: 'initiate' | 'ready' | 'output' | 'complete' | 'progress';
16
  output?: any;
@@ -21,7 +19,6 @@ export interface ZeroShotWorkerInput {
21
  labels: string[];
22
  }
23
 
24
-
25
  export interface TextClassificationWorkerInput {
26
  text: string;
27
  }
 
9
  scores: number[];
10
  }
11
 
 
 
12
  export interface WorkerMessage {
13
  status: 'initiate' | 'ready' | 'output' | 'complete' | 'progress';
14
  output?: any;
 
19
  labels: string[];
20
  }
21
 
 
22
  export interface TextClassificationWorkerInput {
23
  text: string;
24
  }
src/worker.js CHANGED
@@ -1,5 +1,5 @@
1
  /* eslint-disable no-restricted-globals */
2
- import { pipeline } from "@huggingface/transformers";
3
 
4
  class PipelineFactory {
5
  static task = null;
@@ -9,7 +9,7 @@ class PipelineFactory {
9
  static async getInstance(progress_callback = null) {
10
  if (this.instance === null) {
11
  this.instance = pipeline(this.task, this.model, {
12
- progress_callback,
13
  });
14
  }
15
 
@@ -18,7 +18,7 @@ class PipelineFactory {
18
  }
19
 
20
  // Listen for messages from the main thread
21
- self.addEventListener("message", async (event) => {
22
  const { task, model, input } = event.data;
23
 
24
  PipelineFactory.task = task;
@@ -29,15 +29,15 @@ self.addEventListener("message", async (event) => {
29
  const pipe = await PipelineFactory.getInstance((x) => {
30
  // We also add a progress callback to the pipeline so that we can
31
  // track model loading.
32
- self.postMessage({ status: "progress", data: x });
33
  });
34
 
35
  // Run the pipeline
36
  const output = await pipe(...input);
37
 
38
  // Send the output back to the main thread
39
- self.postMessage({ status: "output", output });
40
 
41
  // Send the output back to the main thread
42
- self.postMessage({ status: "complete" });
43
  });
 
1
  /* eslint-disable no-restricted-globals */
2
+ import { pipeline } from '@huggingface/transformers';
3
 
4
  class PipelineFactory {
5
  static task = null;
 
9
  static async getInstance(progress_callback = null) {
10
  if (this.instance === null) {
11
  this.instance = pipeline(this.task, this.model, {
12
+ progress_callback
13
  });
14
  }
15
 
 
18
  }
19
 
20
  // Listen for messages from the main thread
21
+ self.addEventListener('message', async (event) => {
22
  const { task, model, input } = event.data;
23
 
24
  PipelineFactory.task = task;
 
29
  const pipe = await PipelineFactory.getInstance((x) => {
30
  // We also add a progress callback to the pipeline so that we can
31
  // track model loading.
32
+ self.postMessage({ status: 'progress', data: x });
33
  });
34
 
35
  // Run the pipeline
36
  const output = await pipe(...input);
37
 
38
  // Send the output back to the main thread
39
+ self.postMessage({ status: 'output', output });
40
 
41
  // Send the output back to the main thread
42
+ self.postMessage({ status: 'complete' });
43
  });
src/workers/{sentiment-analysis.js → text-classification.js} RENAMED
@@ -1,14 +1,14 @@
1
  /* eslint-disable no-restricted-globals */
2
- import { pipeline } from "@huggingface/transformers";
3
 
4
  class MyTextClassificationPipeline {
5
- static task = "text-classification";
6
- static model = "Xenova/bert-base-multilingual-uncased-sentiment";
7
  static instance = null;
8
 
9
  static async getInstance(progress_callback = null) {
10
  this.instance ??= pipeline(this.task, this.model, {
11
- progress_callback,
12
  });
13
 
14
  return this.instance;
@@ -16,24 +16,24 @@ class MyTextClassificationPipeline {
16
  }
17
 
18
  // Listen for messages from the main thread
19
- self.addEventListener("message", async (event) => {
20
  // Retrieve the pipeline. When called for the first time,
21
  // this will load the pipeline and save it for future use.
22
  const classifier = await MyTextClassificationPipeline.getInstance((x) => {
23
  // We also add a progress callback to the pipeline so that we can
24
  // track model loading.
25
- self.postMessage({ status: "progress", output: x });
26
  });
27
 
28
  const { text } = event.data;
29
 
30
- const split = text.split("\n");
31
  for (const line of split) {
32
  if (line.trim()) {
33
  const output = await classifier(line);
34
  // Send the output back to the main thread
35
- self.postMessage({
36
- status: "output",
37
  output: {
38
  sequence: line,
39
  labels: [output[0].label],
@@ -43,5 +43,5 @@ self.addEventListener("message", async (event) => {
43
  }
44
  }
45
  // Send the output back to the main thread
46
- self.postMessage({ status: "complete" });
47
  });
 
1
  /* eslint-disable no-restricted-globals */
2
+ import { pipeline } from '@huggingface/transformers';
3
 
4
  class MyTextClassificationPipeline {
5
+ static task = 'text-classification';
6
+ static model = 'Xenova/bert-base-multilingual-uncased-sentiment';
7
  static instance = null;
8
 
9
  static async getInstance(progress_callback = null) {
10
  this.instance ??= pipeline(this.task, this.model, {
11
+ progress_callback
12
  });
13
 
14
  return this.instance;
 
16
  }
17
 
18
  // Listen for messages from the main thread
19
+ self.addEventListener('message', async (event) => {
20
  // Retrieve the pipeline. When called for the first time,
21
  // this will load the pipeline and save it for future use.
22
  const classifier = await MyTextClassificationPipeline.getInstance((x) => {
23
  // We also add a progress callback to the pipeline so that we can
24
  // track model loading.
25
+ self.postMessage({ status: 'progress', output: x });
26
  });
27
 
28
  const { text } = event.data;
29
 
30
+ const split = text.split('\n');
31
  for (const line of split) {
32
  if (line.trim()) {
33
  const output = await classifier(line);
34
  // Send the output back to the main thread
35
+ self.postMessage({
36
+ status: 'output',
37
  output: {
38
  sequence: line,
39
  labels: [output[0].label],
 
43
  }
44
  }
45
  // Send the output back to the main thread
46
+ self.postMessage({ status: 'complete' });
47
  });
src/workers/zero-shot.js CHANGED
@@ -1,14 +1,14 @@
1
  /* eslint-disable no-restricted-globals */
2
- import { pipeline } from "@huggingface/transformers";
3
 
4
  class MyZeroShotClassificationPipeline {
5
- static task = "zero-shot-classification";
6
- static model = "MoritzLaurer/deberta-v3-xsmall-zeroshot-v1.1-all-33";
7
  static instance = null;
8
 
9
  static async getInstance(progress_callback = null) {
10
  this.instance ??= pipeline(this.task, this.model, {
11
- progress_callback,
12
  });
13
 
14
  return this.instance;
@@ -16,26 +16,26 @@ class MyZeroShotClassificationPipeline {
16
  }
17
 
18
  // Listen for messages from the main thread
19
- self.addEventListener("message", async (event) => {
20
  // Retrieve the pipeline. When called for the first time,
21
  // this will load the pipeline and save it for future use.
22
  const classifier = await MyZeroShotClassificationPipeline.getInstance((x) => {
23
  // We also add a progress callback to the pipeline so that we can
24
  // track model loading.
25
- self.postMessage({ status: "progress", output: x });
26
  });
27
 
28
  const { text, labels } = event.data;
29
 
30
- const split = text.split("\n");
31
  for (const line of split) {
32
  const output = await classifier(line, labels, {
33
- hypothesis_template: "This text is about {}.",
34
- multi_label: true,
35
  });
36
  // Send the output back to the main thread
37
- self.postMessage({ status: "output", output });
38
  }
39
  // Send the output back to the main thread
40
- self.postMessage({ status: "complete" });
41
- });
 
1
  /* eslint-disable no-restricted-globals */
2
+ import { pipeline } from '@huggingface/transformers';
3
 
4
  class MyZeroShotClassificationPipeline {
5
+ static task = 'zero-shot-classification';
6
+ static model = 'MoritzLaurer/deberta-v3-xsmall-zeroshot-v1.1-all-33';
7
  static instance = null;
8
 
9
  static async getInstance(progress_callback = null) {
10
  this.instance ??= pipeline(this.task, this.model, {
11
+ progress_callback
12
  });
13
 
14
  return this.instance;
 
16
  }
17
 
18
  // Listen for messages from the main thread
19
+ self.addEventListener('message', async (event) => {
20
  // Retrieve the pipeline. When called for the first time,
21
  // this will load the pipeline and save it for future use.
22
  const classifier = await MyZeroShotClassificationPipeline.getInstance((x) => {
23
  // We also add a progress callback to the pipeline so that we can
24
  // track model loading.
25
+ self.postMessage({ status: 'progress', output: x });
26
  });
27
 
28
  const { text, labels } = event.data;
29
 
30
+ const split = text.split('\n');
31
  for (const line of split) {
32
  const output = await classifier(line, labels, {
33
+ hypothesis_template: 'This text is about {}.',
34
+ multi_label: true
35
  });
36
  // Send the output back to the main thread
37
+ self.postMessage({ status: 'output', output });
38
  }
39
  // Send the output back to the main thread
40
+ self.postMessage({ status: 'complete' });
41
+ });
tailwind.config.js CHANGED
@@ -1,10 +1,8 @@
1
  /** @type {import('tailwindcss').Config} */
2
  module.exports = {
3
- content: [
4
- "./src/**/*.{js,jsx,ts,tsx}",
5
- ],
6
  theme: {
7
- extend: {},
8
  },
9
- plugins: [],
10
- }
 
1
  /** @type {import('tailwindcss').Config} */
2
  module.exports = {
3
+ content: ['./src/**/*.{js,jsx,ts,tsx}'],
 
 
4
  theme: {
5
+ extend: {}
6
  },
7
+ plugins: []
8
+ };
tsconfig.json CHANGED
@@ -1,11 +1,7 @@
1
  {
2
  "compilerOptions": {
3
  "target": "es5",
4
- "lib": [
5
- "dom",
6
- "dom.iterable",
7
- "esnext"
8
- ],
9
  "allowJs": true,
10
  "skipLibCheck": true,
11
  "esModuleInterop": true,
@@ -20,7 +16,5 @@
20
  "noEmit": true,
21
  "jsx": "react-jsx"
22
  },
23
- "include": [
24
- "src"
25
- ]
26
  }
 
1
  {
2
  "compilerOptions": {
3
  "target": "es5",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
 
 
 
 
5
  "allowJs": true,
6
  "skipLibCheck": true,
7
  "esModuleInterop": true,
 
16
  "noEmit": true,
17
  "jsx": "react-jsx"
18
  },
19
+ "include": ["src"]
 
 
20
  }