@@ -22,8 +22,8 @@ use crate::application_catalog::InstalledApplication;
2222use crate :: commands:: { CommandConfig , CommandInfo , CommandScope } ;
2323use crate :: desktop_activity:: DesktopActivity ;
2424use crate :: desktop_transcription_picker:: {
25- TranscriptionPickerDelegate , TranscriptionPickerModel , TranscriptionPickerProgress ,
26- TranscriptionPickerStatus , TranscriptionPickerView ,
25+ ModelDeletion , TranscriptionPickerDelegate , TranscriptionPickerModel ,
26+ TranscriptionPickerProgress , TranscriptionPickerStatus , TranscriptionPickerView ,
2727 render_transcription_picker as render_shared_transcription_picker,
2828 transcription_selection_is_active,
2929} ;
@@ -415,6 +415,7 @@ pub struct AppWindowPreview {
415415 pub pane : DeveloperPane ,
416416 pub transcription_picker : Option < ( String , PreviewModelState ) > ,
417417 pub onboarding : bool ,
418+ pub confirm_model_deletion : bool ,
418419 pub collapse_mode_processing : bool ,
419420 pub open_transformation_picker : bool ,
420421 pub select_global_mode : bool ,
@@ -987,6 +988,8 @@ pub struct AppWindow {
987988 transcription_status : PreparationStatus ,
988989 transcription_preparation : TranscriptionPreparation ,
989990 transcription_preview_installed : Option < bool > ,
991+ transcription_deletion : Option < TranscriptionModelId > ,
992+ transcription_deletion_error : Option < String > ,
990993 mode_editors : ModeEditors ,
991994 voice_action_inputs : VoiceActionInputs ,
992995 selected_mode : ModeSelection ,
@@ -1436,6 +1439,23 @@ impl AppWindow {
14361439 ) => Some ( false ) ,
14371440 Some ( PreviewModelState :: Actual ) | None => preview_model_missing. then_some ( false ) ,
14381441 } ,
1442+ transcription_deletion : preview_picker
1443+ . filter ( |_| {
1444+ preview
1445+ . as_ref ( )
1446+ . is_some_and ( |preview| preview. confirm_model_deletion )
1447+ } )
1448+ . and_then ( |( language, _) | {
1449+ crate :: transcription_models:: choices_for_runtime ( language)
1450+ . into_iter ( )
1451+ . map ( |choice| choice. model )
1452+ . find ( |model| {
1453+ matches ! ( model. runtime, ModelRuntime :: Gguf ( _) )
1454+ && model. id != settings. transcription . model
1455+ } )
1456+ . map ( |model| model. id )
1457+ } ) ,
1458+ transcription_deletion_error : None ,
14391459 mode_editors,
14401460 voice_action_inputs,
14411461 selected_mode : if preview
@@ -1974,6 +1994,54 @@ impl AppWindow {
19741994 fn clear_transcription_error ( & mut self ) {
19751995 self . transcription_preparation . set_error ( None ) ;
19761996 self . transcription_status = self . transcription_preparation . status ( ) ;
1997+ self . transcription_deletion = None ;
1998+ self . transcription_deletion_error = None ;
1999+ }
2000+
2001+ /// Downloaded local models other than the dictation model can be deleted,
2002+ /// whatever language the picker shows, unless they are being prepared.
2003+ fn transcription_model_deletion (
2004+ & self ,
2005+ model : & ModelDefinition ,
2006+ installed : bool ,
2007+ ) -> ModelDeletion {
2008+ if !installed
2009+ || !matches ! ( model. runtime, ModelRuntime :: Gguf ( _) )
2010+ || self . settings . transcription . model == model. id
2011+ || self . transcription_status . model == Some ( model. id )
2012+ {
2013+ ModelDeletion :: Unavailable
2014+ } else if self . transcription_deletion == Some ( model. id ) {
2015+ ModelDeletion :: Confirming
2016+ } else {
2017+ ModelDeletion :: Offered
2018+ }
2019+ }
2020+
2021+ fn delete_transcription_model ( & mut self , model : & ' static ModelDefinition ) {
2022+ self . transcription_deletion_error = None ;
2023+ let language = self
2024+ . transcription_picker_language
2025+ . clone ( )
2026+ . unwrap_or_default ( ) ;
2027+ let installed = self . transcription_model_installed ( model, & language) ;
2028+ match self . transcription_model_deletion ( model, installed) {
2029+ ModelDeletion :: Unavailable => self . transcription_deletion = None ,
2030+ ModelDeletion :: Offered => self . transcription_deletion = Some ( model. id ) ,
2031+ ModelDeletion :: Confirming => {
2032+ self . transcription_deletion = None ;
2033+ if self . preview {
2034+ return ;
2035+ }
2036+ match crate :: transcription_models:: remove_installed ( model) {
2037+ Ok ( ( ) ) => tracing:: info!( model = ?model. id, "deleted transcription model" ) ,
2038+ Err ( error) => {
2039+ tracing:: warn!( model = ?model. id, %error, "could not delete transcription model" ) ;
2040+ self . transcription_deletion_error = Some ( error. to_string ( ) ) ;
2041+ }
2042+ }
2043+ }
2044+ }
19772045 }
19782046
19792047 fn transcription_model_installed ( & self , model : & ModelDefinition , language : & str ) -> bool {
@@ -3168,13 +3236,17 @@ impl AppWindow {
31683236 } else if active {
31693237 TranscriptionPickerStatus :: Active
31703238 } else {
3171- TranscriptionPickerStatus :: Available { installed }
3239+ TranscriptionPickerStatus :: Available {
3240+ installed,
3241+ deletion : self . transcription_model_deletion ( model, installed) ,
3242+ }
31723243 } ;
31733244 TranscriptionPickerModel { choice, status }
31743245 } )
31753246 . collect ( ) ;
31763247 TranscriptionPickerView {
31773248 error : self . transcription_status . error . clone ( ) ,
3249+ deletion_error : self . transcription_deletion_error . clone ( ) ,
31783250 language : selected_language. to_string ( ) ,
31793251 models,
31803252 }
@@ -7313,6 +7385,10 @@ impl TranscriptionPickerDelegate for AppWindow {
73137385 AppWindow :: choose_transcription_model ( self , definition ( model) , language, cx) ;
73147386 }
73157387
7388+ fn delete_transcription_model ( & mut self , model : TranscriptionModelId , _cx : & mut Context < Self > ) {
7389+ AppWindow :: delete_transcription_model ( self , definition ( model) ) ;
7390+ }
7391+
73167392 fn dismiss_transcription_picker ( & mut self , cx : & mut Context < Self > ) {
73177393 AppWindow :: dismiss_transcription_picker ( self , cx) ;
73187394 }
@@ -8615,6 +8691,7 @@ mod tests {
86158691 pane : DeveloperPane :: Modes ,
86168692 transcription_picker : None ,
86178693 onboarding : false ,
8694+ confirm_model_deletion : false ,
86188695 collapse_mode_processing : true ,
86198696 open_transformation_picker : false ,
86208697 select_global_mode : true ,
@@ -8695,6 +8772,60 @@ mod tests {
86958772 Err ( color_eyre:: eyre:: eyre!( "fixture persistence failure" ) )
86968773 }
86978774
8775+ #[ gpui:: test]
8776+ fn model_deletion_confirms_first_and_spares_the_dictation_model ( cx : & mut gpui:: TestAppContext ) {
8777+ let ( view, cx) = cx. add_window_view ( editor_fixture) ;
8778+ cx. update ( |_, cx| {
8779+ view. update ( cx, |view, _| {
8780+ view. transcription_preview_installed = Some ( true ) ;
8781+ view. transcription_picker_language = Some ( "en" . into ( ) ) ;
8782+ let current = definition ( view. settings . transcription . model ) ;
8783+ let other = crate :: transcription_models:: choices_for_runtime ( "en" )
8784+ . into_iter ( )
8785+ . map ( |choice| choice. model )
8786+ . find ( |model| {
8787+ matches ! ( model. runtime, ModelRuntime :: Gguf ( _) ) && model. id != current. id
8788+ } )
8789+ . unwrap ( ) ;
8790+
8791+ assert_eq ! (
8792+ view. transcription_model_deletion( current, true ) ,
8793+ ModelDeletion :: Unavailable
8794+ ) ;
8795+ assert_eq ! (
8796+ view. transcription_model_deletion( other, false ) ,
8797+ ModelDeletion :: Unavailable
8798+ ) ;
8799+ assert_eq ! (
8800+ view. transcription_model_deletion( other, true ) ,
8801+ ModelDeletion :: Offered
8802+ ) ;
8803+
8804+ view. delete_transcription_model ( other) ;
8805+ assert_eq ! (
8806+ view. transcription_model_deletion( other, true ) ,
8807+ ModelDeletion :: Confirming
8808+ ) ;
8809+ view. clear_transcription_error ( ) ;
8810+ assert_eq ! (
8811+ view. transcription_model_deletion( other, true ) ,
8812+ ModelDeletion :: Offered
8813+ ) ;
8814+
8815+ view. delete_transcription_model ( other) ;
8816+ view. delete_transcription_model ( other) ;
8817+ assert_eq ! ( view. transcription_deletion, None ) ;
8818+ assert_eq ! ( view. transcription_deletion_error, None ) ;
8819+
8820+ view. transcription_status . model = Some ( other. id ) ;
8821+ assert_eq ! (
8822+ view. transcription_model_deletion( other, true ) ,
8823+ ModelDeletion :: Unavailable
8824+ ) ;
8825+ } ) ;
8826+ } ) ;
8827+ }
8828+
86988829 #[ gpui:: test]
86998830 fn activity_log_is_only_read_when_an_observing_pane_needs_it ( cx : & mut gpui:: TestAppContext ) {
87008831 let path = std:: env:: temp_dir ( ) . join ( format ! (
0 commit comments