Vokturz commited on
Commit
a94a061
·
1 Parent(s): 22f8eb7

improve zero-shot-classification

Browse files
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
- // case 'zero-shot-classification':
16
- // return <ZeroShotProvider>{children}</ZeroShotProvider>;
 
 
 
 
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-6">
22
  <h3 className="text-lg font-semibold text-gray-900">
23
  Text Generation Settings
24
  </h3>
25
 
26
- {/* Generation Parameters */}
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 pt-2">
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
- {/* System Message for Chat */}
115
- {modelInfo?.hasChatTemplate && (
116
- <div>
117
- <h4 className="font-semibold text-gray-800 mb-2">System Message</h4>
118
- <textarea
119
- value={messages.find((m) => m.role === 'system')?.content || ''}
120
- onChange={(e) => updateSystemMessage(e.target.value)}
121
- className="w-full p-2 border border-gray-300 rounded-md text-sm"
122
- rows={4}
123
- placeholder="e.g., You are a helpful assistant."
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 { useState, useEffect, useCallback } from 'react'
2
- import { Section, WorkerMessage, ZeroShotWorkerInput } from '../types'
3
  import { useModel } from '../contexts/ModelContext'
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 {
48
  activeWorker,
@@ -76,7 +40,14 @@ function ZeroShotClassification() {
76
  dtype: selectedQuantization ?? 'fp32'
77
  }
78
  activeWorker.postMessage(message)
79
- }, [text, sections, modelInfo, activeWorker, selectedQuantization])
 
 
 
 
 
 
 
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] > 0.5 ? labels[0] : 'Other'
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
- const handleAddCategory = (): void => {
113
- setSections((sections) => {
114
- const newSections = [...sections]
115
- // add at position 2 from the end
116
- newSections.splice(newSections.length - 1, 0, {
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
- const handleSectionTitleChange = (index: number, newTitle: string): void => {
142
- setSections((sections) => {
143
- const newSections = [...sections]
144
- newSections[index].title = newTitle
145
- return newSections
146
- })
147
- }
 
 
 
 
 
 
 
148
 
149
- return (
150
- <div className="flex flex-col h-screen w-full p-1">
151
- <textarea
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
- className="border py-1 px-2 bg-green-400 rounded text-white text-sm font-medium cursor-pointer"
171
- onClick={handleAddCategory}
 
172
  >
173
- Add category
 
 
 
 
 
 
 
 
 
 
174
  </button>
175
- <button
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
- <div className="flex justify-between flex-grow overflow-x-auto max-h-[40%]">
192
- {sections.map((section, index) => (
193
- <div key={index} className="flex flex-col w-full">
194
- <input
195
- disabled={section.title === 'Other'}
196
- className="w-full border px-1 text-center"
197
- value={section.title}
198
- onChange={(e) => handleSectionTitleChange(index, e.target.value)}
199
- ></input>
200
- <div className="overflow-y-auto h-full border">
201
- {section.items.map((item, itemIndex) => (
202
- <div
203
- className="m-2 border bg-red-50 rounded p-1 text-sm"
204
- key={itemIndex}
205
  >
206
- {item}
 
 
 
207
  </div>
208
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  </div>
210
- </div>
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
+ }