1- import { Flex , Text } from '@invoke-ai/ui-library' ;
1+ import { Flex , Text , useDisclosure , useToast } from '@invoke-ai/ui-library' ;
22import { logger } from 'app/logging/logger' ;
3- import { useAppSelector } from 'app/store/storeHooks' ;
3+ import { useAppDispatch , useAppSelector } from 'app/store/storeHooks' ;
44import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent' ;
55import { MODEL_CATEGORIES_AS_LIST } from 'features/modelManagerV2/models' ;
66import {
7+ clearModelSelection ,
78 type FilterableModelType ,
89 selectFilteredModelType ,
910 selectSearchTerm ,
11+ selectSelectedModelKeys ,
12+ setSelectedModelKey ,
1013} from 'features/modelManagerV2/store/modelManagerV2Slice' ;
11- import { memo , useMemo } from 'react' ;
14+ import { memo , useCallback , useMemo , useState } from 'react' ;
1215import { useTranslation } from 'react-i18next' ;
13- import { modelConfigsAdapterSelectors , useGetModelConfigsQuery } from 'services/api/endpoints/models' ;
16+ import { serializeError } from 'serialize-error' ;
17+ import {
18+ modelConfigsAdapterSelectors ,
19+ useBulkDeleteModelsMutation ,
20+ useGetModelConfigsQuery ,
21+ } from 'services/api/endpoints/models' ;
1422import type { AnyModelConfig } from 'services/api/types' ;
1523
24+ import { BulkDeleteModelsModal } from './BulkDeleteModelsModal' ;
1625import { FetchingModelsLoader } from './FetchingModelsLoader' ;
26+ import { ModelListHeader } from './ModelListHeader' ;
1727import { ModelListWrapper } from './ModelListWrapper' ;
1828
1929const log = logger ( 'models' ) ;
2030
2131const ModelList = ( ) => {
32+ const dispatch = useAppDispatch ( ) ;
2233 const filteredModelType = useAppSelector ( selectFilteredModelType ) ;
2334 const searchTerm = useAppSelector ( selectSearchTerm ) ;
35+ const selectedModelKeys = useAppSelector ( selectSelectedModelKeys ) ;
2436 const { t } = useTranslation ( ) ;
37+ const toast = useToast ( ) ;
38+ const { isOpen, onOpen, onClose } = useDisclosure ( ) ;
39+ const [ isDeleting , setIsDeleting ] = useState ( false ) ;
2540
2641 const { data, isLoading } = useGetModelConfigsQuery ( ) ;
42+ const [ bulkDeleteModels ] = useBulkDeleteModelsMutation ( ) ;
2743
2844 const models = useMemo ( ( ) => {
2945 const modelConfigs = modelConfigsAdapterSelectors . selectAll ( data ?? { ids : [ ] , entities : { } } ) ;
@@ -46,20 +62,99 @@ const ModelList = () => {
4662 return { total, byCategory } ;
4763 } , [ data , filteredModelType , searchTerm ] ) ;
4864
65+ const handleBulkDelete = useCallback ( ( ) => {
66+ onOpen ( ) ;
67+ } , [ onOpen ] ) ;
68+
69+ const handleConfirmBulkDelete = useCallback ( async ( ) => {
70+ setIsDeleting ( true ) ;
71+ try {
72+ const result = await bulkDeleteModels ( { keys : selectedModelKeys } ) . unwrap ( ) ;
73+
74+ // Clear selection and close modal
75+ dispatch ( clearModelSelection ( ) ) ;
76+ dispatch ( setSelectedModelKey ( null ) ) ;
77+ onClose ( ) ;
78+
79+ // Show success/failure toast
80+ if ( result . failed . length === 0 ) {
81+ toast ( {
82+ id : 'BULK_DELETE_SUCCESS' ,
83+ title : t ( 'modelManager.modelsDeleted' , {
84+ count : result . deleted . length ,
85+ defaultValue : `Successfully deleted ${ result . deleted . length } model(s)` ,
86+ } ) ,
87+ status : 'success' ,
88+ } ) ;
89+ } else if ( result . deleted . length === 0 ) {
90+ toast ( {
91+ id : 'BULK_DELETE_FAILED' ,
92+ title : t ( 'modelManager.modelsDeleteFailed' , {
93+ defaultValue : 'Failed to delete models' ,
94+ } ) ,
95+ description : t ( 'modelManager.someModelsFailedToDelete' , {
96+ count : result . failed . length ,
97+ defaultValue : `${ result . failed . length } model(s) could not be deleted` ,
98+ } ) ,
99+ status : 'error' ,
100+ } ) ;
101+ } else {
102+ // Partial success
103+ toast ( {
104+ id : 'BULK_DELETE_PARTIAL' ,
105+ title : t ( 'modelManager.modelsDeletedPartial' , {
106+ defaultValue : 'Partially completed' ,
107+ } ) ,
108+ description : t ( 'modelManager.someModelsDeleted' , {
109+ deleted : result . deleted . length ,
110+ failed : result . failed . length ,
111+ defaultValue : `${ result . deleted . length } deleted, ${ result . failed . length } failed` ,
112+ } ) ,
113+ status : 'warning' ,
114+ } ) ;
115+ }
116+
117+ log . info ( `Bulk delete completed: ${ result . deleted . length } deleted, ${ result . failed . length } failed` ) ;
118+ } catch ( err ) {
119+ log . error ( { error : serializeError ( err as Error ) } , 'Bulk delete error' ) ;
120+ toast ( {
121+ id : 'BULK_DELETE_ERROR' ,
122+ title : t ( 'modelManager.modelsDeleteError' , {
123+ defaultValue : 'Error deleting models' ,
124+ } ) ,
125+ status : 'error' ,
126+ } ) ;
127+ } finally {
128+ setIsDeleting ( false ) ;
129+ }
130+ } , [ bulkDeleteModels , selectedModelKeys , dispatch , onClose , toast , t ] ) ;
131+
49132 return (
50- < ScrollableContent >
51- < Flex flexDirection = "column" w = "full" h = "full" gap = { 4 } >
52- { isLoading && < FetchingModelsLoader loadingMessage = "Loading..." /> }
53- { models . byCategory . map ( ( { i18nKey, configs } ) => (
54- < ModelListWrapper key = { i18nKey } title = { t ( i18nKey ) } modelList = { configs } />
55- ) ) }
56- { ! isLoading && models . total === 0 && (
57- < Flex w = "full" h = "full" alignItems = "center" justifyContent = "center" >
58- < Text > { t ( 'modelManager.noMatchingModels' ) } </ Text >
133+ < >
134+ < Flex flexDirection = "column" w = "full" h = "full" >
135+ < ModelListHeader onBulkDelete = { handleBulkDelete } />
136+ < ScrollableContent >
137+ < Flex flexDirection = "column" w = "full" h = "full" gap = { 4 } >
138+ { isLoading && < FetchingModelsLoader loadingMessage = "Loading..." /> }
139+ { models . byCategory . map ( ( { i18nKey, configs } ) => (
140+ < ModelListWrapper key = { i18nKey } title = { t ( i18nKey ) } modelList = { configs } />
141+ ) ) }
142+ { ! isLoading && models . total === 0 && (
143+ < Flex w = "full" h = "full" alignItems = "center" justifyContent = "center" >
144+ < Text > { t ( 'modelManager.noMatchingModels' ) } </ Text >
145+ </ Flex >
146+ ) }
59147 </ Flex >
60- ) }
148+ </ ScrollableContent >
61149 </ Flex >
62- </ ScrollableContent >
150+ < BulkDeleteModelsModal
151+ isOpen = { isOpen }
152+ onClose = { onClose }
153+ onConfirm = { handleConfirmBulkDelete }
154+ modelCount = { selectedModelKeys . length }
155+ isDeleting = { isDeleting }
156+ />
157+ </ >
63158 ) ;
64159} ;
65160
0 commit comments