Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 74 additions & 70 deletions src/OpenClaw.SetupEngine.UI/Pages/CapabilitiesPage.xaml

Large diffs are not rendered by default.

42 changes: 17 additions & 25 deletions src/OpenClaw.SetupEngine.UI/Pages/CapabilitiesPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
Expand Down Expand Up @@ -342,6 +343,7 @@ _localAiSelectedGpuCapacityBytes is { } capacityBytes &&
LocalAiInstallReviewCard.Visibility = Visibility.Visible;
LocalAiUnavailablePanel.Visibility = Visibility.Collapsed;
LocalAiToggle.Visibility = Visibility.Visible;
SetLocalAiOptionAvailability(isAvailable: true);
_localAiSelectionEligible = eligibility.Status == LocalInferenceEligibilityStatus.Eligible;
_config!.LocalAi.SelectedModelId ??= eligibility.Plan!.Model.Id;
PopulateLocalAiModels();
Expand Down Expand Up @@ -377,41 +379,31 @@ private void ShowLocalAiUnavailable(string reason)
_suppressLocalAiToggle = true;
LocalAiToggle.IsOn = false;
_suppressLocalAiToggle = false;
LocalAiToggle.Visibility = Visibility.Collapsed;
LocalAiToggle.Visibility = Visibility.Visible;
LocalAiDetailsPanel.Visibility = Visibility.Collapsed;
_localAiUnavailableReason = reason;
LocalAiUnavailablePanel.Visibility = Visibility.Visible;
LocalAiInstallReviewCard.Visibility = Visibility.Visible;
SetLocalAiOptionAvailability(isAvailable: false);
_config!.LocalAi.Enabled = false;
_config.SkipWizard = _skipWizardWithoutLocalAi;
ApplySetupReviewSummary(_config);
}

private static string DescribeLocalAiUnavailable(LocalInferenceEligibilityResult eligibility) =>
eligibility.SelectionFailureCode switch
{
LocalInferenceSelectionFailureCode.RuntimeUnavailable =>
"This Local AI release does not include a native llama-server runtime for the detected Windows architecture.",
LocalInferenceSelectionFailureCode.NoNvidiaGpu =>
"No NVIDIA GPU was reported by the NVIDIA driver. Install or repair the NVIDIA driver, then try setup again.",
LocalInferenceSelectionFailureCode.UnknownModel =>
"The selected model is not available in this Local AI release.",
_ => eligibility.FailureCode switch
{
LocalInferenceEligibilityFailureCode.HardwareFactsIncomplete =>
"OpenClaw could not read a stable NVIDIA GPU identifier, memory, driver, or CUDA capability.",
LocalInferenceEligibilityFailureCode.InsufficientGpuMemory =>
$"{eligibility.Plan?.Model.DisplayName ?? "The selected model"} requires " +
$"{FormatSize(eligibility.RequiredTotalMemoryBytes)} of GPU memory for model weights, KV cache, and runtime workspace. " +
$"OpenClaw detected {FormatOptionalSize(eligibility.DetectedTotalMemoryBytes)}.",
LocalInferenceEligibilityFailureCode.DriverTooOld =>
$"NVIDIA driver {eligibility.SelectedGpu?.DriverVersion ?? "unknown"} was detected. " +
$"Local AI requires version {LocalInferenceEligibility.MinimumNvidiaDriverVersion} or newer.",
LocalInferenceEligibilityFailureCode.CudaCapabilityTooLow =>
"The NVIDIA driver does not provide CUDA 13 support. A separate CUDA Toolkit is not required.",
_ => "OpenClaw could not verify the Local AI requirements on this system.",
},
};
LocalInferenceEligibilityDiagnostics.DescribeUnavailable(eligibility);

private void SetLocalAiOptionAvailability(bool isAvailable)
{
LocalAiOptionContent.IsHitTestVisible = isAvailable;
LocalAiOptionContent.Opacity = isAvailable ? 1 : 0.55;
LocalAiToggle.IsEnabled = isAvailable;
LocalAiModelSelector.IsEnabled = isAvailable;
LocalAiNetworkingConsentCheckBox.IsEnabled = isAvailable;
AutomationProperties.SetHelpText(
LocalAiOptionContent,
isAvailable ? string.Empty : "Unavailable because this PC does not meet the Local AI requirements.");
}

private void LocalAiUnavailableDetails_Click(object sender, RoutedEventArgs e) =>
AsyncEventHandlerGuard.Run(
Expand Down
35 changes: 16 additions & 19 deletions src/OpenClaw.SetupEngine.UI/Pages/WelcomePage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,30 +65,27 @@
Style="{StaticResource CaptionTextBlockStyle}"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}" />
</Border>
<Border x:Name="LocalAiAvailabilityBadge"
AutomationProperties.AutomationId="WelcomeLocalAiAvailable"
AutomationProperties.AccessibilityView="Raw"
CornerRadius="4" Padding="7,1" VerticalAlignment="Center"
BorderBrush="{ThemeResource SystemFillColorSuccessBrush}"
BorderThickness="1" Visibility="Collapsed">
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon Glyph="&#xE950;" FontSize="11"
IsTextScaleFactorEnabled="False"
Foreground="{ThemeResource SystemFillColorSuccessBrush}"
AutomationProperties.AccessibilityView="Raw" />
<TextBlock Text="Local AI available"
Style="{StaticResource CaptionTextBlockStyle}"
Foreground="{ThemeResource SystemFillColorSuccessBrush}" />
</StackPanel>
</Border>
</StackPanel>
<TextBlock TextWrapping="Wrap"
Style="{StaticResource CaptionTextBlockStyle}"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="A private, self-hosted OpenClaw gateway in an isolated Ubuntu WSL instance. We'll show exactly what gets installed before anything runs." />
<StackPanel x:Name="LocalAiAvailabilityPanel"
AutomationProperties.AutomationId="WelcomeLocalAiAvailable"
AutomationProperties.LiveSetting="Polite"
Spacing="2" Margin="0,5,0,0" Visibility="Collapsed">
<StackPanel Orientation="Horizontal" Spacing="6">
<FontIcon Glyph="&#xE73E;" FontSize="12"
IsTextScaleFactorEnabled="False"
Foreground="{ThemeResource SystemFillColorSuccessBrush}" />
<TextBlock Text="Local AI available"
Style="{StaticResource CaptionTextBlockStyle}"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemFillColorSuccessBrush}" />
</StackPanel>
<TextBlock x:Name="LocalAiAvailabilityText"
AutomationProperties.AutomationId="WelcomeLocalAiHardware"
TextWrapping="Wrap"
Style="{StaticResource CaptionTextBlockStyle}"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</StackPanel>
</StackPanel>
</Grid>
</ListViewItem>
Expand Down
10 changes: 6 additions & 4 deletions src/OpenClaw.SetupEngine.UI/Pages/WelcomePage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Navigation;
Expand Down Expand Up @@ -63,12 +64,13 @@ private async Task DetectLocalAiAvailabilityAsync()
LocalInferenceEligibilityResult eligibility = LocalInferenceEligibility.Evaluate(
hardware,
config.LocalAi.SelectedModelId);
if (!eligibility.CanInstall || eligibility.SelectedGpu is not { } gpu)
if (!eligibility.CanInstall || eligibility.SelectedGpu is null)
return;

LocalAiAvailabilityText.Text =
$"{gpu.Name} detected. Install a local gateway to use local AI inference on this PC.";
LocalAiAvailabilityPanel.Visibility = Visibility.Visible;
LocalAiAvailabilityBadge.Visibility = Visibility.Visible;
AutomationProperties.SetName(
InstallChoice,
"Install a local gateway (WSL), recommended, Local AI available");
}

private void StartMascotBreatheAnimation()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace OpenClaw.Shared.Inference.Catalog;

public static class LocalInferenceEligibilityDiagnostics
{
public static string DescribeUnavailable(LocalInferenceEligibilityResult eligibility)
{
ArgumentNullException.ThrowIfNull(eligibility);

return eligibility.SelectionFailureCode switch
{
LocalInferenceSelectionFailureCode.RuntimeUnavailable =>
"This Local AI release does not include a native llama-server runtime for the detected Windows architecture.",
LocalInferenceSelectionFailureCode.NoNvidiaGpu =>
"No NVIDIA GPU was reported by the NVIDIA driver. Install or repair the NVIDIA driver, then try setup again.",
LocalInferenceSelectionFailureCode.UnknownModel =>
"The selected model is not available in this Local AI release.",
_ => eligibility.FailureCode switch
{
LocalInferenceEligibilityFailureCode.HardwareFactsIncomplete =>
"OpenClaw could not read a stable NVIDIA GPU identifier, memory, driver, or CUDA capability.",
LocalInferenceEligibilityFailureCode.InsufficientGpuMemory =>
$"{eligibility.Plan?.Model.DisplayName ?? "The selected model"} requires " +
$"{FormatSize(eligibility.RequiredTotalMemoryBytes)} of GPU memory for model weights, KV cache, and runtime workspace. " +
$"OpenClaw detected {FormatOptionalSize(eligibility.DetectedTotalMemoryBytes)}.",
LocalInferenceEligibilityFailureCode.DriverTooOld =>
$"NVIDIA driver {eligibility.SelectedGpu?.DriverVersion ?? "unknown"} was detected. " +
$"Local AI requires version {LocalInferenceEligibility.MinimumNvidiaDriverVersion} or newer.",
LocalInferenceEligibilityFailureCode.CudaCapabilityTooLow =>
"The NVIDIA driver does not provide CUDA 13 support. A separate CUDA Toolkit is not required.",
_ => "OpenClaw could not verify the Local AI requirements on this system.",
},
};
}

private static string FormatSize(long bytes) =>
$"{bytes / 1_000_000_000d:0.#} GB";

private static string FormatOptionalSize(long? bytes) =>
bytes is { } value ? FormatSize(value) : "an unknown amount";
}
65 changes: 53 additions & 12 deletions src/OpenClaw.Tray.WinUI/Pages/LocalAiPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,39 @@
Style="{StaticResource CaptionTextBlockStyle}"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" TextWrapping="Wrap"/>

<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1"
CornerRadius="8" Padding="16" AutomationProperties.AutomationId="LocalAiEngineCard">
<InfoBar x:Name="LocalAiUnavailableInfoBar"
x:Uid="LocalAiPage_UnavailableInfoBar"
Title="Local AI is not available"
Severity="Informational"
IsOpen="True" IsClosable="False"
Visibility="Collapsed"
AutomationProperties.AutomationId="LocalAiUnavailableInfoBar"
AutomationProperties.LiveSetting="Polite">
<HyperlinkButton x:Name="LocalAiUnavailableDetailsButton"
x:Uid="LocalAiPage_UnavailableDetailsButton"
Content="See why" Padding="0"
HorizontalAlignment="Left"
AutomationProperties.AutomationId="LocalAiUnavailableDetailsButton"
Click="LocalAiUnavailableDetails_Click"/>
</InfoBar>
<TeachingTip x:Name="LocalAiUnavailableDetailsTip"
x:Uid="LocalAiPage_UnavailableDetailsTip"
Title="Why Local AI is unavailable"
Target="{x:Bind LocalAiUnavailableDetailsButton}"
PreferredPlacement="Bottom"
IsLightDismissEnabled="True">
<TextBlock x:Name="LocalAiUnavailableReasonText"
TextWrapping="Wrap"
MaxWidth="420"/>
</TeachingTip>

<ContentControl x:Name="LocalAiEngineOption"
IsTabStop="False"
HorizontalContentAlignment="Stretch"
AutomationProperties.AutomationId="LocalAiEngineCard">
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1"
CornerRadius="8" Padding="16">
<StackPanel Spacing="10">
<Grid ColumnDefinitions="Auto,*,Auto" ColumnSpacing="12">
<Ellipse x:Name="EngineStatusDot" Width="12" Height="12" VerticalAlignment="Center"
Expand Down Expand Up @@ -62,11 +92,16 @@
Click="OnOpenLogs" AutomationProperties.AutomationId="LocalAiOpenLogs"/>
</StackPanel>
</StackPanel>
</Border>
</Border>
</ContentControl>

<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1"
CornerRadius="8" Padding="16" AutomationProperties.AutomationId="LocalAiModelCard">
<ContentControl x:Name="LocalAiModelOption"
IsTabStop="False"
HorizontalContentAlignment="Stretch"
AutomationProperties.AutomationId="LocalAiModelCard">
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1"
CornerRadius="8" Padding="16">
<StackPanel Spacing="10">
<TextBlock x:Uid="LocalAiPage_ModelHeading" Text="Model"
Style="{StaticResource BodyStrongTextBlockStyle}"/>
Expand All @@ -86,11 +121,16 @@
Content="Retry setup or download" Click="OnRetrySetup" HorizontalAlignment="Left"
AutomationProperties.AutomationId="LocalAiRetrySetup"/>
</StackPanel>
</Border>
</Border>
</ContentControl>

<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1"
CornerRadius="8" Padding="16" AutomationProperties.AutomationId="LocalAiGatewayCard">
<ContentControl x:Name="LocalAiGatewayOption"
IsTabStop="False"
HorizontalContentAlignment="Stretch"
AutomationProperties.AutomationId="LocalAiGatewayCard">
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1"
CornerRadius="8" Padding="16">
<StackPanel Spacing="10">
<TextBlock x:Uid="LocalAiPage_GatewayHeading" Text="Gateway connection"
Style="{StaticResource BodyStrongTextBlockStyle}"/>
Expand All @@ -113,7 +153,8 @@
AutomationProperties.AutomationId="LocalAiOpenChat"/>
</StackPanel>
</StackPanel>
</Border>
</Border>
</ContentControl>
</StackPanel>
</ScrollViewer>
</Page>
23 changes: 23 additions & 0 deletions src/OpenClaw.Tray.WinUI/Pages/LocalAiPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using OpenClaw.Shared;
Expand Down Expand Up @@ -87,6 +88,13 @@ private void RefreshFromViewModel()
ActionErrorText.Visibility = string.IsNullOrWhiteSpace(ActionErrorText.Text) ? Visibility.Collapsed : Visibility.Visible;
EngineBusyIndicator.IsActive = _viewModel.IsBusy;
EngineBusyIndicator.Visibility = _viewModel.IsBusy ? Visibility.Visible : Visibility.Collapsed;
LocalAiUnavailableInfoBar.Visibility =
_viewModel.IsAvailabilityKnown && !_viewModel.IsLocalAiAvailable
? Visibility.Visible
: Visibility.Collapsed;
SetOptionAvailability(LocalAiEngineOption, _viewModel.AreOptionsEnabled);
SetOptionAvailability(LocalAiModelOption, _viewModel.AreOptionsEnabled);
SetOptionAvailability(LocalAiGatewayOption, _viewModel.AreOptionsEnabled);
StartButton.IsEnabled = _viewModel.CanStart;
StopButton.IsEnabled = _viewModel.CanStop;
RestartButton.IsEnabled = _viewModel.CanRestart;
Expand All @@ -104,6 +112,21 @@ private void RefreshFromViewModel()
private void OnRetrySetup(object sender, RoutedEventArgs e) => _viewModel?.RetrySetup();
private void OnRepairConnection(object sender, RoutedEventArgs e) => _viewModel?.RepairConnection();
private void OnOpenChat(object sender, RoutedEventArgs e) => _viewModel?.OpenChat();
private void LocalAiUnavailableDetails_Click(object sender, RoutedEventArgs e)
{
LocalAiUnavailableReasonText.Text = _viewModel?.LocalAiUnavailableReason ?? string.Empty;
LocalAiUnavailableDetailsTip.IsOpen = !LocalAiUnavailableDetailsTip.IsOpen;
}

private static void SetOptionAvailability(Control option, bool isAvailable)
{
option.IsEnabled = isAvailable;
option.Opacity = isAvailable ? 1 : 0.55;
AutomationProperties.SetHelpText(
option,
isAvailable ? string.Empty : "Unavailable because Local AI is not available on this device.");
}

private static void RunAction(Func<Task<bool>> action, string source) =>
AsyncEventHandlerGuard.Run(action, new AppLogger(), source);
private static Brush ResolveBrush(string key) => (Brush)Application.Current.Resources[key];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using OpenClaw.Shared.ExecApprovals;
using OpenClaw.Shared.Inference;
using Microsoft.Extensions.DependencyInjection;
using OpenClawTray.Chat;
using OpenClawTray.Services;
Expand Down Expand Up @@ -42,6 +43,7 @@ public static IServiceCollection AddOpenClawTrayCore(this IServiceCollection ser
// its Saved-event subscription during shutdown.
services.AddSingleton<ISettingsStore, SettingsStore>();
services.AddSingleton<IPermissionsPageRuntimeSource, PermissionsPageRuntimeSource>();
services.AddSingleton<IHostHardwareProbe, NvmlHostHardwareProbe>();

// Container-owned navigation lifetime manager (disposed with the root provider).
services.AddSingleton<NavigationScopeManager>();
Expand Down
Loading
Loading