Add error handling and message improvements
Browse files- src/App.tsx +21 -3
- src/components/ModelInfo.tsx +12 -4
- src/components/ModelLoader.tsx +26 -16
- src/components/Sidebar.tsx +9 -2
- src/components/ui/button.tsx +21 -21
- src/contexts/ModelContext.tsx +6 -1
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 {
|
22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
}, [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
91 |
setShowAlert(true)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
setTimeout(() => {
|
93 |
setShowAlert(false)
|
94 |
setAlertMessage('')
|
95 |
-
},
|
96 |
}
|
97 |
}
|
98 |
|
@@ -100,7 +105,7 @@ const ModelLoader = () => {
|
|
100 |
|
101 |
return () => {
|
102 |
newWorker.removeEventListener('message', onMessageReceived)
|
103 |
-
|
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=
|
178 |
-
disabled={
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
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
|
2 |
-
import { Slot } from
|
3 |
-
import { cva, type VariantProps } from
|
4 |
|
5 |
-
import { cn } from
|
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 |
-
|
14 |
destructive:
|
15 |
-
|
16 |
outline:
|
17 |
-
|
18 |
secondary:
|
19 |
-
|
20 |
ghost:
|
21 |
-
|
22 |
-
link:
|
23 |
},
|
24 |
size: {
|
25 |
-
default:
|
26 |
-
sm:
|
27 |
-
lg:
|
28 |
-
icon:
|
29 |
-
}
|
30 |
},
|
31 |
defaultVariants: {
|
32 |
-
variant:
|
33 |
-
size:
|
34 |
-
}
|
35 |
}
|
36 |
)
|
37 |
|
@@ -41,11 +41,11 @@ function Button({
|
|
41 |
size,
|
42 |
asChild = false,
|
43 |
...props
|
44 |
-
}: React.ComponentProps<
|
45 |
VariantProps<typeof buttonVariants> & {
|
46 |
asChild?: boolean
|
47 |
}) {
|
48 |
-
const Comp = asChild ? Slot :
|
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}
|