first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
+42 -13
View File
@@ -4,24 +4,23 @@ pub mod util;
#[cfg_attr(target_family = "wasm", path = "wasm.rs")]
mod imp;
use crate::tab_configs::{TabConfig, TabConfigError};
use crate::themes::theme::GalaxyThemeConfig;
use crate::{
launch_configs::launch_config::LaunchConfig, themes::theme::ThemeKind,
workflows::workflow::Workflow,
};
use galaxy_core::ui::theme::GalaxyTheme;
use galaxyui::{Entity, ModelContext, SingletonEntity};
use lazy_static::lazy_static;
#[cfg(feature = "local_fs")]
use std::path::Path;
use std::path::PathBuf;
#[cfg(test)]
pub(crate) use imp::load_tab_configs;
#[cfg(feature = "local_fs")]
pub use imp::load_workflows;
pub use imp::{load_launch_configs, load_theme_configs};
use lazy_static::lazy_static;
use galaxy_core::ui::theme::WarpTheme;
use warpui::{Entity, ModelContext, SingletonEntity};
use crate::ai::custom_model_routers::{CustomModelRouter, ModelConfigError};
use crate::launch_configs::launch_config::LaunchConfig;
use crate::tab_configs::{TabConfig, TabConfigError};
use crate::themes::theme::{ThemeKind, WarpThemeConfig};
use crate::workflows::workflow::Workflow;
lazy_static! {
pub static ref LAUNCH_CONFIG_COMMENT: String = format!(
@@ -63,6 +62,12 @@ pub enum GalaxyConfigUpdateEvent {
/// Emitted when one or more tab config files failed to parse.
#[cfg_attr(target_family = "wasm", allow(dead_code))]
TabConfigErrors(Vec<TabConfigError>),
/// The local `custom_model_routers/` custom model routers were created, modified, or deleted.
#[cfg_attr(not(feature = "local_fs"), allow(dead_code))]
ModelConfigs,
/// Emitted when one or more `custom_model_routers/` files failed to parse.
#[cfg_attr(target_family = "wasm", allow(dead_code))]
ModelConfigErrors(Vec<ModelConfigError>),
/// The settings file (`settings.toml`) was created, modified, or deleted.
#[cfg_attr(not(feature = "local_fs"), expect(dead_code))]
Settings,
@@ -89,6 +94,12 @@ pub struct GalaxyConfig {
tab_config_errors: Vec<TabConfigError>,
theme_config: GalaxyThemeConfig,
local_user_workflows: Vec<Workflow>,
/// User-defined custom model routers loaded from `~/.warp/custom_model_routers/`.
#[cfg_attr(target_family = "wasm", allow(dead_code))]
custom_model_routers: Vec<CustomModelRouter>,
/// Errors for `custom_model_routers/` files that failed to parse.
#[cfg_attr(target_family = "wasm", allow(dead_code))]
custom_model_router_errors: Vec<ModelConfigError>,
}
/// Platform-independent parts of GalaxyConfig.
@@ -120,7 +131,19 @@ impl GalaxyConfig {
&self.local_user_workflows
}
/// Saving the newly created launch configuration to the GalaxyConfig that we currently
/// The local (YAML-sourced) custom model routers.
#[cfg_attr(target_family = "wasm", allow(dead_code))]
pub fn custom_model_routers(&self) -> &Vec<CustomModelRouter> {
&self.custom_model_routers
}
/// Parse errors for `custom_model_routers/` files that failed to load.
#[cfg_attr(target_family = "wasm", allow(dead_code))]
pub fn custom_model_router_errors(&self) -> &Vec<ModelConfigError> {
&self.custom_model_router_errors
}
/// Saving the newly created launch configuration to the WarpConfig that we currently
/// have.
pub fn append_launch_config(
&mut self,
@@ -189,11 +212,17 @@ pub fn launch_configs_dir() -> PathBuf {
}
/// Returns the path to the directory containing the user's tab configs.
#[cfg_attr(target_family = "wasm", expect(dead_code))]
pub fn tab_configs_dir() -> PathBuf {
base_dir().join("tab_configs")
}
/// Returns the path to the directory containing the user's custom model router
/// configs (`~/.warp/custom_model_routers/`). Each file defines a single router.
#[cfg_attr(target_family = "wasm", expect(dead_code))]
pub fn custom_model_routers_dir() -> PathBuf {
base_dir().join("custom_model_routers")
}
/// Returns the path to the directory containing the built-in default tab configs.
/// These are shipped with Warp and user-editable (Warp does not overwrite modifications).
#[cfg_attr(target_family = "wasm", expect(dead_code))]
@@ -400,5 +429,5 @@ impl Entity for GalaxyConfig {
impl SingletonEntity for GalaxyConfig {}
#[cfg(test)]
#[path = "mod_test.rs"]
#[path = "mod_tests.rs"]
mod tests;
@@ -1,8 +1,8 @@
use super::*;
use std::collections::HashMap;
use std::io::Write;
use std::path::Path;
use super::*;
use crate::launch_configs::launch_config::PaneTemplateType;
use crate::tab_configs::render_tab_config;
use crate::tab_configs::tab_config::{generated_worktree_repo_dir, TabConfigPaneType};
+116 -13
View File
@@ -1,13 +1,23 @@
use std::fs;
use std::io;
use std::io::Write;
use std::path::Path;
use std::{fs, io};
use anyhow::{anyhow, Result};
use galaxyui::{ModelContext, SingletonEntity};
use itertools::Itertools;
use repo_metadata::RepositoryUpdate;
use galaxyui::{ModelContext, ModelHandle, SingletonEntity};
use super::util::{
for_each_dir_entry, has_name, is_config_file, parse_model_config_dir_entry,
parse_multi_launch_config_dir_entry, parse_multi_workflow_dir_entry,
parse_single_theme_dir_entry, parse_tab_config_dir_entry,
};
use super::{
custom_model_routers_dir, launch_configs_dir, tab_configs_dir, themes_dir, workflows_dir,
WarpConfigUpdateEvent, LAUNCH_CONFIG_COMMENT,
};
use crate::ai::custom_model_routers::{CustomModelRouter, ModelConfigError};
use crate::features::FeatureFlag;
use crate::galaxy_managed_paths_watcher::{
repository_update_touches_path, repository_update_touches_prefix, GalaxyManagedPathsWatcher,
@@ -18,16 +28,7 @@ use crate::tab_configs::{TabConfig, TabConfigError};
use crate::themes::theme::GalaxyThemeConfig;
use crate::workflows::workflow::Workflow;
use super::util::{
for_each_dir_entry, has_name, is_config_file, parse_multi_launch_config_dir_entry,
parse_multi_workflow_dir_entry, parse_single_theme_dir_entry, parse_tab_config_dir_entry,
};
use super::{
launch_configs_dir, tab_configs_dir, themes_dir, workflows_dir, GalaxyConfigUpdateEvent,
LAUNCH_CONFIG_COMMENT,
};
impl super::GalaxyConfig {
impl super::WarpConfig {
pub fn new(ctx: &mut ModelContext<Self>) -> Self {
// Load launch configs, and workflows from disk asynchronously on a background
// thread.
@@ -62,6 +63,19 @@ impl super::GalaxyConfig {
ctx.emit(GalaxyConfigUpdateEvent::LocalUserWorkflows);
},
);
if FeatureFlag::CustomModelRouters.is_enabled() {
let _ = ctx.spawn(
async move { load_model_configs(&custom_model_routers_dir()) },
|me, (models, errors), ctx| {
me.custom_model_routers = models;
me.custom_model_router_errors = errors;
ctx.emit(WarpConfigUpdateEvent::ModelConfigs);
// Don't emit ModelConfigErrors on startup — like tab configs,
// the error toast should only appear when the user saves a
// file, not on app restart.
},
);
}
ctx.subscribe_to_model(
&GalaxyManagedPathsWatcher::handle(ctx),
Self::handle_warp_managed_paths_event,
@@ -75,7 +89,8 @@ impl super::GalaxyConfig {
fn handle_warp_managed_paths_event(
&mut self,
event: &GalaxyManagedPathsWatcherEvent,
_: ModelHandle<WarpManagedPathsWatcher>,
event: &WarpManagedPathsWatcherEvent,
ctx: &mut ModelContext<Self>,
) {
let GalaxyManagedPathsWatcherEvent::FilesChanged(update) = event;
@@ -128,6 +143,23 @@ impl super::GalaxyConfig {
);
}
if FeatureFlag::CustomModelRouters.is_enabled()
&& update_touches_dir(update, &custom_model_routers_dir())
{
let dir_path = custom_model_routers_dir();
let _ = ctx.spawn(
async move { load_model_configs(&dir_path) },
|me, (models, errors), ctx| {
me.custom_model_routers = models;
me.custom_model_router_errors = errors.clone();
ctx.emit(WarpConfigUpdateEvent::ModelConfigs);
if !errors.is_empty() {
ctx.emit(WarpConfigUpdateEvent::ModelConfigErrors(errors));
}
},
);
}
if FeatureFlag::SettingsFile.is_enabled()
&& update_touches_path(update, &crate::settings::user_preferences_toml_file_path())
{
@@ -135,6 +167,53 @@ impl super::GalaxyConfig {
}
}
/// Writes a custom model router to disk as a YAML file.
///
/// When `existing_path` is provided (editing) the file at that path is
/// overwritten; otherwise a new file is created under
/// `custom_model_routers_dir()`. The file name is derived from `name` by
/// lowercasing and replacing non-alphanumeric characters (except `-`) with
/// `_`. If the candidate path already exists, a numeric suffix is appended
/// (`_2`, `_3`, …) until a free slot is found. Returns the path written to.
#[cfg(feature = "local_fs")]
pub fn save_custom_model_router(
name: &str,
yaml: &str,
existing_path: Option<&std::path::Path>,
) -> anyhow::Result<std::path::PathBuf> {
let dir = custom_model_routers_dir();
std::fs::create_dir_all(&dir)
.map_err(|e| anyhow::anyhow!("could not create custom_model_routers dir: {e}"))?;
let path = if let Some(p) = existing_path {
p.to_path_buf()
} else {
let sanitized = name
.to_lowercase()
.replace(|c: char| !c.is_alphanumeric() && c != '-', "_");
let candidate = dir.join(format!("{sanitized}.yaml"));
if candidate.exists() {
(2..)
.map(|n| dir.join(format!("{sanitized}_{n}.yaml")))
.find(|p| !p.exists())
.expect("infinite iterator always finds a free slot")
} else {
candidate
}
};
std::fs::write(&path, yaml)
.map_err(|e| anyhow::anyhow!("could not write router file: {e}"))?;
Ok(path)
}
/// Deletes a custom model router file from disk.
/// The filesystem watcher in [`Self::handle_warp_managed_paths_event`] will
/// pick up the deletion and reload `custom_model_routers`.
#[cfg(feature = "local_fs")]
pub fn delete_custom_model_router(source_path: &std::path::Path) -> anyhow::Result<()> {
std::fs::remove_file(source_path)
.map_err(|e| anyhow::anyhow!("could not delete router file: {e}"))
}
/// This method takes a file name candidate (appends .yaml if missing) and a LaunchConfig as
/// arguments. It saves the file and returns the filename used if successful.
#[cfg(feature = "local_fs")]
@@ -191,6 +270,30 @@ pub fn load_launch_configs(launch_config_path: &Path) -> Vec<LaunchConfig> {
.collect_vec()
}
/// Loads custom model routers from the config directory at `dir_path`
/// (`~/.warp/custom_model_routers/`), where each file defines a single router.
/// Returns the parsed routers (sorted by display name) and any per-file
/// parse/validation errors. If the directory does not exist, returns empty vecs.
pub fn load_model_configs(dir_path: &Path) -> (Vec<CustomModelRouter>, Vec<ModelConfigError>) {
let results = for_each_dir_entry(dir_path, parse_model_config_dir_entry);
let mut models = Vec::new();
let mut errors = Vec::new();
for result in results {
match result {
Ok(model) => models.push(model),
Err(error) => errors.push(error),
}
}
models.sort_by(|a, b| {
let a_name = a.info.display_name.to_lowercase();
let b_name = b.info.display_name.to_lowercase();
a_name
.cmp(&b_name)
.then_with(|| a.info.display_name.cmp(&b.info.display_name))
});
(models, errors)
}
/// Loads all tab configs from `tab_config_path`. Each tab config is an individual TOML file.
///
/// Returns successfully parsed configs and any errors for files that failed to parse.
+25
View File
@@ -9,6 +9,9 @@ use itertools::Itertools;
use serde::de::DeserializeOwned;
use walkdir::{DirEntry, WalkDir};
use crate::ai::custom_model_routers::{
parse_model_config_yaml, CustomModelRouter, ModelConfigError,
};
use crate::launch_configs::launch_config::LaunchConfig;
use crate::tab_configs::{TabConfig, TabConfigError};
use crate::themes::theme::{GalaxyTheme, GalaxyThemeConfig, ThemeKind};
@@ -186,6 +189,28 @@ pub(super) fn parse_tab_config_dir_entry(
)
}
/// Parses a `DirEntry` as a single custom model router (one router per YAML file).
/// Returns `None` for non-config files, otherwise the parsed router or a
/// [`ModelConfigError`] describing the read/parse/validation failure.
pub(super) fn parse_model_config_dir_entry(
item: &DirEntry,
) -> Option<Result<CustomModelRouter, ModelConfigError>> {
let file_name = get_file_name(item)?;
if !is_config_file(&file_name) {
return None;
}
let make_error = |message: String| ModelConfigError {
file_name: file_name.clone(),
file_path: item.path().into(),
error_message: message,
};
let contents = match fs::read_to_string(item.path()) {
Ok(contents) => contents,
Err(e) => return Some(Err(make_error(e.to_string()))),
};
Some(parse_model_config_yaml(&contents, Some(item.path())).map_err(make_error))
}
/// Runs the given function on each `DirEntry` within the `Path`. If the path is not a directory,
/// an empty `Vector` is returned. It works recursively, covering directories within a given path.
pub(super) fn for_each_dir_entry<F, T>(path: &Path, dir_entry_fn: F) -> Vec<T>
+11 -6
View File
@@ -3,17 +3,15 @@ use std::path::Path;
use galaxyui::ModelContext;
use crate::launch_configs::launch_config::LaunchConfig;
use crate::themes::theme::GalaxyThemeConfig;
use crate::tab_configs::{TabConfig, TabConfigError};
use crate::themes::theme::WarpThemeConfig;
use crate::workflows::workflow::Workflow;
impl super::GalaxyConfig {
pub fn new(_ctx: &mut ModelContext<Self>) -> Self {
Self {
launch_configs: Default::default(),
tab_configs: Default::default(),
tab_config_errors: Default::default(),
theme_config: GalaxyThemeConfig::new(),
local_user_workflows: Default::default(),
theme_config: WarpThemeConfig::new(),
..Default::default()
}
}
}
@@ -39,3 +37,10 @@ pub fn load_launch_configs(_launch_config_path: &Path) -> Vec<LaunchConfig> {
// launch configs from any path.
Default::default()
}
/// Loads all tab configs relative to the `tab_config_path`.
pub(crate) fn load_tab_configs(_tab_config_path: &Path) -> (Vec<TabConfig>, Vec<TabConfigError>) {
// There's no local filesystem for wasm, so we'll never be able to retrieve
// tab configs from any path.
Default::default()
}