diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 54f130f3..0b867a6f 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -4144,6 +4144,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3100928009"] -- AI Studio cannot install updates into this installation. Your IT department provides new versions. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3125872224"] = "AI Studio cannot install updates into this installation. Your IT department provides new versions." +-- Development builds do not install updates. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3138812562"] = "Development builds do not install updates." + -- Spellchecking is enabled UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3165555978"] = "Spellchecking is enabled" @@ -4219,6 +4222,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T71162186"] = -- Energy saving is disabled UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T716338721"] = "Energy saving is disabled" +-- Development builds do not check for updates. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T735114866"] = "Development builds do not check for updates." + -- Start page UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T78084670"] = "Start page" @@ -8533,6 +8539,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3440211747"] = "Used Rust compil -- AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management. UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3449345633"] = "AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management." +-- You are running a development build of AI Studio, which never updates itself. Pull the latest changes and rebuild the app instead. +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3454691558"] = "You are running a development build of AI Studio, which never updates itself. Pull the latest changes and rebuild the app instead." + +-- development build, no self-updates +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3459123358"] = "development build, no self-updates" + -- Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri! UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3494984593"] = "Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!" diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor.cs index 4bb897b1..24a93ca5 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor.cs @@ -15,13 +15,13 @@ public partial class SettingsPanelApp : SettingsPanelBase private UpdatePolicyMode updatePolicyMode; - private bool AreUpdatesHandledOutsideTheApp => this.updatePolicyMode is UpdatePolicyMode.FLATPAK or UpdatePolicyMode.MANAGED_INSTALLATION or UpdatePolicyMode.UNSUPPORTED_INSTALLATION_LOCATION; + private bool CannotUpdateItself => this.updatePolicyMode is UpdatePolicyMode.FLATPAK or UpdatePolicyMode.MANAGED_INSTALLATION or UpdatePolicyMode.UNSUPPORTED_INSTALLATION_LOCATION or UpdatePolicyMode.DEVELOPMENT; - private UpdateInterval DisplayedUpdateInterval => this.AreUpdatesHandledOutsideTheApp + private UpdateInterval DisplayedUpdateInterval => this.CannotUpdateItself ? UpdateInterval.NO_CHECK : this.SettingsManager.ConfigurationData.App.UpdateInterval; - private UpdateInstallation DisplayedUpdateInstallation => this.AreUpdatesHandledOutsideTheApp + private UpdateInstallation DisplayedUpdateInstallation => this.CannotUpdateItself ? UpdateInstallation.MANUAL : this.SettingsManager.ConfigurationData.App.UpdateInstallation; @@ -31,6 +31,7 @@ public partial class SettingsPanelApp : SettingsPanelBase UpdatePolicyMode.FLATPAK => T("AI Studio cannot check for updates when running as a Flatpak. Updates are managed outside the app."), UpdatePolicyMode.MANAGED_INSTALLATION => T("This copy of AI Studio was installed for you by someone else, so it does not check for updates on its own."), UpdatePolicyMode.UNSUPPORTED_INSTALLATION_LOCATION => T("AI Studio cannot update itself from its current location, so it does not check for updates."), + UpdatePolicyMode.DEVELOPMENT => T("Development builds do not check for updates."), _ => T("How often should we check for app updates?") }; @@ -40,13 +41,14 @@ public partial class SettingsPanelApp : SettingsPanelBase UpdatePolicyMode.FLATPAK => T("AI Studio cannot install updates when running as a Flatpak. Use the update method provided by your Flatpak distribution."), UpdatePolicyMode.MANAGED_INSTALLATION => T("AI Studio cannot install updates into this installation. Your IT department provides new versions."), UpdatePolicyMode.UNSUPPORTED_INSTALLATION_LOCATION => T("AI Studio cannot install updates into its current location. Install new versions yourself."), + UpdatePolicyMode.DEVELOPMENT => T("Development builds do not install updates."), _ => T("Should updates be installed automatically or manually?") }; - private bool IsUpdateIntervalLocked() => this.updatePolicyMode is UpdatePolicyMode.ENTERPRISE_DISABLED || this.AreUpdatesHandledOutsideTheApp || + private bool IsUpdateIntervalLocked() => this.updatePolicyMode is UpdatePolicyMode.ENTERPRISE_DISABLED || this.CannotUpdateItself || ManagedConfiguration.TryGet(x => x.App, x => x.UpdateInterval, out var meta) && meta.IsLocked; - private bool IsUpdateInstallationLocked() => this.updatePolicyMode is UpdatePolicyMode.ENTERPRISE_DISABLED || this.AreUpdatesHandledOutsideTheApp || + private bool IsUpdateInstallationLocked() => this.updatePolicyMode is UpdatePolicyMode.ENTERPRISE_DISABLED || this.CannotUpdateItself || ManagedConfiguration.TryGet(x => x.App, x => x.UpdateInstallation, out var meta) && meta.IsLocked; protected override async Task OnInitializedAsync() diff --git a/app/MindWork AI Studio/Pages/Information.razor.cs b/app/MindWork AI Studio/Pages/Information.razor.cs index 97197f84..7fc7c39f 100644 --- a/app/MindWork AI Studio/Pages/Information.razor.cs +++ b/app/MindWork AI Studio/Pages/Information.razor.cs @@ -77,6 +77,7 @@ public partial class Information : MSGComponentBase { Tools.Rust.InstallationKind.MANAGED => T("managed by someone else, no self-updates"), Tools.Rust.InstallationKind.UNSUPPORTED_LOCATION => T("in a location AI Studio cannot update itself from"), + Tools.Rust.InstallationKind.DEVELOPMENT => T("development build, no self-updates"), _ => T("owned by you, self-updates possible") }; @@ -675,6 +676,10 @@ public partial class Information : MSGComponentBase parameters.Add(x => x.Message, T("AI Studio cannot update itself from its current location. Installing an update would leave a second installation behind instead of replacing this one. To get a new version, download the latest release and install it over your current installation.")); parameters.Add(x => x.ReleaseUrl, "https://github.com/MindWorkAI/AI-Studio/releases/latest"); } + else if (this.updatePolicyMode is UpdatePolicyMode.DEVELOPMENT) + { + parameters.Add(x => x.Message, T("You are running a development build of AI Studio, which never updates itself. Pull the latest changes and rebuild the app instead.")); + } else return; diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index b0072b5d..e499b666 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -4146,6 +4146,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3100928009"] -- AI Studio cannot install updates into this installation. Your IT department provides new versions. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3125872224"] = "AI Studio kann in dieser Installation keine Updates installieren. Ihre IT-Abteilung stellt neue Versionen bereit." +-- Development builds do not install updates. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3138812562"] = "Entwicklerversionen installieren keine Updates." + -- Spellchecking is enabled UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3165555978"] = "Rechtschreibprüfung ist aktiviert" @@ -4221,6 +4224,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T71162186"] = -- Energy saving is disabled UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T716338721"] = "Energiesparmodus ist deaktiviert" +-- Development builds do not check for updates. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T735114866"] = "Entwicklerversionen suchen nicht nach Updates." + -- Start page UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T78084670"] = "Startseite" @@ -8535,6 +8541,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3440211747"] = "Verwendeter Rust -- AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management. UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3449345633"] = "AI Studio wird mit Unternehmenskonfigurationen unter Verwendung von Konfigurations-Plugins betrieben. Eine zentrale Konfigurationsverwaltung wird nicht eingesetzt." +-- You are running a development build of AI Studio, which never updates itself. Pull the latest changes and rebuild the app instead. +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3454691558"] = "Du verwendest eine Entwicklerversion von AI Studio, die sich niemals selbst aktualisiert. Hole stattdessen die neuesten Änderungen und erstelle die App neu." + +-- development build, no self-updates +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3459123358"] = "Entwicklerversion, keine automatischen Updates" + -- Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri! UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3494984593"] = "Tauri wird verwendet, um die Blazor-Benutzeroberfläche bereitzustellen. Es ist ein großartiges Projekt, das die Erstellung von Desktop-Anwendungen mit Webtechnologien ermöglicht. Ich liebe Tauri!" diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index 064eac9a..b7fa0dcf 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -4146,6 +4146,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3100928009"] -- AI Studio cannot install updates into this installation. Your IT department provides new versions. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3125872224"] = "AI Studio cannot install updates into this installation. Your IT department provides new versions." +-- Development builds do not install updates. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3138812562"] = "Development builds do not install updates." + -- Spellchecking is enabled UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3165555978"] = "Spellchecking is enabled" @@ -4221,6 +4224,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T71162186"] = -- Energy saving is disabled UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T716338721"] = "Energy saving is disabled" +-- Development builds do not check for updates. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T735114866"] = "Development builds do not check for updates." + -- Start page UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T78084670"] = "Start page" @@ -8535,6 +8541,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3440211747"] = "Used Rust compil -- AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management. UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3449345633"] = "AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management." +-- You are running a development build of AI Studio, which never updates itself. Pull the latest changes and rebuild the app instead. +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3454691558"] = "You are running a development build of AI Studio, which never updates itself. Pull the latest changes and rebuild the app instead." + +-- development build, no self-updates +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3459123358"] = "development build, no self-updates" + -- Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri! UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3494984593"] = "Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!" diff --git a/app/MindWork AI Studio/Tools/Rust/InstallationKind.cs b/app/MindWork AI Studio/Tools/Rust/InstallationKind.cs index 2be0074b..58636044 100644 --- a/app/MindWork AI Studio/Tools/Rust/InstallationKind.cs +++ b/app/MindWork AI Studio/Tools/Rust/InstallationKind.cs @@ -22,4 +22,10 @@ public enum InstallationKind /// to install a new version themselves. /// UNSUPPORTED_LOCATION, + + /// + /// Not an installation at all, but a development build started from a build directory or an + /// IDE. There is nothing here the updater could replace. + /// + DEVELOPMENT, } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Services/UpdatePolicy.cs b/app/MindWork AI Studio/Tools/Services/UpdatePolicy.cs index 8e73dcbd..c41713f1 100644 --- a/app/MindWork AI Studio/Tools/Services/UpdatePolicy.cs +++ b/app/MindWork AI Studio/Tools/Services/UpdatePolicy.cs @@ -20,6 +20,7 @@ public sealed class UpdatePolicy(SettingsManager settingsManager, RuntimeInfoRes // InstallationKind.MANAGED => UpdatePolicyMode.MANAGED_INSTALLATION, InstallationKind.UNSUPPORTED_LOCATION => UpdatePolicyMode.UNSUPPORTED_INSTALLATION_LOCATION, + InstallationKind.DEVELOPMENT => UpdatePolicyMode.DEVELOPMENT, _ => UpdatePolicyMode.SELF_UPDATE } }; diff --git a/app/MindWork AI Studio/Tools/Services/UpdatePolicyMode.cs b/app/MindWork AI Studio/Tools/Services/UpdatePolicyMode.cs index e0280b7d..776deb7f 100644 --- a/app/MindWork AI Studio/Tools/Services/UpdatePolicyMode.cs +++ b/app/MindWork AI Studio/Tools/Services/UpdatePolicyMode.cs @@ -7,4 +7,5 @@ public enum UpdatePolicyMode ENTERPRISE_DISABLED, MANAGED_INSTALLATION, UNSUPPORTED_INSTALLATION_LOCATION, + DEVELOPMENT, } \ No newline at end of file diff --git a/runtime/src/app_window.rs b/runtime/src/app_window.rs index 7e4b7e51..7c01962e 100644 --- a/runtime/src/app_window.rs +++ b/runtime/src/app_window.rs @@ -515,7 +515,7 @@ pub async fn change_location_to(url: &str) { /// Checks for updates. pub async fn check_for_update(_token: APIToken) -> Json { - if let Some(reason) = self_update_blocked_reason(is_dev(), is_flatpak(), installation_kind()) { + if let Some(reason) = self_update_blocked_reason(is_flatpak(), installation_kind()) { warn!(Source = "Updater"; "Skipping update check because {reason}."); return Json(CheckUpdateResponse { update_is_available: false, @@ -600,7 +600,7 @@ pub struct CheckUpdateResponse { /// Installs the update. pub async fn install_update(_token: APIToken) { - if let Some(reason) = self_update_blocked_reason(is_dev(), is_flatpak(), installation_kind()) { + if let Some(reason) = self_update_blocked_reason(is_flatpak(), installation_kind()) { warn!(Source = "Updater"; "Skipping update installation because {reason}."); return; } @@ -660,22 +660,17 @@ pub async fn install_update(_token: APIToken) { } /// Returns why this installation cannot update itself, or `None` when it can. -fn self_update_blocked_reason(development: bool, flatpak: bool, installation_kind: InstallationKind) -> Option<&'static str> { +fn self_update_blocked_reason(flatpak: bool, installation_kind: InstallationKind) -> Option<&'static str> { if flatpak { return Some("Flatpak installations are updated externally"); } match installation_kind { - InstallationKind::Managed => return Some("this installation is centrally managed"), - InstallationKind::UnsupportedLocation => return Some("this installation is in a location the updater cannot replace"), - InstallationKind::User => {}, + InstallationKind::User => None, + InstallationKind::Managed => Some("this installation is centrally managed"), + InstallationKind::UnsupportedLocation => Some("this installation is in a location the updater cannot replace"), + InstallationKind::Development => Some("the app is running in development mode"), } - - if development { - return Some("the app is running in development mode"); - } - - None } /// Response for application exit requests. @@ -909,35 +904,45 @@ mod tests { #[test] fn self_update_is_disabled_in_development() { - assert!(self_update_blocked_reason(true, false, InstallationKind::User).is_some()); + assert!(self_update_blocked_reason(false, InstallationKind::Development).is_some()); } #[test] fn self_update_is_disabled_for_flatpak() { - assert!(self_update_blocked_reason(false, true, InstallationKind::User).is_some()); + assert!(self_update_blocked_reason(true, InstallationKind::User).is_some()); } #[test] fn self_update_is_disabled_for_managed_installations() { - assert!(self_update_blocked_reason(false, false, InstallationKind::Managed).is_some()); + assert!(self_update_blocked_reason(false, InstallationKind::Managed).is_some()); } #[test] fn self_update_is_disabled_for_unsupported_installation_locations() { - assert!(self_update_blocked_reason(false, false, InstallationKind::UnsupportedLocation).is_some()); + assert!(self_update_blocked_reason(false, InstallationKind::UnsupportedLocation).is_some()); } #[test] - fn self_update_blocked_reason_distinguishes_managed_from_unsupported_locations() { - assert_ne!( - self_update_blocked_reason(false, false, InstallationKind::Managed), - self_update_blocked_reason(false, false, InstallationKind::UnsupportedLocation) - ); + fn every_blocked_installation_kind_has_its_own_reason() { + let reasons = [ + self_update_blocked_reason(false, InstallationKind::Managed), + self_update_blocked_reason(false, InstallationKind::UnsupportedLocation), + self_update_blocked_reason(false, InstallationKind::Development), + ]; + + for (index, reason) in reasons.iter().enumerate() { + assert!(reason.is_some(), "expected a reason at index {index}"); + assert_eq!( + reasons.iter().filter(|other| *other == reason).count(), + 1, + "expected the reason at index {index} to be unique" + ); + } } #[test] fn self_update_is_enabled_for_normal_production_installations() { - assert!(self_update_blocked_reason(false, false, InstallationKind::User).is_none()); + assert!(self_update_blocked_reason(false, InstallationKind::User).is_none()); } #[test] diff --git a/runtime/src/environment.rs b/runtime/src/environment.rs index dd93b1cb..28e777da 100644 --- a/runtime/src/environment.rs +++ b/runtime/src/environment.rs @@ -98,6 +98,10 @@ pub enum InstallationKind { /// `D:\Tools\MindWork AI Studio` would leave a second installation behind. Nobody else /// maintains this installation, so its owner has to install a new version themselves. UnsupportedLocation, + + /// Not an installation at all, but a development build started from a build directory or an + /// IDE. There is nothing here the updater could replace. + Development, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] @@ -172,7 +176,15 @@ fn env_var_has_value(key: &str) -> bool { /// including security updates, which is far worse than a second installation. pub(crate) fn installation_kind() -> InstallationKind { *INSTALLATION_KIND.get_or_init(|| { - let kind = detect_installation_kind(); + // A development build lives in a build directory, which is perfectly writable and would + // therefore look like a regular user installation. We check it up front so that the + // platform-specific detection below only ever deals with real installations: + let kind = if is_dev() { + InstallationKind::Development + } else { + detect_installation_kind() + }; + info!(Source = "Updater"; "Detected a {kind:?} installation of AI Studio."); kind })