Vokturz commited on
Commit
de6e73e
·
1 Parent(s): c29872e

Update image classification UI and worker logic

Browse files
public/workers/image-classification.js CHANGED
@@ -62,7 +62,7 @@ class MyImageClassificationPipeline {
62
  // Listen for messages from the main thread
63
  self.addEventListener('message', async (event) => {
64
  try {
65
- const { type, image, model, dtype, topK = 5 } = event.data
66
 
67
  if (!model) {
68
  self.postMessage({
@@ -99,11 +99,9 @@ self.addEventListener('message', async (event) => {
99
  }
100
 
101
  try {
102
- self.postMessage({ status: 'loading' })
103
-
104
  // Run classification
105
  const output = await classifier(image, {
106
- topk: topK
107
  })
108
 
109
  // Format predictions
 
62
  // Listen for messages from the main thread
63
  self.addEventListener('message', async (event) => {
64
  try {
65
+ const { type, image, model, dtype, topK = 1 } = event.data
66
 
67
  if (!model) {
68
  self.postMessage({
 
99
  }
100
 
101
  try {
 
 
102
  // Run classification
103
  const output = await classifier(image, {
104
+ top_k: topK
105
  })
106
 
107
  // Format predictions
src/components/ModelInfo.tsx CHANGED
@@ -30,7 +30,7 @@ const ModelInfo = () => {
30
  const ModelInfoSkeleton = () => (
31
  <div className="bg-gradient-to-r from-secondary to-accent px-3 py-3 rounded-lg border border-border space-y-3 animate-pulse w-4/5">
32
  <div className="flex items-center space-x-2">
33
- <Bot className="w-4 h-4 text-primary/60" />
34
  <div className="h-4 bg-muted rounded-sm flex-1"></div>
35
  <div className="w-4 h-4 bg-muted rounded-full"></div>
36
  </div>
@@ -45,7 +45,7 @@ const ModelInfo = () => {
45
  <div className="h-3 bg-muted/80 rounded-sm w-8"></div>
46
  </div>
47
  <div className="flex items-center space-x-1">
48
- <Download className="w-3 h-3 text-chart-2/60" />
49
  <div className="h-3 bg-muted/80 rounded-sm w-8"></div>
50
  </div>
51
  <div className="flex items-center space-x-1">
@@ -53,7 +53,7 @@ const ModelInfo = () => {
53
  <div className="h-3 bg-muted/80 rounded-sm w-8"></div>
54
  </div>
55
  <div className="flex items-center space-x-1">
56
- <DatabaseIcon className="w-3 h-3 text-chart-4/60" />
57
  <div className="h-3 bg-muted/80 rounded-sm w-12"></div>
58
  </div>
59
  </div>
 
30
  const ModelInfoSkeleton = () => (
31
  <div className="bg-gradient-to-r from-secondary to-accent px-3 py-3 rounded-lg border border-border space-y-3 animate-pulse w-4/5">
32
  <div className="flex items-center space-x-2">
33
+ <Bot className="w-4 h-4 text-green-500" />
34
  <div className="h-4 bg-muted rounded-sm flex-1"></div>
35
  <div className="w-4 h-4 bg-muted rounded-full"></div>
36
  </div>
 
45
  <div className="h-3 bg-muted/80 rounded-sm w-8"></div>
46
  </div>
47
  <div className="flex items-center space-x-1">
48
+ <Download className="w-3 h-3 text-purple-500" />
49
  <div className="h-3 bg-muted/80 rounded-sm w-8"></div>
50
  </div>
51
  <div className="flex items-center space-x-1">
 
53
  <div className="h-3 bg-muted/80 rounded-sm w-8"></div>
54
  </div>
55
  <div className="flex items-center space-x-1">
56
+ <DatabaseIcon className="w-3 h-3 text-purple-500" />
57
  <div className="h-3 bg-muted/80 rounded-sm w-12"></div>
58
  </div>
59
  </div>
src/components/ModelSelector.tsx CHANGED
@@ -464,7 +464,7 @@ function ModelSelector() {
464
  <div className="flex-1 min-w-0 pr-3">
465
  <div className="flex items-center justify-between">
466
  <Tooltip content={model.id}>
467
- <span className="text-sm font-medium truncate block max-w-full">
468
  {model.id}
469
  </span>
470
  </Tooltip>
 
464
  <div className="flex-1 min-w-0 pr-3">
465
  <div className="flex items-center justify-between">
466
  <Tooltip content={model.id}>
467
+ <span className="text-sm font-medium truncate block max-w-[450px]">
468
  {model.id}
469
  </span>
470
  </Tooltip>
src/components/PipelineSelector.tsx CHANGED
@@ -1,18 +1,17 @@
1
  import React from 'react'
2
  import {
3
- Listbox,
4
- ListboxOption,
5
- ListboxButton,
6
- ListboxOptions,
7
- Transition
8
- } from '@headlessui/react'
9
- import { ChevronDown, Check } from 'lucide-react'
10
 
11
  export const supportedPipelines = [
12
- 'text-generation',
13
  'feature-extraction',
14
- 'zero-shot-classification',
15
  'image-classification',
 
 
16
  'text-classification',
17
  'summarization',
18
  'translation'
@@ -27,8 +26,6 @@ const PipelineSelector: React.FC<PipelineSelectorProps> = ({
27
  pipeline,
28
  setPipeline
29
  }) => {
30
- const selectedPipeline = pipeline
31
-
32
  const formatPipelineName = (pipelineId: string) => {
33
  return pipelineId
34
  .split('-')
@@ -37,63 +34,22 @@ const PipelineSelector: React.FC<PipelineSelectorProps> = ({
37
  }
38
 
39
  return (
40
- <div className="relative">
41
- <Listbox value={selectedPipeline} onChange={setPipeline}>
42
- <div className="relative">
43
- <ListboxButton className="relative w-full cursor-default rounded-lg bg-white py-2 pl-3 pr-10 text-left focus:outline-hidden focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 text-sm xl:text-base border border-gray-300">
44
- <span className="block truncate font-medium">
45
- {formatPipelineName(selectedPipeline)}
46
- </span>
47
- <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
48
- <ChevronDown
49
- className="h-5 w-5 text-gray-400 ui-open:rotate-180 transition-transform"
50
- aria-hidden="true"
51
- />
52
- </span>
53
- </ListboxButton>
54
-
55
- <Transition
56
- enter="transition duration-100 ease-out"
57
- enterFrom="transform scale-95 opacity-0"
58
- enterTo="transform scale-100 opacity-100"
59
- leave="transition duration-75 ease-out"
60
- leaveFrom="transform scale-100 opacity-100"
61
- leaveTo="transform scale-95 opacity-0"
62
  >
63
- <ListboxOptions className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-sm xl:text-base ring-1 ring-black ring-opacity-5 focus:outline-hidden">
64
- {supportedPipelines.map((p) => (
65
- <ListboxOption
66
- key={p}
67
- className={({ active }) =>
68
- `relative cursor-default select-none py-2 px-4 ${
69
- active ? 'bg-amber-100 text-amber-900' : 'text-gray-900'
70
- }`
71
- }
72
- value={p}
73
- >
74
- {({ selected }) => (
75
- <div className="flex items-center justify-between">
76
- <span
77
- className={`block truncate ${
78
- selected ? 'font-medium' : 'font-normal'
79
- }`}
80
- >
81
- {formatPipelineName(p)}
82
- </span>
83
- {selected && (
84
- <span className="flex items-center text-amber-600">
85
- <Check className="h-5 w-5" aria-hidden="true" />
86
- </span>
87
- )}
88
- </div>
89
- )}
90
- </ListboxOption>
91
- ))}
92
- </ListboxOptions>
93
- </Transition>
94
- </div>
95
- </Listbox>
96
- </div>
97
  )
98
  }
99
 
 
1
  import React from 'react'
2
  import {
3
+ Select,
4
+ SelectContent,
5
+ SelectItem,
6
+ SelectTrigger,
7
+ SelectValue
8
+ } from '@/components/ui/select' // Adjust the import path as needed
 
9
 
10
  export const supportedPipelines = [
 
11
  'feature-extraction',
 
12
  'image-classification',
13
+ 'text-generation',
14
+ 'zero-shot-classification',
15
  'text-classification',
16
  'summarization',
17
  'translation'
 
26
  pipeline,
27
  setPipeline
28
  }) => {
 
 
29
  const formatPipelineName = (pipelineId: string) => {
30
  return pipelineId
31
  .split('-')
 
34
  }
35
 
36
  return (
37
+ <Select value={pipeline} onValueChange={setPipeline}>
38
+ <SelectTrigger className="w-full text-sm xl:text-base">
39
+ <SelectValue placeholder="Select a pipeline" />
40
+ </SelectTrigger>
41
+ <SelectContent>
42
+ {supportedPipelines.map((p) => (
43
+ <SelectItem
44
+ key={p}
45
+ value={p}
46
+ className="text-sm xl:text-base data-[state=checked]:font-bold"
 
 
 
 
 
 
 
 
 
 
 
 
47
  >
48
+ {formatPipelineName(p)}
49
+ </SelectItem>
50
+ ))}
51
+ </SelectContent>
52
+ </Select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  )
54
  }
55
 
src/components/Sidebar.tsx CHANGED
@@ -38,7 +38,7 @@ const Sidebar = ({ isOpen, onClose, setIsModalOpen }: SidebarProps) => {
38
  <div className="flex flex-col h-full">
39
  {/* Header */}
40
  <div className="flex items-center justify-between p-4 border-b border-gray-200 lg:hidden">
41
- <h2 className="text-lg font-semibold text-gray-900">
42
  Configuration
43
  </h2>
44
  <button
@@ -52,21 +52,16 @@ const Sidebar = ({ isOpen, onClose, setIsModalOpen }: SidebarProps) => {
52
  {/* Content */}
53
  <div className="flex-1 overflow-y-auto overflow-x-hidden p-4 space-y-6">
54
  {/* Pipeline Selection */}
55
- <div className="space-y-3">
56
- <h3 className="text-md xl:text-lg font-semibold text-gray-900 mb-2">
57
  Choose a Pipeline
58
  </h3>
59
- <div className="w-full">
60
- <PipelineSelector
61
- pipeline={pipeline}
62
- setPipeline={setPipeline}
63
- />
64
- </div>
65
  </div>
66
 
67
  {/* Model Selection */}
68
  <div className="space-y-3">
69
- <h3 className="text-lg font-semibold text-gray-900">
70
  Select Model
71
  </h3>
72
  <ModelSelector />
 
38
  <div className="flex flex-col h-full">
39
  {/* Header */}
40
  <div className="flex items-center justify-between p-4 border-b border-gray-200 lg:hidden">
41
+ <h2 className="text-lg font-semibold text-foreground">
42
  Configuration
43
  </h2>
44
  <button
 
52
  {/* Content */}
53
  <div className="flex-1 overflow-y-auto overflow-x-hidden p-4 space-y-6">
54
  {/* Pipeline Selection */}
55
+ <div className="space-x-3 flex flex-row justify-center align-center">
56
+ <h3 className="text-md xl:text-lg font-semibold text-foreground text-nowrap mt-1">
57
  Choose a Pipeline
58
  </h3>
59
+ <PipelineSelector pipeline={pipeline} setPipeline={setPipeline} />
 
 
 
 
 
60
  </div>
61
 
62
  {/* Model Selection */}
63
  <div className="space-y-3">
64
+ <h3 className="text-lg font-semibold text-foreground">
65
  Select Model
66
  </h3>
67
  <ModelSelector />
src/components/pipelines/ImageClassification.tsx CHANGED
@@ -49,7 +49,6 @@ function ImageClassification() {
49
  } = useImageClassification()
50
 
51
  const [isClassifying, setIsClassifying] = useState<boolean>(false)
52
- const [showPreviews, setShowPreviews] = useState<boolean>(true)
53
  const [dragOver, setDragOver] = useState<boolean>(false)
54
  const [progress, setProgress] = useState<number | null>(null)
55
 
@@ -71,7 +70,6 @@ function ImageClassification() {
71
  updateExample(example.id, { isLoading: true })
72
  setIsClassifying(true)
73
  setProgress(0)
74
-
75
  const message: ImageClassificationWorkerInput = {
76
  type: 'classify',
77
  image: example.url,
@@ -135,7 +133,9 @@ function ImageClassification() {
135
  )
136
 
137
  const handleLoadSampleImages = useCallback(async () => {
 
138
  for (const sample of SAMPLE_IMAGES) {
 
139
  try {
140
  const response = await fetch(sample.url)
141
  const blob = await response.blob()
@@ -145,14 +145,13 @@ function ImageClassification() {
145
  console.error(`Failed to load sample image ${sample.name}:`, error)
146
  }
147
  }
148
- }, [addExample])
149
 
150
  useEffect(() => {
151
  if (!activeWorker) return
152
 
153
  const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
154
  const { status, output, progress: workerProgress } = e.data
155
-
156
  if (status === 'progress' && workerProgress !== undefined) {
157
  setProgress(workerProgress)
158
  } else if (status === 'output' && output?.predictions) {
@@ -197,17 +196,6 @@ function ImageClassification() {
197
  >
198
  Load Samples
199
  </button>
200
- <button
201
- onClick={() => setShowPreviews(!showPreviews)}
202
- className="p-2 bg-blue-100 hover:bg-blue-200 rounded-lg transition-colors"
203
- title={showPreviews ? 'Hide Previews' : 'Show Previews'}
204
- >
205
- {showPreviews ? (
206
- <EyeOff className="w-4 h-4" />
207
- ) : (
208
- <Eye className="w-4 h-4" />
209
- )}
210
- </button>
211
  <button
212
  onClick={clearExamples}
213
  className="p-2 bg-red-100 hover:bg-red-200 rounded-lg transition-colors"
@@ -218,7 +206,7 @@ function ImageClassification() {
218
  </div>
219
  </div>
220
 
221
- <div className="flex flex-col lg:flex-row gap-4 flex-1">
222
  {/* Left Panel - Image Upload and List */}
223
  <div className="lg:w-1/2 flex flex-col">
224
  {/* Upload Area */}
@@ -281,9 +269,9 @@ function ImageClassification() {
281
  )}
282
 
283
  {/* Images List */}
284
- <div className="flex-1 overflow-y-auto border border-gray-300 rounded-lg bg-white">
285
  <div className="p-4">
286
- <h3 className="text-sm font-medium text-gray-700 mb-3">
287
  Images ({examples.length})
288
  </h3>
289
  {examples.length === 0 ? (
@@ -292,7 +280,7 @@ function ImageClassification() {
292
  started.
293
  </div>
294
  ) : (
295
- <div className="space-y-3">
296
  {examples.map((example) => (
297
  <div
298
  key={example.id}
@@ -304,15 +292,13 @@ function ImageClassification() {
304
  onClick={() => handleSelectExample(example)}
305
  >
306
  <div className="flex gap-3">
307
- {showPreviews && (
308
- <div className="shrink-0">
309
- <img
310
- src={example.url}
311
- alt={example.name}
312
- className="w-16 h-16 object-cover rounded-lg"
313
- />
314
- </div>
315
- )}
316
  <div className="flex-1 min-w-0">
317
  <div className="flex justify-between items-start">
318
  <div className="flex-1 min-w-0">
@@ -334,11 +320,6 @@ function ImageClassification() {
334
  Not classified
335
  </div>
336
  )}
337
- {selectedExample?.id === example.id && (
338
- <div className="text-xs text-blue-600">
339
- Selected
340
- </div>
341
- )}
342
  </div>
343
  </div>
344
  <button
@@ -362,19 +343,19 @@ function ImageClassification() {
362
  </div>
363
 
364
  {/* Right Panel - Preview and Results */}
365
- <div className="lg:w-1/2 flex flex-col">
366
  {/* Image Preview */}
367
  {selectedExample && (
368
  <div className="mb-4">
369
  <h3 className="text-sm font-medium text-gray-700 mb-2">
370
  Selected Image
371
  </h3>
372
- <div className="border border-gray-300 rounded-lg bg-white p-4">
373
  <div className="flex flex-col items-center">
374
  <img
375
  src={selectedExample.url}
376
  alt={selectedExample.name}
377
- className="max-w-full max-h-64 object-contain rounded-lg mb-2"
378
  />
379
  <div className="text-sm text-gray-600 text-center">
380
  {selectedExample.name}
@@ -385,9 +366,9 @@ function ImageClassification() {
385
  )}
386
 
387
  {/* Classification Results */}
388
- <div className="flex-1 overflow-y-auto border border-gray-300 rounded-lg bg-white">
389
  <div className="p-4">
390
- <h3 className="text-sm font-medium text-gray-700 mb-3">
391
  Classification Results
392
  {selectedExample && ` - ${selectedExample.name}`}
393
  </h3>
@@ -419,7 +400,7 @@ function ImageClassification() {
419
  </button>
420
  </div>
421
  ) : (
422
- <div className="space-y-3">
423
  {selectedExample.predictions.map((prediction, index) => {
424
  const confidencePercent = (prediction.score * 100).toFixed(
425
  1
 
49
  } = useImageClassification()
50
 
51
  const [isClassifying, setIsClassifying] = useState<boolean>(false)
 
52
  const [dragOver, setDragOver] = useState<boolean>(false)
53
  const [progress, setProgress] = useState<number | null>(null)
54
 
 
70
  updateExample(example.id, { isLoading: true })
71
  setIsClassifying(true)
72
  setProgress(0)
 
73
  const message: ImageClassificationWorkerInput = {
74
  type: 'classify',
75
  image: example.url,
 
133
  )
134
 
135
  const handleLoadSampleImages = useCallback(async () => {
136
+ const existstingImages = new Set(examples.map((ex) => ex.name))
137
  for (const sample of SAMPLE_IMAGES) {
138
+ if (existstingImages.has(sample.name)) continue
139
  try {
140
  const response = await fetch(sample.url)
141
  const blob = await response.blob()
 
145
  console.error(`Failed to load sample image ${sample.name}:`, error)
146
  }
147
  }
148
+ }, [addExample, examples])
149
 
150
  useEffect(() => {
151
  if (!activeWorker) return
152
 
153
  const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
154
  const { status, output, progress: workerProgress } = e.data
 
155
  if (status === 'progress' && workerProgress !== undefined) {
156
  setProgress(workerProgress)
157
  } else if (status === 'output' && output?.predictions) {
 
196
  >
197
  Load Samples
198
  </button>
 
 
 
 
 
 
 
 
 
 
 
199
  <button
200
  onClick={clearExamples}
201
  className="p-2 bg-red-100 hover:bg-red-200 rounded-lg transition-colors"
 
206
  </div>
207
  </div>
208
 
209
+ <div className="flex flex-col lg:flex-row gap-4 flex-1 min-h-0">
210
  {/* Left Panel - Image Upload and List */}
211
  <div className="lg:w-1/2 flex flex-col">
212
  {/* Upload Area */}
 
269
  )}
270
 
271
  {/* Images List */}
272
+ <div className="flex-1 overflow-y-auto border border-gray-300 rounded-lg bg-white min-h-0 max-h-[30vh] sm:max-h-[20vh] lg:max-h-none">
273
  <div className="p-4">
274
+ <h3 className="text-sm font-medium text-gray-700 mb-3 sticky top-0 bg-white z-10">
275
  Images ({examples.length})
276
  </h3>
277
  {examples.length === 0 ? (
 
280
  started.
281
  </div>
282
  ) : (
283
+ <div className="overflow-y-auto max-h-[calc(100%-10rem)] grid-cols-2 grid sm:grid-cols-3 lg:grid-cols-1 gap-2 ">
284
  {examples.map((example) => (
285
  <div
286
  key={example.id}
 
292
  onClick={() => handleSelectExample(example)}
293
  >
294
  <div className="flex gap-3">
295
+ <div className="shrink-0">
296
+ <img
297
+ src={example.url}
298
+ alt={example.name}
299
+ className="w-16 h-16 object-cover rounded-lg"
300
+ />
301
+ </div>
 
 
302
  <div className="flex-1 min-w-0">
303
  <div className="flex justify-between items-start">
304
  <div className="flex-1 min-w-0">
 
320
  Not classified
321
  </div>
322
  )}
 
 
 
 
 
323
  </div>
324
  </div>
325
  <button
 
343
  </div>
344
 
345
  {/* Right Panel - Preview and Results */}
346
+ <div className="lg:w-1/2 flex lg:flex-col flex-row space-x-4 sm:max-h-[34vh] lg:max-h-none">
347
  {/* Image Preview */}
348
  {selectedExample && (
349
  <div className="mb-4">
350
  <h3 className="text-sm font-medium text-gray-700 mb-2">
351
  Selected Image
352
  </h3>
353
+ <div className="sm:border-none border border-gray-300 rounded-lg bg-white p-4 sm:p-0">
354
  <div className="flex flex-col items-center">
355
  <img
356
  src={selectedExample.url}
357
  alt={selectedExample.name}
358
+ className="max-w-64 lg:max-w-full max-h-60 lg:max-h-64 object-contain rounded-lg mb-2"
359
  />
360
  <div className="text-sm text-gray-600 text-center">
361
  {selectedExample.name}
 
366
  )}
367
 
368
  {/* Classification Results */}
369
+ <div className="flex-1 overflow-y-auto border border-gray-300 rounded-lg bg-white ">
370
  <div className="p-4">
371
+ <h3 className="text-sm font-medium text-gray-700 mb-3 sticky top-0 bg-white">
372
  Classification Results
373
  {selectedExample && ` - ${selectedExample.name}`}
374
  </h3>
 
400
  </button>
401
  </div>
402
  ) : (
403
+ <div className="space-y-3 overflow-y-auto max-h-[calc(100%-3rem)]">
404
  {selectedExample.predictions.map((prediction, index) => {
405
  const confidencePercent = (prediction.score * 100).toFixed(
406
  1
src/components/pipelines/ImageClassificationConfig.tsx CHANGED
@@ -1,5 +1,6 @@
1
  import React from 'react'
2
  import { useImageClassification } from '../../contexts/ImageClassificationContext'
 
3
 
4
  const ImageClassificationConfig = () => {
5
  const { topK, setTopK } = useImageClassification()
@@ -15,18 +16,18 @@ const ImageClassificationConfig = () => {
15
  <label className="block text-sm font-medium text-foreground/80 mb-1">
16
  Top K Predictions: {topK}
17
  </label>
18
- <input
19
- type="range"
20
- min="1"
21
- max="10"
22
- step="1"
23
- value={topK}
24
- onChange={(e) => setTopK(parseInt(e.target.value))}
25
- className="w-full h-2 bg-muted rounded-lg appearance-none cursor-pointer"
26
  />
27
  <div className="flex justify-between text-xs text-muted-foreground/60 mt-1">
28
  <span>1</span>
29
- <span>5</span>
 
30
  <span>10</span>
31
  </div>
32
  <p className="text-xs text-muted-foreground mt-1">
@@ -36,7 +37,7 @@ const ImageClassificationConfig = () => {
36
 
37
  <div className="p-3 bg-chart-4/10 border border-chart-4/20 rounded-lg">
38
  <h4 className="text-sm font-medium text-chart-4 mb-2">💡 Tips</h4>
39
- <div className="text-xs text-chart-4/80 space-y-1">
40
  <p>• Use Top K = 3-5 for most cases</p>
41
  <p>• Smaller images process faster</p>
42
  <p>• Try quantized models for speed</p>
 
1
  import React from 'react'
2
  import { useImageClassification } from '../../contexts/ImageClassificationContext'
3
+ import { Slider } from '../ui/slider'
4
 
5
  const ImageClassificationConfig = () => {
6
  const { topK, setTopK } = useImageClassification()
 
16
  <label className="block text-sm font-medium text-foreground/80 mb-1">
17
  Top K Predictions: {topK}
18
  </label>
19
+ <Slider
20
+ defaultValue={[topK]}
21
+ min={1}
22
+ max={10}
23
+ step={1}
24
+ onValueChange={(value) => setTopK(value[0])}
25
+ className="w-full rounded-lg"
 
26
  />
27
  <div className="flex justify-between text-xs text-muted-foreground/60 mt-1">
28
  <span>1</span>
29
+ <span>4</span>
30
+ <span>7</span>
31
  <span>10</span>
32
  </div>
33
  <p className="text-xs text-muted-foreground mt-1">
 
37
 
38
  <div className="p-3 bg-chart-4/10 border border-chart-4/20 rounded-lg">
39
  <h4 className="text-sm font-medium text-chart-4 mb-2">💡 Tips</h4>
40
+ <div className="text-xs text-chart-4 space-y-1">
41
  <p>• Use Top K = 3-5 for most cases</p>
42
  <p>• Smaller images process faster</p>
43
  <p>• Try quantized models for speed</p>