Vokturz commited on
Commit
63dbafb
·
1 Parent(s): a68c0b7

Add error handling and message improvements

Browse files
src/App.tsx CHANGED
@@ -8,6 +8,7 @@ import { getModelsByPipeline } from './lib/huggingface'
8
  import TextGeneration from './components/pipelines/TextGeneration'
9
  import FeatureExtraction from './components/pipelines/FeatureExtraction'
10
  import ImageClassification from './components/pipelines/ImageClassification'
 
11
  import Sidebar from './components/Sidebar'
12
  import ModelReadme from './components/ModelReadme'
13
  import { PipelineLayout } from './components/PipelineLayout'
@@ -18,13 +19,22 @@ function App() {
18
  const [isSidebarOpen, setIsSidebarOpen] = useState(false)
19
  const [isModalOpen, setIsModalOpen] = useState(false)
20
  const [isCodeModalOpen, setIsCodeModalOpen] = useState(false)
21
- const { pipeline, setModels, setModelInfo, modelInfo, setIsFetching } =
22
- useModel()
 
 
 
 
 
 
 
23
 
24
  useEffect(() => {
25
  setModelInfo(null)
26
  setModels([])
27
  setIsFetching(true)
 
 
28
 
29
  const fetchModels = async () => {
30
  try {
@@ -36,7 +46,14 @@ function App() {
36
  }
37
  }
38
  fetchModels()
39
- }, [setModels, setModelInfo, setIsFetching, pipeline])
 
 
 
 
 
 
 
40
 
41
  return (
42
  <div className="relative min-h-screen flex flex-col bg-gradient-to-br from-blue-50 to-indigo-100">
@@ -67,6 +84,7 @@ function App() {
67
  {pipeline === 'text-generation' && <TextGeneration />}
68
  {pipeline === 'feature-extraction' && <FeatureExtraction />}
69
  {pipeline === 'image-classification' && <ImageClassification />}
 
70
  </div>
71
  </div>
72
  </main>
 
8
  import TextGeneration from './components/pipelines/TextGeneration'
9
  import FeatureExtraction from './components/pipelines/FeatureExtraction'
10
  import ImageClassification from './components/pipelines/ImageClassification'
11
+ import TextToSpeech from './components/pipelines/TextToSpeech'
12
  import Sidebar from './components/Sidebar'
13
  import ModelReadme from './components/ModelReadme'
14
  import { PipelineLayout } from './components/PipelineLayout'
 
19
  const [isSidebarOpen, setIsSidebarOpen] = useState(false)
20
  const [isModalOpen, setIsModalOpen] = useState(false)
21
  const [isCodeModalOpen, setIsCodeModalOpen] = useState(false)
22
+ const {
23
+ pipeline,
24
+ setModels,
25
+ setModelInfo,
26
+ modelInfo,
27
+ setIsFetching,
28
+ setErrorText,
29
+ setStatus
30
+ } = useModel()
31
 
32
  useEffect(() => {
33
  setModelInfo(null)
34
  setModels([])
35
  setIsFetching(true)
36
+ setStatus('initiate')
37
+ setErrorText('')
38
 
39
  const fetchModels = async () => {
40
  try {
 
46
  }
47
  }
48
  fetchModels()
49
+ }, [
50
+ setModels,
51
+ setModelInfo,
52
+ setIsFetching,
53
+ setStatus,
54
+ setErrorText,
55
+ pipeline
56
+ ])
57
 
58
  return (
59
  <div className="relative min-h-screen flex flex-col bg-gradient-to-br from-blue-50 to-indigo-100">
 
84
  {pipeline === 'text-generation' && <TextGeneration />}
85
  {pipeline === 'feature-extraction' && <FeatureExtraction />}
86
  {pipeline === 'image-classification' && <ImageClassification />}
87
+ {pipeline === 'text-to-speech' && <TextToSpeech />}
88
  </div>
89
  </div>
90
  </main>
src/components/ModelInfo.tsx CHANGED
@@ -25,7 +25,14 @@ const ModelInfo = () => {
25
  return num.toString()
26
  }
27
 
28
- const { models, modelInfo, selectedQuantization, isFetching } = useModel()
 
 
 
 
 
 
 
29
 
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">
@@ -73,7 +80,7 @@ const ModelInfo = () => {
73
  {/* Compatibility Status */}
74
  {typeof modelInfo.isCompatible === 'boolean' && (
75
  <div className="shrink-0 ">
76
- {modelInfo.isCompatible ? (
77
  <CheckCircle className="w-4 h-4 text-green-500" />
78
  ) : (
79
  <XCircle className="w-4 h-4 text-destructive" />
@@ -156,10 +163,11 @@ const ModelInfo = () => {
156
  <ModelLoader />
157
 
158
  {/* Incompatibility Message */}
159
- {modelInfo.isCompatible === false && modelInfo.incompatibilityReason && (
 
160
  <div className="bg-destructive/10 border border-destructive/20 rounded-md px-2 py-2">
161
  <p className="text-xs text-destructive whitespace-break-spaces">
162
- {modelInfo.incompatibilityReason}
163
  </p>
164
  </div>
165
  )}
 
25
  return num.toString()
26
  }
27
 
28
+ const {
29
+ models,
30
+ status,
31
+ modelInfo,
32
+ selectedQuantization,
33
+ isFetching,
34
+ errorText
35
+ } = useModel()
36
 
37
  const ModelInfoSkeleton = () => (
38
  <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">
 
80
  {/* Compatibility Status */}
81
  {typeof modelInfo.isCompatible === 'boolean' && (
82
  <div className="shrink-0 ">
83
+ {modelInfo.isCompatible && status !== 'error' ? (
84
  <CheckCircle className="w-4 h-4 text-green-500" />
85
  ) : (
86
  <XCircle className="w-4 h-4 text-destructive" />
 
163
  <ModelLoader />
164
 
165
  {/* Incompatibility Message */}
166
+ {((modelInfo.isCompatible === false && modelInfo.incompatibilityReason) ||
167
+ errorText) && (
168
  <div className="bg-destructive/10 border border-destructive/20 rounded-md px-2 py-2">
169
  <p className="text-xs text-destructive whitespace-break-spaces">
170
+ {errorText ? errorText : modelInfo.incompatibilityReason}
171
  </p>
172
  </div>
173
  )}
src/components/ModelLoader.tsx CHANGED
@@ -2,12 +2,13 @@ import { useEffect, useCallback, useState } from 'react'
2
  import { ChevronDown, Loader2, X } from 'lucide-react'
3
  import { QuantizationType, WorkerMessage } from '../types'
4
  import { useModel } from '../contexts/ModelContext'
5
- import { getWorker } from '../lib/workerManager'
6
  import { Alert, AlertDescription } from './ui/alert'
7
 
8
  const ModelLoader = () => {
9
  const [showAlert, setShowAlert] = useState(false)
10
  const [alertMessage, setAlertMessage] = useState<React.ReactNode>('')
 
11
  const {
12
  modelInfo,
13
  selectedQuantization,
@@ -20,7 +21,8 @@ const ModelLoader = () => {
20
  setActiveWorker,
21
  pipeline,
22
  hasBeenLoaded,
23
- setHasBeenLoaded
 
24
  } = useModel()
25
 
26
  useEffect(() => {
@@ -57,8 +59,10 @@ const ModelLoader = () => {
57
  }
58
 
59
  if (!hasBeenLoaded) {
 
60
  setStatus('initiate')
61
  setActiveWorker(newWorker)
 
62
  }
63
 
64
  const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
@@ -75,24 +79,25 @@ const ModelLoader = () => {
75
  output.file.startsWith('onnx')
76
  ) {
77
  setProgress(output.progress)
78
- // setShowAlert(true)
79
- // setAlertMessage(
80
- // <div className="flex items-center">
81
- // <Loader2 className="animate-spin h-4 w-4 mr-2" />
82
- // Loading Model
83
- // </div>
84
- // )
85
  }
86
  } else if (status === 'error') {
87
  setStatus('error')
88
  const error = e.data.output
89
  console.error(error)
90
- setAlertMessage(error.split('.')[0] + '. See console for details.')
 
91
  setShowAlert(true)
 
 
 
 
 
 
 
92
  setTimeout(() => {
93
  setShowAlert(false)
94
  setAlertMessage('')
95
- }, 3000)
96
  }
97
  }
98
 
@@ -100,7 +105,7 @@ const ModelLoader = () => {
100
 
101
  return () => {
102
  newWorker.removeEventListener('message', onMessageReceived)
103
- // terminateWorker(pipeline);
104
  }
105
  }, [
106
  pipeline,
@@ -110,7 +115,8 @@ const ModelLoader = () => {
110
  setStatus,
111
  setProgress,
112
  hasBeenLoaded,
113
- setHasBeenLoaded
 
114
  ])
115
 
116
  useEffect(() => {
@@ -174,8 +180,10 @@ const ModelLoader = () => {
174
  {selectedQuantization && (
175
  <div className="flex justify-center">
176
  <button
177
- className="w-32 py-2 px-4 bg-green-500 hover:bg-green-600 rounded-sm text-white font-medium disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm inline-flex items-center text-center justify-center space-x-2"
178
- disabled={hasBeenLoaded || status === 'loading'}
 
 
179
  onClick={loadModel}
180
  >
181
  {status === 'loading' && !hasBeenLoaded ? (
@@ -183,8 +191,10 @@ const ModelLoader = () => {
183
  <Loader2 className="animate-spin h-4 w-4" />
184
  <span>{progress.toFixed(0)}%</span>
185
  </>
186
- ) : (
187
  <span>{!hasBeenLoaded ? 'Load Model' : 'Model Ready'}</span>
 
 
188
  )}
189
  </button>
190
  </div>
 
2
  import { ChevronDown, Loader2, X } from 'lucide-react'
3
  import { QuantizationType, WorkerMessage } from '../types'
4
  import { useModel } from '../contexts/ModelContext'
5
+ import { getWorker, terminateWorker } from '../lib/workerManager'
6
  import { Alert, AlertDescription } from './ui/alert'
7
 
8
  const ModelLoader = () => {
9
  const [showAlert, setShowAlert] = useState(false)
10
  const [alertMessage, setAlertMessage] = useState<React.ReactNode>('')
11
+ const [lastModel, setLastModel] = useState<string | null>(null)
12
  const {
13
  modelInfo,
14
  selectedQuantization,
 
21
  setActiveWorker,
22
  pipeline,
23
  hasBeenLoaded,
24
+ setHasBeenLoaded,
25
+ setErrorText
26
  } = useModel()
27
 
28
  useEffect(() => {
 
59
  }
60
 
61
  if (!hasBeenLoaded) {
62
+ setErrorText('')
63
  setStatus('initiate')
64
  setActiveWorker(newWorker)
65
+ setProgress(0)
66
  }
67
 
68
  const onMessageReceived = (e: MessageEvent<WorkerMessage>) => {
 
79
  output.file.startsWith('onnx')
80
  ) {
81
  setProgress(output.progress)
 
 
 
 
 
 
 
82
  }
83
  } else if (status === 'error') {
84
  setStatus('error')
85
  const error = e.data.output
86
  console.error(error)
87
+ const errText = error.split(' WASM error: ')[1]
88
+ setErrorText(errText)
89
  setShowAlert(true)
90
+ let time = 3000
91
+ if (!hasBeenLoaded)
92
+ setAlertMessage(error.split('.')[0] + '. See console for details.')
93
+ else {
94
+ setAlertMessage(`${errText}. Refresh the page and try again.`)
95
+ time = 5000
96
+ }
97
  setTimeout(() => {
98
  setShowAlert(false)
99
  setAlertMessage('')
100
+ }, time)
101
  }
102
  }
103
 
 
105
 
106
  return () => {
107
  newWorker.removeEventListener('message', onMessageReceived)
108
+ terminateWorker(pipeline)
109
  }
110
  }, [
111
  pipeline,
 
115
  setStatus,
116
  setProgress,
117
  hasBeenLoaded,
118
+ setHasBeenLoaded,
119
+ setErrorText
120
  ])
121
 
122
  useEffect(() => {
 
180
  {selectedQuantization && (
181
  <div className="flex justify-center">
182
  <button
183
+ className={`w-32 py-2 px-4 ${status !== 'error' ? 'bg-green-500 hover:bg-green-600 cursor-pointer' : 'bg-red-500 hover:bg-red-600'} rounded-sm text-white font-medium disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm inline-flex items-center text-center justify-center space-x-2`}
184
+ disabled={
185
+ hasBeenLoaded || status === 'loading' || status === 'error'
186
+ }
187
  onClick={loadModel}
188
  >
189
  {status === 'loading' && !hasBeenLoaded ? (
 
191
  <Loader2 className="animate-spin h-4 w-4" />
192
  <span>{progress.toFixed(0)}%</span>
193
  </>
194
+ ) : status !== 'error' ? (
195
  <span>{!hasBeenLoaded ? 'Load Model' : 'Model Ready'}</span>
196
+ ) : (
197
+ <span>Error</span>
198
  )}
199
  </button>
200
  </div>
src/components/Sidebar.tsx CHANGED
@@ -8,6 +8,7 @@ import FeatureExtractionConfig from './pipelines/FeatureExtractionConfig'
8
  import ZeroShotClassificationConfig from './pipelines/ZeroShotClassificationConfig'
9
  import ImageClassificationConfig from './pipelines/ImageClassificationConfig'
10
  import TextClassificationConfig from './pipelines/TextClassificationConfig'
 
11
  import { Button } from '@/components/ui/button'
12
  import Tooltip from './Tooltip'
13
 
@@ -24,7 +25,7 @@ const Sidebar = ({
24
  setIsModalOpen,
25
  setIsCodeModalOpen
26
  }: SidebarProps) => {
27
- const { pipeline, setPipeline } = useModel()
28
 
29
  return (
30
  <>
@@ -95,13 +96,18 @@ const Sidebar = ({
95
  <ModelInfo />
96
  {/* Model README Button */}
97
  <div className="flex flex-row mt-2 space-x-4 ">
98
- <Button variant="outline" onClick={() => setIsModalOpen(true)}>
 
 
 
 
99
  <FileText className="w-4 h-4 flex-shrink-0" />
100
  <span>View README.md</span>
101
  </Button>
102
  <Button
103
  variant="outline"
104
  onClick={() => setIsCodeModalOpen(true)}
 
105
  >
106
  <Code2 className="w-4 h-4 flex-shrink-0" />
107
  <span>See Code</span>
@@ -119,6 +125,7 @@ const Sidebar = ({
119
  <ImageClassificationConfig />
120
  )}
121
  {pipeline === 'text-classification' && <TextClassificationConfig />}
 
122
  </div>
123
  </div>
124
  </div>
 
8
  import ZeroShotClassificationConfig from './pipelines/ZeroShotClassificationConfig'
9
  import ImageClassificationConfig from './pipelines/ImageClassificationConfig'
10
  import TextClassificationConfig from './pipelines/TextClassificationConfig'
11
+ import TextToSpeechConfig from './pipelines/TextToSpeechConfig'
12
  import { Button } from '@/components/ui/button'
13
  import Tooltip from './Tooltip'
14
 
 
25
  setIsModalOpen,
26
  setIsCodeModalOpen
27
  }: SidebarProps) => {
28
+ const { pipeline, setPipeline, modelInfo } = useModel()
29
 
30
  return (
31
  <>
 
96
  <ModelInfo />
97
  {/* Model README Button */}
98
  <div className="flex flex-row mt-2 space-x-4 ">
99
+ <Button
100
+ variant="outline"
101
+ onClick={() => setIsModalOpen(true)}
102
+ disabled={!modelInfo}
103
+ >
104
  <FileText className="w-4 h-4 flex-shrink-0" />
105
  <span>View README.md</span>
106
  </Button>
107
  <Button
108
  variant="outline"
109
  onClick={() => setIsCodeModalOpen(true)}
110
+ disabled={!modelInfo}
111
  >
112
  <Code2 className="w-4 h-4 flex-shrink-0" />
113
  <span>See Code</span>
 
125
  <ImageClassificationConfig />
126
  )}
127
  {pipeline === 'text-classification' && <TextClassificationConfig />}
128
+ {pipeline === 'text-to-speech' && <TextToSpeechConfig />}
129
  </div>
130
  </div>
131
  </div>
src/components/ui/button.tsx CHANGED
@@ -1,37 +1,37 @@
1
- import * as React from "react"
2
- import { Slot } from "@radix-ui/react-slot"
3
- import { cva, type VariantProps } from "class-variance-authority"
4
 
5
- import { cn } from "@/lib/utils"
6
 
7
  const buttonVariants = cva(
8
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
  {
10
  variants: {
11
  variant: {
12
  default:
13
- "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
14
  destructive:
15
- "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
16
  outline:
17
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
18
  secondary:
19
- "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
20
  ghost:
21
- "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
22
- link: "text-primary underline-offset-4 hover:underline",
23
  },
24
  size: {
25
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
26
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
27
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
28
- icon: "size-9",
29
- },
30
  },
31
  defaultVariants: {
32
- variant: "default",
33
- size: "default",
34
- },
35
  }
36
  )
37
 
@@ -41,11 +41,11 @@ function Button({
41
  size,
42
  asChild = false,
43
  ...props
44
- }: React.ComponentProps<"button"> &
45
  VariantProps<typeof buttonVariants> & {
46
  asChild?: boolean
47
  }) {
48
- const Comp = asChild ? Slot : "button"
49
 
50
  return (
51
  <Comp
 
1
+ import * as React from 'react'
2
+ import { Slot } from '@radix-ui/react-slot'
3
+ import { cva, type VariantProps } from 'class-variance-authority'
4
 
5
+ import { cn } from '@/lib/utils'
6
 
7
  const buttonVariants = cva(
8
+ "cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
  {
10
  variants: {
11
  variant: {
12
  default:
13
+ 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
14
  destructive:
15
+ 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
16
  outline:
17
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
18
  secondary:
19
+ 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
20
  ghost:
21
+ 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
22
+ link: 'text-primary underline-offset-4 hover:underline'
23
  },
24
  size: {
25
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
26
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
27
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
28
+ icon: 'size-9'
29
+ }
30
  },
31
  defaultVariants: {
32
+ variant: 'default',
33
+ size: 'default'
34
+ }
35
  }
36
  )
37
 
 
41
  size,
42
  asChild = false,
43
  ...props
44
+ }: React.ComponentProps<'button'> &
45
  VariantProps<typeof buttonVariants> & {
46
  asChild?: boolean
47
  }) {
48
+ const Comp = asChild ? Slot : 'button'
49
 
50
  return (
51
  <Comp
src/contexts/ModelContext.tsx CHANGED
@@ -25,6 +25,8 @@ interface ModelContextType {
25
  setIsFetching: (isFetching: boolean) => void
26
  hasBeenLoaded: boolean
27
  setHasBeenLoaded: (hasBeenLoaded: boolean) => void
 
 
28
  }
29
 
30
  const ModelContext = createContext<ModelContextType | undefined>(undefined)
@@ -42,6 +44,7 @@ export function ModelProvider({ children }: { children: React.ReactNode }) {
42
  const [activeWorker, setActiveWorker] = useState<Worker | null>(null)
43
  const [isFetching, setIsFetching] = useState(false)
44
  const [hasBeenLoaded, setHasBeenLoaded] = useState(false)
 
45
 
46
  // set progress to 0 when model is changed
47
  useEffect(() => {
@@ -68,7 +71,9 @@ export function ModelProvider({ children }: { children: React.ReactNode }) {
68
  isFetching,
69
  setIsFetching,
70
  hasBeenLoaded,
71
- setHasBeenLoaded
 
 
72
  }}
73
  >
74
  {children}
 
25
  setIsFetching: (isFetching: boolean) => void
26
  hasBeenLoaded: boolean
27
  setHasBeenLoaded: (hasBeenLoaded: boolean) => void
28
+ errorText: string
29
+ setErrorText: (errorText: string) => void
30
  }
31
 
32
  const ModelContext = createContext<ModelContextType | undefined>(undefined)
 
44
  const [activeWorker, setActiveWorker] = useState<Worker | null>(null)
45
  const [isFetching, setIsFetching] = useState(false)
46
  const [hasBeenLoaded, setHasBeenLoaded] = useState(false)
47
+ const [errorText, setErrorText] = useState('')
48
 
49
  // set progress to 0 when model is changed
50
  useEffect(() => {
 
71
  isFetching,
72
  setIsFetching,
73
  hasBeenLoaded,
74
+ setHasBeenLoaded,
75
+ errorText,
76
+ setErrorText
77
  }}
78
  >
79
  {children}