diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 49ea140f..a4addbf3 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -389,6 +389,9 @@ impl LightcraftApp { let open = matches!(self.ui.dialog, Some(state::Dialog::SamModel { .. })); match (&download.error, open) { (_, true) => {} + (Some(e), false) if e.contains("cancelled") => { + self.toast(ctx, crate::i18n::tr("SAM 3 download stopped: it resumes where it left off next time.")) + } (Some(e), false) => self.toast_error(ctx, format!("The SAM 3 download failed: {e}")), (None, false) if download.finished => { self.toast_error(ctx, crate::i18n::tr("The SAM 3 model is installed: Object and Describe masks are ready.")) diff --git a/crates/ui-egui/src/panels/dialogs.rs b/crates/ui-egui/src/panels/dialogs.rs index 67d495de..bc23383b 100644 --- a/crates/ui-egui/src/panels/dialogs.rs +++ b/crates/ui-egui/src/panels/dialogs.rs @@ -771,8 +771,10 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) { let informational = matches!(dlg, Dialog::About | Dialog::Shortcuts | Dialog::Settings { .. }); let sam = &app.session.segmenter; let (sam_installed, sam_running, sam_failed) = (sam.installed(), sam.download_status().running, sam.download_status().error.is_some()); + // no download location in this build: nothing to offer but the manual install + let sam_nowhere = !sam_installed && !sam_running && sam.mirrors().is_empty(); let cancel = match &dlg { - Dialog::SamModel { .. } if sam_running || sam_installed => "Close", + Dialog::SamModel { .. } if sam_running || sam_installed || sam_nowhere => "Close", Dialog::SamModel { .. } => "Not Now", _ => "Cancel", }; @@ -794,7 +796,7 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) { Dialog::Merge { .. } => "Merge", Dialog::ConfirmDelete { .. } => "Delete", Dialog::SamModel { then: Some(_), .. } if sam_installed => "Continue", - Dialog::SamModel { .. } if sam_installed || sam_running => "", + Dialog::SamModel { .. } if sam_installed || sam_running || sam_nowhere => "", Dialog::SamModel { error, .. } if error.is_some() || sam_failed => "Try Again", Dialog::SamModel { .. } => "Download", _ if informational => "Close", @@ -876,7 +878,7 @@ fn sam_model_body(app: &mut LightcraftApp, ui: &mut egui::Ui, error: Option<&str "Object and Describe masks use SAM 3, Meta's segmentation model. It isn't part of LightCraft, and everything else works without it.", )); let dir = seg.dir.as_ref().map(|d| d.display().to_string()).unwrap_or_default(); - ui.label(format!("{} {:.1} GB · {dir}", crate::i18n::tr("Download it once (about):"), gb(MODEL_BYTES))); + ui.label(format!("{} {:.1} GB, {} {dir}", crate::i18n::tr("A one-time download of about"), gb(MODEL_BYTES), crate::i18n::tr("saved in"))); ui.label( egui::RichText::new(format!( "{} {LICENSE_NAME} — {}", diff --git a/crates/ui-egui/src/tests_masking.rs b/crates/ui-egui/src/tests_masking.rs index c3aac95c..2abc141f 100644 --- a/crates/ui-egui/src/tests_masking.rs +++ b/crates/ui-egui/src/tests_masking.rs @@ -644,14 +644,19 @@ fn ai_masks_without_the_model_offer_the_download() { assert_eq!(h.app.ui.dialog, Some(Dialog::SamModel { then: Some(("object".into(), "new".into())), error: None })); assert!(!h.app.session.segmenter.download_status().running, "nothing downloads without a yes"); if h.app.session.segmenter.mirrors().is_empty() { - // Download: no location configured in this build → the reason, in the dialog + // no location configured in this build: no Download button (only Close), and an agent + // confirming anyway gets the reason; the dialog stays + h.step(); + let r = h.request("ui.clickWidget", json!({"id": "button:dialogOk"}), T); + assert_eq!(r["ok"], false, "{r}"); let r = h.request("ui.dialog.confirm", json!({}), T); assert_eq!(r["ok"], false, "{r}"); + assert!(r["error"].as_str().unwrap_or_default().contains("LIGHTCRAFT_SAM3_MIRRORS"), "{r}"); assert!(matches!(h.app.ui.dialog, Some(Dialog::SamModel { .. })), "stays open"); + } else { + // with a mirror: Download starts it in the background (here it fails: nothing listens) let r = h.request("ui.clickWidget", json!({"id": "button:dialogOk"}), T); assert_eq!(r["ok"], true, "{r}"); - let Some(Dialog::SamModel { error: Some(e), .. }) = &h.app.ui.dialog else { panic!("{:?}", h.app.ui.dialog) }; - assert!(e.contains("LIGHTCRAFT_SAM3_MIRRORS"), "{e}"); } // (a frame with the message laid out, so the buttons are where they are drawn) h.step();