mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-04-27 23:19:46 +00:00
Added update system to the app (#13)
This commit is contained in:
parent
50a03e6c9d
commit
d6e80a4563
@ -1,2 +1,4 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue">AI</s:String></wpf:ResourceDictionary>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue">AI</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MSG/@EntryIndexedValue">MSG</s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=tauri_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
@ -13,8 +13,9 @@ public partial class Changelog
|
||||
|
||||
public static readonly Log[] LOGS =
|
||||
[
|
||||
new (156, "v0.6.0, build 156 (2024-06-30 12:49 UTC)", "v0.6.0.md"),
|
||||
new (155, "v0.5.2, build 155 (2024-06-25 18:07 UTC)", "v0.5.2.md"),
|
||||
new (154, "v0.5.1, build 154 (2024-06-25 15:35 UTC)", "v0.5.1.md"),
|
||||
new (154, "v0.5.2, build 154 (2024-06-25 15:35 UTC)", "v0.5.2.md"),
|
||||
new (149, "v0.5.0, build 149 (2024-06-02 18:51 UTC)", "v0.5.0.md"),
|
||||
new (138, "v0.4.0, build 138 (2024-05-26 13:26 UTC)", "v0.4.0.md"),
|
||||
new (120, "v0.3.0, build 120 (2024-05-18 21:57 UTC)", "v0.3.0.md"),
|
||||
|
@ -1,3 +1,5 @@
|
||||
@inherits AIStudio.Tools.MSGComponentBase
|
||||
|
||||
<div class="d-flex flex-column" style="@this.Height">
|
||||
<div class="flex-auto overflow-auto">
|
||||
@this.ChildContent
|
||||
|
@ -1,12 +1,15 @@
|
||||
using AIStudio.Components.Layout;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components.Blocks;
|
||||
|
||||
public partial class InnerScrolling : ComponentBase
|
||||
public partial class InnerScrolling : MSGComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Set the height of anything above the scrolling content; usually a header.
|
||||
/// What we do is calc(100vh - THIS). Means, you can use multiple measures like
|
||||
/// What we do is calc(100vh - HeaderHeight). Means, you can use multiple measures like
|
||||
/// 230px - 3em. Default is 3em.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
@ -21,5 +24,34 @@ public partial class InnerScrolling : ComponentBase
|
||||
[Parameter]
|
||||
public RenderFragment? FooterContent { get; set; }
|
||||
|
||||
private string Height => $"height: calc(100vh - {this.HeaderHeight});";
|
||||
[CascadingParameter]
|
||||
private MainLayout MainLayout { get; set; } = null!;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.ApplyFilters([], [ Event.STATE_HAS_CHANGED ]);
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
|
||||
public override Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.STATE_HAS_CHANGED:
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string Height => $"height: calc(100vh - {this.HeaderHeight} - {this.MainLayout.AdditionalHeight});";
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
namespace AIStudio.Components.CommonDialogs;
|
||||
|
||||
public static class DialogOptions
|
||||
{
|
||||
public static readonly MudBlazor.DialogOptions FULLSCREEN = new()
|
||||
{
|
||||
CloseOnEscapeKey = true,
|
||||
FullWidth = true, MaxWidth = MaxWidth.Medium,
|
||||
};
|
||||
|
||||
public static readonly MudBlazor.DialogOptions FULLSCREEN_NO_HEADER = new()
|
||||
{
|
||||
NoHeader = true,
|
||||
CloseOnEscapeKey = true,
|
||||
FullWidth = true, MaxWidth = MaxWidth.Medium,
|
||||
};
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
@using AIStudio.Tools
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudText Typo="Typo.h4" Class="d-inline-flex align-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Update" Size="Size.Large" Class="mr-3"/>
|
||||
Update from v@(META_DATA.Version) to v@(this.UpdateResponse.NewVersion)
|
||||
</MudText>
|
||||
<MudMarkdown Value="@this.UpdateResponse.Changelog" OverrideHeaderTypo="@Markdown.OverrideHeaderTypo"/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Install later</MudButton>
|
||||
<MudButton OnClick="@this.Confirm" Variant="Variant.Filled" Color="Color.Info">Install now</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
@ -0,0 +1,26 @@
|
||||
using System.Reflection;
|
||||
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components.CommonDialogs;
|
||||
|
||||
/// <summary>
|
||||
/// The update dialog that is used to inform the user about an available update.
|
||||
/// </summary>
|
||||
public partial class UpdateDialog : ComponentBase
|
||||
{
|
||||
private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly();
|
||||
private static readonly MetaDataAttribute META_DATA = ASSEMBLY.GetCustomAttribute<MetaDataAttribute>()!;
|
||||
|
||||
[CascadingParameter]
|
||||
private MudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public UpdateResponse UpdateResponse { get; set; }
|
||||
|
||||
private void Cancel() => this.MudDialog.Cancel();
|
||||
|
||||
private void Confirm() => this.MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
@ -21,4 +21,13 @@ public static class ConfigurationSelectDataFactory
|
||||
yield return new("Modifier key + enter is sending the input", SendBehavior.MODIFER_ENTER_IS_SENDING);
|
||||
yield return new("Enter is sending the input", SendBehavior.ENTER_IS_SENDING);
|
||||
}
|
||||
|
||||
public static IEnumerable<ConfigurationSelectData<UpdateBehavior>> GetUpdateBehaviorData()
|
||||
{
|
||||
yield return new("No automatic update checks", UpdateBehavior.NO_CHECK);
|
||||
yield return new("Once at startup", UpdateBehavior.ONCE_STARTUP);
|
||||
yield return new("Check every hour", UpdateBehavior.HOURLY);
|
||||
yield return new("Check every day", UpdateBehavior.DAILY);
|
||||
yield return new ("Check every week", UpdateBehavior.WEEKLY);
|
||||
}
|
||||
}
|
@ -2,31 +2,57 @@
|
||||
|
||||
<MudPaper Height="calc(100vh);" Elevation="0">
|
||||
<MudLayout>
|
||||
<MudDrawerContainer Class="mud-height-full absolute">
|
||||
<MudDrawer Elevation="0" Variant="@DrawerVariant.Mini" OpenMiniOnHover="@true" Color="Color.Default">
|
||||
<MudNavMenu>
|
||||
<MudTooltip Text="Home" Placement="Placement.Right">
|
||||
<MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home">Home</MudNavLink>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Chats" Placement="Placement.Right">
|
||||
<MudNavLink Href="/chat" Icon="@Icons.Material.Filled.Chat">Chats</MudNavLink>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Supporters" Placement="Placement.Right">
|
||||
<MudNavLink Href="/supporters" Icon="@Icons.Material.Filled.Favorite" IconColor="Color.Error">Supporters</MudNavLink>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="About" Placement="Placement.Right">
|
||||
<MudNavLink Href="/about" Icon="@Icons.Material.Filled.Info">About</MudNavLink>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Settings" Placement="Placement.Right">
|
||||
<MudNavLink Href="/settings" Icon="@Icons.Material.Filled.Settings">Settings</MudNavLink>
|
||||
</MudTooltip>
|
||||
</MudNavMenu>
|
||||
</MudDrawer>
|
||||
</MudDrawerContainer>
|
||||
@if (!this.performingUpdate)
|
||||
{
|
||||
<MudDrawerContainer Class="mud-height-full absolute">
|
||||
<MudDrawer Elevation="0" Variant="@DrawerVariant.Mini" OpenMiniOnHover="@true" Color="Color.Default">
|
||||
<MudNavMenu>
|
||||
<MudTooltip Text="Home" Placement="Placement.Right">
|
||||
<MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home">Home</MudNavLink>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Chats" Placement="Placement.Right">
|
||||
<MudNavLink Href="/chat" Icon="@Icons.Material.Filled.Chat">Chats</MudNavLink>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Supporters" Placement="Placement.Right">
|
||||
<MudNavLink Href="/supporters" Icon="@Icons.Material.Filled.Favorite" IconColor="Color.Error">Supporters</MudNavLink>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="About" Placement="Placement.Right">
|
||||
<MudNavLink Href="/about" Icon="@Icons.Material.Filled.Info">About</MudNavLink>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Settings" Placement="Placement.Right">
|
||||
<MudNavLink Href="/settings" Icon="@Icons.Material.Filled.Settings">Settings</MudNavLink>
|
||||
</MudTooltip>
|
||||
</MudNavMenu>
|
||||
</MudDrawer>
|
||||
</MudDrawerContainer>
|
||||
}
|
||||
|
||||
<MudMainContent Class="mud-height-full pt-1">
|
||||
<MudContainer Fixed="@true" Class="mud-height-full" Style="margin-left: 5em; width: calc(100% - 5em);">
|
||||
@this.Body
|
||||
@if (!this.performingUpdate && this.IsUpdateAlertVisible)
|
||||
{
|
||||
<MudAlert NoIcon="@true" Severity="Severity.Info" Variant="Variant.Filled" ShowCloseIcon="@true" Dense="@true" CloseIconClicked="() => this.DismissUpdate()" Class="mt-2 mb-2">
|
||||
<div class="d-inline-flex align-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Update" Size="Size.Medium" Class="mr-3"/>
|
||||
An update to version @this.updateToVersion is available.
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Dark" Size="Size.Small" Class="ml-3" OnClick="() => this.ShowUpdateDialog()">
|
||||
Show details
|
||||
</MudButton>
|
||||
</div>
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
@if (!this.performingUpdate)
|
||||
{
|
||||
<CascadingValue Value="@this" IsFixed="true">
|
||||
@this.Body
|
||||
</CascadingValue>
|
||||
}
|
||||
|
||||
<MudOverlay Visible="@this.performingUpdate" DarkBackground="@true" LockScroll="@true">
|
||||
<MudText Typo="Typo.h3">Please wait for the update to complete...</MudText>
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="@true" Size="Size.Large" Rounded="@true"/>
|
||||
</MudOverlay>
|
||||
</MudContainer>
|
||||
</MudMainContent>
|
||||
</MudLayout>
|
||||
|
@ -1,12 +1,37 @@
|
||||
using AIStudio.Components.CommonDialogs;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using DialogOptions = AIStudio.Components.CommonDialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Layout;
|
||||
|
||||
public partial class MainLayout : LayoutComponentBase
|
||||
public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver
|
||||
{
|
||||
[Inject]
|
||||
private IJSRuntime JsRuntime { get; set; } = null!;
|
||||
private IJSRuntime JsRuntime { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private MessageBus MessageBus { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
public IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
public Rust Rust { get; init; } = null!;
|
||||
|
||||
public string AdditionalHeight { get; private set; } = "0em";
|
||||
|
||||
private bool isUpdateAvailable;
|
||||
private bool performingUpdate;
|
||||
private bool userDismissedUpdate;
|
||||
private string updateToVersion = string.Empty;
|
||||
private UpdateResponse? currentUpdateResponse;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
@ -23,8 +48,98 @@ public partial class MainLayout : LayoutComponentBase
|
||||
SettingsManager.ConfigDirectory = configDir;
|
||||
SettingsManager.DataDirectory = dataDir;
|
||||
|
||||
// Ensure that all settings are loaded:
|
||||
await this.SettingsManager.LoadSettings();
|
||||
|
||||
// Register this component with the message bus:
|
||||
this.MessageBus.RegisterComponent(this);
|
||||
this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.USER_SEARCH_FOR_UPDATE ]);
|
||||
|
||||
// Set the js runtime for the update service:
|
||||
UpdateService.SetJsRuntime(this.JsRuntime);
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
|
||||
public async Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.USER_SEARCH_FOR_UPDATE:
|
||||
this.userDismissedUpdate = false;
|
||||
break;
|
||||
|
||||
case Event.UPDATE_AVAILABLE:
|
||||
if (data is UpdateResponse updateResponse)
|
||||
{
|
||||
this.currentUpdateResponse = updateResponse;
|
||||
this.isUpdateAvailable = updateResponse.UpdateIsAvailable;
|
||||
this.updateToVersion = updateResponse.NewVersion;
|
||||
|
||||
await this.InvokeAsync(this.StateHasChanged);
|
||||
await this.SendMessage<bool>(Event.STATE_HAS_CHANGED);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task DismissUpdate()
|
||||
{
|
||||
this.userDismissedUpdate = true;
|
||||
this.AdditionalHeight = "0em";
|
||||
|
||||
await this.SendMessage<bool>(Event.STATE_HAS_CHANGED);
|
||||
}
|
||||
|
||||
private bool IsUpdateAlertVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
var state = this.isUpdateAvailable && !this.userDismissedUpdate;
|
||||
this.AdditionalHeight = state ? "3em" : "0em";
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowUpdateDialog()
|
||||
{
|
||||
if(this.currentUpdateResponse is null)
|
||||
return;
|
||||
|
||||
//
|
||||
// Replace the fir line with `# Changelog`:
|
||||
//
|
||||
var changelog = this.currentUpdateResponse.Value.Changelog;
|
||||
if (!string.IsNullOrWhiteSpace(changelog))
|
||||
{
|
||||
var lines = changelog.Split('\n');
|
||||
if (lines.Length > 0)
|
||||
lines[0] = "# Changelog";
|
||||
|
||||
changelog = string.Join('\n', lines);
|
||||
}
|
||||
|
||||
var updatedResponse = this.currentUpdateResponse.Value with { Changelog = changelog };
|
||||
var dialogParameters = new DialogParameters<UpdateDialog>
|
||||
{
|
||||
{ x => x.UpdateResponse, updatedResponse }
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<UpdateDialog>("Update", dialogParameters, DialogOptions.FULLSCREEN_NO_HEADER);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
this.performingUpdate = true;
|
||||
this.StateHasChanged();
|
||||
await this.Rust.InstallUpdate(this.JsRuntime);
|
||||
}
|
||||
}
|
@ -18,6 +18,9 @@
|
||||
<MudListItem Icon="@Icons.Material.Outlined.Widgets" Text="@MudBlazorVersion"/>
|
||||
<MudListItem Icon="@Icons.Material.Outlined.Memory" Text="@TauriVersion"/>
|
||||
</MudList>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Update" OnClick="() => this.CheckForUpdate()">
|
||||
Check for updates
|
||||
</MudButton>
|
||||
</ExpansionPanel>
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.EventNote" HeaderText="Changelog">
|
||||
|
@ -1,11 +1,16 @@
|
||||
using System.Reflection;
|
||||
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components.Pages;
|
||||
|
||||
public partial class About : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private MessageBus MessageBus { get; init; } = null!;
|
||||
|
||||
private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly();
|
||||
private static readonly MetaDataAttribute META_DATA = ASSEMBLY.GetCustomAttribute<MetaDataAttribute>()!;
|
||||
|
||||
@ -135,4 +140,9 @@ public partial class About : ComponentBase
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
""";
|
||||
|
||||
private async Task CheckForUpdate()
|
||||
{
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.USER_SEARCH_FOR_UPDATE);
|
||||
}
|
||||
}
|
||||
|
@ -37,9 +37,6 @@ public partial class Chat : ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Ensure that the settings are loaded:
|
||||
await this.SettingsManager.LoadSettings();
|
||||
|
||||
// Configure the spellchecking for the user input:
|
||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||
|
||||
|
@ -52,5 +52,6 @@
|
||||
<ConfigurationOption OptionDescription="Save energy?" LabelOn="Energy saving is enabled" LabelOff="Energy saving is disabled" State="@(() => this.SettingsManager.ConfigurationData.IsSavingEnergy)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.IsSavingEnergy = updatedState)" OptionHelp="When enabled, streamed content from the AI is updated once every third second. When disabled, streamed content will be updated as soon as it is available."/>
|
||||
<ConfigurationOption OptionDescription="Enable spellchecking?" LabelOn="Spellchecking is enabled" LabelOff="Spellchecking is disabled" State="@(() => this.SettingsManager.ConfigurationData.EnableSpellchecking)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.EnableSpellchecking = updatedState)" OptionHelp="When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections." />
|
||||
<ConfigurationSelect OptionDescription="Shortcut to send input" SelectedValue="@(() => this.SettingsManager.ConfigurationData.ShortcutSendBehavior)" Data="@ConfigurationSelectDataFactory.GetSendBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.ShortcutSendBehavior = selectedValue)" OptionHelp="Do you want to use any shortcut to send your input?"/>
|
||||
<ConfigurationSelect OptionDescription="Check for updates" SelectedValue="@(() => this.SettingsManager.ConfigurationData.UpdateBehavior)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.UpdateBehavior = selectedValue)" OptionHelp="How often should we check for app updates?"/>
|
||||
</MudPaper>
|
||||
</InnerScrolling>
|
@ -3,6 +3,8 @@ using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using DialogOptions = AIStudio.Components.CommonDialogs.DialogOptions;
|
||||
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
|
||||
namespace AIStudio.Components.Pages;
|
||||
@ -18,22 +20,6 @@ public partial class Settings : ComponentBase
|
||||
[Inject]
|
||||
public IJSRuntime JsRuntime { get; init; } = null!;
|
||||
|
||||
private static readonly DialogOptions DIALOG_OPTIONS = new()
|
||||
{
|
||||
CloseOnEscapeKey = true,
|
||||
FullWidth = true, MaxWidth = MaxWidth.Medium,
|
||||
};
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await this.SettingsManager.LoadSettings();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Provider related
|
||||
|
||||
private async Task AddProvider()
|
||||
@ -43,7 +29,7 @@ public partial class Settings : ComponentBase
|
||||
{ x => x.IsEditing, false },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Add Provider", dialogParameters, DIALOG_OPTIONS);
|
||||
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Add Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult.Canceled)
|
||||
return;
|
||||
@ -67,7 +53,7 @@ public partial class Settings : ComponentBase
|
||||
{ x => x.IsEditing, true },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Edit Provider", dialogParameters, DIALOG_OPTIONS);
|
||||
var dialogReference = await this.DialogService.ShowAsync<ProviderDialog>("Edit Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult.Canceled)
|
||||
return;
|
||||
@ -90,7 +76,7 @@ public partial class Settings : ComponentBase
|
||||
{ "Message", $"Are you sure you want to delete the provider '{provider.InstanceName}'?" },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Provider", dialogParameters, DIALOG_OPTIONS);
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Provider", dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult.Canceled)
|
||||
return;
|
||||
|
@ -25,10 +25,12 @@ builder.Services.AddMudServices(config =>
|
||||
});
|
||||
|
||||
builder.Services.AddMudMarkdownServices();
|
||||
builder.Services.AddSingleton(MessageBus.INSTANCE);
|
||||
builder.Services.AddSingleton<Rust>();
|
||||
builder.Services.AddMudMarkdownClipboardService<MarkdownClipboardService>();
|
||||
builder.Services.AddSingleton<SettingsManager>();
|
||||
builder.Services.AddSingleton<Random>();
|
||||
builder.Services.AddHostedService<UpdateService>();
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents()
|
||||
.AddHubOptions(options =>
|
||||
|
@ -6,7 +6,7 @@ namespace AIStudio.Settings;
|
||||
public sealed class Data
|
||||
{
|
||||
/// <summary>
|
||||
/// The version of the settings file. Allows us to upgrade the settings,
|
||||
/// The version of the settings file. Allows us to upgrade the settings
|
||||
/// when a new version is available.
|
||||
/// </summary>
|
||||
public Version Version { get; init; } = Version.V1;
|
||||
@ -36,4 +36,9 @@ public sealed class Data
|
||||
/// Should we enable spellchecking for all input fields?
|
||||
/// </summary>
|
||||
public bool EnableSpellchecking { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If and when we should look for updates.
|
||||
/// </summary>
|
||||
public UpdateBehavior UpdateBehavior { get; set; } = UpdateBehavior.ONCE_STARTUP;
|
||||
}
|
10
app/MindWork AI Studio/Settings/UpdateBehavior.cs
Normal file
10
app/MindWork AI Studio/Settings/UpdateBehavior.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
public enum UpdateBehavior
|
||||
{
|
||||
NO_CHECK,
|
||||
ONCE_STARTUP,
|
||||
HOURLY,
|
||||
DAILY,
|
||||
WEEKLY,
|
||||
}
|
13
app/MindWork AI Studio/Tools/Event.cs
Normal file
13
app/MindWork AI Studio/Tools/Event.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
public enum Event
|
||||
{
|
||||
NONE,
|
||||
|
||||
// Common events:
|
||||
STATE_HAS_CHANGED,
|
||||
|
||||
// Update events:
|
||||
USER_SEARCH_FOR_UPDATE,
|
||||
UPDATE_AVAILABLE,
|
||||
}
|
8
app/MindWork AI Studio/Tools/IMessageBusReceiver.cs
Normal file
8
app/MindWork AI Studio/Tools/IMessageBusReceiver.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
public interface IMessageBusReceiver
|
||||
{
|
||||
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data);
|
||||
}
|
44
app/MindWork AI Studio/Tools/MSGComponentBase.cs
Normal file
44
app/MindWork AI Studio/Tools/MSGComponentBase.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBusReceiver
|
||||
{
|
||||
[Inject]
|
||||
protected MessageBus MessageBus { get; init; } = null!;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
this.MessageBus.RegisterComponent(this);
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
|
||||
public abstract Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.MessageBus.Unregister(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected async Task SendMessage<T>(Event triggeredEvent, T? data = default)
|
||||
{
|
||||
await this.MessageBus.SendMessage(this, triggeredEvent, data);
|
||||
}
|
||||
|
||||
protected void ApplyFilters(ComponentBase[] components, Event[] events)
|
||||
{
|
||||
this.MessageBus.ApplyFilters(this, components, events);
|
||||
}
|
||||
}
|
67
app/MindWork AI Studio/Tools/MessageBus.cs
Normal file
67
app/MindWork AI Studio/Tools/MessageBus.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
// ReSharper disable RedundantRecordClassKeyword
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
public sealed class MessageBus
|
||||
{
|
||||
public static readonly MessageBus INSTANCE = new();
|
||||
|
||||
private readonly ConcurrentDictionary<IMessageBusReceiver, ComponentBase[]> componentFilters = new();
|
||||
private readonly ConcurrentDictionary<IMessageBusReceiver, Event[]> componentEvents = new();
|
||||
private readonly ConcurrentQueue<Message> messageQueue = new();
|
||||
private readonly SemaphoreSlim sendingSemaphore = new(1, 1);
|
||||
|
||||
private MessageBus()
|
||||
{
|
||||
}
|
||||
|
||||
public void ApplyFilters(IMessageBusReceiver receiver, ComponentBase[] components, Event[] events)
|
||||
{
|
||||
this.componentFilters[receiver] = components;
|
||||
this.componentEvents[receiver] = events;
|
||||
}
|
||||
|
||||
public void RegisterComponent(IMessageBusReceiver receiver)
|
||||
{
|
||||
this.componentFilters.TryAdd(receiver, []);
|
||||
this.componentEvents.TryAdd(receiver, []);
|
||||
}
|
||||
|
||||
public void Unregister(IMessageBusReceiver receiver)
|
||||
{
|
||||
this.componentFilters.TryRemove(receiver, out _);
|
||||
this.componentEvents.TryRemove(receiver, out _);
|
||||
}
|
||||
|
||||
private record class Message(ComponentBase? SendingComponent, Event TriggeredEvent, object? Data);
|
||||
|
||||
public async Task SendMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data = default)
|
||||
{
|
||||
this.messageQueue.Enqueue(new Message(sendingComponent, triggeredEvent, data));
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendingSemaphore.WaitAsync();
|
||||
while (this.messageQueue.TryDequeue(out var message))
|
||||
{
|
||||
foreach (var (receiver, componentFilter) in this.componentFilters)
|
||||
{
|
||||
if (componentFilter.Length > 0 && sendingComponent is not null && !componentFilter.Contains(sendingComponent))
|
||||
continue;
|
||||
|
||||
var eventFilter = this.componentEvents[receiver];
|
||||
if (eventFilter.Length == 0 || eventFilter.Contains(triggeredEvent))
|
||||
// We don't await the task here because we don't want to block the message bus:
|
||||
_ = receiver.ProcessMessage(message.SendingComponent, message.TriggeredEvent, message.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.sendingSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
16
app/MindWork AI Studio/Tools/MessageBusExtensions.cs
Normal file
16
app/MindWork AI Studio/Tools/MessageBusExtensions.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
public static class MessageBusExtensions
|
||||
{
|
||||
public static async Task SendMessage<T>(this ComponentBase component, Event triggeredEvent, T? data = default)
|
||||
{
|
||||
await MessageBus.INSTANCE.SendMessage(component, triggeredEvent, data);
|
||||
}
|
||||
|
||||
public static void ApplyFilters(this IMessageBusReceiver component, ComponentBase[] components, Event[] events)
|
||||
{
|
||||
MessageBus.INSTANCE.ApplyFilters(component, components, events);
|
||||
}
|
||||
}
|
@ -39,4 +39,16 @@ public sealed class Rust
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<UpdateResponse> CheckForUpdate(IJSRuntime jsRuntime)
|
||||
{
|
||||
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(16));
|
||||
return await jsRuntime.InvokeAsync<UpdateResponse>("window.__TAURI__.invoke", cts.Token, "check_for_update");
|
||||
}
|
||||
|
||||
public async Task InstallUpdate(IJSRuntime jsRuntime)
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
await jsRuntime.InvokeVoidAsync("window.__TAURI__.invoke", cts.Token, "install_update");
|
||||
}
|
||||
}
|
@ -3,6 +3,6 @@ namespace AIStudio.Tools;
|
||||
/// <summary>
|
||||
/// The response from the set clipboard operation.
|
||||
/// </summary>
|
||||
/// <param name="Success">True when the operation was successful.</param>
|
||||
/// <param name="Issue">The issue that occurred during the operation, empty when successful.</param>
|
||||
/// <param name="Success">True, when the operation was successful.</param>
|
||||
/// <param name="Issue">The issues, which occurred during the operation, empty when successful.</param>
|
||||
public readonly record struct SetClipboardResponse(bool Success, string Issue);
|
16
app/MindWork AI Studio/Tools/UpdateResponse.cs
Normal file
16
app/MindWork AI Studio/Tools/UpdateResponse.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// The response of the update check.
|
||||
/// </summary>
|
||||
/// <param name="UpdateIsAvailable">True if an update is available.</param>
|
||||
/// <param name="NewVersion">The new version, when available.</param>
|
||||
/// <param name="Changelog">The changelog of the new version, when available.</param>
|
||||
public readonly record struct UpdateResponse(
|
||||
[property:JsonPropertyName("update_is_available")] bool UpdateIsAvailable,
|
||||
[property:JsonPropertyName("error")] bool Error,
|
||||
[property:JsonPropertyName("new_version")] string NewVersion,
|
||||
string Changelog
|
||||
);
|
105
app/MindWork AI Studio/Tools/UpdateService.cs
Normal file
105
app/MindWork AI Studio/Tools/UpdateService.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
||||
{
|
||||
// We cannot inject IJSRuntime into our service. This is due to the fact that
|
||||
// the service is not a Blaozor component. We need to pass the IJSRuntime from
|
||||
// the MainLayout component to the service.
|
||||
private static IJSRuntime? JS_RUNTIME;
|
||||
private static bool IS_INITALIZED;
|
||||
|
||||
private readonly SettingsManager settingsManager;
|
||||
private readonly TimeSpan updateInterval;
|
||||
private readonly MessageBus messageBus;
|
||||
private readonly Rust rust;
|
||||
|
||||
public UpdateService(MessageBus messageBus, SettingsManager settingsManager, Rust rust)
|
||||
{
|
||||
this.settingsManager = settingsManager;
|
||||
this.messageBus = messageBus;
|
||||
this.rust = rust;
|
||||
|
||||
this.messageBus.RegisterComponent(this);
|
||||
this.ApplyFilters([], [ Event.USER_SEARCH_FOR_UPDATE ]);
|
||||
|
||||
this.updateInterval = settingsManager.ConfigurationData.UpdateBehavior switch
|
||||
{
|
||||
UpdateBehavior.NO_CHECK => Timeout.InfiniteTimeSpan,
|
||||
UpdateBehavior.ONCE_STARTUP => Timeout.InfiniteTimeSpan,
|
||||
|
||||
UpdateBehavior.HOURLY => TimeSpan.FromHours(1),
|
||||
UpdateBehavior.DAILY => TimeSpan.FromDays(1),
|
||||
UpdateBehavior.WEEKLY => TimeSpan.FromDays(7),
|
||||
|
||||
_ => TimeSpan.FromHours(1)
|
||||
};
|
||||
}
|
||||
|
||||
#region Overrides of BackgroundService
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested && !IS_INITALIZED)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
|
||||
}
|
||||
|
||||
await this.settingsManager.LoadSettings();
|
||||
if(this.settingsManager.ConfigurationData.UpdateBehavior != UpdateBehavior.NO_CHECK)
|
||||
await this.CheckForUpdate();
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(this.updateInterval, stoppingToken);
|
||||
await this.CheckForUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
|
||||
public async Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.USER_SEARCH_FOR_UPDATE:
|
||||
await this.CheckForUpdate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides of BackgroundService
|
||||
|
||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
this.messageBus.Unregister(this);
|
||||
await base.StopAsync(cancellationToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task CheckForUpdate()
|
||||
{
|
||||
if(!IS_INITALIZED)
|
||||
return;
|
||||
|
||||
var response = await this.rust.CheckForUpdate(JS_RUNTIME!);
|
||||
if (response.UpdateIsAvailable)
|
||||
{
|
||||
await this.messageBus.SendMessage(null, Event.UPDATE_AVAILABLE, response);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetJsRuntime(IJSRuntime jsRuntime)
|
||||
{
|
||||
JS_RUNTIME = jsRuntime;
|
||||
IS_INITALIZED = true;
|
||||
}
|
||||
}
|
8
app/MindWork AI Studio/wwwroot/changelog/v0.6.0.md
Normal file
8
app/MindWork AI Studio/wwwroot/changelog/v0.6.0.md
Normal file
@ -0,0 +1,8 @@
|
||||
# v0.6.0, build 156 (2024-06-30 12:49 UTC)
|
||||
- Added a setting to determine whether and how often to check for updates
|
||||
- Added a bidirectional message bus for inter-component communication
|
||||
- Added an update dialog for the app
|
||||
- Added an update banner to show when a new version is available
|
||||
- Added an option to manually check for updates to the about page
|
||||
- Fixed an issue with previous automatic updates on Windows, where background processes were not terminated
|
||||
- Disabled Tauri's built-in update dialog
|
@ -1,9 +1,9 @@
|
||||
0.5.2
|
||||
2024-06-25 18:07:06 UTC
|
||||
155
|
||||
0.6.0
|
||||
2024-06-30 12:49:38 UTC
|
||||
156
|
||||
8.0.206 (commit bb12410699)
|
||||
8.0.6 (commit 3b8b000a0e)
|
||||
1.79.0 (commit 129f3b996)
|
||||
6.20.0
|
||||
1.6.1
|
||||
2818aa93411, release
|
||||
dab121a7217, release
|
||||
|
3
runtime/Cargo.lock
generated
3
runtime/Cargo.lock
generated
@ -2313,12 +2313,13 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mindwork-ai-studio"
|
||||
version = "0.5.2"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"flexi_logger",
|
||||
"keyring",
|
||||
"log",
|
||||
"once_cell",
|
||||
"reqwest 0.12.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mindwork-ai-studio"
|
||||
version = "0.5.2"
|
||||
version = "0.6.0"
|
||||
edition = "2021"
|
||||
description = "MindWork AI Studio"
|
||||
authors = ["Thorsten Sommer"]
|
||||
@ -18,6 +18,7 @@ arboard = "3.4.0"
|
||||
tokio = "1.37.0"
|
||||
flexi_logger = "0.28"
|
||||
log = "0.4"
|
||||
once_cell = "1.19.0"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
# See issue https://github.com/tauri-apps/tauri/issues/4470
|
||||
|
@ -5,6 +5,7 @@ extern crate core;
|
||||
|
||||
use std::net::TcpListener;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use arboard::Clipboard;
|
||||
use keyring::Entry;
|
||||
@ -15,6 +16,11 @@ use tauri::utils::config::AppUrl;
|
||||
use tokio::time;
|
||||
use flexi_logger::{AdaptiveFormat, Logger};
|
||||
use log::{debug, error, info, warn};
|
||||
use tauri::updater::UpdateResponse;
|
||||
|
||||
static SERVER: Lazy<Arc<Mutex<Option<CommandChild>>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
|
||||
static MAIN_WINDOW: Lazy<Mutex<Option<Window>>> = Lazy::new(|| Mutex::new(None));
|
||||
static CHECK_UPDATE_RESPONSE: Lazy<Mutex<Option<UpdateResponse<tauri::Wry>>>> = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
fn main() {
|
||||
|
||||
@ -30,7 +36,14 @@ fn main() {
|
||||
let tauri_version = metadata_lines.next().unwrap();
|
||||
let app_commit_hash = metadata_lines.next().unwrap();
|
||||
|
||||
Logger::try_with_str("debug").expect("Cannot create logging")
|
||||
// Set the log level according to the environment:
|
||||
// In debug mode, the log level is set to debug, in release mode to info.
|
||||
let log_level = match is_dev() {
|
||||
true => "debug",
|
||||
false => "info",
|
||||
};
|
||||
|
||||
Logger::try_with_str(log_level).expect("Cannot create logging")
|
||||
.log_to_stdout()
|
||||
.adaptive_format_for_stdout(AdaptiveFormat::Detailed)
|
||||
.start().expect("Cannot start logging");
|
||||
@ -69,8 +82,7 @@ fn main() {
|
||||
info!("Try to start the .NET server on {app_url_log}...");
|
||||
|
||||
// Arc for the server process to stop it later:
|
||||
let server: Arc<Mutex<Option<CommandChild>>> = Arc::new(Mutex::new(None));
|
||||
let server_spawn_clone = server.clone();
|
||||
let server_spawn_clone = SERVER.clone();
|
||||
|
||||
// Channel to communicate with the server process:
|
||||
let (sender, mut receiver) = tauri::async_runtime::channel(100);
|
||||
@ -114,9 +126,8 @@ fn main() {
|
||||
warn!("Running in development mode, no .NET server will be started.");
|
||||
}
|
||||
|
||||
let main_window: Arc<Mutex<Option<Window>>> = Arc::new(Mutex::new(None));
|
||||
let main_window_spawn_clone = main_window.clone();
|
||||
let server_receive_clone = server.clone();
|
||||
let main_window_spawn_clone = &MAIN_WINDOW;
|
||||
let server_receive_clone = SERVER.clone();
|
||||
|
||||
// Create a thread to handle server events:
|
||||
tauri::async_runtime::spawn(async move {
|
||||
@ -181,29 +192,93 @@ fn main() {
|
||||
});
|
||||
|
||||
info!("Starting Tauri app...");
|
||||
tauri::Builder::default()
|
||||
let app = tauri::Builder::default()
|
||||
.setup(move |app| {
|
||||
let window = app.get_window("main").expect("Failed to get main window.");
|
||||
*main_window.lock().unwrap() = Some(window);
|
||||
*MAIN_WINDOW.lock().unwrap() = Some(window);
|
||||
Ok(())
|
||||
})
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.invoke_handler(tauri::generate_handler![store_secret, get_secret, delete_secret, set_clipboard])
|
||||
.run(tauri::generate_context!())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
store_secret, get_secret, delete_secret, set_clipboard,
|
||||
check_for_update, install_update
|
||||
])
|
||||
.build(tauri::generate_context!())
|
||||
.expect("Error while running Tauri application");
|
||||
|
||||
app.run(|app_handle, event| match event {
|
||||
|
||||
tauri::RunEvent::WindowEvent { event, label, .. } => {
|
||||
match event {
|
||||
tauri::WindowEvent::CloseRequested { .. } => {
|
||||
warn!("Window '{label}': close was requested.");
|
||||
}
|
||||
|
||||
tauri::WindowEvent::Destroyed => {
|
||||
warn!("Window '{label}': was destroyed.");
|
||||
}
|
||||
|
||||
tauri::WindowEvent::FileDrop(files) => {
|
||||
info!("Window '{label}': files were dropped: {files:?}");
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
tauri::RunEvent::Updater(updater_event) => {
|
||||
match updater_event {
|
||||
|
||||
tauri::UpdaterEvent::UpdateAvailable { body, date, version } => {
|
||||
let body_len = body.len();
|
||||
info!("Updater: update available: body size={body_len} time={date:?} version={version}");
|
||||
}
|
||||
|
||||
tauri::UpdaterEvent::Pending => {
|
||||
info!("Updater: update is pending!");
|
||||
}
|
||||
|
||||
tauri::UpdaterEvent::DownloadProgress { chunk_length, content_length } => {
|
||||
info!("Updater: downloaded {} of {:?}", chunk_length, content_length);
|
||||
}
|
||||
|
||||
tauri::UpdaterEvent::Downloaded => {
|
||||
info!("Updater: update has been downloaded!");
|
||||
warn!("Try to stop the .NET server now...");
|
||||
stop_server();
|
||||
}
|
||||
|
||||
tauri::UpdaterEvent::Updated => {
|
||||
info!("Updater: app has been updated");
|
||||
warn!("Try to restart the app now...");
|
||||
app_handle.restart();
|
||||
}
|
||||
|
||||
tauri::UpdaterEvent::AlreadyUpToDate => {
|
||||
info!("Updater: app is already up to date");
|
||||
}
|
||||
|
||||
tauri::UpdaterEvent::Error(error) => {
|
||||
warn!("Updater: failed to update: {error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tauri::RunEvent::ExitRequested { .. } => {
|
||||
warn!("Run event: exit was requested.");
|
||||
}
|
||||
|
||||
tauri::RunEvent::Ready => {
|
||||
info!("Run event: Tauri app is ready.");
|
||||
}
|
||||
|
||||
_ => {}
|
||||
});
|
||||
|
||||
info!("Tauri app was stopped.");
|
||||
if is_prod() {
|
||||
info!("Try to stop the .NET server as well...");
|
||||
if let Some(server_process) = server.lock().unwrap().take() {
|
||||
let server_kill_result = server_process.kill();
|
||||
match server_kill_result {
|
||||
Ok(_) => info!("The .NET server process was stopped."),
|
||||
Err(e) => error!("Failed to stop the .NET server process: {e}."),
|
||||
}
|
||||
} else {
|
||||
warn!("The .NET server process was not started or already stopped.");
|
||||
}
|
||||
stop_server();
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,6 +305,87 @@ fn get_available_port() -> Option<u16> {
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn stop_server() {
|
||||
if let Some(server_process) = SERVER.lock().unwrap().take() {
|
||||
let server_kill_result = server_process.kill();
|
||||
match server_kill_result {
|
||||
Ok(_) => info!("The .NET server process was stopped."),
|
||||
Err(e) => error!("Failed to stop the .NET server process: {e}."),
|
||||
}
|
||||
} else {
|
||||
warn!("The .NET server process was not started or already stopped.");
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn check_for_update() -> CheckUpdateResponse {
|
||||
let app_handle = MAIN_WINDOW.lock().unwrap().as_ref().unwrap().app_handle();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let response = app_handle.updater().check().await;
|
||||
match response {
|
||||
Ok(update_response) => match update_response.is_update_available() {
|
||||
true => {
|
||||
*CHECK_UPDATE_RESPONSE.lock().unwrap() = Some(update_response.clone());
|
||||
let new_version = update_response.latest_version();
|
||||
info!("Updater: update to version '{new_version}' is available.");
|
||||
let changelog = update_response.body();
|
||||
CheckUpdateResponse {
|
||||
update_is_available: true,
|
||||
error: false,
|
||||
new_version: new_version.to_string(),
|
||||
changelog: match changelog {
|
||||
Some(c) => c.to_string(),
|
||||
None => String::from(""),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
false => {
|
||||
info!("Updater: no updates available.");
|
||||
CheckUpdateResponse {
|
||||
update_is_available: false,
|
||||
error: false,
|
||||
new_version: String::from(""),
|
||||
changelog: String::from(""),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
Err(e) => {
|
||||
warn!("Failed to check updater: {e}.");
|
||||
CheckUpdateResponse {
|
||||
update_is_available: false,
|
||||
error: true,
|
||||
new_version: String::from(""),
|
||||
changelog: String::from(""),
|
||||
}
|
||||
},
|
||||
}
|
||||
}).await.unwrap()
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CheckUpdateResponse {
|
||||
update_is_available: bool,
|
||||
error: bool,
|
||||
new_version: String,
|
||||
changelog: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn install_update() {
|
||||
let cloned_response_option = CHECK_UPDATE_RESPONSE.lock().unwrap().clone();
|
||||
match cloned_response_option {
|
||||
Some(update_response) => {
|
||||
update_response.download_and_install().await.unwrap();
|
||||
},
|
||||
|
||||
None => {
|
||||
error!("Update installer: no update available to install. Did you check for updates first?");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn store_secret(destination: String, user_name: String, secret: String) -> StoreSecretResponse {
|
||||
let service = format!("mindwork-ai-studio::{}", destination);
|
||||
|
@ -6,7 +6,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "MindWork AI Studio",
|
||||
"version": "0.5.2"
|
||||
"version": "0.6.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
@ -76,7 +76,7 @@
|
||||
"endpoints": [
|
||||
"https://github.com/MindWorkAI/AI-Studio/releases/latest/download/latest.json"
|
||||
],
|
||||
"dialog": true,
|
||||
"dialog": false,
|
||||
"windows": {
|
||||
"installMode": "passive"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user