Vokturz commited on
Commit
85a4687
·
1 Parent(s): 948b11c
src/App.tsx CHANGED
@@ -1,215 +1,20 @@
1
  // src/App.tsx
2
- import { useState, useRef, useEffect, useCallback } from "react";
3
- import { Section, WorkerMessage, WorkerInput } 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
-
11
- // overheating
12
- "This phone heats up way too quickly, especially when using demanding apps. It's uncomfortable to hold, and I'm concerned it might damage the internal components over time. Not what I expected",
13
- "This phone is like holding a hot potato. Video calls turn it into a scalding nightmare. Seriously, can't it keep its cool?",
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 App() {
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
-
49
- // Create a reference to the worker object.
50
- const worker = useRef<Worker | null>(null);
51
-
52
- // We use the `useEffect` hook to setup the worker as soon as the `App` component is mounted.
53
- useEffect(() => {
54
- if (!worker.current) {
55
- // Create the worker if it does not yet exist.
56
- worker.current = new Worker(new URL("./worker.js", import.meta.url), {
57
- type: "module",
58
- });
59
- }
60
-
61
- // Create a callback function for messages from the worker thread.
62
- const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
63
- const status = e.data.status;
64
- if (status === "initiate") {
65
- setStatus("loading");
66
- } else if (status === "ready") {
67
- setStatus("ready");
68
- } else if (status === "output") {
69
- const { sequence, labels, scores } = e.data.output!;
70
-
71
- // Threshold for classification
72
- const label = scores[0] > 0.5 ? labels[0] : "Other";
73
-
74
- const sectionID =
75
- sections.map((x) => x.title).indexOf(label) ?? sections.length - 1;
76
- setSections((sections) => {
77
- const newSections = [...sections];
78
- newSections[sectionID] = {
79
- ...newSections[sectionID],
80
- items: [...newSections[sectionID].items, sequence],
81
- };
82
- return newSections;
83
- });
84
- } else if (status === "complete") {
85
- setStatus("idle");
86
- }
87
- };
88
-
89
- // Attach the callback function as an event listener.
90
- worker.current.addEventListener("message", onMessageReceived);
91
-
92
- // Define a cleanup function for when the component is unmounted.
93
- return () =>
94
- worker.current?.removeEventListener("message", onMessageReceived);
95
- }, [sections]);
96
-
97
- const classify = useCallback(() => {
98
- setStatus("processing");
99
- const message: WorkerInput = {
100
- text,
101
- labels: sections
102
- .slice(0, sections.length - 1)
103
- .map((section) => section.title),
104
- };
105
- worker.current?.postMessage(message);
106
- }, [text, sections]);
107
-
108
- const busy: boolean = status !== "idle";
109
-
110
- const handleAddCategory = (): void => {
111
- setSections((sections) => {
112
- const newSections = [...sections];
113
- // add at position 2 from the end
114
- newSections.splice(newSections.length - 1, 0, {
115
- title: "New Category",
116
- items: [],
117
- });
118
- return newSections;
119
- });
120
- };
121
-
122
- const handleRemoveCategory = (): void => {
123
- setSections((sections) => {
124
- const newSections = [...sections];
125
- newSections.splice(newSections.length - 2, 1); // Remove second last element
126
- return newSections;
127
- });
128
- };
129
-
130
- const handleClear = (): void => {
131
- setSections((sections) =>
132
- sections.map((section) => ({
133
- ...section,
134
- items: [],
135
- })),
136
- );
137
- };
138
-
139
- const handleSectionTitleChange = (index: number, newTitle: string): void => {
140
- setSections((sections) => {
141
- const newSections = [...sections];
142
- newSections[index].title = newTitle;
143
- return newSections;
144
- });
145
- };
146
 
147
  return (
148
  <div className="flex flex-col h-screen w-screen p-1">
149
- <textarea
150
- className="border w-full p-1 h-1/2"
151
- value={text}
152
- onChange={(e) => setText(e.target.value)}
153
- ></textarea>
154
- <div className="flex flex-col justify-center items-center m-2 gap-1">
155
- <button
156
- className="border py-1 px-2 bg-blue-400 rounded text-white text-lg font-medium disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"
157
- disabled={busy}
158
- onClick={classify}
159
- >
160
- {!busy
161
- ? "Categorize"
162
- : status === "loading"
163
- ? "Model loading..."
164
- : "Processing"}
165
- </button>
166
- <div className="flex gap-1">
167
- <button
168
- className="border py-1 px-2 bg-green-400 rounded text-white text-sm font-medium cursor-pointer"
169
- onClick={handleAddCategory}
170
- >
171
- Add category
172
- </button>
173
- <button
174
- className="border py-1 px-2 bg-red-400 rounded text-white text-sm font-medium cursor-pointer"
175
- disabled={sections.length <= 1}
176
- onClick={handleRemoveCategory}
177
- >
178
- Remove category
179
- </button>
180
- <button
181
- className="border py-1 px-2 bg-orange-400 rounded text-white text-sm font-medium cursor-pointer"
182
- onClick={handleClear}
183
- >
184
- Clear
185
- </button>
186
- </div>
187
- </div>
188
-
189
- <div className="flex justify-between flex-grow overflow-x-auto max-h-[40%]">
190
- {sections.map((section, index) => (
191
- <div key={index} className="flex flex-col w-full">
192
- <input
193
- disabled={section.title === "Other"}
194
- className="w-full border px-1 text-center"
195
- value={section.title}
196
- onChange={(e) => handleSectionTitleChange(index, e.target.value)}
197
- ></input>
198
- <div className="overflow-y-auto h-full border">
199
- {section.items.map((item, itemIndex) => (
200
- <div
201
- className="m-2 border bg-red-50 rounded p-1 text-sm"
202
- key={itemIndex}
203
- >
204
- {item}
205
- </div>
206
- ))}
207
- </div>
208
- </div>
209
- ))}
210
- </div>
211
  </div>
212
  );
213
  }
214
 
215
  export default App;
 
 
1
  // src/App.tsx
2
+ import { useState } from "react";
3
+ import PipelineSelector from "./components/PipelineSelector";
4
+ import ZeroShotClassification from "./components/ZeroShotClassification";
5
+ import TextClassification from "./components/TextClassification";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  function App() {
8
+ const [pipeline, setPipeline] = useState("zero-shot-classification");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  return (
11
  <div className="flex flex-col h-screen w-screen p-1">
12
+ <PipelineSelector pipeline={pipeline} setPipeline={setPipeline} />
13
+ {pipeline === "zero-shot-classification" && <ZeroShotClassification />}
14
+ {pipeline === "text-classification" && <TextClassification />}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  </div>
16
  );
17
  }
18
 
19
  export default App;
20
+
src/components/CodeExporter.tsx ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ interface CodeExporterProps {
4
+ code: string;
5
+ }
6
+
7
+ const CodeExporter: React.FC<CodeExporterProps> = ({ code }) => {
8
+ return (
9
+ <div className="flex flex-col">
10
+ <h2 className="text-lg font-medium">Code</h2>
11
+ <pre className="bg-gray-100 p-2 rounded">{code}</pre>
12
+ </div>
13
+ );
14
+ };
15
+
16
+ export default CodeExporter;
src/components/ModelSelector.tsx ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ interface ModelSelectorProps {
4
+ model: string;
5
+ setModel: (model: string) => void;
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) => (
13
+ <option key={m} value={m}>
14
+ {m}
15
+ </option>
16
+ ))}
17
+ </select>
18
+ );
19
+ };
20
+
21
+ export default ModelSelector;
src/components/PipelineSelector.tsx CHANGED
@@ -2,6 +2,7 @@
2
  import React from 'react';
3
 
4
  const pipelines = [
 
5
  'text-classification',
6
  'image-classification',
7
  'question-answering',
@@ -9,16 +10,16 @@ const pipelines = [
9
  ];
10
 
11
  interface PipelineSelectorProps {
12
- onPipelineSelect: (pipeline: string) => void;
 
13
  }
14
 
15
- const PipelineSelector: React.FC<PipelineSelectorProps> = ({ onPipelineSelect }) => {
16
  return (
17
- <select onChange={(e) => onPipelineSelect(e.target.value)}>
18
- <option value="">Select a pipeline</option>
19
- {pipelines.map((pipeline) => (
20
- <option key={pipeline} value={pipeline}>
21
- {pipeline}
22
  </option>
23
  ))}
24
  </select>
 
2
  import React from 'react';
3
 
4
  const pipelines = [
5
+ 'zero-shot-classification',
6
  'text-classification',
7
  'image-classification',
8
  'question-answering',
 
10
  ];
11
 
12
  interface PipelineSelectorProps {
13
+ pipeline: string;
14
+ setPipeline: (pipeline: string) => void;
15
  }
16
 
17
+ const PipelineSelector: React.FC<PipelineSelectorProps> = ({ pipeline, setPipeline }) => {
18
  return (
19
+ <select value={pipeline} onChange={(e) => setPipeline(e.target.value)}>
20
+ {pipelines.map((p) => (
21
+ <option key={p} value={p}>
22
+ {p}
 
23
  </option>
24
  ))}
25
  </select>
src/components/TextClassification.tsx ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useRef, useEffect, useCallback } from "react";
2
+
3
+ interface ClassificationResult {
4
+ sequence: string;
5
+ label: string;
6
+ score: number;
7
+ }
8
+
9
+ interface TextClassificationWorkerMessage {
10
+ status: "initiate" | "ready" | "output" | "complete";
11
+ output?: ClassificationResult;
12
+ }
13
+
14
+ interface TextClassificationWorkerInput {
15
+ text: string;
16
+ }
17
+
18
+ const PLACEHOLDER_TEXTS: string[] = [
19
+ "I absolutely love this product! It exceeded all my expectations.",
20
+ "This is the worst purchase I've ever made. Complete waste of money.",
21
+ "The service was okay, nothing special but not terrible either.",
22
+ "Amazing quality and fast delivery. Highly recommended!",
23
+ "I'm not sure how I feel about this. It's decent but could be better.",
24
+ "Terrible customer service. They were rude and unhelpful.",
25
+ "Great value for money. I'm very satisfied with my purchase.",
26
+ "The product arrived damaged and the return process was a nightmare.",
27
+ "Pretty good overall. A few minor issues but mostly positive experience.",
28
+ "Outstanding! This company really knows how to treat their customers.",
29
+ ].sort(() => Math.random() - 0.5);
30
+
31
+ function TextClassification() {
32
+ const [text, setText] = useState<string>(PLACEHOLDER_TEXTS.join("\n"));
33
+ const [results, setResults] = useState<ClassificationResult[]>([]);
34
+ const [status, setStatus] = useState<string>("idle");
35
+
36
+ // Create a reference to the worker object.
37
+ const worker = useRef<Worker | null>(null);
38
+
39
+ // We use the `useEffect` hook to setup the worker as soon as the component is mounted.
40
+ useEffect(() => {
41
+ if (!worker.current) {
42
+ // Create the worker if it does not yet exist.
43
+ worker.current = new Worker(
44
+ new URL("../workers/text-classification.js", import.meta.url),
45
+ {
46
+ type: "module",
47
+ }
48
+ );
49
+ }
50
+
51
+ // Create a callback function for messages from the worker thread.
52
+ const onMessageReceived = (e: MessageEvent<TextClassificationWorkerMessage>) => {
53
+ const status = e.data.status;
54
+ if (status === "initiate") {
55
+ setStatus("loading");
56
+ } else if (status === "ready") {
57
+ setStatus("ready");
58
+ } else if (status === "output") {
59
+ const result = e.data.output!;
60
+ setResults((prevResults) => [...prevResults, result]);
61
+ } else if (status === "complete") {
62
+ setStatus("idle");
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
+ const getSentimentColor = (label: string): string => {
88
+ switch (label.toLowerCase()) {
89
+ case "positive":
90
+ case "label_2":
91
+ return "bg-green-100 border-green-300";
92
+ case "negative":
93
+ case "label_0":
94
+ return "bg-red-100 border-red-300";
95
+ case "neutral":
96
+ case "label_1":
97
+ return "bg-yellow-100 border-yellow-300";
98
+ default:
99
+ return "bg-gray-100 border-gray-300";
100
+ }
101
+ };
102
+
103
+ const formatLabel = (label: string): string => {
104
+ switch (label) {
105
+ case "LABEL_0":
106
+ return "Negative";
107
+ case "LABEL_1":
108
+ return "Neutral";
109
+ case "LABEL_2":
110
+ return "Positive";
111
+ default:
112
+ return label;
113
+ }
114
+ };
115
+
116
+ return (
117
+ <div className="flex flex-col h-screen w-screen p-4">
118
+ <h1 className="text-2xl font-bold mb-4">Text Classification</h1>
119
+
120
+ <div className="flex flex-col lg:flex-row gap-4 h-full">
121
+ {/* Input Section */}
122
+ <div className="flex flex-col w-full lg:w-1/2">
123
+ <label className="text-lg font-medium mb-2">Input Text:</label>
124
+ <textarea
125
+ className="border border-gray-300 rounded p-3 flex-grow resize-none"
126
+ value={text}
127
+ onChange={(e) => setText(e.target.value)}
128
+ placeholder="Enter text to classify (one per line)..."
129
+ />
130
+
131
+ <div className="flex gap-2 mt-4">
132
+ <button
133
+ className="flex-1 py-2 px-4 bg-blue-500 hover:bg-blue-600 rounded text-white font-medium disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
134
+ disabled={busy}
135
+ onClick={classify}
136
+ >
137
+ {!busy
138
+ ? "Classify Text"
139
+ : status === "loading"
140
+ ? "Model loading..."
141
+ : "Processing..."}
142
+ </button>
143
+
144
+ <button
145
+ className="py-2 px-4 bg-gray-500 hover:bg-gray-600 rounded text-white font-medium transition-colors"
146
+ onClick={handleClear}
147
+ >
148
+ Clear Results
149
+ </button>
150
+ </div>
151
+ </div>
152
+
153
+ {/* Results Section */}
154
+ <div className="flex flex-col w-full lg:w-1/2">
155
+ <label className="text-lg font-medium mb-2">
156
+ Classification Results ({results.length}):
157
+ </label>
158
+
159
+ <div className="border border-gray-300 rounded p-3 flex-grow overflow-y-auto">
160
+ {results.length === 0 ? (
161
+ <div className="text-gray-500 text-center py-8">
162
+ No results yet. Click "Classify Text" to analyze your input.
163
+ </div>
164
+ ) : (
165
+ <div className="space-y-3">
166
+ {results.map((result, index) => (
167
+ <div
168
+ key={index}
169
+ className={`p-3 rounded border-2 ${getSentimentColor(result.label)}`}
170
+ >
171
+ <div className="flex justify-between items-start mb-2">
172
+ <span className="font-semibold text-sm">
173
+ {formatLabel(result.label)}
174
+ </span>
175
+ <span className="text-sm font-mono">
176
+ {(result.score * 100).toFixed(1)}%
177
+ </span>
178
+ </div>
179
+ <div className="text-sm text-gray-700">
180
+ {result.sequence}
181
+ </div>
182
+ </div>
183
+ ))}
184
+ </div>
185
+ )}
186
+ </div>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ );
191
+ }
192
+
193
+ export default TextClassification;
src/components/ZeroShotClassification.tsx ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // src/App.tsx
2
+ import { useState, useRef, useEffect, useCallback } from "react";
3
+ import { Section, WorkerMessage, WorkerInput } 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
+
11
+ // overheating
12
+ "This phone heats up way too quickly, especially when using demanding apps. It's uncomfortable to hold, and I'm concerned it might damage the internal components over time. Not what I expected",
13
+ "This phone is like holding a hot potato. Video calls turn it into a scalding nightmare. Seriously, can't it keep its cool?",
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
+
49
+ // Create a reference to the worker object.
50
+ const worker = useRef<Worker | null>(null);
51
+
52
+ // We use the `useEffect` hook to setup the worker as soon as the `App` component is mounted.
53
+ useEffect(() => {
54
+ if (!worker.current) {
55
+ // Create the worker if it does not yet exist.
56
+ worker.current = new Worker(
57
+ new URL("../workers/zero-shot.js", import.meta.url),
58
+ {
59
+ type: "module",
60
+ }
61
+ );
62
+ }
63
+
64
+ // Create a callback function for messages from the worker thread.
65
+ const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
66
+ const status = e.data.status;
67
+ if (status === "initiate") {
68
+ setStatus("loading");
69
+ } else if (status === "ready") {
70
+ setStatus("ready");
71
+ } else if (status === "output") {
72
+ const { sequence, labels, scores } = e.data.output!;
73
+
74
+ // Threshold for classification
75
+ const label = scores[0] > 0.5 ? labels[0] : "Other";
76
+
77
+ const sectionID =
78
+ sections.map((x) => x.title).indexOf(label) ?? sections.length - 1;
79
+ setSections((sections) => {
80
+ const newSections = [...sections];
81
+ newSections[sectionID] = {
82
+ ...newSections[sectionID],
83
+ items: [...newSections[sectionID].items, sequence],
84
+ };
85
+ return newSections;
86
+ });
87
+ } else if (status === "complete") {
88
+ setStatus("idle");
89
+ }
90
+ };
91
+
92
+ // Attach the callback function as an event listener.
93
+ worker.current.addEventListener("message", onMessageReceived);
94
+
95
+ // Define a cleanup function for when the component is unmounted.
96
+ return () =>
97
+ worker.current?.removeEventListener("message", onMessageReceived);
98
+ }, [sections]);
99
+
100
+ const classify = useCallback(() => {
101
+ setStatus("processing");
102
+ const message: WorkerInput = {
103
+ text,
104
+ labels: sections
105
+ .slice(0, sections.length - 1)
106
+ .map((section) => section.title),
107
+ };
108
+ worker.current?.postMessage(message);
109
+ }, [text, sections]);
110
+
111
+ const busy: boolean = status !== "idle";
112
+
113
+ const handleAddCategory = (): void => {
114
+ setSections((sections) => {
115
+ const newSections = [...sections];
116
+ // add at position 2 from the end
117
+ newSections.splice(newSections.length - 1, 0, {
118
+ title: "New Category",
119
+ items: [],
120
+ });
121
+ return newSections;
122
+ });
123
+ };
124
+
125
+ const handleRemoveCategory = (): void => {
126
+ setSections((sections) => {
127
+ const newSections = [...sections];
128
+ newSections.splice(newSections.length - 2, 1); // Remove second last element
129
+ return newSections;
130
+ });
131
+ };
132
+
133
+ const handleClear = (): void => {
134
+ setSections((sections) =>
135
+ sections.map((section) => ({
136
+ ...section,
137
+ items: [],
138
+ })),
139
+ );
140
+ };
141
+
142
+ const handleSectionTitleChange = (index: number, newTitle: string): void => {
143
+ setSections((sections) => {
144
+ const newSections = [...sections];
145
+ newSections[index].title = newTitle;
146
+ return newSections;
147
+ });
148
+ };
149
+
150
+ return (
151
+ <div className="flex flex-col h-screen w-screen p-1">
152
+ <textarea
153
+ className="border w-full p-1 h-1/2"
154
+ value={text}
155
+ onChange={(e) => setText(e.target.value)}
156
+ ></textarea>
157
+ <div className="flex flex-col justify-center items-center m-2 gap-1">
158
+ <button
159
+ className="border py-1 px-2 bg-blue-400 rounded text-white text-lg font-medium disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"
160
+ disabled={busy}
161
+ onClick={classify}
162
+ >
163
+ {!busy
164
+ ? "Categorize"
165
+ : status === "loading"
166
+ ? "Model loading..."
167
+ : "Processing"}
168
+ </button>
169
+ <div className="flex gap-1">
170
+ <button
171
+ className="border py-1 px-2 bg-green-400 rounded text-white text-sm font-medium cursor-pointer"
172
+ onClick={handleAddCategory}
173
+ >
174
+ Add category
175
+ </button>
176
+ <button
177
+ className="border py-1 px-2 bg-red-400 rounded text-white text-sm font-medium cursor-pointer"
178
+ disabled={sections.length <= 1}
179
+ onClick={handleRemoveCategory}
180
+ >
181
+ Remove category
182
+ </button>
183
+ <button
184
+ className="border py-1 px-2 bg-orange-400 rounded text-white text-sm font-medium cursor-pointer"
185
+ onClick={handleClear}
186
+ >
187
+ Clear
188
+ </button>
189
+ </div>
190
+ </div>
191
+
192
+ <div className="flex justify-between flex-grow overflow-x-auto max-h-[40%]">
193
+ {sections.map((section, index) => (
194
+ <div key={index} className="flex flex-col w-full">
195
+ <input
196
+ disabled={section.title === "Other"}
197
+ className="w-full border px-1 text-center"
198
+ value={section.title}
199
+ onChange={(e) => handleSectionTitleChange(index, e.target.value)}
200
+ ></input>
201
+ <div className="overflow-y-auto h-full border">
202
+ {section.items.map((item, itemIndex) => (
203
+ <div
204
+ className="m-2 border bg-red-50 rounded p-1 text-sm"
205
+ key={itemIndex}
206
+ >
207
+ {item}
208
+ </div>
209
+ ))}
210
+ </div>
211
+ </div>
212
+ ))}
213
+ </div>
214
+ </div>
215
+ );
216
+ }
217
+
218
+ export default ZeroShotClassification;
src/types.ts CHANGED
@@ -11,7 +11,7 @@ export interface ClassificationOutput {
11
 
12
  export interface WorkerMessage {
13
  status: 'initiate' | 'ready' | 'output' | 'complete';
14
- output?: ClassificationOutput;
15
  }
16
 
17
  export interface WorkerInput {
 
11
 
12
  export interface WorkerMessage {
13
  status: 'initiate' | 'ready' | 'output' | 'complete';
14
+ output?: any;
15
  }
16
 
17
  export interface WorkerInput {
src/worker.js CHANGED
@@ -1,15 +1,17 @@
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;
15
  }
@@ -17,25 +19,25 @@ class MyZeroShotClassificationPipeline {
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(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 PipelineFactory {
5
+ static task = null;
6
+ static model = null;
7
  static instance = null;
8
 
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
 
16
  return this.instance;
17
  }
 
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;
25
+ PipelineFactory.model = model;
26
+
27
  // Retrieve the pipeline. When called for the first time,
28
  // this will load the pipeline and save it for future use.
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(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/text-classification.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable no-restricted-globals */
2
+ import { pipeline } from "@huggingface/transformers";
3
+
4
+ class MyTextClassificationPipeline {
5
+ static task = "sentiment-analysis";
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;
15
+ }
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(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
+ label: output[0].label,
40
+ score: output[0].score
41
+ }
42
+ });
43
+ }
44
+ }
45
+ // Send the output back to the main thread
46
+ self.postMessage({ status: "complete" });
47
+ });
src/workers/zero-shot.js ADDED
@@ -0,0 +1,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;
15
+ }
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(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
+ });