351 lines
22 KiB
XML
351 lines
22 KiB
XML
<Window xmlns="https://github.com/avaloniaui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
x:Class="Lumi.Companion.App.MainWindow"
|
|
Title="Lumi Companion"
|
|
Icon="/Assets/Lumi.Companion.ico"
|
|
Width="1120" Height="760" MinWidth="900" MinHeight="620"
|
|
WindowStartupLocation="CenterScreen">
|
|
<Grid ColumnDefinitions="248,*">
|
|
<Border Grid.Column="0" Background="#F4F7F8" Padding="22,24">
|
|
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
|
<StackPanel Orientation="Horizontal" Spacing="11">
|
|
<Border Width="34" Height="34" CornerRadius="9" Background="#28B7C8">
|
|
<Path Data="M 17,7 L 27,17 L 17,27 L 7,17 Z" Fill="White" Stretch="None" />
|
|
</Border>
|
|
<StackPanel VerticalAlignment="Center" Spacing="0">
|
|
<TextBlock Text="LUMI" Classes="eyebrow" />
|
|
<TextBlock Text="Companion" FontWeight="SemiBold" FontSize="16" />
|
|
</StackPanel>
|
|
</StackPanel>
|
|
|
|
<Border Grid.Row="1" Margin="0,25,0,20" Padding="11,9" CornerRadius="9" Background="#E6EEF0">
|
|
<Grid ColumnDefinitions="Auto,*" ColumnSpacing="9">
|
|
<Border x:Name="StatusMark" Width="10" Height="10" CornerRadius="5" Background="#A96612" VerticalAlignment="Center" />
|
|
<StackPanel Grid.Column="1" Spacing="1">
|
|
<TextBlock x:Name="StatusLabel" Text="Setup required" FontSize="13" FontWeight="SemiBold" />
|
|
<TextBlock x:Name="StatusSymbol" Text="○ Incomplete" FontSize="11" Foreground="{DynamicResource LumiMutedBrush}" />
|
|
</StackPanel>
|
|
</Grid>
|
|
</Border>
|
|
|
|
<Grid Grid.Row="2" RowDefinitions="Auto,*,Auto">
|
|
<Button x:Name="OverviewNav" Classes="nav selected" Content="Overview" Tag="Overview" />
|
|
|
|
<ScrollViewer Grid.Row="1" Margin="0,14,0,14" VerticalScrollBarVisibility="Auto">
|
|
<StackPanel Spacing="6">
|
|
<TextBlock Text="PLUGINS" Classes="eyebrow" Margin="12,0,0,4" />
|
|
<StackPanel x:Name="PluginNavigation" Spacing="4" />
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
|
|
<StackPanel Grid.Row="2" x:Name="TechnicalNavigation">
|
|
<Border Height="1" Background="{DynamicResource LumiBorderBrush}" Margin="0,0,0,8" />
|
|
<Button x:Name="ConnectionNav" Classes="nav" Content="Connection & device" Tag="Connection" />
|
|
<Button x:Name="LogsNav" Classes="nav" Content="Logs & diagnostics" Tag="Logs" />
|
|
<Button x:Name="SettingsNav" Classes="nav" Content="Settings" Tag="Settings" />
|
|
</StackPanel>
|
|
</Grid>
|
|
|
|
<TextBlock Grid.Row="3" Text="Runs quietly in the notification area" Classes="muted" FontSize="11" />
|
|
</Grid>
|
|
</Border>
|
|
|
|
<Grid Grid.Column="1">
|
|
<ScrollViewer x:Name="OverviewPage">
|
|
<StackPanel Margin="44,38,52,48" MaxWidth="760" HorizontalAlignment="Left" Spacing="24">
|
|
<StackPanel Spacing="6">
|
|
<TextBlock Text="AT A GLANCE" Classes="eyebrow" />
|
|
<TextBlock Text="Overview" Classes="pageTitle" />
|
|
<TextBlock x:Name="OverviewDetail" Text="Checking what is ready…" Classes="muted" FontSize="15" />
|
|
</StackPanel>
|
|
|
|
<Border Classes="soft" Padding="22">
|
|
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="24">
|
|
<StackPanel Spacing="6">
|
|
<TextBlock x:Name="NextActionTitle" Text="Pair this computer" Classes="sectionTitle" />
|
|
<TextBlock x:Name="NextActionDetail" Text="Use a one-time pairing package from Lumi to connect this streaming computer." Classes="muted" MaxWidth="520" />
|
|
</StackPanel>
|
|
<Button Grid.Column="1" x:Name="NextActionButton" Classes="primary" Content="Choose pairing package" VerticalAlignment="Center" />
|
|
</Grid>
|
|
</Border>
|
|
|
|
<Border x:Name="UpdatePanel" Classes="soft" Padding="18" IsVisible="False">
|
|
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="20">
|
|
<StackPanel Spacing="4">
|
|
<TextBlock x:Name="UpdateTitle" Text="Companion update available" FontWeight="SemiBold" />
|
|
<TextBlock x:Name="UpdateDetail" Text="A verified update is ready." Classes="muted" />
|
|
</StackPanel>
|
|
<Button Grid.Column="1" x:Name="ApplyUpdateButton" Classes="primary" Content="Update Companion" VerticalAlignment="Center" />
|
|
</Grid>
|
|
</Border>
|
|
|
|
<StackPanel Spacing="12">
|
|
<TextBlock Text="Setup progress" Classes="sectionTitle" />
|
|
<Grid ColumnDefinitions="*,*,*" ColumnSpacing="12">
|
|
<Border Classes="card">
|
|
<StackPanel Spacing="7">
|
|
<TextBlock x:Name="PairingStepSymbol" Text="○" FontSize="20" />
|
|
<TextBlock Text="Lumi connection" FontWeight="SemiBold" />
|
|
<TextBlock x:Name="PairingStepDetail" Text="Not paired" Classes="muted" FontSize="12" />
|
|
</StackPanel>
|
|
</Border>
|
|
<Border Grid.Column="1" Classes="card">
|
|
<StackPanel Spacing="7">
|
|
<TextBlock x:Name="ObsStepSymbol" Text="○" FontSize="20" />
|
|
<TextBlock Text="OBS integration" FontWeight="SemiBold" />
|
|
<TextBlock x:Name="ObsStepDetail" Text="Not installed" Classes="muted" FontSize="12" />
|
|
</StackPanel>
|
|
</Border>
|
|
<Border Grid.Column="2" Classes="card">
|
|
<StackPanel Spacing="7">
|
|
<TextBlock x:Name="TestStepSymbol" Text="○" FontSize="20" />
|
|
<TextBlock Text="Full path test" FontWeight="SemiBold" />
|
|
<TextBlock x:Name="TestStepDetail" Text="Not run" Classes="muted" FontSize="12" />
|
|
</StackPanel>
|
|
</Border>
|
|
</Grid>
|
|
</StackPanel>
|
|
|
|
<Border Classes="soft">
|
|
<TextBlock Text="Lumi Companion never runs speech recognition on this streaming computer. Audio stays in bounded memory and is sent to your paired Lumi host." Classes="muted" />
|
|
</Border>
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
|
|
<ScrollViewer x:Name="TranscriptionPage" IsVisible="False">
|
|
<StackPanel Margin="44,38,52,48" MaxWidth="760" HorizontalAlignment="Left" Spacing="25">
|
|
<StackPanel Spacing="6">
|
|
<TextBlock Text="CAPTURE" Classes="eyebrow" />
|
|
<TextBlock Text="Transcription" Classes="pageTitle" />
|
|
<TextBlock Text="Choose the OBS microphone Lumi should caption. Source identity remains stable if you rename it." Classes="muted" FontSize="15" />
|
|
</StackPanel>
|
|
<StackPanel Spacing="8">
|
|
<TextBlock Text="Primary microphone" FontWeight="SemiBold" />
|
|
<ComboBox x:Name="SourcePicker" IsEnabled="False" PlaceholderText="Open OBS after installing the managed integration" MinWidth="420" HorizontalAlignment="Left" />
|
|
<TextBlock x:Name="SourceHelp" Text="No OBS source list is available yet." Classes="muted" />
|
|
</StackPanel>
|
|
<Border Classes="soft">
|
|
<StackPanel Spacing="7">
|
|
<TextBlock Text="Optional second microphone" FontWeight="SemiBold" />
|
|
<TextBlock Text="The protocol supports more than one source, but source overlap policy remains disabled until the primary path is validated." Classes="muted" />
|
|
</StackPanel>
|
|
</Border>
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
|
|
<ScrollViewer x:Name="TestPage" IsVisible="False">
|
|
<StackPanel Margin="44,38,52,48" MaxWidth="760" HorizontalAlignment="Left" Spacing="24">
|
|
<StackPanel Spacing="6">
|
|
<TextBlock Text="SAFE CHECK" Classes="eyebrow" />
|
|
<TextBlock Text="Transcription test" Classes="pageTitle" />
|
|
<TextBlock Text="Checks the selected OBS source, secure connection, model readiness, and safe caption return path. You do not need to speak, and nothing is sent to Twitch." Classes="muted" FontSize="15" />
|
|
</StackPanel>
|
|
<Button x:Name="RunTestButton" Classes="primary" Content="Run full path test" HorizontalAlignment="Left" />
|
|
<StackPanel x:Name="TestStagesPanel" Spacing="8" />
|
|
<Border Classes="soft">
|
|
<StackPanel Spacing="5">
|
|
<TextBlock Text="Simulated outgoing caption" FontWeight="SemiBold" />
|
|
<TextBlock x:Name="SimulatedCaption" Text="A real caption will appear here only after every upstream stage passes." Classes="muted" FontStyle="Italic" />
|
|
</StackPanel>
|
|
</Border>
|
|
<Border Classes="card">
|
|
<StackPanel Spacing="16">
|
|
<StackPanel Spacing="5">
|
|
<TextBlock Text="ACCURACY & LATENCY" Classes="eyebrow" />
|
|
<TextBlock Text="Dedicated transcription test" Classes="sectionTitle" />
|
|
<TextBlock Text="Speak naturally for as long as needed. End the test yourself, or Lumi will finalize it after 10 seconds of silence. Nothing is sent to Twitch." Classes="muted" />
|
|
</StackPanel>
|
|
<Grid ColumnDefinitions="Auto,Auto,*,Auto" ColumnSpacing="10">
|
|
<Button x:Name="StartBenchmarkButton" Classes="primary" Content="Start dedicated test" />
|
|
<Button Grid.Column="1" x:Name="StopBenchmarkButton" Classes="secondary" Content="End test" IsEnabled="False" />
|
|
<ProgressBar Grid.Column="2" x:Name="VoiceLevelMeter" Minimum="-60" Maximum="0" Value="-60" Height="12" MinWidth="220" VerticalAlignment="Center" Foreground="{DynamicResource LumiDangerBrush}" />
|
|
<TextBlock Grid.Column="3" x:Name="VoiceLevelText" Text="-60 dBFS" Classes="muted" FontSize="11" VerticalAlignment="Center" MinWidth="58" />
|
|
</Grid>
|
|
<TextBlock Text="Input level: green from -30 to -12 dBFS; orange within 10 dB outside that range; red otherwise." Classes="muted" FontSize="11" />
|
|
<TextBlock x:Name="BenchmarkStatusText" Text="Not started." Classes="muted" />
|
|
<Border Classes="soft">
|
|
<StackPanel Spacing="5">
|
|
<TextBlock Text="Measured transcript" FontWeight="SemiBold" />
|
|
<TextBlock x:Name="BenchmarkTranscript" Text="Finalized speech will appear here." Classes="muted" TextWrapping="Wrap" />
|
|
</StackPanel>
|
|
</Border>
|
|
<StackPanel Spacing="8">
|
|
<TextBlock Text="Latency" FontWeight="SemiBold" />
|
|
<WrapPanel x:Name="LatencyStatsPanel" Orientation="Horizontal" />
|
|
</StackPanel>
|
|
<StackPanel Spacing="8">
|
|
<TextBlock Text="Final confidence" FontWeight="SemiBold" />
|
|
<WrapPanel x:Name="ConfidenceStatsPanel" Orientation="Horizontal" />
|
|
</StackPanel>
|
|
<TextBlock Text="Low and high 1% values are tail averages. Confidence is included only after an utterance is finalized." Classes="muted" FontSize="12" />
|
|
</StackPanel>
|
|
</Border>
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
|
|
|
|
<ScrollViewer x:Name="SongOverlayPage" IsVisible="False">
|
|
<StackPanel Margin="44,38,52,48" MaxWidth="760" HorizontalAlignment="Left" Spacing="24">
|
|
<StackPanel Spacing="6">
|
|
<TextBlock Text="MEDIA" Classes="eyebrow" />
|
|
<TextBlock Text="Song Overlay" Classes="pageTitle" />
|
|
<TextBlock Text="Spotify is the first media provider. The provider contract remains generic so later services can use the same event and overlay pipeline." Classes="muted" FontSize="15" />
|
|
</StackPanel>
|
|
|
|
<Border Classes="soft">
|
|
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="20">
|
|
<StackPanel Spacing="4">
|
|
<TextBlock x:Name="SongOverlayStatusTitle" Text="Starting" FontWeight="SemiBold" />
|
|
<TextBlock x:Name="SongOverlayStatusDetail" Text="Waiting for the media provider…" Classes="muted" />
|
|
<TextBlock x:Name="SongOverlayCurrentTrack" Text="No active song" Classes="muted" FontSize="12" />
|
|
</StackPanel>
|
|
<ToggleSwitch Grid.Column="1" x:Name="SongOverlayEnabledToggle" OffContent="Off" OnContent="On" VerticalAlignment="Center" />
|
|
</Grid>
|
|
</Border>
|
|
|
|
<StackPanel Spacing="10">
|
|
<TextBlock Text="Lumi authentication" Classes="sectionTitle" />
|
|
<Border Classes="soft">
|
|
<StackPanel Spacing="4">
|
|
<TextBlock x:Name="SongOverlayLumiAuthenticationText" Text="Uses the Companion device pairing" FontWeight="SemiBold" />
|
|
<TextBlock Text="Song Overlay inherits the protected Companion credential automatically. Revoking or forgetting this Companion also revokes plugin access; no separate plugin key is needed." Classes="muted" />
|
|
</StackPanel>
|
|
</Border>
|
|
</StackPanel>
|
|
|
|
<StackPanel Spacing="12">
|
|
<TextBlock Text="Playback provider" Classes="sectionTitle" />
|
|
<Grid ColumnDefinitions="150,*" RowDefinitions="Auto,Auto,Auto" RowSpacing="10" ColumnSpacing="12">
|
|
<TextBlock Text="Provider" Classes="muted" VerticalAlignment="Center" />
|
|
<ComboBox Grid.Column="1" x:Name="SongOverlayProviderPicker" SelectedIndex="0" IsEnabled="False">
|
|
<ComboBoxItem Content="Spotify" Tag="spotify" />
|
|
</ComboBox>
|
|
<TextBlock Grid.Row="1" Text="Recovery heartbeat" Classes="muted" VerticalAlignment="Center" />
|
|
<NumericUpDown Grid.Row="1" Grid.Column="1" x:Name="SongOverlayHeartbeatBox" Minimum="15" Maximum="300" Increment="5" FormatString="0 seconds" HorizontalAlignment="Left" Width="160" />
|
|
<TextBlock Grid.Row="2" Text="Seek sensitivity" Classes="muted" VerticalAlignment="Center" />
|
|
<NumericUpDown Grid.Row="2" Grid.Column="1" x:Name="SongOverlaySeekBox" Minimum="500" Maximum="10000" Increment="250" FormatString="0 ms" HorizontalAlignment="Left" Width="160" />
|
|
</Grid>
|
|
<CheckBox x:Name="SongOverlayCoverToggle" Content="Send cover art only with track metadata" />
|
|
<CheckBox x:Name="SongOverlaySearchLinkToggle" Content="Use a Spotify search link when an exact song link is unavailable" />
|
|
</StackPanel>
|
|
|
|
<Border Classes="card">
|
|
<StackPanel Spacing="12">
|
|
<StackPanel Spacing="4">
|
|
<TextBlock Text="SPOTIFY-SPECIFIC" Classes="eyebrow" />
|
|
<TextBlock Text="Optional metadata enrichment" Classes="sectionTitle" />
|
|
<TextBlock Text="Windows normally supplies title, artist, album, timeline and artwork. Connecting your own Spotify application adds an exact link, release year and official artwork when available. Spotify is contacted only when track metadata changes or needs recovery." Classes="muted" />
|
|
</StackPanel>
|
|
<TextBox x:Name="SongOverlaySpotifyClientIdBox" PlaceholderText="Spotify application Client ID" />
|
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
|
<Button x:Name="ConnectSongOverlaySpotifyButton" Classes="secondary" Content="Connect Spotify metadata" />
|
|
<Button x:Name="DisconnectSongOverlaySpotifyButton" Classes="secondary" Content="Disconnect" />
|
|
</StackPanel>
|
|
<TextBlock x:Name="SongOverlaySpotifyStatus" Text="Not connected. Core playback detection still works through Windows." Classes="muted" />
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
|
<Button x:Name="SaveSongOverlayButton" Classes="primary" Content="Save Song Overlay" />
|
|
<Button x:Name="SendSongOverlayStateButton" Classes="secondary" Content="Send current song now" />
|
|
</StackPanel>
|
|
<TextBlock x:Name="SongOverlayFeedback" Classes="muted" />
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
|
|
<ScrollViewer x:Name="ConnectionPage" IsVisible="False">
|
|
<StackPanel Margin="44,38,52,48" MaxWidth="760" HorizontalAlignment="Left" Spacing="24">
|
|
<StackPanel Spacing="6">
|
|
<TextBlock Text="DEVICE" Classes="eyebrow" />
|
|
<TextBlock Text="Connection & device" Classes="pageTitle" />
|
|
<TextBlock Text="The device credential is protected for your Windows account and never shared with OBS." Classes="muted" FontSize="15" />
|
|
</StackPanel>
|
|
<Grid ColumnDefinitions="150,*" RowDefinitions="Auto,Auto,Auto" RowSpacing="12">
|
|
<TextBlock Text="Computer" Classes="muted" /><TextBlock Grid.Column="1" x:Name="DeviceNameText" Text="Not paired" FontWeight="SemiBold" />
|
|
<TextBlock Grid.Row="1" Text="Lumi host" Classes="muted" /><TextBlock Grid.Row="1" Grid.Column="1" x:Name="HostText" Text="—" />
|
|
<TextBlock Grid.Row="2" Text="Last connected" Classes="muted" /><TextBlock Grid.Row="2" Grid.Column="1" x:Name="LastConnectedText" Text="Never" />
|
|
</Grid>
|
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
|
<Button x:Name="ReconnectButton" Classes="secondary" Content="Retry connection" />
|
|
<Button x:Name="PairButton" Classes="secondary" Content="Choose pairing package" />
|
|
<Button x:Name="OpenWebButton" Classes="secondary" Content="Open Lumi WebUI" />
|
|
</StackPanel>
|
|
<Border Classes="soft">
|
|
<StackPanel Spacing="10">
|
|
<TextBlock Text="Managed OBS integration" FontWeight="SemiBold" />
|
|
<TextBlock x:Name="BridgeStatusText" Text="Checking the bundled integration…" Classes="muted" />
|
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
|
<Button x:Name="InstallBridgeButton" Classes="primary" Content="Install integration" />
|
|
<Button x:Name="RemoveBridgeButton" Classes="secondary" Content="Remove integration" />
|
|
</StackPanel>
|
|
<TextBlock Text="Close OBS before install, repair, or removal. Restart OBS afterward so it can load the verified plugin." Classes="muted" FontSize="12" />
|
|
</StackPanel>
|
|
</Border>
|
|
<Border Classes="soft">
|
|
<StackPanel Spacing="8">
|
|
<TextBlock Text="Remove this device" FontWeight="SemiBold" />
|
|
<TextBlock Text="Removes the local credential. You can also revoke this computer from Lumi." Classes="muted" />
|
|
<Button x:Name="ForgetButton" Classes="secondary" Content="Forget this device" HorizontalAlignment="Left" />
|
|
</StackPanel>
|
|
</Border>
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
|
|
<ScrollViewer x:Name="LogsPage" IsVisible="False">
|
|
<StackPanel Margin="44,38,52,48" MaxWidth="760" HorizontalAlignment="Left" Spacing="24">
|
|
<StackPanel Spacing="6">
|
|
<TextBlock Text="DIAGNOSTICS" Classes="eyebrow" />
|
|
<TextBlock Text="Logs & diagnostics" Classes="pageTitle" />
|
|
<TextBlock Text="Short-lived diagnostic logs help explain connection and delivery failures. Raw audio is never written to disk." Classes="muted" FontSize="15" />
|
|
</StackPanel>
|
|
<Button x:Name="OpenLogsButton" Classes="secondary" Content="Open logs folder" HorizontalAlignment="Left" />
|
|
<Border Background="#182026" CornerRadius="12" Padding="16" MinHeight="230">
|
|
<StackPanel x:Name="RecentLogsPanel" Spacing="6">
|
|
<TextBlock Text="Waiting for companion events…" Foreground="#CFDADD" FontFamily="Consolas" FontSize="12" />
|
|
</StackPanel>
|
|
</Border>
|
|
<TextBlock Text="Logs are retained for 7 days and capped at 256 MiB. Caption text may appear in diagnostics during testing or troubleshooting." Classes="muted" FontSize="12" />
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
|
|
<ScrollViewer x:Name="SettingsPage" IsVisible="False">
|
|
<StackPanel Margin="44,38,52,48" MaxWidth="760" HorizontalAlignment="Left" Spacing="25">
|
|
<StackPanel Spacing="6">
|
|
<TextBlock Text="PREFERENCES" Classes="eyebrow" />
|
|
<TextBlock Text="Settings" Classes="pageTitle" />
|
|
<TextBlock Text="Only controls useful on the streaming computer live here. Model and server administration stay in Lumi." Classes="muted" FontSize="15" />
|
|
</StackPanel>
|
|
<StackPanel Spacing="16">
|
|
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="24">
|
|
<StackPanel Spacing="3"><TextBlock Text="Start with Windows" FontWeight="SemiBold" /><TextBlock Text="Keep Lumi Companion ready in the notification area after sign-in." Classes="muted" /></StackPanel>
|
|
<ToggleSwitch Grid.Column="1" x:Name="AutoStartToggle" OffContent="Off" OnContent="On" />
|
|
</Grid>
|
|
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="24">
|
|
<StackPanel Spacing="3"><TextBlock Text="Start companion features with OBS" FontWeight="SemiBold" /><TextBlock Text="Prepare selected features when OBS becomes available." Classes="muted" /></StackPanel>
|
|
<ToggleSwitch Grid.Column="1" x:Name="StartWithObsToggle" OffContent="Off" OnContent="On" />
|
|
</Grid>
|
|
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="24">
|
|
<StackPanel Spacing="3"><TextBlock Text="Advanced mode" FontWeight="SemiBold" /><TextBlock Text="Reveal protocol and component details useful for troubleshooting." Classes="muted" /></StackPanel>
|
|
<ToggleSwitch Grid.Column="1" x:Name="AdvancedToggle" OffContent="Off" OnContent="On" />
|
|
</Grid>
|
|
</StackPanel>
|
|
<Border x:Name="AdvancedPanel" Classes="soft" IsVisible="False">
|
|
<StackPanel Spacing="6">
|
|
<TextBlock Text="Advanced details" FontWeight="SemiBold" />
|
|
<TextBlock Text="Protocol v1 · bounded in-memory audio · secure WebSocket transport · server-hosted inference" Classes="muted" />
|
|
</StackPanel>
|
|
</Border>
|
|
<Border Classes="soft">
|
|
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="20">
|
|
<StackPanel Spacing="4"><TextBlock Text="Companion updates" FontWeight="SemiBold" /><TextBlock x:Name="UpdateStatusText" Text="Checking for updates…" Classes="muted" /><TextBlock Text="Current version" Classes="muted" FontSize="11" /><TextBlock x:Name="CurrentVersionText" Text="—" FontSize="12" /></StackPanel>
|
|
<Button Grid.Column="1" x:Name="CheckUpdateButton" Classes="secondary" Content="Check now" VerticalAlignment="Center" />
|
|
</Grid>
|
|
</Border>
|
|
<Button x:Name="SaveSettingsButton" Classes="primary" Content="Save preferences" HorizontalAlignment="Left" />
|
|
<TextBlock x:Name="SettingsFeedback" Classes="muted" />
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
</Grid>
|
|
</Grid>
|
|
</Window>
|