mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-04 17:27:21 +00:00
Added a setting to change the size of the interface (#1136)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
1 parent
c2b62c6dbd
commit
e0337a99bc
27 files changed
+1742
-27
No files matched your search
@@ -4168,6 +4168,33 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONFILE::T1984243691"] = "You c
|
||||
-- Choose File
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONFILE::T4285779702"] = "Choose File"
|
||||
|
||||
-- Applies to this screen only ({0}). The app default shows AI Studio in the same size on every laptop. System scaling follows your operating system.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T1103704506"] = "Applies to this screen only ({0}). The app default shows AI Studio in the same size on every laptop. System scaling follows your operating system."
|
||||
|
||||
-- You can choose from {0} % to {1} %. The app default on this screen is {2} %.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T1110635214"] = "You can choose from {0} % to {1} %. The app default on this screen is {2} %."
|
||||
|
||||
-- {0} × {1} pixels, {2:0.#} inches
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T2376030041"] = "{0} × {1} pixels, {2:0.#} inches"
|
||||
|
||||
-- You can choose from {0} % to {1} %. The system scaling on this screen is {2} %.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3009448744"] = "You can choose from {0} % to {1} %. The system scaling on this screen is {2} %."
|
||||
|
||||
-- Custom interface size on this screen
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3268281815"] = "Custom interface size on this screen"
|
||||
|
||||
-- AI Studio cannot measure this screen right now, so you cannot set the interface size here.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3560281885"] = "AI Studio cannot measure this screen right now, so you cannot set the interface size here."
|
||||
|
||||
-- Interface size on this screen
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3568633122"] = "Interface size on this screen"
|
||||
|
||||
-- Applies to this screen only ({0}). System scaling follows your operating system.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3653703912"] = "Applies to this screen only ({0}). System scaling follows your operating system."
|
||||
|
||||
-- {0} × {1} pixels
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T990356864"] = "{0} × {1} pixels"
|
||||
|
||||
-- Choose the minimum confidence level that all LLM providers must meet. This way, you can ensure that only trustworthy providers are used. You cannot use any provider that falls below this level.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T2526727283"] = "Choose the minimum confidence level that all LLM providers must meet. This way, you can ensure that only trustworthy providers are used. You cannot use any provider that falls below this level."
|
||||
|
||||
@@ -12517,6 +12544,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1258653480
|
||||
-- No key is sending the input
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1311973034"] = "No key is sending the input"
|
||||
|
||||
-- App default
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1382522201"] = "App default"
|
||||
|
||||
-- Navigation never expands, no tooltips
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1402851833"] = "Navigation never expands, no tooltips"
|
||||
|
||||
@@ -12562,6 +12592,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T238099712"
|
||||
-- Delete disappearing chats older than 1 year
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2405705561"] = "Delete disappearing chats older than 1 year"
|
||||
|
||||
-- Custom
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2502277006"] = "Custom"
|
||||
|
||||
-- Store chats manually
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2520325466"] = "Store chats manually"
|
||||
|
||||
@@ -12625,6 +12658,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T4256323669
|
||||
-- All preview features are hidden
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T4289410063"] = "All preview features are hidden"
|
||||
|
||||
-- System scaling
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T453866521"] = "System scaling"
|
||||
|
||||
-- Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T578410699"] = "Chat"
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
@using AIStudio.Settings
|
||||
@using AIStudio.Settings.DataModel
|
||||
@inherits MSGComponentBase
|
||||
|
||||
@*
|
||||
Another screen may offer other sizes, and a select that gets its new value before the item of
|
||||
that value exists keeps showing the old one. The key builds the select anew for every screen:
|
||||
*@
|
||||
<ConfigurationSelect @key="@this.ScreenKey" OptionDescription="@T("Interface size on this screen")" SelectedValue="@(() => this.SizeOnScreen)" Data="@ConfigurationSelectDataFactory.GetInterfaceSizeData(this.OffersAppDefault)" SelectionUpdateAsync="@this.UpdateSize" Disabled="@(() => this.MeasuredScreen is null)" OptionHelp="@this.SizeHelp"/>
|
||||
@if (this.MeasuredScreen is { } screen && this.SizeOnScreen is InterfaceSize.CUSTOM)
|
||||
{
|
||||
<ConfigurationSlider T="int" OptionDescription="@T("Custom interface size on this screen")" Min="@screen.MinPercent" Max="@screen.MaxPercent" Step="5" Unit="%" Value="@(() => this.ScalePercent(screen))" ValueUpdate="@this.UpdateScalePercent" Immediate="@false" OptionHelp="@this.ScaleHelp(screen)"/>
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
/// <summary>
|
||||
/// The interface size of the screen AI Studio shows on: a selection of the size, and a slider for the
|
||||
/// scale of the custom size.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Always shows and changes the screen the window is on right now. The runtime reports that screen
|
||||
/// when the option opens, and again whenever the size on the screen changed, e.g., because the window
|
||||
/// moved to another screen. The runtime applies the sizes itself; InterfaceSizeService hands them over
|
||||
/// whenever the settings change.
|
||||
/// </remarks>
|
||||
public partial class ConfigurationInterfaceSize : MSGComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The screen AI Studio shows on, as the runtime reported it last: when the option opened, and
|
||||
/// again whenever the size on the screen changed, e.g., because the window moved to another
|
||||
/// screen. Null when the runtime could not measure the screen.
|
||||
/// </summary>
|
||||
private InterfaceSizeResponse? interfaceSizeOnScreen;
|
||||
|
||||
/// <summary>
|
||||
/// The screen whose size the option shows and changes, or null when it is unknown.
|
||||
/// </summary>
|
||||
private InterfaceSizeResponse? MeasuredScreen => this.interfaceSizeOnScreen is { ScreenKey.Length: > 0 } screen ? screen : null;
|
||||
|
||||
/// <summary>
|
||||
/// The key of the screen whose size the option shows, or an empty one when the screen is unknown.
|
||||
/// </summary>
|
||||
private string ScreenKey => this.MeasuredScreen?.ScreenKey ?? string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this screen offers the app default. It only differs from the system scaling on the
|
||||
/// screen of a laptop, which is also the only screen it is the default of.
|
||||
/// </summary>
|
||||
private bool OffersAppDefault => this.MeasuredScreen?.DefaultSize is InterfaceSize.APP_DEFAULT;
|
||||
|
||||
/// <summary>
|
||||
/// The size on this screen, as the selection shows it: the one the user chose, or else the
|
||||
/// default size of the screen.
|
||||
/// </summary>
|
||||
private InterfaceSize SizeOnScreen
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.MeasuredScreen is not { } screen)
|
||||
return InterfaceSize.SYSTEM_SCALING;
|
||||
|
||||
var size = this.SettingsManager.ConfigurationData.App.GetInterfaceScreenSize(screen.ScreenKey)?.Size ?? screen.DefaultSize;
|
||||
|
||||
// Where the selection does not offer the app default, the app default shows the system scaling:
|
||||
return size is InterfaceSize.APP_DEFAULT && !this.OffersAppDefault ? InterfaceSize.SYSTEM_SCALING : size;
|
||||
}
|
||||
}
|
||||
|
||||
private string SizeHelp => this.MeasuredScreen switch
|
||||
{
|
||||
null => T("AI Studio cannot measure this screen right now, so you cannot set the interface size here."),
|
||||
{ } screen when this.OffersAppDefault => string.Format(I18N.I.Culture, T("Applies to this screen only ({0}). The app default shows AI Studio in the same size on every laptop. System scaling follows your operating system."), this.DescribeScreen(screen)),
|
||||
{ } screen => string.Format(I18N.I.Culture, T("Applies to this screen only ({0}). System scaling follows your operating system."), this.DescribeScreen(screen)),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The custom scale of the given screen, as the slider shows it: the one the user set, or else the
|
||||
/// one the screen shows with. Within the range of the screen, as the runtime applies it.
|
||||
/// </summary>
|
||||
private int ScalePercent(InterfaceSizeResponse screen)
|
||||
{
|
||||
var percent = this.SettingsManager.ConfigurationData.App.GetInterfaceScreenSize(screen.ScreenKey)?.CustomPercent ?? screen.CurrentPercent;
|
||||
return Math.Clamp(percent, screen.MinPercent, screen.MaxPercent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The help of the slider: the range of the given screen, and what its default size comes to for
|
||||
/// a comparison, since the slider starts there when the user comes from the default.
|
||||
/// </summary>
|
||||
private string ScaleHelp(InterfaceSizeResponse screen) => this.OffersAppDefault
|
||||
? string.Format(I18N.I.Culture, T("You can choose from {0} % to {1} %. The app default on this screen is {2} %."), screen.MinPercent, screen.MaxPercent, screen.AppDefaultPercent)
|
||||
: string.Format(I18N.I.Culture, T("You can choose from {0} % to {1} %. The system scaling on this screen is {2} %."), screen.MinPercent, screen.MaxPercent, screen.SystemPercent);
|
||||
|
||||
private string DescribeScreen(InterfaceSizeResponse screen) => screen.ScreenDiagonalInches is { } diagonal
|
||||
? string.Format(I18N.I.Culture, T("{0} × {1} pixels, {2:0.#} inches"), screen.ScreenWidth, screen.ScreenHeight, diagonal)
|
||||
: string.Format(I18N.I.Culture, T("{0} × {1} pixels"), screen.ScreenWidth, screen.ScreenHeight);
|
||||
|
||||
private void UpdateScalePercent(int percent)
|
||||
{
|
||||
if (this.MeasuredScreen is { } screen)
|
||||
this.SettingsManager.ConfigurationData.App.RememberInterfaceScreenSize(new(screen.ScreenKey, InterfaceSize.CUSTOM, percent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the size on the screen the window shows on.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The screen is asked from the runtime right now rather than taken from its last report, in
|
||||
/// case the window has only just moved. A screen whose custom scale the user set before goes
|
||||
/// back to it with the custom size. Any other starts from the scale AI Studio shows with right
|
||||
/// now, so that nothing changes on the screen until the user moves the slider; only a scale
|
||||
/// beyond the range of the screen moves to its nearest end.
|
||||
/// </remarks>
|
||||
private async Task UpdateSize(InterfaceSize size)
|
||||
{
|
||||
this.interfaceSizeOnScreen = await this.RustService.GetInterfaceSize() ?? this.interfaceSizeOnScreen;
|
||||
if (this.MeasuredScreen is not { } screen)
|
||||
return;
|
||||
|
||||
var app = this.SettingsManager.ConfigurationData.App;
|
||||
var customPercent = app.GetInterfaceScreenSize(screen.ScreenKey)?.CustomPercent;
|
||||
if (size is InterfaceSize.CUSTOM)
|
||||
customPercent ??= Math.Clamp(screen.CurrentPercent, screen.MinPercent, screen.MaxPercent);
|
||||
|
||||
app.RememberInterfaceScreenSize(new(screen.ScreenKey, size, customPercent));
|
||||
}
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED, Event.TAURI_EVENT_RECEIVED ]);
|
||||
await base.OnInitializedAsync();
|
||||
this.interfaceSizeOnScreen = await this.RustService.GetInterfaceSize();
|
||||
}
|
||||
|
||||
protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
// Another size may call for the slider, or no longer:
|
||||
case Event.CONFIGURATION_CHANGED:
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
|
||||
//
|
||||
// The window moved to another screen, or the size on the screen changed otherwise. The
|
||||
// option has to show and change the screen the window is on now:
|
||||
//
|
||||
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.INTERFACE_SIZE_CHANGED }:
|
||||
this.interfaceSizeOnScreen = await this.RustService.GetInterfaceSize() ?? this.interfaceSizeOnScreen;
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
await base.ProcessIncomingMessage(sendingComponent, triggeredEvent, data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@typeparam T
|
||||
@inherits ConfigurationBaseCore
|
||||
|
||||
<MudSlider T="@T" Size="Size.Medium" Value="@this.Value()" ValueChanged="@this.OptionChanged" Min="@this.Min" Max="@this.Max" Step="@this.Step" Immediate="@true" Disabled="@this.IsDisabled" Class="mb-1">
|
||||
@this.Value() @this.Unit
|
||||
@* The input event bubbles up from the range input inside, also while the value waits for the release: *@
|
||||
<MudSlider T="@T" Size="Size.Medium" Value="@this.Value()" ValueChanged="@this.OptionChanged" Min="@this.Min" Max="@this.Max" Step="@this.Step" Immediate="@this.Immediate" Disabled="@this.IsDisabled" Class="mb-1" @oninput="@this.ShowDraggedValue">
|
||||
@(this.draggedValue ?? this.Value()) @this.Unit
|
||||
</MudSlider>
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -42,6 +43,22 @@ public partial class ConfigurationSlider<T> : ConfigurationBaseCore where T : st
|
||||
[Parameter]
|
||||
public Action<T> ValueUpdate { get; set; } = _ => { };
|
||||
|
||||
/// <summary>
|
||||
/// Whether the option changes while the user drags the slider, or only once they release it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults to true. An option which changes the layout of the page, such as the interface size,
|
||||
/// should wait for the release: a page which changes under the pointer moves the slider away from
|
||||
/// it. The slider shows the value it is dragged to either way.
|
||||
/// </remarks>
|
||||
[Parameter]
|
||||
public bool Immediate { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The value the user drags the slider to, while the option waits for the release, see Immediate.
|
||||
/// </summary>
|
||||
private T? draggedValue;
|
||||
|
||||
#region Overrides of ConfigurationBase
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -72,10 +89,24 @@ public partial class ConfigurationSlider<T> : ConfigurationBaseCore where T : st
|
||||
|
||||
private async Task OptionChanged(T updatedValue)
|
||||
{
|
||||
this.draggedValue = null;
|
||||
this.ValueUpdate(updatedValue);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.InformAboutChange();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the value the user drags the slider to, while the option waits for the release.
|
||||
/// </summary>
|
||||
private void ShowDraggedValue(ChangeEventArgs args)
|
||||
{
|
||||
if (this.Immediate)
|
||||
return;
|
||||
|
||||
// The range input reports its value with a dot as decimal separator, whatever the language:
|
||||
if (T.TryParse(args.Value?.ToString(), CultureInfo.InvariantCulture, out var value))
|
||||
this.draggedValue = value;
|
||||
}
|
||||
|
||||
private async Task EnsureMinMax()
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
}
|
||||
|
||||
<ConfigurationSelect OptionDescription="@T("Color theme")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreferredTheme)" Data="@ConfigurationSelectDataFactory.GetThemesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreferredTheme = selectedValue)" OptionHelp="@T("Choose the color theme that best suits for you.")"/>
|
||||
<ConfigurationInterfaceSize/>
|
||||
<ConfigurationOption OptionDescription="@T("Save energy?")" LabelOn="@T("Energy saving is enabled")" LabelOff="@T("Energy saving is disabled")" State="@(() => this.SettingsManager.ConfigurationData.App.IsSavingEnergy)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.IsSavingEnergy = updatedState)" OptionHelp="@T("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.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.IsSavingEnergy, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationOption OptionDescription="@T("Enable spellchecking?")" LabelOn="@T("Spellchecking is enabled")" LabelOff="@T("Spellchecking is disabled")" State="@(() => this.SettingsManager.ConfigurationData.App.EnableSpellchecking)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.EnableSpellchecking = updatedState)" OptionHelp="@T("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.")"/>
|
||||
<ConfigurationOption OptionDescription="@T("Close messages about exported files automatically?")" LabelOn="@T("The message closes after a few seconds")" LabelOff="@T("The message stays until you close it")" State="@(() => this.SettingsManager.ConfigurationData.App.CloseExportMessagesAutomatically)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.CloseExportMessagesAutomatically = updatedState)" OptionHelp="@T("After you export a file, for example, an answer or a presentation, a message offers to show the file in your file manager or to open it. When enabled, this message closes after a few seconds, like other messages. When disabled, it stays until you close it.")"/>
|
||||
|
||||
+36
@@ -4170,6 +4170,33 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONFILE::T1984243691"] = "Sie k
|
||||
-- Choose File
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONFILE::T4285779702"] = "Datei auswählen"
|
||||
|
||||
-- Applies to this screen only ({0}). The app default shows AI Studio in the same size on every laptop. System scaling follows your operating system.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T1103704506"] = "Gilt nur für diesen Bildschirm ({0}). Der App-Standard zeigt AI Studio auf jedem Laptop in derselben Größe an. Die Systemskalierung richtet sich nach Ihrem Betriebssystem."
|
||||
|
||||
-- You can choose from {0} % to {1} %. The app default on this screen is {2} %.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T1110635214"] = "Sie können zwischen {0} % und {1} % wählen. Der App-Standard beträgt auf diesem Bildschirm {2} %."
|
||||
|
||||
-- {0} × {1} pixels, {2:0.#} inches
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T2376030041"] = "{0} × {1} Pixel, {2:0.#} Zoll"
|
||||
|
||||
-- You can choose from {0} % to {1} %. The system scaling on this screen is {2} %.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3009448744"] = "Sie können zwischen {0} % und {1} % wählen. Die Systemskalierung beträgt auf diesem Bildschirm {2} %."
|
||||
|
||||
-- Custom interface size on this screen
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3268281815"] = "Benutzerdefinierte Größe der Benutzeroberfläche auf diesem Bildschirm"
|
||||
|
||||
-- AI Studio cannot measure this screen right now, so you cannot set the interface size here.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3560281885"] = "AI Studio kann diesen Bildschirm derzeit nicht vermessen, daher können Sie die Größe der Benutzeroberfläche hier nicht festlegen."
|
||||
|
||||
-- Interface size on this screen
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3568633122"] = "Größe der Benutzeroberfläche auf diesem Bildschirm"
|
||||
|
||||
-- Applies to this screen only ({0}). System scaling follows your operating system.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3653703912"] = "Gilt nur für diesen Bildschirm ({0}). Die Systemskalierung richtet sich nach Ihrem Betriebssystem."
|
||||
|
||||
-- {0} × {1} pixels
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T990356864"] = "{0} × {1} Pixel"
|
||||
|
||||
-- Choose the minimum confidence level that all LLM providers must meet. This way, you can ensure that only trustworthy providers are used. You cannot use any provider that falls below this level.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T2526727283"] = "Wählen Sie das minimale Vertrauensniveau, das alle LLM-Anbieter erfüllen müssen. So stellen Sie sicher, dass nur vertrauenswürdige Anbieter verwendet werden. Anbieter, die dieses Niveau unterschreiten, können nicht verwendet werden."
|
||||
|
||||
@@ -12519,6 +12546,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1258653480
|
||||
-- No key is sending the input
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1311973034"] = "Keine Taste sendet die Eingabe; Sie müssen auf die Schaltfläche klicken, um zu senden"
|
||||
|
||||
-- App default
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1382522201"] = "App-Standard"
|
||||
|
||||
-- Navigation never expands, no tooltips
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1402851833"] = "Navigationsleiste wird nie erweitert, keine Tooltips; es werden nur Icons angezeigt"
|
||||
|
||||
@@ -12564,6 +12594,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T238099712"
|
||||
-- Delete disappearing chats older than 1 year
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2405705561"] = "Selbstlöschende Chats: lösche Chats die älter als 1 Jahr sind"
|
||||
|
||||
-- Custom
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2502277006"] = "Benutzerdefiniert"
|
||||
|
||||
-- Store chats manually
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2520325466"] = "Chats manuell speichern"
|
||||
|
||||
@@ -12627,6 +12660,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T4256323669
|
||||
-- All preview features are hidden
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T4289410063"] = "Alle Vorschaufunktionen sind ausgeblendet"
|
||||
|
||||
-- System scaling
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T453866521"] = "Systemskalierung"
|
||||
|
||||
-- Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T578410699"] = "Chat"
|
||||
|
||||
|
||||
+36
@@ -4170,6 +4170,33 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONFILE::T1984243691"] = "You c
|
||||
-- Choose File
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONFILE::T4285779702"] = "Choose File"
|
||||
|
||||
-- Applies to this screen only ({0}). The app default shows AI Studio in the same size on every laptop. System scaling follows your operating system.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T1103704506"] = "Applies to this screen only ({0}). The app default shows AI Studio in the same size on every laptop. System scaling follows your operating system."
|
||||
|
||||
-- You can choose from {0} % to {1} %. The app default on this screen is {2} %.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T1110635214"] = "You can choose from {0} % to {1} %. The app default on this screen is {2} %."
|
||||
|
||||
-- {0} × {1} pixels, {2:0.#} inches
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T2376030041"] = "{0} × {1} pixels, {2:0.#} inches"
|
||||
|
||||
-- You can choose from {0} % to {1} %. The system scaling on this screen is {2} %.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3009448744"] = "You can choose from {0} % to {1} %. The system scaling on this screen is {2} %."
|
||||
|
||||
-- Custom interface size on this screen
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3268281815"] = "Custom interface size on this screen"
|
||||
|
||||
-- AI Studio cannot measure this screen right now, so you cannot set the interface size here.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3560281885"] = "AI Studio cannot measure this screen right now, so you cannot set the interface size here."
|
||||
|
||||
-- Interface size on this screen
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3568633122"] = "Interface size on this screen"
|
||||
|
||||
-- Applies to this screen only ({0}). System scaling follows your operating system.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T3653703912"] = "Applies to this screen only ({0}). System scaling follows your operating system."
|
||||
|
||||
-- {0} × {1} pixels
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONINTERFACESIZE::T990356864"] = "{0} × {1} pixels"
|
||||
|
||||
-- Choose the minimum confidence level that all LLM providers must meet. This way, you can ensure that only trustworthy providers are used. You cannot use any provider that falls below this level.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T2526727283"] = "Choose the minimum confidence level that all LLM providers must meet. This way, you can ensure that only trustworthy providers are used. You cannot use any provider that falls below this level."
|
||||
|
||||
@@ -12519,6 +12546,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1258653480
|
||||
-- No key is sending the input
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1311973034"] = "No key is sending the input; you have to click the send button"
|
||||
|
||||
-- App default
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1382522201"] = "App default"
|
||||
|
||||
-- Navigation never expands, no tooltips
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1402851833"] = "Navigation never expands, no tooltips; there are only icons"
|
||||
|
||||
@@ -12564,6 +12594,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T238099712"
|
||||
-- Delete disappearing chats older than 1 year
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2405705561"] = "Disappearing chats: delete chats older than 1 year"
|
||||
|
||||
-- Custom
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2502277006"] = "Custom"
|
||||
|
||||
-- Store chats manually
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2520325466"] = "Store chats manually"
|
||||
|
||||
@@ -12627,6 +12660,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T4256323669
|
||||
-- All preview features are hidden
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T4289410063"] = "All preview features are hidden"
|
||||
|
||||
-- System scaling
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T453866521"] = "System scaling"
|
||||
|
||||
-- Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T578410699"] = "Chat"
|
||||
|
||||
|
||||
@@ -242,6 +242,7 @@ internal sealed class Program
|
||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<DataSourceEmbeddingService>());
|
||||
builder.Services.AddSingleton<DatabaseClientProvider>();
|
||||
builder.Services.AddHostedService<GlobalShortcutService>(serviceProvider => serviceProvider.GetRequiredService<GlobalShortcutService>());
|
||||
builder.Services.AddHostedService<InterfaceSizeService>();
|
||||
builder.Services.AddHostedService<RustAvailabilityMonitorService>();
|
||||
builder.Services.AddSingleton<NativeShareService>();
|
||||
builder.Services.AddScoped<PluginShareService>();
|
||||
|
||||
@@ -146,6 +146,19 @@ public static class ConfigurationSelectDataFactory
|
||||
yield return new(TB("Settings"), StartPage.SETTINGS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The interface sizes a screen offers.
|
||||
/// </summary>
|
||||
/// <param name="offerAppDefault">Whether to offer the app default, which only differs from the system scaling on the screen of a laptop.</param>
|
||||
public static IEnumerable<ConfigurationSelectData<InterfaceSize>> GetInterfaceSizeData(bool offerAppDefault)
|
||||
{
|
||||
yield return new(TB("System scaling"), InterfaceSize.SYSTEM_SCALING);
|
||||
if (offerAppDefault)
|
||||
yield return new(TB("App default"), InterfaceSize.APP_DEFAULT);
|
||||
|
||||
yield return new(TB("Custom"), InterfaceSize.CUSTOM);
|
||||
}
|
||||
|
||||
public static IEnumerable<ConfigurationSelectData<IconSources>> GetIconSourcesData()
|
||||
{
|
||||
foreach (var source in Enum.GetValues<IconSources>())
|
||||
|
||||
@@ -66,6 +66,49 @@ public sealed class DataApp(Expression<Func<Data, DataApp>>? configSelection = n
|
||||
/// </summary>
|
||||
public StartPage StartPage { get; set; } = ManagedConfiguration.Register(configSelection, n => n.StartPage, StartPage.HOME);
|
||||
|
||||
/// <summary>
|
||||
/// How large AI Studio shows its interface on the screens the user chose a size for, the most
|
||||
/// recently set first.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Kept per screen, as the operating systems keep their scaling per monitor: a laptop calls for
|
||||
/// another size than an external monitor, and the same scale looks different on screens of
|
||||
/// different densities. A screen missing here gets InterfaceSize.APP_DEFAULT when it is the
|
||||
/// screen of a laptop, and InterfaceSize.SYSTEM_SCALING otherwise. The
|
||||
/// user's own, like the scaling of the operating system: no configuration plugin manages it.
|
||||
/// </remarks>
|
||||
public List<InterfaceScreenSize> InterfaceScreenSizes { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// How many screens keep their interface size. Setting the size of one more forgets the screen
|
||||
/// whose size was set the longest time ago, which then gets its default size again.
|
||||
/// </summary>
|
||||
public const int MAX_REMEMBERED_INTERFACE_SCREENS = 20;
|
||||
|
||||
/// <summary>
|
||||
/// What the user chose for the given screen, or null when they chose nothing for it.
|
||||
/// </summary>
|
||||
/// <param name="screenKey">The screen, as the runtime names it.</param>
|
||||
public InterfaceScreenSize? GetInterfaceScreenSize(string screenKey) => this.InterfaceScreenSizes.FirstOrDefault(screen => screen.ScreenKey == screenKey);
|
||||
|
||||
/// <summary>
|
||||
/// Remembers what the user chose for a screen, as the most recently set one.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Keeps MAX_REMEMBERED_INTERFACE_SCREENS screens at most and forgets the ones beyond, those whose
|
||||
/// size was set the longest time ago. Replaces the list instead of changing it, since the
|
||||
/// InterfaceSizeService may read it at the same time.
|
||||
/// </remarks>
|
||||
/// <param name="screenSize">What the user chose for the screen.</param>
|
||||
public void RememberInterfaceScreenSize(InterfaceScreenSize screenSize)
|
||||
{
|
||||
this.InterfaceScreenSizes = this.InterfaceScreenSizes
|
||||
.Where(screen => screen.ScreenKey != screenSize.ScreenKey)
|
||||
.Prepend(screenSize)
|
||||
.Take(MAX_REMEMBERED_INTERFACE_SCREENS)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether an alert dialog should be shown when prompt-injection content is blocked.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
/// <summary>
|
||||
/// The interface size the user chose for one screen.
|
||||
/// </summary>
|
||||
/// <param name="ScreenKey">The screen, as the runtime names it: its resolution and its physical size, such as "1920x1200@287x179mm".</param>
|
||||
/// <param name="Size">The size on this screen.</param>
|
||||
/// <param name="CustomPercent">The scale of the custom size in percent, counted like the scaling of the operating system, or null when the user never set one for this screen. Kept while another size applies, so that it returns with the custom size.</param>
|
||||
public sealed record InterfaceScreenSize(string ScreenKey, InterfaceSize Size, int? CustomPercent);
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
/// <summary>
|
||||
/// How large AI Studio shows its interface on a screen.
|
||||
/// </summary>
|
||||
public enum InterfaceSize
|
||||
{
|
||||
/// <summary>
|
||||
/// The same size on every laptop, whatever the scaling of its system. The default on the screen
|
||||
/// of a laptop. Larger screens, such as external monitors, and screens which do not report their
|
||||
/// size keep the scaling of the system with it.
|
||||
/// </summary>
|
||||
APP_DEFAULT,
|
||||
|
||||
/// <summary>
|
||||
/// The scaling of the system, unchanged. The default on any screen but the one of a laptop.
|
||||
/// </summary>
|
||||
SYSTEM_SCALING,
|
||||
|
||||
/// <summary>
|
||||
/// A scale of the user's own, see InterfaceScreenSize.CustomPercent.
|
||||
/// </summary>
|
||||
CUSTOM,
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>
|
||||
/// What the runtime reports about the interface size on the screen AI Studio shows on.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All percentages count like the scaling of the operating system: device pixels per pixel of the app.
|
||||
/// </remarks>
|
||||
/// <param name="Success">Whether the runtime could measure the screen.</param>
|
||||
/// <param name="ErrorMessage">What went wrong, when it could not.</param>
|
||||
/// <param name="Zoom">The zoom of the webview: 1.0 is the scaling of the system.</param>
|
||||
/// <param name="Size">The size on this screen: the one the user chose for it, or else its default size.</param>
|
||||
/// <param name="DefaultSize">The size of this screen while the user chose none for it: the app default on the screen of a laptop, the scaling of the system on any other.</param>
|
||||
/// <param name="ScreenKey">What the size of this screen is stored by: its resolution and its physical size.</param>
|
||||
/// <param name="ScreenWidth">The width of the screen in device pixels.</param>
|
||||
/// <param name="ScreenHeight">The height of the screen in device pixels.</param>
|
||||
/// <param name="ScreenDiagonalInches">The diagonal of the screen in inches, or null when the screen does not report its size.</param>
|
||||
/// <param name="SystemPercent">The scaling of the system on this screen.</param>
|
||||
/// <param name="CurrentPercent">The scale AI Studio shows with.</param>
|
||||
/// <param name="AppDefaultPercent">The scale the app default gives this screen, which is also where its custom scale starts.</param>
|
||||
/// <param name="MinPercent">The smallest custom scale this screen allows.</param>
|
||||
/// <param name="MaxPercent">The largest custom scale this screen allows.</param>
|
||||
public sealed record InterfaceSizeResponse(
|
||||
bool Success,
|
||||
string ErrorMessage,
|
||||
double Zoom,
|
||||
InterfaceSize Size,
|
||||
InterfaceSize DefaultSize,
|
||||
string ScreenKey,
|
||||
int ScreenWidth,
|
||||
int ScreenHeight,
|
||||
double? ScreenDiagonalInches,
|
||||
int SystemPercent,
|
||||
int CurrentPercent,
|
||||
int AppDefaultPercent,
|
||||
int MinPercent,
|
||||
int MaxPercent);
|
||||
@@ -0,0 +1,9 @@
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>
|
||||
/// Asks the runtime to show AI Studio in the sizes the user chose.
|
||||
/// </summary>
|
||||
/// <param name="Screens">What the user chose for the screens they set a size for, each screen once. Any other screen gets its default size.</param>
|
||||
public sealed record SetInterfaceSizeRequest(List<InterfaceScreenSize> Screens);
|
||||
@@ -19,4 +19,6 @@ public enum TauriEventType
|
||||
|
||||
GLOBAL_SHORTCUT_PRESSED,
|
||||
GLOBAL_SHORTCUT_CHANGED,
|
||||
|
||||
INTERFACE_SIZE_CHANGED,
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Hands the interface sizes the user chose to the runtime, which zooms the webview accordingly.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The runtime shows every screen in its default size from the moment the window opens, keeps the
|
||||
/// sizes, measures the screen again whenever the window moves to another one, and applies the size of
|
||||
/// that screen. This service only tells it what the user chose: as soon as the settings have loaded,
|
||||
/// and again whenever they change.
|
||||
/// </remarks>
|
||||
public sealed class InterfaceSizeService : BackgroundService, IMessageBusReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// The smallest custom scale on any screen, in percent. A screen may allow less of the range,
|
||||
/// which the runtime reports. Keep in sync with MIN_SCALE in runtime/src/interface_zoom.rs.
|
||||
/// </summary>
|
||||
private const int MIN_CUSTOM_PERCENT = 60;
|
||||
|
||||
/// <summary>
|
||||
/// The largest custom scale on any screen, in percent. A screen may allow less of the range,
|
||||
/// which the runtime reports. Keep in sync with MAX_SCALE in runtime/src/interface_zoom.rs.
|
||||
/// </summary>
|
||||
private const int MAX_CUSTOM_PERCENT = 300;
|
||||
|
||||
/// <summary>
|
||||
/// How often the service looks whether the settings have loaded. Short, because a screen with a
|
||||
/// size of its own shows in its default size until then.
|
||||
/// </summary>
|
||||
private static readonly TimeSpan SETTINGS_LOAD_CHECK_INTERVAL = TimeSpan.FromMilliseconds(100);
|
||||
|
||||
private readonly SemaphoreSlim sendingSemaphore = new(1, 1);
|
||||
private readonly ILogger<InterfaceSizeService> logger;
|
||||
private readonly SettingsManager settingsManager;
|
||||
private readonly MessageBus messageBus;
|
||||
private readonly RustService rustService;
|
||||
private string? lastSentSignature;
|
||||
private volatile bool areSettingsLoaded;
|
||||
|
||||
public InterfaceSizeService(ILogger<InterfaceSizeService> logger, SettingsManager settingsManager, MessageBus messageBus, RustService rustService)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.settingsManager = settingsManager;
|
||||
this.messageBus = messageBus;
|
||||
this.rustService = rustService;
|
||||
|
||||
this.messageBus.RegisterComponent(this);
|
||||
this.ApplyFilters([], [Event.CONFIGURATION_CHANGED]);
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
this.logger.LogInformation("The interface size service was initialized.");
|
||||
|
||||
//
|
||||
// The app loads the settings early, long before its startup completed with the plugins. The
|
||||
// sizes go to the runtime right then, so that a screen with a size of its own changes from
|
||||
// its default size as soon as possible:
|
||||
//
|
||||
while (!this.settingsManager.HasCompletedInitialSettingsLoad && !stoppingToken.IsCancellationRequested)
|
||||
await Task.Delay(SETTINGS_LOAD_CHECK_INTERVAL, stoppingToken);
|
||||
|
||||
this.areSettingsLoaded = true;
|
||||
await this.SendInterfaceSize();
|
||||
await Task.Delay(Timeout.InfiniteTimeSpan, stoppingToken);
|
||||
}
|
||||
|
||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
this.messageBus.Unregister(this);
|
||||
await base.StopAsync(cancellationToken);
|
||||
}
|
||||
|
||||
#region IMessageBusReceiver
|
||||
|
||||
public async Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
|
||||
{
|
||||
// Until the settings have loaded, ExecuteAsync waits to send them:
|
||||
if (triggeredEvent is Event.CONFIGURATION_CHANGED && this.areSettingsLoaded)
|
||||
await this.SendInterfaceSize();
|
||||
}
|
||||
|
||||
public Task<MessageBusAnswer<TResult>> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) => Task.FromResult(MessageBusAnswer<TResult>.NONE);
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Sends the chosen sizes to the runtime, unless the runtime has them already.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Every change of any setting announces itself the same way, so most of them leave the sizes
|
||||
/// as they were. A scale beyond the range of any screen, e.g., in settings edited by hand, is
|
||||
/// brought within that range here, and the runtime narrows it to the screen.
|
||||
/// </remarks>
|
||||
private async Task SendInterfaceSize()
|
||||
{
|
||||
var screens = this.settingsManager.ConfigurationData.App.InterfaceScreenSizes
|
||||
.DistinctBy(screen => screen.ScreenKey)
|
||||
.Select(screen => screen with { CustomPercent = screen.CustomPercent is { } percent ? Math.Clamp(percent, MIN_CUSTOM_PERCENT, MAX_CUSTOM_PERCENT) : null })
|
||||
.ToList();
|
||||
|
||||
// The order of the screens tells when they were set, which the runtime does not need to know:
|
||||
var signature = string.Join(";", screens.OrderBy(screen => screen.ScreenKey, StringComparer.Ordinal).Select(screen => $"{screen.ScreenKey}={screen.Size}:{screen.CustomPercent}"));
|
||||
|
||||
await this.sendingSemaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (signature == this.lastSentSignature)
|
||||
return;
|
||||
|
||||
if (await this.rustService.SetInterfaceSize(new SetInterfaceSizeRequest(screens)) is not null)
|
||||
this.lastSentSignature = signature;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.sendingSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,4 +193,63 @@ public sealed partial class RustService
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asks the runtime to show AI Studio in the given size, which it reaches by zooming the webview.
|
||||
/// </summary>
|
||||
/// <param name="request">The size, and the scale of the custom size.</param>
|
||||
/// <returns>What the size comes to on the screen AI Studio shows on, or null when the runtime could not apply it.</returns>
|
||||
public async Task<InterfaceSizeResponse?> SetInterfaceSize(SetInterfaceSizeRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await this.http.PostAsJsonAsync("/app/interface-size", request, this.jsonRustSerializerOptions);
|
||||
var result = await this.ReadInterfaceSizeResponse(response, "set the interface size");
|
||||
if (result is not null)
|
||||
this.logger?.LogInformation("Set the interface sizes of {ScreenCount} screens. AI Studio shows with the size '{Size}' at {CurrentPercent} % and a zoom of {Zoom:0.###} on the screen '{ScreenKey}'.", request.Screens.Count, result.Size, result.CurrentPercent, result.Zoom, result.ScreenKey);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.logger?.LogError(ex, "Exception while setting the interface size.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asks the runtime what the interface size comes to on the screen AI Studio shows on, e.g., which custom scales it allows.
|
||||
/// </summary>
|
||||
/// <returns>The report of the runtime, or null when it could not measure the screen.</returns>
|
||||
public async Task<InterfaceSizeResponse?> GetInterfaceSize()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await this.http.GetAsync("/app/interface-size");
|
||||
return await this.ReadInterfaceSizeResponse(response, "read the interface size");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.logger?.LogError(ex, "Exception while reading the interface size.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<InterfaceSizeResponse?> ReadInterfaceSizeResponse(HttpResponseMessage response, string action)
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger?.LogError("Failed to {Action} due to network error: {StatusCode}.", action, response.StatusCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<InterfaceSizeResponse>(this.jsonRustSerializerOptions);
|
||||
if (result is null || !result.Success)
|
||||
{
|
||||
this.logger?.LogError("Failed to {Action}: {Error}", action, result?.ErrorMessage ?? "Unknown error");
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,46 @@
|
||||
color: var(--custom-icon-color) !important;
|
||||
}
|
||||
|
||||
/*
|
||||
WebKitGTK, the webview on Linux, leaves the zoom of the page out of the font size of an SVG
|
||||
element. MudBlazor sizes its icons with 1em of that font size, so they would keep their size while
|
||||
everything around them grows or shrinks with the interface size. The runtime therefore sets
|
||||
--svg-icon-zoom to its zoom on Linux, see interface_zoom.rs. The webviews of Windows and macOS zoom
|
||||
SVG elements like everything else, and the variable stays unset there. The :where keeps this as
|
||||
weak as MudBlazor's own rule, so that every more specific size still wins.
|
||||
*/
|
||||
.mud-icon-root:where(svg) {
|
||||
width: calc(1em * var(--svg-icon-zoom, 1));
|
||||
height: calc(1em * var(--svg-icon-zoom, 1));
|
||||
}
|
||||
|
||||
/*
|
||||
MudBlazor draws the thumb of a slider as a dot of 4px, scaled up five times. Zoomed to an interface
|
||||
size, the dot covers a fraction of a pixel, which the webview rounds to whole pixels before the
|
||||
scaling magnifies the error: the thumb turns into an uneven shape. These rules draw the thumb in its
|
||||
final size instead, with the shadows MudBlazor's scaling gave it. All sliders of the app have the
|
||||
medium size and the primary color.
|
||||
*/
|
||||
.mud-slider.mud-slider-medium .mud-slider-input::-webkit-slider-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: -8px;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.mud-slider.mud-slider-medium.mud-slider-primary .mud-slider-input::-webkit-slider-thumb {
|
||||
box-shadow: 0 5px 10px -5px rgba(var(--mud-palette-primary-rgb), 0.3);
|
||||
}
|
||||
|
||||
.mud-slider.mud-slider-medium.mud-slider-primary .mud-slider-input::-webkit-slider-thumb:hover {
|
||||
box-shadow: 0 5px 10px -5px rgba(var(--mud-palette-primary-rgb), 0.3), 0 0 0 5px rgba(var(--mud-palette-primary-rgb), 0.24);
|
||||
}
|
||||
|
||||
.mud-slider.mud-slider-medium.mud-slider-primary .mud-slider-input:focus-visible::-webkit-slider-thumb,
|
||||
.mud-slider.mud-slider-medium.mud-slider-primary .mud-slider-input:active::-webkit-slider-thumb {
|
||||
box-shadow: 0 5px 10px -5px rgba(var(--mud-palette-primary-rgb), 0.3), 0 0 0 10px rgba(var(--mud-palette-primary-rgb), 0.24);
|
||||
}
|
||||
|
||||
.improvedDiff .wikEdDiffFragment {
|
||||
background-color: initial !important;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# v26.11.1, build 260 (2026-11-xx xx:xx UTC)
|
||||
- Added a setting for the size of the interface, which you find in the app settings. You can choose a size for each of your screens, and AI Studio switches to it when you move its window to another screen. Thanks, Paul Koudelka (`PaulKoudelka`), for this contribution.
|
||||
- Changed the size of AI Studio on laptop screens: it now appears in the same size on every laptop, whatever the scaling of your operating system. If you prefer the previous size, choose "System scaling" for the interface size in the app settings. Contributed by Paul Koudelka.
|
||||
- Fixed AI Studio becoming unusable when an update failed after its download. The app now restarts with the version you have installed, so you can simply try the update again.
|
||||
- Upgraded several libraries to improve security.
|
||||
- Upgraded Tauri to v2.12.5.
|
||||
Reference in new issue
Block a user