Implement sentiment analysis feature and update pipeline selection
Browse files
src/App.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
import { useState } from "react";
|
| 3 |
import PipelineSelector from "./components/PipelineSelector";
|
| 4 |
import ZeroShotClassification from "./components/ZeroShotClassification";
|
| 5 |
-
import
|
| 6 |
|
| 7 |
function App() {
|
| 8 |
const [pipeline, setPipeline] = useState("zero-shot-classification");
|
|
@@ -11,7 +11,7 @@ function App() {
|
|
| 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 === "
|
| 15 |
</div>
|
| 16 |
);
|
| 17 |
}
|
|
|
|
| 2 |
import { useState } from "react";
|
| 3 |
import PipelineSelector from "./components/PipelineSelector";
|
| 4 |
import ZeroShotClassification from "./components/ZeroShotClassification";
|
| 5 |
+
import SentimentAnalysis from "./components/SentimentAnalysis";
|
| 6 |
|
| 7 |
function App() {
|
| 8 |
const [pipeline, setPipeline] = useState("zero-shot-classification");
|
|
|
|
| 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 === "sentiment-analysis" && <SentimentAnalysis />}
|
| 15 |
</div>
|
| 16 |
);
|
| 17 |
}
|
src/components/PipelineSelector.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import React from 'react';
|
|
| 3 |
|
| 4 |
const pipelines = [
|
| 5 |
'zero-shot-classification',
|
| 6 |
-
'
|
| 7 |
'image-classification',
|
| 8 |
'question-answering',
|
| 9 |
'translation',
|
|
|
|
| 3 |
|
| 4 |
const pipelines = [
|
| 5 |
'zero-shot-classification',
|
| 6 |
+
'sentiment-analysis',
|
| 7 |
'image-classification',
|
| 8 |
'question-answering',
|
| 9 |
'translation',
|
src/components/{TextClassification.tsx → SentimentAnalysis.tsx}
RENAMED
|
@@ -1,19 +1,5 @@
|
|
| 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.",
|
|
@@ -28,9 +14,9 @@ const PLACEHOLDER_TEXTS: string[] = [
|
|
| 28 |
"Outstanding! This company really knows how to treat their customers.",
|
| 29 |
].sort(() => Math.random() - 0.5);
|
| 30 |
|
| 31 |
-
function
|
| 32 |
const [text, setText] = useState<string>(PLACEHOLDER_TEXTS.join("\n"));
|
| 33 |
-
const [results, setResults] = useState<
|
| 34 |
const [status, setStatus] = useState<string>("idle");
|
| 35 |
|
| 36 |
// Create a reference to the worker object.
|
|
@@ -41,7 +27,7 @@ function TextClassification() {
|
|
| 41 |
if (!worker.current) {
|
| 42 |
// Create the worker if it does not yet exist.
|
| 43 |
worker.current = new Worker(
|
| 44 |
-
new URL("../workers/
|
| 45 |
{
|
| 46 |
type: "module",
|
| 47 |
}
|
|
@@ -49,7 +35,7 @@ function TextClassification() {
|
|
| 49 |
}
|
| 50 |
|
| 51 |
// Create a callback function for messages from the worker thread.
|
| 52 |
-
const onMessageReceived = (e: MessageEvent<
|
| 53 |
const status = e.data.status;
|
| 54 |
if (status === "initiate") {
|
| 55 |
setStatus("loading");
|
|
@@ -74,7 +60,7 @@ function TextClassification() {
|
|
| 74 |
const classify = useCallback(() => {
|
| 75 |
setStatus("processing");
|
| 76 |
setResults([]); // Clear previous results
|
| 77 |
-
const message:
|
| 78 |
worker.current?.postMessage(message);
|
| 79 |
}, [text]);
|
| 80 |
|
|
@@ -166,14 +152,14 @@ function TextClassification() {
|
|
| 166 |
{results.map((result, index) => (
|
| 167 |
<div
|
| 168 |
key={index}
|
| 169 |
-
className={`p-3 rounded border-2 ${getSentimentColor(result.
|
| 170 |
>
|
| 171 |
<div className="flex justify-between items-start mb-2">
|
| 172 |
<span className="font-semibold text-sm">
|
| 173 |
-
{formatLabel(result.
|
| 174 |
</span>
|
| 175 |
<span className="text-sm font-mono">
|
| 176 |
-
{(result.
|
| 177 |
</span>
|
| 178 |
</div>
|
| 179 |
<div className="text-sm text-gray-700">
|
|
@@ -190,4 +176,4 @@ function TextClassification() {
|
|
| 190 |
);
|
| 191 |
}
|
| 192 |
|
| 193 |
-
export default
|
|
|
|
| 1 |
import { useState, useRef, useEffect, useCallback } from "react";
|
| 2 |
+
import { ClassificationOutput, SentimentAnalysisWorkerInput, WorkerMessage } from "../types";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
const PLACEHOLDER_TEXTS: string[] = [
|
| 5 |
"I absolutely love this product! It exceeded all my expectations.",
|
|
|
|
| 14 |
"Outstanding! This company really knows how to treat their customers.",
|
| 15 |
].sort(() => Math.random() - 0.5);
|
| 16 |
|
| 17 |
+
function SentimentAnalysis() {
|
| 18 |
const [text, setText] = useState<string>(PLACEHOLDER_TEXTS.join("\n"));
|
| 19 |
+
const [results, setResults] = useState<ClassificationOutput[]>([]);
|
| 20 |
const [status, setStatus] = useState<string>("idle");
|
| 21 |
|
| 22 |
// Create a reference to the worker object.
|
|
|
|
| 27 |
if (!worker.current) {
|
| 28 |
// Create the worker if it does not yet exist.
|
| 29 |
worker.current = new Worker(
|
| 30 |
+
new URL("../workers/sentiment-analysis.js", import.meta.url),
|
| 31 |
{
|
| 32 |
type: "module",
|
| 33 |
}
|
|
|
|
| 35 |
}
|
| 36 |
|
| 37 |
// Create a callback function for messages from the worker thread.
|
| 38 |
+
const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
|
| 39 |
const status = e.data.status;
|
| 40 |
if (status === "initiate") {
|
| 41 |
setStatus("loading");
|
|
|
|
| 60 |
const classify = useCallback(() => {
|
| 61 |
setStatus("processing");
|
| 62 |
setResults([]); // Clear previous results
|
| 63 |
+
const message: SentimentAnalysisWorkerInput = { text };
|
| 64 |
worker.current?.postMessage(message);
|
| 65 |
}, [text]);
|
| 66 |
|
|
|
|
| 152 |
{results.map((result, index) => (
|
| 153 |
<div
|
| 154 |
key={index}
|
| 155 |
+
className={`p-3 rounded border-2 ${getSentimentColor(result.labels[0])}`}
|
| 156 |
>
|
| 157 |
<div className="flex justify-between items-start mb-2">
|
| 158 |
<span className="font-semibold text-sm">
|
| 159 |
+
{formatLabel(result.labels[0])}
|
| 160 |
</span>
|
| 161 |
<span className="text-sm font-mono">
|
| 162 |
+
{(result.scores[0] * 100).toFixed(1)}%
|
| 163 |
</span>
|
| 164 |
</div>
|
| 165 |
<div className="text-sm text-gray-700">
|
|
|
|
| 176 |
);
|
| 177 |
}
|
| 178 |
|
| 179 |
+
export default SentimentAnalysis;
|
src/components/ZeroShotClassification.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
// src/App.tsx
|
| 2 |
import { useState, useRef, useEffect, useCallback } from "react";
|
| 3 |
-
import { Section, WorkerMessage,
|
| 4 |
|
| 5 |
const PLACEHOLDER_REVIEWS: string[] = [
|
| 6 |
// battery/charging problems
|
|
@@ -70,7 +70,7 @@ function ZeroShotClassification() {
|
|
| 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 |
|
|
@@ -99,7 +99,7 @@ function ZeroShotClassification() {
|
|
| 99 |
|
| 100 |
const classify = useCallback(() => {
|
| 101 |
setStatus("processing");
|
| 102 |
-
const message:
|
| 103 |
text,
|
| 104 |
labels: sections
|
| 105 |
.slice(0, sections.length - 1)
|
|
|
|
| 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
|
|
|
|
| 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 |
|
|
|
|
| 99 |
|
| 100 |
const classify = useCallback(() => {
|
| 101 |
setStatus("processing");
|
| 102 |
+
const message: ZeroShotWorkerInput = {
|
| 103 |
text,
|
| 104 |
labels: sections
|
| 105 |
.slice(0, sections.length - 1)
|
src/types.ts
CHANGED
|
@@ -9,14 +9,21 @@ export interface ClassificationOutput {
|
|
| 9 |
scores: number[];
|
| 10 |
}
|
| 11 |
|
|
|
|
|
|
|
| 12 |
export interface WorkerMessage {
|
| 13 |
status: 'initiate' | 'ready' | 'output' | 'complete';
|
| 14 |
output?: any;
|
| 15 |
}
|
| 16 |
|
| 17 |
-
export interface
|
| 18 |
text: string;
|
| 19 |
labels: string[];
|
| 20 |
}
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
export type AppStatus = 'idle' | 'loading' | 'processing';
|
|
|
|
| 9 |
scores: number[];
|
| 10 |
}
|
| 11 |
|
| 12 |
+
|
| 13 |
+
|
| 14 |
export interface WorkerMessage {
|
| 15 |
status: 'initiate' | 'ready' | 'output' | 'complete';
|
| 16 |
output?: any;
|
| 17 |
}
|
| 18 |
|
| 19 |
+
export interface ZeroShotWorkerInput {
|
| 20 |
text: string;
|
| 21 |
labels: string[];
|
| 22 |
}
|
| 23 |
|
| 24 |
+
|
| 25 |
+
export interface SentimentAnalysisWorkerInput {
|
| 26 |
+
text: string;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
export type AppStatus = 'idle' | 'loading' | 'processing';
|
src/workers/{text-classification.js → sentiment-analysis.js}
RENAMED
|
@@ -36,8 +36,8 @@ self.addEventListener("message", async (event) => {
|
|
| 36 |
status: "output",
|
| 37 |
output: {
|
| 38 |
sequence: line,
|
| 39 |
-
|
| 40 |
-
|
| 41 |
}
|
| 42 |
});
|
| 43 |
}
|
|
|
|
| 36 |
status: "output",
|
| 37 |
output: {
|
| 38 |
sequence: line,
|
| 39 |
+
labels: [output[0].label],
|
| 40 |
+
scores: [output[0].score]
|
| 41 |
}
|
| 42 |
});
|
| 43 |
}
|