11import {
22 Button ,
33 Checkbox ,
4+ Collapse ,
45 Flex ,
56 Heading ,
7+ IconButton ,
68 Modal ,
79 ModalBody ,
810 ModalCloseButton ,
@@ -16,6 +18,7 @@ import {
1618} from '@invoke-ai/ui-library' ;
1719import { memo , useCallback , useEffect , useState } from 'react' ;
1820import { useTranslation } from 'react-i18next' ;
21+ import { PiCaretDownBold , PiCaretRightBold } from 'react-icons/pi' ;
1922import { useDeleteOrphanedModelsMutation , useGetOrphanedModelsQuery } from 'services/api/endpoints/models' ;
2023
2124type OrphanedModel = {
@@ -38,6 +41,7 @@ export const SyncModelsDialog = memo(({ isOpen, onClose }: SyncModelsDialogProps
3841
3942 const [ selectedModels , setSelectedModels ] = useState < Set < string > > ( new Set ( ) ) ;
4043 const [ selectAll , setSelectAll ] = useState ( true ) ;
44+ const [ expandedModels , setExpandedModels ] = useState < Set < string > > ( new Set ( ) ) ;
4145
4246 // Initialize selected models when data loads
4347 useEffect ( ( ) => {
@@ -85,6 +89,18 @@ export const SyncModelsDialog = memo(({ isOpen, onClose }: SyncModelsDialogProps
8589 }
8690 } , [ selectAll , orphanedModels ] ) ;
8791
92+ const handleToggleExpanded = useCallback ( ( path : string ) => {
93+ setExpandedModels ( ( prev ) => {
94+ const next = new Set ( prev ) ;
95+ if ( next . has ( path ) ) {
96+ next . delete ( path ) ;
97+ } else {
98+ next . add ( path ) ;
99+ }
100+ return next ;
101+ } ) ;
102+ } , [ ] ) ;
103+
88104 const handleDelete = useCallback ( async ( ) => {
89105 try {
90106 const result = await deleteOrphanedModels ( { paths : Array . from ( selectedModels ) } ) . unwrap ( ) ;
@@ -202,19 +218,48 @@ export const SyncModelsDialog = memo(({ isOpen, onClose }: SyncModelsDialogProps
202218 bg = "base.750"
203219 >
204220 < Flex justifyContent = "space-between" alignItems = "center" >
205- < Checkbox
206- isChecked = { selectedModels . has ( model . path ) }
207- onChange = { ( ) => handleToggleModel ( model . path ) }
208- >
209- < Text fontWeight = "semibold" > { model . path } </ Text >
210- </ Checkbox >
221+ < Flex alignItems = "center" gap = { 2 } flex = { 1 } >
222+ < IconButton
223+ aria-label = { expandedModels . has ( model . path ) ? 'Collapse' : 'Expand' }
224+ icon = { expandedModels . has ( model . path ) ? < PiCaretDownBold /> : < PiCaretRightBold /> }
225+ size = "xs"
226+ variant = "ghost"
227+ onClick = { ( ) => handleToggleExpanded ( model . path ) }
228+ />
229+ < Checkbox
230+ isChecked = { selectedModels . has ( model . path ) }
231+ onChange = { ( ) => handleToggleModel ( model . path ) }
232+ >
233+ < Text fontWeight = "semibold" > { model . path } </ Text >
234+ </ Checkbox >
235+ </ Flex >
211236 < Text fontSize = "sm" color = "base.400" >
212237 { formatSize ( model . size_bytes ) }
213238 </ Text >
214239 </ Flex >
215- < Text fontSize = "sm" color = "base.400" >
216- { t ( 'modelManager.filesCount' , { count : model . files . length } ) }
217- </ Text >
240+ < Flex justifyContent = "space-between" alignItems = "center" >
241+ < Text fontSize = "sm" color = "base.400" >
242+ { t ( 'modelManager.filesCount' , { count : model . files . length } ) }
243+ </ Text >
244+ </ Flex >
245+ < Collapse in = { expandedModels . has ( model . path ) } >
246+ < Flex
247+ flexDir = "column"
248+ gap = { 1 }
249+ mt = { 2 }
250+ p = { 2 }
251+ bg = "base.800"
252+ borderRadius = "md"
253+ maxH = "200px"
254+ overflowY = "auto"
255+ >
256+ { model . files . map ( ( file ) => (
257+ < Text key = { file } fontSize = "xs" color = "base.300" fontFamily = "mono" >
258+ { file }
259+ </ Text >
260+ ) ) }
261+ </ Flex >
262+ </ Collapse >
218263 </ Flex >
219264 ) ) }
220265 </ Flex >
0 commit comments