M12: SAM 3 download dialog: no Download button without a location, clearer wording

Looked at in the running app: without a configured mirror the dialog
offered Download / Try Again that could only fail; it now shows the
manual-install hint and Close (an agent confirming still gets the
reason). "A one-time download of about 3.4 GB, saved in <folder>"; a
cancelled download is reported as stopped (resumable), not failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas 2026-10-07 10:06:46 -04:00
parent d379e780cc
commit d5be201431
3 changed files with 16 additions and 6 deletions

View File

@ -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."))

View File

@ -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} — {}",

View File

@ -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();