@@ -7,7 +7,7 @@ import { flatMap, negate, uniqWith } from 'lodash-es';
7
7
import { memo , useCallback } from 'react' ;
8
8
import { useTranslation } from 'react-i18next' ;
9
9
import { PiFolderOpenBold , PiLinkBold , PiStarBold } from 'react-icons/pi' ;
10
- import { SiHuggingface } from " react-icons/si" ;
10
+ import { SiHuggingface } from ' react-icons/si' ;
11
11
import { useGetStarterModelsQuery , useInstallModelMutation } from 'services/api/endpoints/models' ;
12
12
13
13
export const LaunchpadForm = memo ( ( ) => {
@@ -16,49 +16,52 @@ export const LaunchpadForm = memo(() => {
16
16
const { getIsInstalled, buildModelInstallArg } = useBuildModelInstallArg ( ) ;
17
17
const { data : starterModelsData } = useGetStarterModelsQuery ( ) ;
18
18
// Function to install models from a bundle
19
- const installBundle = useCallback ( ( bundleName : string ) => {
20
- if ( ! starterModelsData ?. starter_bundles ) {
21
- return ;
22
- }
23
-
24
- const bundle = starterModelsData . starter_bundles [ bundleName ] ;
25
- if ( ! bundle ) {
26
- return ;
27
- }
19
+ const installBundle = useCallback (
20
+ ( bundleName : string ) => {
21
+ if ( ! starterModelsData ?. starter_bundles ) {
22
+ return ;
23
+ }
28
24
29
- // Flatten the models and remove duplicates, which is expected as models can have the same dependencies
30
- const flattenedModels = flatMap ( bundle , flattenStarterModel ) ;
31
- const uniqueModels = uniqWith (
32
- flattenedModels ,
33
- ( m1 , m2 ) => m1 . source === m2 . source || ( m1 . name === m2 . name && m1 . base === m2 . base && m1 . type === m2 . type )
34
- ) ;
35
- // We want to install models that are not installed and skip models that are already installed
36
- const install = uniqueModels . filter ( negate ( getIsInstalled ) ) . map ( buildModelInstallArg ) ;
37
- const skip = uniqueModels . filter ( getIsInstalled ) . map ( buildModelInstallArg ) ;
25
+ const bundle = starterModelsData . starter_bundles [ bundleName ] ;
26
+ if ( ! bundle ) {
27
+ return ;
28
+ }
29
+
30
+ // Flatten the models and remove duplicates, which is expected as models can have the same dependencies
31
+ const flattenedModels = flatMap ( bundle , flattenStarterModel ) ;
32
+ const uniqueModels = uniqWith (
33
+ flattenedModels ,
34
+ ( m1 , m2 ) => m1 . source === m2 . source || ( m1 . name === m2 . name && m1 . base === m2 . base && m1 . type === m2 . type )
35
+ ) ;
36
+ // We want to install models that are not installed and skip models that are already installed
37
+ const install = uniqueModels . filter ( negate ( getIsInstalled ) ) . map ( buildModelInstallArg ) ;
38
+ const skip = uniqueModels . filter ( getIsInstalled ) . map ( buildModelInstallArg ) ;
39
+
40
+ if ( install . length === 0 ) {
41
+ toast ( {
42
+ status : 'info' ,
43
+ title : t ( 'modelManager.bundleAlreadyInstalled' , { bundleName } ) ,
44
+ description : t ( 'modelManager.allModelsAlreadyInstalled' ) ,
45
+ } ) ;
46
+ return ;
47
+ }
48
+
49
+ // Install all models in the bundle
50
+ install . forEach ( installModel ) ;
51
+
52
+ let description = t ( 'modelManager.installingXModels' , { count : install . length } ) ;
53
+ if ( skip . length > 1 ) {
54
+ description += t ( 'modelManager.skippingXDuplicates' , { count : skip . length - 1 } ) ;
55
+ }
38
56
39
- if ( install . length === 0 ) {
40
57
toast ( {
41
58
status : 'info' ,
42
- title : t ( 'modelManager.bundleAlreadyInstalled' , { bundleName } ) ,
43
- description : t ( 'modelManager.allModelsAlreadyInstalled' ) ,
59
+ title : t ( 'modelManager.installingBundle' ) ,
60
+ description,
44
61
} ) ;
45
- return ;
46
- }
47
-
48
- // Install all models in the bundle
49
- install . forEach ( installModel ) ;
50
-
51
- let description = t ( 'modelManager.installingXModels' , { count : install . length } ) ;
52
- if ( skip . length > 1 ) {
53
- description += t ( 'modelManager.skippingXDuplicates' , { count : skip . length - 1 } ) ;
54
- }
55
-
56
- toast ( {
57
- status : 'info' ,
58
- title : t ( 'modelManager.installingBundle' ) ,
59
- description,
60
- } ) ;
61
- } , [ starterModelsData , getIsInstalled , buildModelInstallArg , installModel , t ] ) ;
62
+ } ,
63
+ [ starterModelsData , getIsInstalled , buildModelInstallArg , installModel , t ]
64
+ ) ;
62
65
63
66
const navigateToUrlTab = useCallback ( ( ) => {
64
67
$installModelsTab . set ( 1 ) ; // URL/Local Path tab (now index 1)
@@ -71,7 +74,7 @@ export const LaunchpadForm = memo(() => {
71
74
const navigateToScanFolderTab = useCallback ( ( ) => {
72
75
$installModelsTab . set ( 3 ) ; // Scan Folder tab (now index 3)
73
76
} , [ ] ) ;
74
-
77
+
75
78
const navigateToStarterModelsTab = useCallback ( ( ) => {
76
79
$installModelsTab . set ( 4 ) ; // Starter Models tab (now index 4)
77
80
} , [ ] ) ;
@@ -85,90 +88,91 @@ export const LaunchpadForm = memo(() => {
85
88
86
89
const handleFluxBundleClick = useCallback ( ( ) => {
87
90
installBundle ( 'flux' ) ;
88
- } , [ installBundle ] ) ; return (
91
+ } , [ installBundle ] ) ;
92
+ return (
89
93
< Flex flexDir = "column" height = "100%" gap = { 3 } >
90
94
< ScrollableContent >
91
95
< Flex flexDir = "column" gap = { 6 } p = { 3 } >
92
- { /* Welcome Section */ }
93
- < Box >
94
- < Heading size = "md" mb = { 1 } >
95
- { t ( 'modelManager.launchpad.welcome' ) }
96
- </ Heading >
97
- < Text color = "base.300" fontSize = "sm" >
98
- { t ( 'modelManager.launchpad.description' ) }
99
- </ Text >
100
- </ Box >
101
-
102
- { /* Manual Installation Options */ }
103
- < Box >
104
- < Heading size = "sm" mb = { 2 } >
105
- { t ( 'modelManager.launchpad.manualInstall' ) }
106
- </ Heading > < Grid templateColumns = "repeat(auto-fit, minmax(280px, 1fr))" gap = { 3 } >
107
- < LaunchpadCard
108
- title = { t ( 'modelManager.urlOrLocalPath' ) }
109
- description = { t ( 'modelManager.launchpad.urlDescription' ) }
110
- icon = { < PiLinkBold size = { 24 } /> }
111
- onClick = { navigateToUrlTab }
112
- />
113
- < LaunchpadCard
114
- title = { t ( 'modelManager.huggingFace' ) }
115
- description = { t ( 'modelManager.launchpad.huggingFaceDescription' ) }
116
- icon = { < SiHuggingface size = { 24 } /> }
117
- onClick = { navigateToHuggingFaceTab }
118
- />
119
- < LaunchpadCard
120
- title = { t ( 'modelManager.scanFolder' ) }
121
- description = { t ( 'modelManager.launchpad.scanFolderDescription' ) }
122
- icon = { < PiFolderOpenBold size = { 24 } /> }
123
- onClick = { navigateToScanFolderTab }
124
- />
125
- </ Grid >
126
- </ Box > { /* Recommended Section */ }
127
- < Box >
128
- < Heading size = "sm" mb = { 2 } >
129
- { t ( 'modelManager.launchpad.recommendedModels' ) }
130
- </ Heading >
131
- < Flex flexDir = "column" gap = { 2 } > { /* Starter Model Bundles - More Prominent */ }
96
+ { /* Welcome Section */ }
132
97
< Box >
133
- < Heading size = "xs" color = "base.100 " mb = { 1 } >
134
- { t ( 'modelManager.launchpad.quickStart ' ) }
98
+ < Heading size = "md " mb = { 1 } >
99
+ { t ( 'modelManager.launchpad.welcome ' ) }
135
100
</ Heading >
136
- < Text fontSize = "xs" color = "base.300" mb = { 2 } >
137
- { t ( 'modelManager.launchpad.bundleDescription' ) }
138
- </ Text > < Grid templateColumns = "repeat(auto-fit, minmax(180px, 1fr))" gap = { 2 } >
139
- < LaunchpadBundleCard
140
- title = "Stable Diffusion 1.5"
141
- onClick = { handleSD15BundleClick }
101
+ < Text color = "base.300" fontSize = "sm" >
102
+ { t ( 'modelManager.launchpad.description' ) }
103
+ </ Text >
104
+ </ Box >
105
+ { /* Manual Installation Options */ }
106
+ < Box >
107
+ < Heading size = "sm" mb = { 2 } >
108
+ { t ( 'modelManager.launchpad.manualInstall' ) }
109
+ </ Heading > { ' ' }
110
+ < Grid templateColumns = "repeat(auto-fit, minmax(280px, 1fr))" gap = { 3 } >
111
+ < LaunchpadCard
112
+ title = { t ( 'modelManager.urlOrLocalPath' ) }
113
+ description = { t ( 'modelManager.launchpad.urlDescription' ) }
114
+ icon = { < PiLinkBold size = { 24 } /> }
115
+ onClick = { navigateToUrlTab }
142
116
/>
143
- < LaunchpadBundleCard
144
- title = "SDXL"
145
- onClick = { handleSDXLBundleClick }
117
+ < LaunchpadCard
118
+ title = { t ( 'modelManager.huggingFace' ) }
119
+ description = { t ( 'modelManager.launchpad.huggingFaceDescription' ) }
120
+ icon = { < SiHuggingface size = { 24 } /> }
121
+ onClick = { navigateToHuggingFaceTab }
146
122
/>
147
- < LaunchpadBundleCard
148
- title = "FLUX.1 [dev]"
149
- onClick = { handleFluxBundleClick }
123
+ < LaunchpadCard
124
+ title = { t ( 'modelManager.scanFolder' ) }
125
+ description = { t ( 'modelManager.launchpad.scanFolderDescription' ) }
126
+ icon = { < PiFolderOpenBold size = { 24 } /> }
127
+ onClick = { navigateToScanFolderTab }
150
128
/>
151
129
</ Grid >
152
- </ Box > { /* Browse All - Simple Link */ }
153
- < Box pt = { 1 } borderTop = "1px solid" borderColor = "base.700" >
154
- < Text fontSize = "xs" color = "base.400" mb = { 1 } >
155
- { t ( 'modelManager.launchpad.browseAll' ) }
156
- </ Text >
157
- < Button
158
- onClick = { navigateToStarterModelsTab }
159
- variant = "link" color = "invokeBlue.300"
160
- fontSize = "sm"
161
- fontWeight = "medium"
162
- p = { 0 }
163
- h = "auto"
164
- leftIcon = { < PiStarBold size = { 16 } /> }
165
- _hover = { {
166
- color : "invokeBlue.200" ,
167
- textDecoration : "underline"
168
- } }
169
- >
170
- { t ( 'modelManager.launchpad.exploreStarter' ) }
171
- </ Button > </ Box > </ Flex > </ Box >
130
+ </ Box > { ' ' }
131
+ { /* Recommended Section */ }
132
+ < Box >
133
+ < Heading size = "sm" mb = { 2 } >
134
+ { t ( 'modelManager.launchpad.recommendedModels' ) }
135
+ </ Heading >
136
+ < Flex flexDir = "column" gap = { 2 } >
137
+ { ' ' }
138
+ { /* Starter Model Bundles - More Prominent */ }
139
+ < Box >
140
+ < Heading size = "xs" color = "base.100" mb = { 1 } >
141
+ { t ( 'modelManager.launchpad.quickStart' ) }
142
+ </ Heading >
143
+ < Text fontSize = "xs" color = "base.300" mb = { 2 } >
144
+ { t ( 'modelManager.launchpad.bundleDescription' ) }
145
+ </ Text > { ' ' }
146
+ < Grid templateColumns = "repeat(auto-fit, minmax(180px, 1fr))" gap = { 2 } >
147
+ < LaunchpadBundleCard title = "Stable Diffusion 1.5" onClick = { handleSD15BundleClick } />
148
+ < LaunchpadBundleCard title = "SDXL" onClick = { handleSDXLBundleClick } />
149
+ < LaunchpadBundleCard title = "FLUX.1 [dev]" onClick = { handleFluxBundleClick } />
150
+ </ Grid >
151
+ </ Box > { ' ' }
152
+ { /* Browse All - Simple Link */ }
153
+ < Box pt = { 1 } borderTop = "1px solid" borderColor = "base.700" >
154
+ < Text fontSize = "xs" color = "base.400" mb = { 1 } >
155
+ { t ( 'modelManager.launchpad.browseAll' ) }
156
+ </ Text >
157
+ < Button
158
+ onClick = { navigateToStarterModelsTab }
159
+ variant = "link"
160
+ color = "invokeBlue.300"
161
+ fontSize = "sm"
162
+ fontWeight = "medium"
163
+ p = { 0 }
164
+ h = "auto"
165
+ leftIcon = { < PiStarBold size = { 16 } /> }
166
+ _hover = { {
167
+ color : 'invokeBlue.200' ,
168
+ textDecoration : 'underline' ,
169
+ } }
170
+ >
171
+ { t ( 'modelManager.launchpad.exploreStarter' ) }
172
+ </ Button > { ' ' }
173
+ </ Box > { ' ' }
174
+ </ Flex > { ' ' }
175
+ </ Box >
172
176
</ Flex >
173
177
</ ScrollableContent >
174
178
</ Flex >
@@ -186,7 +190,8 @@ interface LaunchpadCardProps {
186
190
}
187
191
188
192
const LaunchpadCard = memo ( ( { title, description, icon, onClick, variant = 'default' } : LaunchpadCardProps ) => {
189
- return ( < Button
193
+ return (
194
+ < Button
190
195
onClick = { onClick }
191
196
variant = "outline"
192
197
h = "auto"
@@ -221,9 +226,9 @@ const LaunchpadCard = memo(({ title, description, icon, onClick, variant = 'defa
221
226
{ title }
222
227
</ Heading >
223
228
</ Flex >
224
- < Text
225
- fontSize = "sm"
226
- color = { variant === 'featured' ? 'invokeBlue.200' : 'base.400' }
229
+ < Text
230
+ fontSize = "sm"
231
+ color = { variant === 'featured' ? 'invokeBlue.200' : 'base.400' }
227
232
lineHeight = "1.4"
228
233
flex = "1"
229
234
whiteSpace = "normal"
@@ -254,19 +259,20 @@ const LaunchpadBundleCard = memo(({ title, onClick }: LaunchpadBundleCardProps)
254
259
borderColor = "invokeBlue.400"
255
260
bg = "invokeBlue.950"
256
261
_hover = { {
257
- bg : " invokeBlue.900" ,
258
- borderColor : " invokeBlue.300" ,
259
- transform : " translateY(-2px)" ,
260
- boxShadow : " 0 4px 20px rgba(66, 153, 225, 0.15)" ,
262
+ bg : ' invokeBlue.900' ,
263
+ borderColor : ' invokeBlue.300' ,
264
+ transform : ' translateY(-2px)' ,
265
+ boxShadow : ' 0 4px 20px rgba(66, 153, 225, 0.15)' ,
261
266
} }
262
267
_active = { {
263
- transform : " translateY(0px)" ,
268
+ transform : ' translateY(0px)' ,
264
269
} }
265
270
transition = "all 0.2s"
266
271
cursor = "pointer"
267
272
textAlign = "center"
268
273
justifyContent = "center"
269
- alignItems = "center" borderRadius = "lg"
274
+ alignItems = "center"
275
+ borderRadius = "lg"
270
276
whiteSpace = "normal"
271
277
>
272
278
< Text fontSize = "sm" fontWeight = "bold" color = "invokeBlue.100" noOfLines = { 1 } >
0 commit comments