improve zero-shot-classification
Browse files- src/components/ModelSelector.tsx +1 -0
- src/components/PipelineLayout.tsx +7 -2
- src/components/Sidebar.tsx +4 -0
- src/components/TextGenerationConfig.tsx +55 -16
- src/components/ZeroShotClassification.tsx +93 -135
- src/components/ZeroShotClassificationConfig.tsx +111 -0
- src/contexts/ZeroShotClassificationContext.tsx +132 -0
src/components/ModelSelector.tsx
CHANGED
@@ -114,6 +114,7 @@ function ModelSelector() {
|
|
114 |
),
|
115 |
widgetData: modelInfoResponse.widgetData
|
116 |
}
|
|
|
117 |
setModelInfo(modelInfo)
|
118 |
setIsCustomModel(isCustom)
|
119 |
setIsFetching(false)
|
|
|
114 |
),
|
115 |
widgetData: modelInfoResponse.widgetData
|
116 |
}
|
117 |
+
console.log(modelInfo)
|
118 |
setModelInfo(modelInfo)
|
119 |
setIsCustomModel(isCustom)
|
120 |
setIsFetching(false)
|
src/components/PipelineLayout.tsx
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import { useModel } from '../contexts/ModelContext'
|
2 |
import { TextGenerationProvider } from '../contexts/TextGenerationContext'
|
3 |
import { FeatureExtractionProvider } from '../contexts/FeatureExtractionContext'
|
|
|
4 |
|
5 |
export const PipelineLayout = ({ children }: { children: React.ReactNode }) => {
|
6 |
const { pipeline } = useModel()
|
@@ -12,8 +13,12 @@ export const PipelineLayout = ({ children }: { children: React.ReactNode }) => {
|
|
12 |
case 'feature-extraction':
|
13 |
return <FeatureExtractionProvider>{children}</FeatureExtractionProvider>
|
14 |
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
|
|
17 |
|
18 |
default:
|
19 |
return <>{children}</>
|
|
|
1 |
import { useModel } from '../contexts/ModelContext'
|
2 |
import { TextGenerationProvider } from '../contexts/TextGenerationContext'
|
3 |
import { FeatureExtractionProvider } from '../contexts/FeatureExtractionContext'
|
4 |
+
import { ZeroShotClassificationProvider } from '../contexts/ZeroShotClassificationContext'
|
5 |
|
6 |
export const PipelineLayout = ({ children }: { children: React.ReactNode }) => {
|
7 |
const { pipeline } = useModel()
|
|
|
13 |
case 'feature-extraction':
|
14 |
return <FeatureExtractionProvider>{children}</FeatureExtractionProvider>
|
15 |
|
16 |
+
case 'zero-shot-classification':
|
17 |
+
return (
|
18 |
+
<ZeroShotClassificationProvider>
|
19 |
+
{children}
|
20 |
+
</ZeroShotClassificationProvider>
|
21 |
+
)
|
22 |
|
23 |
default:
|
24 |
return <>{children}</>
|
src/components/Sidebar.tsx
CHANGED
@@ -5,6 +5,7 @@ import ModelInfo from './ModelInfo'
|
|
5 |
import { useModel } from '../contexts/ModelContext'
|
6 |
import TextGenerationConfig from './TextGenerationConfig'
|
7 |
import FeatureExtractionConfig from './FeatureExtractionConfig'
|
|
|
8 |
|
9 |
interface SidebarProps {
|
10 |
isOpen: boolean
|
@@ -77,6 +78,9 @@ const Sidebar = ({ isOpen, onClose }: SidebarProps) => {
|
|
77 |
<hr className="border-gray-200" />
|
78 |
{pipeline === 'text-generation' && <TextGenerationConfig />}
|
79 |
{pipeline === 'feature-extraction' && <FeatureExtractionConfig />}
|
|
|
|
|
|
|
80 |
</div>
|
81 |
</div>
|
82 |
</div>
|
|
|
5 |
import { useModel } from '../contexts/ModelContext'
|
6 |
import TextGenerationConfig from './TextGenerationConfig'
|
7 |
import FeatureExtractionConfig from './FeatureExtractionConfig'
|
8 |
+
import ZeroShotClassificationConfig from './ZeroShotClassificationConfig'
|
9 |
|
10 |
interface SidebarProps {
|
11 |
isOpen: boolean
|
|
|
78 |
<hr className="border-gray-200" />
|
79 |
{pipeline === 'text-generation' && <TextGenerationConfig />}
|
80 |
{pipeline === 'feature-extraction' && <FeatureExtractionConfig />}
|
81 |
+
{pipeline === 'zero-shot-classification' && (
|
82 |
+
<ZeroShotClassificationConfig />
|
83 |
+
)}
|
84 |
</div>
|
85 |
</div>
|
86 |
</div>
|
src/components/TextGenerationConfig.tsx
CHANGED
@@ -18,13 +18,12 @@ function TextGenerationConfig() {
|
|
18 |
}
|
19 |
|
20 |
return (
|
21 |
-
<div className="space-y-
|
22 |
<h3 className="text-lg font-semibold text-gray-900">
|
23 |
Text Generation Settings
|
24 |
</h3>
|
25 |
|
26 |
-
|
27 |
-
<div className="space-y-4 px-10">
|
28 |
<div>
|
29 |
<label className="block text-sm font-medium text-gray-700 mb-1">
|
30 |
Temperature: {config.temperature}
|
@@ -40,6 +39,10 @@ function TextGenerationConfig() {
|
|
40 |
}
|
41 |
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
42 |
/>
|
|
|
|
|
|
|
|
|
43 |
</div>
|
44 |
|
45 |
<div>
|
@@ -57,6 +60,9 @@ function TextGenerationConfig() {
|
|
57 |
}
|
58 |
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
59 |
/>
|
|
|
|
|
|
|
60 |
</div>
|
61 |
|
62 |
<div>
|
@@ -74,6 +80,10 @@ function TextGenerationConfig() {
|
|
74 |
}
|
75 |
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
76 |
/>
|
|
|
|
|
|
|
|
|
77 |
</div>
|
78 |
|
79 |
<div>
|
@@ -91,9 +101,12 @@ function TextGenerationConfig() {
|
|
91 |
}
|
92 |
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
93 |
/>
|
|
|
|
|
|
|
94 |
</div>
|
95 |
|
96 |
-
<div className="flex items-center
|
97 |
<Switch
|
98 |
checked={config.doSample}
|
99 |
onChange={(checked) => handleConfigChange('doSample', checked)}
|
@@ -109,21 +122,47 @@ function TextGenerationConfig() {
|
|
109 |
Do Sample
|
110 |
</label>
|
111 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
</div>
|
113 |
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
|
|
|
|
|
|
125 |
</div>
|
126 |
-
|
127 |
</div>
|
128 |
)
|
129 |
}
|
|
|
18 |
}
|
19 |
|
20 |
return (
|
21 |
+
<div className="space-y-4">
|
22 |
<h3 className="text-lg font-semibold text-gray-900">
|
23 |
Text Generation Settings
|
24 |
</h3>
|
25 |
|
26 |
+
<div className="space-y-3">
|
|
|
27 |
<div>
|
28 |
<label className="block text-sm font-medium text-gray-700 mb-1">
|
29 |
Temperature: {config.temperature}
|
|
|
39 |
}
|
40 |
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
41 |
/>
|
42 |
+
<p className="text-xs text-gray-500 mt-1">
|
43 |
+
Controls randomness in generation (lower = more focused, higher =
|
44 |
+
more creative)
|
45 |
+
</p>
|
46 |
</div>
|
47 |
|
48 |
<div>
|
|
|
60 |
}
|
61 |
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
62 |
/>
|
63 |
+
<p className="text-xs text-gray-500 mt-1">
|
64 |
+
Maximum number of tokens to generate in the response
|
65 |
+
</p>
|
66 |
</div>
|
67 |
|
68 |
<div>
|
|
|
80 |
}
|
81 |
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
82 |
/>
|
83 |
+
<p className="text-xs text-gray-500 mt-1">
|
84 |
+
Nucleus sampling - considers tokens with cumulative probability up
|
85 |
+
to this value
|
86 |
+
</p>
|
87 |
</div>
|
88 |
|
89 |
<div>
|
|
|
101 |
}
|
102 |
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
103 |
/>
|
104 |
+
<p className="text-xs text-gray-500 mt-1">
|
105 |
+
Only consider the top K most likely tokens at each step
|
106 |
+
</p>
|
107 |
</div>
|
108 |
|
109 |
+
<div className="flex items-center">
|
110 |
<Switch
|
111 |
checked={config.doSample}
|
112 |
onChange={(checked) => handleConfigChange('doSample', checked)}
|
|
|
122 |
Do Sample
|
123 |
</label>
|
124 |
</div>
|
125 |
+
<p className="text-xs text-gray-500 mt-1">
|
126 |
+
Enable sampling-based generation (disable for deterministic output)
|
127 |
+
</p>
|
128 |
+
|
129 |
+
{/* System Message for Chat */}
|
130 |
+
{modelInfo?.hasChatTemplate && (
|
131 |
+
<div className="pt-2 border-t border-gray-200">
|
132 |
+
<h4 className="text-sm font-semibold text-gray-800 mb-2">
|
133 |
+
System Message
|
134 |
+
</h4>
|
135 |
+
<textarea
|
136 |
+
value={messages.find((m) => m.role === 'system')?.content || ''}
|
137 |
+
onChange={(e) => updateSystemMessage(e.target.value)}
|
138 |
+
className="w-full p-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
139 |
+
rows={4}
|
140 |
+
placeholder="e.g., You are a helpful assistant."
|
141 |
+
/>
|
142 |
+
<p className="text-xs text-gray-500 mt-1">
|
143 |
+
Initial instructions that guide the model's behavior throughout
|
144 |
+
the conversation
|
145 |
+
</p>
|
146 |
+
</div>
|
147 |
+
)}
|
148 |
</div>
|
149 |
|
150 |
+
<div className="pt-2 border-t border-gray-200">
|
151 |
+
<div className="text-xs text-gray-500">
|
152 |
+
<p className="mb-1">
|
153 |
+
<strong>Temperature:</strong> Higher values make output more random,
|
154 |
+
lower values more focused
|
155 |
+
</p>
|
156 |
+
<p className="mb-1">
|
157 |
+
<strong>Top-p & Top-k:</strong> Control which tokens are considered
|
158 |
+
during generation
|
159 |
+
</p>
|
160 |
+
<p>
|
161 |
+
<strong>Sampling:</strong> When disabled, always picks the most
|
162 |
+
likely next token (greedy decoding)
|
163 |
+
</p>
|
164 |
</div>
|
165 |
+
</div>
|
166 |
</div>
|
167 |
)
|
168 |
}
|
src/components/ZeroShotClassification.tsx
CHANGED
@@ -1,48 +1,12 @@
|
|
1 |
-
import {
|
2 |
-
import {
|
3 |
import { useModel } from '../contexts/ModelContext'
|
4 |
-
|
5 |
-
|
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
|
42 |
-
|
43 |
-
const [sections, setSections] = useState<Section[]>(
|
44 |
-
PLACEHOLDER_SECTIONS.map((title) => ({ title, items: [] }))
|
45 |
-
)
|
46 |
|
47 |
const {
|
48 |
activeWorker,
|
@@ -76,7 +40,14 @@ function ZeroShotClassification() {
|
|
76 |
dtype: selectedQuantization ?? 'fp32'
|
77 |
}
|
78 |
activeWorker.postMessage(message)
|
79 |
-
}, [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
|
81 |
// Handle worker messages
|
82 |
useEffect(() => {
|
@@ -88,7 +59,7 @@ function ZeroShotClassification() {
|
|
88 |
const { sequence, labels, scores } = e.data.output!
|
89 |
|
90 |
// Threshold for classification
|
91 |
-
const label = scores[0] >
|
92 |
|
93 |
const sectionID =
|
94 |
sections.map((x) => x.title).indexOf(label) ?? sections.length - 1
|
@@ -105,111 +76,98 @@ function ZeroShotClassification() {
|
|
105 |
|
106 |
activeWorker.addEventListener('message', onMessageReceived)
|
107 |
return () => activeWorker.removeEventListener('message', onMessageReceived)
|
108 |
-
}, [sections, activeWorker])
|
109 |
|
110 |
const busy: boolean = status !== 'ready'
|
111 |
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
title: 'New Category',
|
118 |
-
items: []
|
119 |
-
})
|
120 |
-
return newSections
|
121 |
-
})
|
122 |
-
}
|
123 |
-
|
124 |
-
const handleRemoveCategory = (): void => {
|
125 |
-
setSections((sections) => {
|
126 |
-
const newSections = [...sections]
|
127 |
-
newSections.splice(newSections.length - 2, 1) // Remove second last element
|
128 |
-
return newSections
|
129 |
-
})
|
130 |
-
}
|
131 |
-
|
132 |
-
const handleClear = (): void => {
|
133 |
-
setSections((sections) =>
|
134 |
-
sections.map((section) => ({
|
135 |
-
...section,
|
136 |
-
items: []
|
137 |
-
}))
|
138 |
-
)
|
139 |
-
}
|
140 |
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
148 |
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
className="border w-full p-1 h-1/2"
|
153 |
-
value={text}
|
154 |
-
onChange={(e) => setText(e.target.value)}
|
155 |
-
></textarea>
|
156 |
-
<div className="flex flex-col justify-center items-center m-2 gap-1">
|
157 |
-
<button
|
158 |
-
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"
|
159 |
-
disabled={busy}
|
160 |
-
onClick={classify}
|
161 |
-
>
|
162 |
-
{hasBeenLoaded
|
163 |
-
? !busy
|
164 |
-
? 'Categorize'
|
165 |
-
: 'Processing...'
|
166 |
-
: 'Load model first'}
|
167 |
-
</button>
|
168 |
-
<div className="flex gap-1">
|
169 |
<button
|
170 |
-
|
171 |
-
|
|
|
172 |
>
|
173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
</button>
|
175 |
-
|
176 |
-
className="border py-1 px-2 bg-red-400 rounded text-white text-sm font-medium cursor-pointer"
|
177 |
-
disabled={sections.length <= 1}
|
178 |
-
onClick={handleRemoveCategory}
|
179 |
-
>
|
180 |
-
Remove category
|
181 |
-
</button>
|
182 |
-
<button
|
183 |
-
className="border py-1 px-2 bg-orange-400 rounded text-white text-sm font-medium cursor-pointer"
|
184 |
-
onClick={handleClear}
|
185 |
-
>
|
186 |
-
Clear
|
187 |
-
</button>
|
188 |
-
</div>
|
189 |
</div>
|
190 |
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
className="m-2 border bg-red-50 rounded p-1 text-sm"
|
204 |
-
key={itemIndex}
|
205 |
>
|
206 |
-
{
|
|
|
|
|
|
|
207 |
</div>
|
208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
209 |
</div>
|
210 |
-
|
211 |
-
|
212 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
213 |
</div>
|
214 |
)
|
215 |
}
|
|
|
1 |
+
import { useEffect, useCallback } from 'react'
|
2 |
+
import { WorkerMessage, ZeroShotWorkerInput } from '../types'
|
3 |
import { useModel } from '../contexts/ModelContext'
|
4 |
+
import { useZeroShotClassification } from '../contexts/ZeroShotClassificationContext'
|
5 |
+
import { Send, Loader2 } from 'lucide-react'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
function ZeroShotClassification() {
|
8 |
+
const { text, setText, sections, setSections, config } =
|
9 |
+
useZeroShotClassification()
|
|
|
|
|
|
|
10 |
|
11 |
const {
|
12 |
activeWorker,
|
|
|
40 |
dtype: selectedQuantization ?? 'fp32'
|
41 |
}
|
42 |
activeWorker.postMessage(message)
|
43 |
+
}, [
|
44 |
+
text,
|
45 |
+
sections,
|
46 |
+
modelInfo,
|
47 |
+
activeWorker,
|
48 |
+
selectedQuantization,
|
49 |
+
setSections
|
50 |
+
])
|
51 |
|
52 |
// Handle worker messages
|
53 |
useEffect(() => {
|
|
|
59 |
const { sequence, labels, scores } = e.data.output!
|
60 |
|
61 |
// Threshold for classification
|
62 |
+
const label = scores[0] > config.threshold ? labels[0] : 'Other'
|
63 |
|
64 |
const sectionID =
|
65 |
sections.map((x) => x.title).indexOf(label) ?? sections.length - 1
|
|
|
76 |
|
77 |
activeWorker.addEventListener('message', onMessageReceived)
|
78 |
return () => activeWorker.removeEventListener('message', onMessageReceived)
|
79 |
+
}, [sections, activeWorker, config.threshold, setSections])
|
80 |
|
81 |
const busy: boolean = status !== 'ready'
|
82 |
|
83 |
+
return (
|
84 |
+
<div className="flex flex-col h-[70vh] max-h-[100vh] w-full p-4">
|
85 |
+
<div className="flex items-center justify-between mb-4">
|
86 |
+
<h1 className="text-2xl font-bold">Zero-Shot Classification</h1>
|
87 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
|
89 |
+
{/* Input Text Area */}
|
90 |
+
<div className="mb-4">
|
91 |
+
<label className="block text-sm font-medium text-gray-700 mb-2">
|
92 |
+
Text to classify (one item per line):
|
93 |
+
</label>
|
94 |
+
<textarea
|
95 |
+
value={text}
|
96 |
+
onChange={(e) => setText(e.target.value)}
|
97 |
+
placeholder="Enter text items to classify, one per line..."
|
98 |
+
className="w-full p-3 border border-gray-300 rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
99 |
+
rows={8}
|
100 |
+
disabled={!hasBeenLoaded || busy}
|
101 |
+
/>
|
102 |
+
</div>
|
103 |
|
104 |
+
{/* Classify Button */}
|
105 |
+
<div className="mb-4">
|
106 |
+
{hasBeenLoaded && (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
<button
|
108 |
+
onClick={classify}
|
109 |
+
disabled={!text.trim() || busy || !hasBeenLoaded}
|
110 |
+
className="px-6 py-2 bg-blue-500 hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed text-white rounded-lg transition-colors flex items-center gap-2"
|
111 |
>
|
112 |
+
{busy ? (
|
113 |
+
<>
|
114 |
+
<Loader2 className="w-4 h-4 animate-spin" />
|
115 |
+
Processing...
|
116 |
+
</>
|
117 |
+
) : (
|
118 |
+
<>
|
119 |
+
<Send className="w-4 h-4" />
|
120 |
+
Categorize
|
121 |
+
</>
|
122 |
+
)}
|
123 |
</button>
|
124 |
+
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
</div>
|
126 |
|
127 |
+
{/* Results Grid */}
|
128 |
+
<div className="flex-1 overflow-hidden">
|
129 |
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-4 h-full">
|
130 |
+
{sections.map((section, index) => (
|
131 |
+
<div
|
132 |
+
key={index}
|
133 |
+
className="flex flex-col bg-white border border-gray-200 rounded-lg overflow-hidden"
|
134 |
+
>
|
135 |
+
<div className="px-3 py-2 bg-gray-50 border-b border-gray-200">
|
136 |
+
<h3
|
137 |
+
className="font-medium text-gray-900 text-center truncate"
|
138 |
+
title={section.title}
|
|
|
|
|
139 |
>
|
140 |
+
{section.title}
|
141 |
+
</h3>
|
142 |
+
<div className="text-xs text-gray-500 text-center">
|
143 |
+
{section.items.length} items
|
144 |
</div>
|
145 |
+
</div>
|
146 |
+
<div className="flex-1 overflow-y-auto p-3 space-y-2">
|
147 |
+
{section.items.map((item, itemIndex) => (
|
148 |
+
<div
|
149 |
+
key={itemIndex}
|
150 |
+
className="p-2 bg-blue-50 border border-blue-200 rounded text-sm"
|
151 |
+
>
|
152 |
+
{item}
|
153 |
+
</div>
|
154 |
+
))}
|
155 |
+
{section.items.length === 0 && (
|
156 |
+
<div className="text-gray-400 text-sm italic text-center py-4">
|
157 |
+
No items classified here yet
|
158 |
+
</div>
|
159 |
+
)}
|
160 |
+
</div>
|
161 |
</div>
|
162 |
+
))}
|
163 |
+
</div>
|
164 |
</div>
|
165 |
+
|
166 |
+
{!hasBeenLoaded && (
|
167 |
+
<div className="text-center text-gray-500 text-sm mt-2">
|
168 |
+
Please load a model first to start classifying text
|
169 |
+
</div>
|
170 |
+
)}
|
171 |
</div>
|
172 |
)
|
173 |
}
|
src/components/ZeroShotClassificationConfig.tsx
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from 'react'
|
2 |
+
import { Plus, Minus, Trash2 } from 'lucide-react'
|
3 |
+
import { useZeroShotClassification } from '../contexts/ZeroShotClassificationContext'
|
4 |
+
|
5 |
+
const ZeroShotClassificationConfig = () => {
|
6 |
+
const {
|
7 |
+
config,
|
8 |
+
setConfig,
|
9 |
+
sections,
|
10 |
+
addCategory,
|
11 |
+
removeCategory,
|
12 |
+
clearResults,
|
13 |
+
updateSectionTitle
|
14 |
+
} = useZeroShotClassification()
|
15 |
+
|
16 |
+
return (
|
17 |
+
<div className="space-y-4">
|
18 |
+
<h3 className="text-lg font-semibold text-gray-900">
|
19 |
+
Zero-Shot Classification Settings
|
20 |
+
</h3>
|
21 |
+
|
22 |
+
<div className="space-y-3">
|
23 |
+
<div>
|
24 |
+
<label className="block text-sm font-medium text-gray-700 mb-1">
|
25 |
+
Classification Threshold: {config.threshold}
|
26 |
+
</label>
|
27 |
+
<input
|
28 |
+
type="range"
|
29 |
+
min="0.1"
|
30 |
+
max="0.9"
|
31 |
+
step="0.1"
|
32 |
+
value={config.threshold}
|
33 |
+
onChange={(e) => setConfig(prev => ({
|
34 |
+
...prev,
|
35 |
+
threshold: parseFloat(e.target.value)
|
36 |
+
}))}
|
37 |
+
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
38 |
+
/>
|
39 |
+
<p className="text-xs text-gray-500 mt-1">
|
40 |
+
Minimum confidence score required for classification (lower values classify more items)
|
41 |
+
</p>
|
42 |
+
</div>
|
43 |
+
|
44 |
+
<div className="pt-2 border-t border-gray-200">
|
45 |
+
<h4 className="text-sm font-semibold text-gray-800 mb-3">Categories</h4>
|
46 |
+
|
47 |
+
<div className="space-y-2 max-h-40 overflow-y-auto">
|
48 |
+
{sections.map((section, index) => (
|
49 |
+
<div key={index} className="flex items-center space-x-2">
|
50 |
+
<input
|
51 |
+
type="text"
|
52 |
+
value={section.title}
|
53 |
+
onChange={(e) => updateSectionTitle(index, e.target.value)}
|
54 |
+
disabled={section.title === 'Other'}
|
55 |
+
className="flex-1 px-2 py-1 text-xs border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
56 |
+
/>
|
57 |
+
<span className="text-xs text-gray-500 min-w-[2rem]">
|
58 |
+
({section.items.length})
|
59 |
+
</span>
|
60 |
+
</div>
|
61 |
+
))}
|
62 |
+
</div>
|
63 |
+
|
64 |
+
<div className="flex gap-2 mt-3">
|
65 |
+
<button
|
66 |
+
onClick={addCategory}
|
67 |
+
className="flex items-center gap-1 px-3 py-1 text-xs bg-green-500 hover:bg-green-600 text-white rounded transition-colors"
|
68 |
+
title="Add Category"
|
69 |
+
>
|
70 |
+
<Plus className="w-3 h-3" />
|
71 |
+
Add
|
72 |
+
</button>
|
73 |
+
<button
|
74 |
+
onClick={removeCategory}
|
75 |
+
disabled={sections.length <= 1}
|
76 |
+
className="flex items-center gap-1 px-3 py-1 text-xs bg-red-500 hover:bg-red-600 disabled:bg-gray-300 disabled:cursor-not-allowed text-white rounded transition-colors"
|
77 |
+
title="Remove Category"
|
78 |
+
>
|
79 |
+
<Minus className="w-3 h-3" />
|
80 |
+
Remove
|
81 |
+
</button>
|
82 |
+
<button
|
83 |
+
onClick={clearResults}
|
84 |
+
className="flex items-center gap-1 px-3 py-1 text-xs bg-orange-500 hover:bg-orange-600 text-white rounded transition-colors"
|
85 |
+
title="Clear Results"
|
86 |
+
>
|
87 |
+
<Trash2 className="w-3 h-3" />
|
88 |
+
Clear
|
89 |
+
</button>
|
90 |
+
</div>
|
91 |
+
</div>
|
92 |
+
</div>
|
93 |
+
|
94 |
+
<div className="pt-2 border-t border-gray-200">
|
95 |
+
<div className="text-xs text-gray-500">
|
96 |
+
<p className="mb-1">
|
97 |
+
<strong>Threshold:</strong> Items with confidence scores below this threshold will be classified as "Other"
|
98 |
+
</p>
|
99 |
+
<p className="mb-1">
|
100 |
+
<strong>Categories:</strong> Edit category names to customize classification labels
|
101 |
+
</p>
|
102 |
+
<p>
|
103 |
+
<strong>Other:</strong> Fallback category for items that don't meet the threshold for any specific category
|
104 |
+
</p>
|
105 |
+
</div>
|
106 |
+
</div>
|
107 |
+
</div>
|
108 |
+
)
|
109 |
+
}
|
110 |
+
|
111 |
+
export default ZeroShotClassificationConfig
|
src/contexts/ZeroShotClassificationContext.tsx
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { createContext, useContext, useState, useCallback } from 'react'
|
2 |
+
import { Section } from '../types'
|
3 |
+
|
4 |
+
interface ZeroShotClassificationConfig {
|
5 |
+
threshold: number
|
6 |
+
}
|
7 |
+
|
8 |
+
interface ZeroShotClassificationContextType {
|
9 |
+
config: ZeroShotClassificationConfig
|
10 |
+
setConfig: React.Dispatch<React.SetStateAction<ZeroShotClassificationConfig>>
|
11 |
+
text: string
|
12 |
+
setText: React.Dispatch<React.SetStateAction<string>>
|
13 |
+
sections: Section[]
|
14 |
+
setSections: React.Dispatch<React.SetStateAction<Section[]>>
|
15 |
+
addCategory: () => void
|
16 |
+
removeCategory: () => void
|
17 |
+
clearResults: () => void
|
18 |
+
updateSectionTitle: (index: number, newTitle: string) => void
|
19 |
+
}
|
20 |
+
|
21 |
+
const ZeroShotClassificationContext = createContext<ZeroShotClassificationContextType | undefined>(undefined)
|
22 |
+
|
23 |
+
export const useZeroShotClassification = () => {
|
24 |
+
const context = useContext(ZeroShotClassificationContext)
|
25 |
+
if (!context) {
|
26 |
+
throw new Error('useZeroShotClassification must be used within a ZeroShotClassificationProvider')
|
27 |
+
}
|
28 |
+
return context
|
29 |
+
}
|
30 |
+
|
31 |
+
const PLACEHOLDER_REVIEWS: string[] = [
|
32 |
+
// battery/charging problems
|
33 |
+
'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.',
|
34 |
+
"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",
|
35 |
+
"The charging port is so finicky. Sometimes it takes forever to charge, and other times it doesn't even recognize the charger. Frustrating experience!",
|
36 |
+
|
37 |
+
// overheating
|
38 |
+
"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",
|
39 |
+
"This phone is like holding a hot potato. Video calls turn it into a scalding nightmare. Seriously, can't it keep its cool?",
|
40 |
+
"Forget about a heatwave outside; my phone's got its own. It's like a little portable heater. Not what I signed up for.",
|
41 |
+
|
42 |
+
// poor build quality
|
43 |
+
'I dropped the phone from a short distance, and the screen cracked easily. Not as durable as I expected from a flagship device.',
|
44 |
+
'Took a slight bump in my bag, and the frame got dinged. Are we back in the flip phone era?',
|
45 |
+
"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.",
|
46 |
+
|
47 |
+
// software
|
48 |
+
'The software updates are a nightmare. Each update seems to introduce new bugs, and it takes forever for them to be fixed.',
|
49 |
+
'Constant crashes and freezes make me want to throw it into a black hole.',
|
50 |
+
"Every time I open Instagram, my phone freezes and crashes. It's so frustrating!",
|
51 |
+
|
52 |
+
// other
|
53 |
+
"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.",
|
54 |
+
"I hate the color of this phone. It's so ugly!",
|
55 |
+
"This phone sucks! I'm returning it."
|
56 |
+
].sort(() => Math.random() - 0.5)
|
57 |
+
|
58 |
+
const PLACEHOLDER_SECTIONS: string[] = [
|
59 |
+
'Battery and charging problems',
|
60 |
+
'Overheating',
|
61 |
+
'Poor build quality',
|
62 |
+
'Software issues',
|
63 |
+
'Other'
|
64 |
+
]
|
65 |
+
|
66 |
+
export const ZeroShotClassificationProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
67 |
+
const [config, setConfig] = useState<ZeroShotClassificationConfig>({
|
68 |
+
threshold: 0.5
|
69 |
+
})
|
70 |
+
|
71 |
+
const [text, setText] = useState<string>(PLACEHOLDER_REVIEWS.join('\n'))
|
72 |
+
|
73 |
+
const [sections, setSections] = useState<Section[]>(
|
74 |
+
PLACEHOLDER_SECTIONS.map((title) => ({ title, items: [] }))
|
75 |
+
)
|
76 |
+
|
77 |
+
const addCategory = useCallback(() => {
|
78 |
+
setSections((sections) => {
|
79 |
+
const newSections = [...sections]
|
80 |
+
// add at position 2 from the end
|
81 |
+
newSections.splice(newSections.length - 1, 0, {
|
82 |
+
title: 'New Category',
|
83 |
+
items: []
|
84 |
+
})
|
85 |
+
return newSections
|
86 |
+
})
|
87 |
+
}, [])
|
88 |
+
|
89 |
+
const removeCategory = useCallback(() => {
|
90 |
+
setSections((sections) => {
|
91 |
+
const newSections = [...sections]
|
92 |
+
newSections.splice(newSections.length - 2, 1) // Remove second last element
|
93 |
+
return newSections
|
94 |
+
})
|
95 |
+
}, [])
|
96 |
+
|
97 |
+
const clearResults = useCallback(() => {
|
98 |
+
setSections((sections) =>
|
99 |
+
sections.map((section) => ({
|
100 |
+
...section,
|
101 |
+
items: []
|
102 |
+
}))
|
103 |
+
)
|
104 |
+
}, [])
|
105 |
+
|
106 |
+
const updateSectionTitle = useCallback((index: number, newTitle: string) => {
|
107 |
+
setSections((sections) => {
|
108 |
+
const newSections = [...sections]
|
109 |
+
newSections[index].title = newTitle
|
110 |
+
return newSections
|
111 |
+
})
|
112 |
+
}, [])
|
113 |
+
|
114 |
+
const value: ZeroShotClassificationContextType = {
|
115 |
+
config,
|
116 |
+
setConfig,
|
117 |
+
text,
|
118 |
+
setText,
|
119 |
+
sections,
|
120 |
+
setSections,
|
121 |
+
addCategory,
|
122 |
+
removeCategory,
|
123 |
+
clearResults,
|
124 |
+
updateSectionTitle
|
125 |
+
}
|
126 |
+
|
127 |
+
return (
|
128 |
+
<ZeroShotClassificationContext.Provider value={value}>
|
129 |
+
{children}
|
130 |
+
</ZeroShotClassificationContext.Provider>
|
131 |
+
)
|
132 |
+
}
|