Polish provider setup and local-first settings

This commit is contained in:
2026-08-21 20:06:50 -05:00
parent be1dbb600a
commit 1f1d0737a9
21 changed files with 388 additions and 523 deletions
+62 -48
View File
@@ -7,10 +7,13 @@
use aws_config::BehaviorVersion;
use aws_sdk_bedrock::Client;
use aws_sdk_bedrockruntime::config::Region;
use futures::{stream, StreamExt};
use super::client::{BedrockClientConfig, BedrockError};
use crate::settings::ai::BedrockModelConfig;
const AVAILABILITY_CHECK_CONCURRENCY: usize = 8;
pub async fn discover_available_models(
config: BedrockClientConfig,
) -> Result<Vec<BedrockModelConfig>, String> {
@@ -24,61 +27,72 @@ pub async fn discover_available_models(
.await
.map_err(|error| format!("Could not list AWS Bedrock foundation models: {error}"))?;
let mut models = Vec::new();
for summary in catalog.model_summaries() {
let model_id = summary.model_id();
let availability = match client
.get_foundation_model_availability()
.model_id(model_id)
.send()
.await
{
Ok(availability) => availability,
Err(error) => {
log::debug!(
"[bedrock] Availability check failed for {model_id}; excluding model: {error}"
);
continue;
}
};
// AWS exposes agreement/authorization state only through an individual
// GetFoundationModelAvailability call. Keep using that control-plane API,
// but bound the independent checks so a large catalog does not serialize
// startup discovery one model at a time.
let checks = catalog.model_summaries().iter().cloned().map(|summary| {
let client = client.clone();
async move {
let model_id = summary.model_id();
let availability = match client
.get_foundation_model_availability()
.model_id(model_id)
.send()
.await
{
Ok(availability) => availability,
Err(error) => {
log::debug!(
"[bedrock] Availability check failed for {model_id}; excluding model: {error}"
);
return None;
}
};
if !model_availability_is_usable(
availability
.agreement_availability()
.map(|agreement| agreement.status().as_str()),
availability.authorization_status().as_str(),
availability.entitlement_availability().as_str(),
availability.region_availability().as_str(),
) {
log::debug!(
"[bedrock] Excluding {model_id}: agreement={}, authorization={}, entitlement={}, region={}",
if !model_availability_is_usable(
availability
.agreement_availability()
.map(|agreement| agreement.status().as_str())
.unwrap_or("MISSING"),
.map(|agreement| agreement.status().as_str()),
availability.authorization_status().as_str(),
availability.entitlement_availability().as_str(),
availability.region_availability().as_str(),
);
continue;
) {
log::debug!(
"[bedrock] Excluding {model_id}: agreement={}, authorization={}, entitlement={}, region={}",
availability
.agreement_availability()
.map(|agreement| agreement.status().as_str())
.unwrap_or("MISSING"),
availability.authorization_status().as_str(),
availability.entitlement_availability().as_str(),
availability.region_availability().as_str(),
);
return None;
}
let display_name = summary
.model_name()
.map(str::to_owned)
.unwrap_or_else(|| prettify_model_id(model_id));
let vision_supported = summary
.input_modalities()
.iter()
.any(|modality| modality.as_str() == "IMAGE");
Some(BedrockModelConfig {
model_id: model_id.to_owned(),
display_name,
vision_supported,
use_rig: false,
})
}
let display_name = summary
.model_name()
.map(str::to_owned)
.unwrap_or_else(|| prettify_model_id(model_id));
let vision_supported = summary
.input_modalities()
.iter()
.any(|modality| modality.as_str() == "IMAGE");
models.push(BedrockModelConfig {
model_id: model_id.to_owned(),
display_name,
vision_supported,
use_rig: false,
});
}
});
let mut models = stream::iter(checks)
.buffer_unordered(AVAILABILITY_CHECK_CONCURRENCY)
.filter_map(futures::future::ready)
.collect::<Vec<_>>()
.await;
models.sort_by(|left, right| left.display_name.cmp(&right.display_name));
if models.is_empty() {
@@ -166,26 +166,24 @@ impl ResponseStream {
// Check if this specific model has an OpenAI-compatible routing entry.
// This allows OpenAI/LiteLLM models to coexist with Bedrock models —
// only models fetched from the OpenAI endpoint route through it.
if *settings.openai_enabled.value() {
let llm_prefs = LLMPreferences::as_ref(ctx);
if let Some(client_config) = llm_prefs.openai_client_config_for_model(model_id) {
return ProviderConfig::OpenAI(OpenAIClientConfig {
kind: client_config.kind,
base_url: client_config.base_url.clone(),
api_key: client_config.api_key.clone(),
project_id: client_config.project_id.clone(),
location: client_config.location.clone(),
model: client_config
.model
.clone()
.or_else(|| Some(model_id.to_string())),
reasoning_effort: client_config.reasoning_effort.clone(),
max_input_tokens: client_config.max_input_tokens,
max_output_tokens: client_config.max_output_tokens,
use_rig: client_config.use_rig,
supports_system_messages: client_config.supports_system_messages,
});
}
let llm_prefs = LLMPreferences::as_ref(ctx);
if let Some(client_config) = llm_prefs.openai_client_config_for_model(model_id) {
return ProviderConfig::OpenAI(OpenAIClientConfig {
kind: client_config.kind,
base_url: client_config.base_url.clone(),
api_key: client_config.api_key.clone(),
project_id: client_config.project_id.clone(),
location: client_config.location.clone(),
model: client_config
.model
.clone()
.or_else(|| Some(model_id.to_string())),
reasoning_effort: client_config.reasoning_effort.clone(),
max_input_tokens: client_config.max_input_tokens,
max_output_tokens: client_config.max_output_tokens,
use_rig: client_config.use_rig,
supports_system_messages: client_config.supports_system_messages,
});
}
// Fall back to Bedrock
+10 -1
View File
@@ -46,8 +46,17 @@ pub(crate) struct ChatGPTAuthModel {
impl ChatGPTAuthModel {
pub(crate) fn new() -> Self {
let state = match load_or_import_auth_credentials() {
Ok(_) => ChatGPTAuthState::Connected,
Err(error) => {
log::debug!(
"[chatgpt/auth] No usable persisted ChatGPT credentials at startup: {error}"
);
ChatGPTAuthState::NotConnected
}
};
Self {
state: ChatGPTAuthState::NotConnected,
state,
pending_code_verifier: None,
pending_state: None,
}
+18 -20
View File
@@ -160,26 +160,24 @@ impl CrosscheckReviewer {
let settings = AISettings::as_ref(ctx);
// Check if this model has an OpenAI-compatible routing entry
if *settings.openai_enabled.value() {
let llm_prefs = LLMPreferences::as_ref(ctx);
if let Some(client_config) = llm_prefs.openai_client_config_for_model(model_id) {
return ProviderConfig::OpenAI(OpenAIClientConfig {
kind: client_config.kind,
base_url: client_config.base_url.clone(),
api_key: client_config.api_key.clone(),
project_id: client_config.project_id.clone(),
location: client_config.location.clone(),
model: client_config
.model
.clone()
.or_else(|| Some(model_id.to_string())),
reasoning_effort: client_config.reasoning_effort.clone(),
max_input_tokens: client_config.max_input_tokens,
max_output_tokens: Some(REVIEWER_MAX_OUTPUT_TOKENS),
use_rig: client_config.use_rig,
supports_system_messages: client_config.supports_system_messages,
});
}
let llm_prefs = LLMPreferences::as_ref(ctx);
if let Some(client_config) = llm_prefs.openai_client_config_for_model(model_id) {
return ProviderConfig::OpenAI(OpenAIClientConfig {
kind: client_config.kind,
base_url: client_config.base_url.clone(),
api_key: client_config.api_key.clone(),
project_id: client_config.project_id.clone(),
location: client_config.location.clone(),
model: client_config
.model
.clone()
.or_else(|| Some(model_id.to_string())),
reasoning_effort: client_config.reasoning_effort.clone(),
max_input_tokens: client_config.max_input_tokens,
max_output_tokens: Some(REVIEWER_MAX_OUTPUT_TOKENS),
use_rig: client_config.use_rig,
supports_system_messages: client_config.supports_system_messages,
});
}
// Fall back to Bedrock via external config
+56 -18
View File
@@ -634,6 +634,27 @@ impl LLMPreferences {
}
});
#[cfg(not(target_family = "wasm"))]
if ctx
.try_get_singleton_model_as_ref::<super::chatgpt_auth::ChatGPTAuthModel>()
.is_some()
{
ctx.subscribe_to_model(
&super::chatgpt_auth::ChatGPTAuthModel::handle(ctx),
|me, _, event, ctx| {
if matches!(
event,
super::chatgpt_auth::ChatGPTAuthModelEvent::StateChanged
) && matches!(
super::chatgpt_auth::ChatGPTAuthModel::as_ref(ctx).state(),
super::chatgpt_auth::ChatGPTAuthState::Connected
) {
me.refresh_chatgpt_subscription_models(ctx);
}
},
);
}
ctx.subscribe_to_model(&UserWorkspaces::handle(ctx), |me, _, event, ctx| {
if let UserWorkspacesEvent::TeamsChanged = event {
me.sanitize_disabled_custom_model_preferences(ctx);
@@ -735,6 +756,10 @@ impl LLMPreferences {
#[cfg(not(target_family = "wasm"))]
{
// Make the persisted Bedrock catalog available immediately. Agreement
// revalidation stays in the background and replaces this cache when it
// completes, so startup never waits on the per-model AWS checks.
me.inject_bedrock_models(ctx);
me.refresh_bedrock_models(ctx);
me.inject_openai_models(ctx);
me.ensure_default_model_present();
@@ -978,7 +1003,7 @@ impl LLMPreferences {
let settings = AISettings::as_ref(ctx);
self.inject_acp_models(ctx);
if !*settings.openai_enabled.value() {
if !settings.is_openai_provider_enabled() {
return;
}
@@ -1163,7 +1188,7 @@ impl LLMPreferences {
},
)]),
discount_percentage: None,
context_window: openai_model_context_window(model),
context_window: openai_model_context_window(model, provider_kind),
};
self.models_by_feature
.agent_mode
@@ -1447,7 +1472,7 @@ impl LLMPreferences {
#[cfg(not(target_family = "wasm"))]
pub fn fetch_openai_models_from_endpoint(&mut self, ctx: &mut ModelContext<Self>) {
let settings = AISettings::as_ref(ctx);
if !*settings.openai_enabled.value() {
if !settings.is_openai_provider_enabled() {
return;
}
@@ -1509,7 +1534,7 @@ impl LLMPreferences {
ctx: &mut ModelContext<Self>,
) {
let settings = AISettings::as_ref(ctx);
if !*settings.openai_enabled.value() {
if !settings.is_openai_provider_enabled() {
return;
}
@@ -1716,7 +1741,7 @@ impl LLMPreferences {
}
let settings = AISettings::as_ref(ctx);
if !*settings.openai_enabled.value()
if !settings.is_openai_provider_enabled()
|| !settings.openai_providers.value().iter().any(|provider| {
provider.enabled && provider.kind == OpenAIProviderKind::ChatGPTSubscription
})
@@ -2682,13 +2707,21 @@ fn openai_model_variant_id(model_id: &str, reasoning_effort: &str) -> String {
}
#[cfg(not(target_family = "wasm"))]
fn openai_model_context_window(model: &OpenAIModelConfig) -> LLMContextWindow {
let context_size = openai_model_context_size(model);
fn openai_model_context_window(
model: &OpenAIModelConfig,
provider_kind: OpenAIProviderKind,
) -> LLMContextWindow {
let default_context_size = openai_model_context_size(model);
let max_context_size = if provider_kind == OpenAIProviderKind::ChatGPTSubscription {
model.context_size.max(default_context_size)
} else {
default_context_size
};
LLMContextWindow {
is_configurable: false,
min: context_size,
max: context_size,
default_max: context_size,
is_configurable: max_context_size > default_context_size,
min: default_context_size,
max: max_context_size,
default_max: default_context_size,
}
}
@@ -2822,18 +2855,23 @@ fn chatgpt_models_from_codex_response(body: &serde_json::Value) -> Vec<OpenAIMod
return None;
}
let context_size = u32_from_json_any(model, &["context_window", "max_context_window"])
let default_context_size = u32_from_json_any(model, &["context_window"])
.or_else(|| u32_from_json_any(model, &["max_context_window"]))
.unwrap_or(DEFAULT_DISCOVERED_MODEL_CONTEXT_SIZE);
let max_context_size = u32_from_json_any(model, &["max_context_window"])
.unwrap_or(default_context_size)
.max(default_context_size);
let effective_context_percent = model["effective_context_window_percent"]
.as_u64()
.and_then(|value| u32::try_from(value).ok())
.filter(|value| (1..=100).contains(value))
.unwrap_or(100);
let max_input_tokens = Some(
context_size
.checked_mul(effective_context_percent)
.map(|tokens| tokens / 100)
.unwrap_or(context_size),
);
let effective_context_size = |context_size: u32| {
u32::try_from(u64::from(context_size) * u64::from(effective_context_percent) / 100)
.unwrap_or(context_size)
};
let max_input_tokens = Some(effective_context_size(default_context_size));
let context_size = effective_context_size(max_context_size);
let vision_supported = model["input_modalities"]
.as_array()
+19 -3
View File
@@ -760,7 +760,7 @@ fn chatgpt_codex_models_parse_visible_catalog_entries() {
"display_name": "GPT-5.6-Sol",
"visibility": "list",
"context_window": 272000,
"max_context_window": 272000,
"max_context_window": 872000,
"effective_context_window_percent": 95,
"input_modalities": ["text", "image"],
"supported_reasoning_levels": [
@@ -795,7 +795,7 @@ fn chatgpt_codex_models_parse_visible_catalog_entries() {
assert_eq!(models[0].model_id, "gpt-5.6-sol");
assert_eq!(models[0].display_name, "GPT-5.6-Sol");
assert!(models[0].vision_supported);
assert_eq!(models[0].context_size, 272_000);
assert_eq!(models[0].context_size, 828_400);
assert_eq!(models[0].max_input_tokens, Some(258_400));
assert_eq!(models[0].reasoning_efforts, ["low", "xhigh", "ultra"]);
assert!(models[0].use_rig);
@@ -804,8 +804,24 @@ fn chatgpt_codex_models_parse_visible_catalog_entries() {
assert_eq!(models[1].model_id, "gpt-text-only");
assert!(!models[1].vision_supported);
assert_eq!(models[1].context_size, 128_000);
assert_eq!(models[1].context_size, 115_200);
assert_eq!(models[1].max_input_tokens, Some(115_200));
let configurable = openai_model_context_window(
&models[0],
crate::settings::OpenAIProviderKind::ChatGPTSubscription,
);
assert!(configurable.is_configurable);
assert_eq!(configurable.min, 258_400);
assert_eq!(configurable.default_max, 258_400);
assert_eq!(configurable.max, 828_400);
let fixed =
openai_model_context_window(&models[0], crate::settings::OpenAIProviderKind::LiteLLM);
assert!(!fixed.is_configurable);
assert_eq!(fixed.min, 258_400);
assert_eq!(fixed.default_max, 258_400);
assert_eq!(fixed.max, 258_400);
}
#[test]
+38 -15
View File
@@ -1167,15 +1167,16 @@ impl settings_value::SettingsValue for AcpAgentSettings {}
// Nested ACP discovery data is intentionally persisted as one setting so refreshes are atomic.
define_settings_group!(AISettings, settings: [
// If `false`, all AI features are disabled.
// Legacy compatibility value. Effective AI availability is derived from configured,
// enabled providers rather than this historical global switch.
is_any_ai_enabled: IsAnyAIEnabled {
type: bool,
default: false,
default: true,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "agents.warp_agent.is_any_ai_enabled",
description: "Controls whether all AI features are enabled.",
description: "Legacy global AI enablement value retained for settings compatibility.",
},
// This field should not be referenced directly to lookup active AI enablement -- use the
// `is_active_ai_enabled()` getter.
@@ -1206,7 +1207,7 @@ define_settings_group!(AISettings, settings: [
// This is only used when `FeatureFlag::AgentView` is enabled.
nld_in_terminal_enabled_internal: NLDInTerminalEnabled {
type: bool,
default: false,
default: true,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: false,
@@ -2338,11 +2339,35 @@ impl AISettings {
pub fn is_any_ai_enabled(&self, app: &AppContext) -> bool {
// Galaxy does not require Warp authentication for AI.
// AI is enabled only when the user hasn't explicitly disabled it, at least one local
// runtime is enabled, and there's no org policy blocking it.
*self.is_any_ai_enabled
&& self.has_enabled_ai_runtime()
&& !self.is_ai_disabled_due_to_remote_session_org_policy(app)
// Configuring and enabling a provider is the single source of truth for AI availability.
self.has_enabled_ai_runtime() && !self.is_ai_disabled_due_to_remote_session_org_policy(app)
}
/// Returns whether an OpenAI-compatible provider is enabled. The legacy global switch is
/// consulted only for the legacy single-endpoint configuration, which has no per-provider
/// enablement field.
pub fn is_openai_provider_enabled(&self) -> bool {
if !self.openai_providers.value().is_empty() {
return self
.openai_providers
.value()
.iter()
.any(|provider| provider.enabled);
}
*self.openai_enabled.value()
}
/// Returns whether an OpenAI-compatible provider is enabled and has at least one enabled model.
pub fn has_enabled_openai_provider(&self) -> bool {
if !self.openai_providers.value().is_empty() {
return self.openai_providers.value().iter().any(|provider| {
provider.enabled && provider.models.iter().any(|model| model.enabled)
});
}
self.is_openai_provider_enabled()
&& self.openai_models.value().iter().any(|model| model.enabled)
}
pub(crate) fn configured_acp_providers(&self) -> Vec<AcpProviderConfig> {
@@ -2409,11 +2434,11 @@ impl AISettings {
/// Returns whether Galaxy has a local model provider or agent runtime enabled.
pub fn has_enabled_ai_runtime(&self) -> bool {
*self.bedrock_enabled.value()
|| *self.openai_enabled.value()
(*self.bedrock_enabled.value() && !self.bedrock_models.value().is_empty())
|| self.has_enabled_openai_provider()
|| (cfg!(unix)
&& FeatureFlag::AgentClientProtocol.is_enabled()
&& *self.acp_enabled.value())
&& !self.enabled_acp_providers().is_empty())
}
pub fn default_session_mode(&self, app: &AppContext) -> DefaultSessionMode {
@@ -2465,9 +2490,7 @@ impl AISettings {
}
pub fn is_active_ai_enabled(&self, app: &galaxyui::AppContext) -> bool {
self.is_any_ai_enabled(app)
&& *self.is_active_ai_enabled_internal
&& AppExecutionMode::as_ref(app).allows_active_ai()
self.is_any_ai_enabled(app) && AppExecutionMode::as_ref(app).allows_active_ai()
}
pub fn is_prompt_suggestions_enabled(&self, app: &galaxyui::AppContext) -> bool {
+15
View File
@@ -433,6 +433,21 @@ fn orchestration_is_enabled_when_ai_is_enabled() {
initialize_settings_for_tests(&mut app);
add_ai_enablement_dependencies_for_test(&mut app);
AISettings::handle(&app).update(&mut app, |settings, ctx| {
settings
.bedrock_models
.set_value(
vec![BedrockModelConfig {
model_id: "test-model".to_string(),
display_name: "Test model".to_string(),
vision_supported: false,
use_rig: false,
}],
ctx,
)
.expect("Bedrock models should update");
});
AISettings::handle(&app).read(&app, |settings, ctx| {
assert!(settings.is_orchestration_enabled(ctx));
});
-32
View File
@@ -86,38 +86,6 @@ impl SettingsInitializer {
}
}
// Migrate NLD settings when AgentView is enabled.
//
// Explicitly set `nld_in_terminal_enabled_internal` for all users if
// it has not previously been set.
//
// For existing users, when the old, previously-global autodetection setting
// (`ai_autodetection_enabled_internal`) true, set `nld_in_terminal_enabled_internal` to
// true. Otherwise, explicitly set to `false`.
//
// Any further user modification of the setting will be via explicit update, so it'll
// be exempt from this logic, which is effectively one-time upon first startup of a binary
// containing this logic.
//
// TODO(zachbai): Remove this approximately 6 weeks from 2/5/26.
if FeatureFlag::AgentView.is_enabled() {
AISettings::handle(ctx).update(ctx, |ai_settings, ctx| {
if ai_settings
.nld_in_terminal_enabled_internal
.is_value_explicitly_set()
{
return;
}
let is_existing_user = auth_state.is_onboarded() == Some(true);
let was_global_autodetection_enabled_for_existing_user =
*ai_settings.ai_autodetection_enabled_internal && is_existing_user;
report_if_error!(ai_settings
.nld_in_terminal_enabled_internal
.set_value(was_global_autodetection_enabled_for_existing_user, ctx));
});
}
// Migrate the old `KeepThinkingExpanded` bool setting to the new
// `ThinkingDisplayMode` enum setting.
//
+1 -2
View File
@@ -14,8 +14,7 @@ use crate::themes::theme::{RespectSystemTheme, SelectedSystemThemes, ThemeKind};
define_settings_group!(ThemeSettings, settings: [
theme_kind: Theme {
type: ThemeKind,
// Note that for new users, we now override this default value in SettingsInitializer
// to set the default theme to Phenomenon.
// New installations start with Galaxy's built-in brand theme.
default: ThemeKind::default(),
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
+50 -7
View File
@@ -1,6 +1,11 @@
use galaxyui::elements::{
Align, CacheOption, ConstrainedBox, Container, CrossAxisAlignment, Element, Flex,
FormattedTextElement, Image, Padding, ParentElement, Text,
};
use galaxyui::fonts::Weight;
use galaxyui::text_layout::TextAlignment;
use galaxyui::{AppContext, Entity, SingletonEntity, View, ViewContext, ViewHandle};
use warpui::assets::asset_cache::AssetSource;
use warpui::elements::{Align, CacheOption, ConstrainedBox, Element, Image};
use super::settings_page::{
MatchData, PageType, SettingsPageEvent, SettingsPageMeta, SettingsPageViewHandle,
@@ -48,7 +53,7 @@ impl SettingsWidget for AboutPageWidget {
fn render(
&self,
_view: &AboutPageView,
_appearance: &Appearance,
appearance: &Appearance,
app: &AppContext,
) -> Box<dyn Element> {
let icon_file =
@@ -62,16 +67,54 @@ impl SettingsWidget for AboutPageWidget {
_ => "bundled/png/galaxy.png",
};
let icon = ConstrainedBox::new(
Image::new(
AssetSource::Bundled { path: image_path },
CacheOption::BySize,
)
.finish(),
)
.with_max_height(144.)
.with_max_width(144.)
.finish();
let title = FormattedTextElement::from_str("Galaxy", appearance.ui_font_family(), 28.)
.with_weight(Weight::Bold)
.with_color(appearance.theme().active_ui_text_color().into_solid())
.with_alignment(TextAlignment::Center)
.finish();
let version = Text::new(
format!("Version {}", env!("CARGO_PKG_VERSION")),
appearance.ui_font_family(),
12.,
)
.with_color(appearance.theme().nonactive_ui_text_color().into())
.finish();
let description = FormattedTextElement::from_str(
"Galaxy is a local-first terminal and AI workspace built for developers. Your settings, terminal data, conversations, and Galaxy Drive content stay on this machine in your Galaxy directory. Galaxy sends request data only to the AI providers you configure.",
appearance.ui_font_family(),
14.,
)
.with_color(appearance.theme().nonactive_ui_text_color().into_solid())
.with_alignment(TextAlignment::Center)
.with_line_height_ratio(1.35)
.finish();
Align::new(
ConstrainedBox::new(
Image::new(
AssetSource::Bundled { path: image_path },
CacheOption::BySize,
Container::new(
Flex::column()
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_spacing(12.)
.with_child(icon)
.with_child(title)
.with_child(version)
.with_child(description)
.finish(),
)
.with_padding(Padding::uniform(24.))
.finish(),
)
.with_max_height(144.)
.with_max_width(144.)
.with_max_width(560.)
.finish(),
)
.finish()
+6 -147
View File
@@ -101,7 +101,7 @@ use crate::settings::{
BedrockEnabled, BedrockModelConfig, CodeSettings, CodebaseContextEnabled, CrosscheckEnabled,
FileBasedMcpEnabled, GitOperationsAutogenEnabled, IncludeAgentCommandsInHistory, InputSettings,
IntelligentAutosuggestionsEnabled, LongRunningCommandSubmissionMode, MemoryEnabled,
NLDInTerminalEnabled, NaturalLanguageAutosuggestionsEnabled, OpenAIEnabled, OpenAIModelConfig,
NLDInTerminalEnabled, NaturalLanguageAutosuggestionsEnabled, OpenAIModelConfig,
OpenAIProviderConfig, OrchestrationMessageDisplayMode, PromptSubmissionMode,
RuleSuggestionsEnabled, SharedBlockTitleGenerationEnabled, ShouldRenderCLIAgentToolbar,
ShouldRenderUseAgentToolbarForUserCommands, ShowAgentTips, ShowConversationHistory,
@@ -129,8 +129,6 @@ use crate::{
};
const CONTENT_FONT_SIZE: f32 = 12.;
const PRIMARY_HEADER_FONT_SIZE: f32 = 24.;
const AI_SETTINGS_DROPDOWN_WIDTH: f32 = 250.;
const AI_SETTINGS_DROPDOWN_MAX_HEIGHT: f32 = 250.;
const CONTEXT_WINDOW_SLIDER_WIDTH: f32 = 220.;
@@ -151,7 +149,7 @@ const WISPR_FLOW_URL: &str = "https://wisprflow.ai/";
/// When `None`, the page shows all widgets (legacy/full view).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AISubpage {
/// The main Galaxy Agent page: global AI toggle + Active AI + Input + Other sections.
/// The main Galaxy Agent page: suggestions, input, and other agent settings.
WarpAgent,
/// Agent profiles and permissions.
Profiles,
@@ -217,28 +215,6 @@ pub fn init_actions_from_parent_view<T: Action + Clone>(
app,
);
ToggleSettingActionPair::add_toggle_setting_action_pairs_as_bindings(
vec![ToggleSettingActionPair::new(
"AI",
builder(SettingsAction::AI(AISettingsPageAction::ToggleGlobalAI)),
context,
flags::IS_ANY_AI_ENABLED,
)
.with_group(bindings::BindingGroup::WarpAi)],
app,
);
ToggleSettingActionPair::add_toggle_setting_action_pairs_as_bindings(
vec![ToggleSettingActionPair::new(
"Active AI",
builder(SettingsAction::AI(AISettingsPageAction::ToggleActiveAI)),
&(context.clone() & id!(flags::IS_ANY_AI_ENABLED)),
flags::IS_ACTIVE_AI_ENABLED,
)
.with_group(bindings::BindingGroup::WarpAi)],
app,
);
ToggleSettingActionPair::add_toggle_setting_action_pairs_as_bindings(
vec![ToggleSettingActionPair::new(
if FeatureFlag::AgentView.is_enabled() {
@@ -2115,7 +2091,6 @@ impl AISettingsPageView {
match subpage {
None => {
// Full page: all widgets (legacy behavior)
widgets.push(Box::new(GlobalAIWidget::default()));
if ai_settings
.intelligent_autosuggestions_enabled_internal
.is_supported_on_current_platform()
@@ -2158,8 +2133,7 @@ impl AISettingsPageView {
widgets.push(Box::new(OtherAIWidget::default()));
}
Some(AISubpage::WarpAgent) => {
// Galaxy Agent page: global toggle + Active AI + Input + Other
widgets.push(Box::new(GlobalAIWidget::default()));
// Galaxy Agent page: suggestions, input, and other agent settings.
if ai_settings
.intelligent_autosuggestions_enabled_internal
.is_supported_on_current_platform()
@@ -4127,85 +4101,8 @@ fn render_ai_list(
.finish()
}
#[derive(Default)]
struct GlobalAIWidget {
switch_state: SwitchStateHandle,
}
impl SettingsWidget for GlobalAIWidget {
type View = AISettingsPageView;
fn search_terms(&self) -> &str {
"galaxy agent global ai a.i. active next command prompt code diffs suggestion suggested suggestions \
agent mode natural language detection input hint"
}
fn render(
&self,
_view: &Self::View,
appearance: &Appearance,
app: &AppContext,
) -> Box<dyn Element> {
let ui_builder = appearance.ui_builder();
let is_ai_disabled_due_to_remote_session_org_policy =
AISettings::as_ref(app).is_ai_disabled_due_to_remote_session_org_policy(app);
let mut row = Flex::row()
.with_main_axis_size(MainAxisSize::Max)
.with_main_axis_alignment(MainAxisAlignment::SpaceBetween)
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(
Text::new_inline(
"Galaxy Agent",
appearance.ui_font_family(),
PRIMARY_HEADER_FONT_SIZE,
)
.with_style(Properties::default().weight(Weight::Bold))
.with_color(appearance.theme().active_ui_text_color().into())
.finish(),
);
if is_ai_disabled_due_to_remote_session_org_policy {
row.add_child(
ConstrainedBox::new(
Container::new(
Text::new("Your organization disallows AI when the active pane contains content from a remote session", appearance.ui_font_family(), 12.)
.with_color(appearance.theme().ui_warning_color())
.finish()
)
.with_padding_left(8.)
.with_padding_right(8.)
.finish()
)
.with_max_width(400.)
.finish()
);
}
row.add_child(
Container::new(
ui_builder
.switch(self.switch_state.clone())
.check(AISettings::as_ref(app).is_any_ai_enabled(app))
.build()
.on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(AISettingsPageAction::ToggleGlobalAI);
})
.finish(),
)
.with_padding_right(TOGGLE_BUTTON_RIGHT_PADDING)
.finish(),
);
Container::new(row.finish())
.with_padding_bottom(15.)
.finish()
}
}
#[derive(Default)]
struct ActiveAIWidget {
active_ai_toggle: SwitchStateHandle,
intelligent_autosuggestions_toggle: SwitchStateHandle,
prompt_suggestions_toggle: SwitchStateHandle,
code_suggestions_toggle: SwitchStateHandle,
@@ -4452,38 +4349,12 @@ impl SettingsWidget for ActiveAIWidget {
appearance: &Appearance,
app: &AppContext,
) -> Box<dyn Element> {
let ai_settings = AISettings::as_ref(app);
let is_any_ai_enabled = ai_settings.is_any_ai_enabled(app);
let mut column = Flex::column()
.with_child(render_separator(appearance))
.with_child(
Container::new(
Flex::row()
.with_main_axis_size(MainAxisSize::Max)
.with_main_axis_alignment(MainAxisAlignment::SpaceBetween)
.with_child(
build_sub_header(
appearance,
"Active AI",
Some(styles::header_font_color(is_any_ai_enabled, app)),
)
.finish(),
)
.with_child(
Container::new(render_ai_feature_switch(
self.active_ai_toggle.clone(),
*ai_settings.is_active_ai_enabled_internal,
is_any_ai_enabled,
AISettingsPageAction::ToggleActiveAI,
app,
))
.with_padding_right(TOGGLE_BUTTON_RIGHT_PADDING)
.finish(),
)
.finish(),
)
.with_padding_bottom(HEADER_PADDING)
.finish(),
Container::new(build_sub_header(appearance, "AI suggestions", None).finish())
.with_padding_bottom(HEADER_PADDING)
.finish(),
);
if self.is_next_command_toggleable(app) {
@@ -7230,7 +7101,6 @@ struct AcpProviderCardState {
struct ProviderSettingsWidget {
provider_type: ProviderSetupProviderType,
enabled_toggle: SwitchStateHandle,
bedrock_enabled_toggle: SwitchStateHandle,
add_openai_provider_button: ViewHandle<ActionButton>,
add_litellm_provider_button: ViewHandle<ActionButton>,
@@ -7342,7 +7212,6 @@ impl ProviderSettingsWidget {
});
Self {
provider_type,
enabled_toggle: SwitchStateHandle::default(),
bedrock_enabled_toggle: SwitchStateHandle::default(),
add_openai_provider_button,
add_litellm_provider_button,
@@ -7990,16 +7859,6 @@ impl SettingsWidget for ProviderSettingsWidget {
ProviderSetupProviderType::OpenAI
| ProviderSetupProviderType::LiteLLM
| ProviderSetupProviderType::ChatGPTSubscription => {
column.add_child(render_ai_setting_toggle::<OpenAIEnabled>(
"Enable direct providers",
AISettingsPageAction::ToggleOpenAIEnabled,
*settings.openai_enabled.value(),
true,
self.enabled_toggle.clone(),
&RefCell::new(HashMap::new()),
app,
));
if is_setup_visible {
column.add_child(Self::render_inline_setup(appearance, view));
}
+2 -28
View File
@@ -104,8 +104,8 @@ pub use code_page::CodeSettingsPageView;
pub use features_page::FeaturesPageAction;
pub use privacy_page::PrivacyPageAction;
pub use settings_page::{
render_body_item_label, render_info_icon, render_input_list, render_separator, AdditionalInfo,
InputListItem, LocalOnlyIconState, ToggleState,
render_body_item_label, render_input_list, render_separator, AdditionalInfo, InputListItem,
LocalOnlyIconState, ToggleState,
};
pub use teams_page::{OpenTeamsSettingsModalArgs, TeamsInviteOption};
@@ -1216,11 +1216,6 @@ impl SettingsView {
let warp_drive_page_handle =
ctx.add_typed_action_view(warp_drive_page::WarpDriveSettingsPageView::new);
let platform_page_handle = ctx.add_typed_action_view(platform_page::PlatformPageView::new);
ctx.subscribe_to_view(&platform_page_handle, |me, _, event, ctx| {
me.handle_platform_page_event(event, ctx);
});
// MCP Servers page
let mcp_servers_page_handle = ctx.add_typed_action_view(MCPServersSettingsPageView::new);
ctx.subscribe_to_view(&mcp_servers_page_handle, |me, _, event, ctx| {
@@ -1261,7 +1256,6 @@ impl SettingsView {
SettingsPage::new(appearance_page_handle),
SettingsPage::new(features_page_handle),
SettingsPage::new(keybindings_handle),
SettingsPage::new(platform_page_handle),
SettingsPage::new(warpify_page_handle),
SettingsPage::new(warp_drive_page_handle),
];
@@ -1730,9 +1724,6 @@ impl SettingsView {
ctx: &mut ViewContext<Self>,
) {
match event {
PrivacyPageViewEvent::LaunchNetworkLogging => {
ctx.emit(SettingsViewEvent::LaunchNetworkLogging);
}
PrivacyPageViewEvent::ShowAddRegexModal => {
// Modal rendering is handled in get_modal_content_for_page
ctx.notify();
@@ -1744,23 +1735,6 @@ impl SettingsView {
}
}
fn handle_platform_page_event(
&mut self,
event: &platform_page::PlatformPageViewEvent,
ctx: &mut ViewContext<Self>,
) {
match event {
platform_page::PlatformPageViewEvent::ShowCreateApiKeyModal => {
// Modal rendering is handled in get_modal_content_for_page
ctx.notify();
}
platform_page::PlatformPageViewEvent::HideCreateApiKeyModal => {
// Modal rendering is handled in get_modal_content_for_page
ctx.notify();
}
}
}
fn handle_mcp_servers_page_event(
&mut self,
event: &MCPServersSettingsPageEvent,
+20 -98
View File
@@ -4,13 +4,12 @@ use std::collections::{HashMap, HashSet};
use std::sync::LazyLock;
use std::time::Duration;
use galaxy_core::context_flag::ContextFlag;
use galaxy_core::features::FeatureFlag;
use galaxy_core::ui::theme::color::internal_colors;
use galaxy_core::ui::theme::GalaxyTheme;
use galaxyui::elements::{
Align, ChildAnchor, ChildView, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment,
Empty, Expanded, Flex, Hoverable, MainAxisAlignment, MainAxisSize, MouseStateHandle,
ChildAnchor, ChildView, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Empty,
Expanded, Flex, Hoverable, MainAxisAlignment, MainAxisSize, MouseStateHandle,
OffsetPositioning, ParentAnchor, ParentElement, ParentOffsetBounds, Radius, Rect, Shrinkable,
Stack, Text,
};
@@ -79,9 +78,7 @@ const TELEMETRY_DESCRIPTION_OLD: &str =
const TELEMETRY_TITLE: &str = "Help improve Galaxy";
const TELEMETRY_DESCRIPTION: &str =
"App analytics help us make the product better for you. We may collect \
certain console interactions to improve Warp's AI capabilities.";
const TELEMETRY_DOCS_URL: &str =
"https://docs.warp.dev/support-and-community/privacy-and-security/privacy#what-telemetry-data-does-warp-collect-and-why";
certain app interactions to improve Galaxy.";
pub struct PrivacyPageView {
page: PageType<Self>,
@@ -102,7 +99,6 @@ pub struct PrivacyPageView {
#[derive(Clone, Copy)]
pub enum PrivacyPageViewEvent {
LaunchNetworkLogging,
ShowAddRegexModal,
HideAddRegexModal,
}
@@ -203,14 +199,12 @@ impl PrivacyPageView {
}
fn build_page() -> PageType<Self> {
let mut widgets: Vec<Box<dyn SettingsWidget<View = Self>>> = vec![
let widgets: Vec<Box<dyn SettingsWidget<View = Self>>> = vec![
Box::new(AIProviderPrivacyWidget),
Box::new(SecretRedactionWidget::default()),
Box::new(AppAnalyticsWidget::default()),
Box::new(CrashReportsWidget::default()),
];
if ContextFlag::NetworkLogConsole.is_enabled() {
widgets.push(Box::new(NetworkLogWidget::default()));
}
PageType::new_uncategorized(widgets, Some("Privacy"))
}
@@ -343,10 +337,6 @@ impl PrivacyPageView {
ctx.notify();
}
fn launch_network_logging(&mut self, ctx: &mut ViewContext<Self>) {
ctx.emit(PrivacyPageViewEvent::LaunchNetworkLogging);
}
fn show_add_regex_modal(&mut self, ctx: &mut ViewContext<Self>) {
self.add_regex_modal_state.open(ctx);
ctx.emit(PrivacyPageViewEvent::ShowAddRegexModal);
@@ -443,7 +433,6 @@ pub enum PrivacyPageAction {
ToggleHideSecretsInBlockList,
SetSecretDisplayMode(SecretDisplayMode),
ToggleTelemetry,
LaunchNetworkLogging,
RemoveCustomRegex(usize),
AddAllRecommendedRegexes,
ShowAddRegexModal,
@@ -532,7 +521,6 @@ impl TypedActionView for PrivacyPageView {
});
ctx.notify();
}
PrivacyPageAction::LaunchNetworkLogging => self.launch_network_logging(ctx),
PrivacyPageAction::RemoveCustomRegex(idx) => {
self.queue_regex_removal(*idx, ctx);
}
@@ -1321,7 +1309,6 @@ impl SettingsWidget for SecretRedactionWidget {
#[derive(Default)]
struct AppAnalyticsWidget {
switch_state: SwitchStateHandle,
docs_link_mouse_state: MouseStateHandle,
zdr_badge_mouse_state: MouseStateHandle,
}
@@ -1491,24 +1478,6 @@ impl SettingsWidget for AppAnalyticsWidget {
.finish(),
);
column.add_child(
Align::new(
ui_builder
.link(
"Read more about Galaxy's use of data".into(),
Some(TELEMETRY_DOCS_URL.into()),
None,
self.docs_link_mouse_state.clone(),
)
.soft_wrap(false)
.build()
.with_margin_bottom(styles::DESCRIPTION_MARGIN_BOTTOM)
.finish(),
)
.left()
.finish(),
);
column.finish()
}
}
@@ -1565,16 +1534,13 @@ impl SettingsWidget for CrashReportsWidget {
}
}
#[derive(Default)]
struct NetworkLogWidget {
link_mouse_state: MouseStateHandle,
}
struct AIProviderPrivacyWidget;
impl SettingsWidget for NetworkLogWidget {
impl SettingsWidget for AIProviderPrivacyWidget {
type View = PrivacyPageView;
fn search_terms(&self) -> &str {
"network log audit console data collection"
"local privacy data ai providers storage conversations terminal drive"
}
fn render(
@@ -1583,62 +1549,18 @@ impl SettingsWidget for NetworkLogWidget {
appearance: &Appearance,
_app: &AppContext,
) -> Box<dyn Element> {
let ui_builder = appearance.ui_builder();
Flex::column()
.with_child(render_body_item::<PrivacyPageAction>(
"Network log console".into(),
None,
// Not rendering a setting, so no need to show local only icon state.
LocalOnlyIconState::Hidden,
ToggleState::Enabled,
appearance,
Empty::new().finish(),
None,
))
.with_child(
ui_builder
.paragraph(
"This tool uses AWS Bedrock and is subject to its data collection practices. \
No data is stored locally by Galaxy."
.to_owned(),
)
.with_style(UiComponentStyles {
font_color: Some(
appearance
.theme()
.sub_text_color(appearance.theme().surface_2())
.into_solid(),
),
margin: Some(
Coords::default()
.top(styles::DESCRIPTION_NEGATIVE_MARGIN_OFFSET)
.bottom(styles::DESCRIPTION_LINE_MARGIN_BOTTOM),
),
..Default::default()
})
.build()
.finish(),
)
.with_child(
Align::new(
ui_builder
.link(
"View network logging".to_owned(),
None,
Some(Box::new(|ctx| {
ctx.dispatch_typed_action(PrivacyPageAction::LaunchNetworkLogging);
})),
self.link_mouse_state.clone(),
)
.soft_wrap(false)
.build()
.with_margin_bottom(styles::DESCRIPTION_MARGIN_BOTTOM)
.finish(),
)
.left()
.finish(),
)
.finish()
render_body_item::<PrivacyPageAction>(
"Local-first data".into(),
None,
LocalOnlyIconState::Hidden,
ToggleState::Enabled,
appearance,
Empty::new().finish(),
Some(
"Galaxy keeps your settings, terminal data, conversations, and Galaxy Drive content on this machine. When you use AI, Galaxy sends only the request context needed to the AI providers you have configured."
.into(),
),
)
}
}
+1 -60
View File
@@ -59,7 +59,6 @@ const ALTERNATING_LIST_ITEM_PADDING: f32 = 8.0;
const GREY_TEXT_OPACITY: u8 = 60;
const MIN_PAGE_WIDTH: f32 = 520.;
const MAX_PAGE_WIDTH: f32 = 800.;
const INFO_TOOLTIP_MAX_WIDTH: f32 = 320.;
/// Left margin for top-level sidebar nav items (pages and umbrella labels).
pub(super) const NAV_ITEM_LEFT_MARGIN: f32 = 12.;
@@ -529,61 +528,6 @@ impl LocalOnlyIconState {
}
}
pub fn render_info_icon<T: Clone + Action>(
appearance: &Appearance,
additional_info: AdditionalInfo<T>,
) -> Box<dyn Element> {
let tooltip_text = additional_info
.tooltip_override_text
.unwrap_or("Click to learn more in docs".to_owned());
let icon = Container::new(
ConstrainedBox::new(
Icon::Info
.to_warpui_icon(appearance.theme().active_ui_text_color())
.finish(),
)
.with_width(13.)
.with_height(13.)
.finish(),
)
.finish();
let mut info_button = Hoverable::new(additional_info.mouse_state.clone(), move |state| {
let mut stack = Stack::new().with_child(icon);
if state.is_hovered() {
let tool_tip = ConstrainedBox::new(
appearance
.ui_builder()
.tool_tip(tooltip_text)
.build()
.finish(),
)
.with_max_width(INFO_TOOLTIP_MAX_WIDTH)
.finish();
stack.add_positioned_child(
tool_tip,
OffsetPositioning::offset_from_parent(
vec2f(0., -3.),
ParentOffsetBounds::WindowByPosition,
ParentAnchor::TopMiddle,
ChildAnchor::BottomMiddle,
),
);
}
stack.finish()
})
.with_cursor(Cursor::PointingHand);
if let Some(on_click_action) = additional_info.on_click_action {
info_button = info_button
.on_click(move |ctx, _, _| ctx.dispatch_typed_action(on_click_action.clone()));
}
Container::new(Box::new(info_button))
.with_margin_left(4.)
.finish()
}
pub fn render_local_only_icon(
appearance: &Appearance,
mouse_state: MouseStateHandle,
@@ -675,8 +619,6 @@ pub fn render_body_item_label_internal<T: Clone + Action>(
let label = label.finish();
if let Some(additional_info) = additional_info {
// Construct a child element for the secondary text, if necessary, before
// `additional_info` gets moved into `render_info_icon()`.
let secondary_text_child =
if let Some(secondary_text) = additional_info.secondary_text.clone() {
let warp_theme = appearance.theme();
@@ -705,8 +647,7 @@ pub fn render_body_item_label_internal<T: Clone + Action>(
let mut row = Flex::row()
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(label)
.with_child(render_info_icon(appearance, additional_info));
.with_child(label);
if let LocalOnlyIconState::Visible {
mouse_state,
custom_tooltip,
+4 -17
View File
@@ -7,11 +7,10 @@ use galaxyui::ui_components::switch::SwitchStateHandle;
use galaxyui::{
id, Action, AppContext, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle,
};
use warpui::elements::{Element, MouseStateHandle};
use warpui::elements::Element;
use super::settings_page::{
render_body_item, AdditionalInfo, MatchData, PageType, SettingsPageMeta,
SettingsPageViewHandle, SettingsWidget,
render_body_item, MatchData, PageType, SettingsPageMeta, SettingsPageViewHandle, SettingsWidget,
};
use super::{
flags, LocalOnlyIconState, SettingActionPairContexts, SettingActionPairDescriptions,
@@ -23,7 +22,6 @@ use crate::drive::settings::WarpDriveSettings;
#[derive(Debug, Clone)]
pub enum WarpDriveSettingsPageAction {
ToggleShowWarpDrive,
OpenUrl(String),
}
pub fn init_actions_from_parent_view<T: Action + Clone>(
@@ -78,9 +76,6 @@ impl TypedActionView for WarpDriveSettingsPageView {
});
ctx.notify();
}
WarpDriveSettingsPageAction::OpenUrl(url) => {
ctx.open_url(url.as_str());
}
}
}
}
@@ -126,7 +121,6 @@ impl From<ViewHandle<WarpDriveSettingsPageView>> for SettingsPageViewHandle {
#[derive(Default)]
struct WarpDriveToggleWidget {
switch_state: SwitchStateHandle,
info_icon_mouse_state: MouseStateHandle,
}
impl SettingsWidget for WarpDriveToggleWidget {
@@ -146,14 +140,7 @@ impl SettingsWidget for WarpDriveToggleWidget {
render_body_item::<WarpDriveSettingsPageAction>(
"Galaxy Drive".into(),
Some(AdditionalInfo {
mouse_state: self.info_icon_mouse_state.clone(),
on_click_action: Some(WarpDriveSettingsPageAction::OpenUrl(
"https://docs.warp.dev/knowledge-and-collaboration/warp-drive".to_string(),
)),
secondary_text: None,
tooltip_override_text: None,
}),
None,
LocalOnlyIconState::Hidden,
ToggleState::Enabled,
appearance,
@@ -166,7 +153,7 @@ impl SettingsWidget for WarpDriveToggleWidget {
ctx.dispatch_typed_action(WarpDriveSettingsPageAction::ToggleShowWarpDrive);
})
.finish(),
Some("Galaxy Drive is a workspace in your terminal where you can save Workflows, Notebooks, Prompts, and Environment Variables for personal use or to share with a team.".into()),
Some("Galaxy Drive is a local workspace for Workflows, Notebooks, Prompts, and Environment Variables. Its contents are stored on this machine in your ~/.galaxy directory.".into()),
)
}
}
@@ -124,9 +124,11 @@ fn test_non_ai_commands_remain_active_when_ai_is_disabled() {
let slash_command_data_source =
input.read(&app, |input, _| input.slash_command_data_source.clone());
// Disable AI globally.
// AI is unavailable when no provider runtime is enabled.
AISettings::handle(&app).update(&mut app, |settings, ctx| {
report_if_error!(settings.is_any_ai_enabled.set_value(false, ctx));
report_if_error!(settings.bedrock_enabled.set_value(false, ctx));
report_if_error!(settings.openai_enabled.set_value(false, ctx));
report_if_error!(settings.acp_enabled.set_value(false, ctx));
});
slash_command_data_source.read(&app, |data_source, _| {
+3 -3
View File
@@ -47,13 +47,13 @@ pub enum ThemeKind {
ReceivedReferralReward,
#[schemars(description = "Adeberry")]
Adeberry,
#[default]
#[schemars(description = "Galaxy Dark")]
GalaxyDark,
#[schemars(description = "Galaxy Day")]
GalaxyDay,
#[schemars(description = "Phenomenon")]
Phenomenon,
#[default]
#[schemars(description = "Dark")]
Dark,
#[schemars(description = "Dracula")]
@@ -566,8 +566,8 @@ impl RespectSystemTheme {
impl Default for SelectedSystemThemes {
fn default() -> Self {
Self {
light: ThemeKind::Light,
dark: ThemeKind::Dark,
light: ThemeKind::GalaxyDay,
dark: ThemeKind::GalaxyDark,
}
}
}