Move preferences from the database to a YAML config file
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
137
src/app.rs
137
src/app.rs
@@ -12,7 +12,8 @@ use preferences::PreferenceDraft;
|
||||
#[cfg(test)]
|
||||
use preferences::{parse_optional_gib, parse_streaming_cache};
|
||||
|
||||
use crate::database::{AppPreferences, Database, ProjectWithSessions, SessionState, StoredMessage};
|
||||
use crate::config::{Config, EndpointConfig};
|
||||
use crate::database::{Database, ProjectWithSessions, SessionState, StoredMessage};
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::engine::ChatTurn;
|
||||
use crate::metrics::{KvCacheReport, Metrics, MetricsSnapshot};
|
||||
@@ -20,9 +21,9 @@ use crate::model::{self, DownloadOutcome, DownloadProgress, ManagedArtifactId, M
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::runtime::{ActiveGeneration, CheckpointTarget, GenerationEvent, GenerationService};
|
||||
use crate::settings::{
|
||||
DEFAULT_KV_BUDGET_GIB, DiagnosticPreferences, ExecutionPreferences, GIB, GenerationPreferences,
|
||||
KvCachePreferences, ReasoningMode, RuntimePreferences, SpeculativePreferences, SsdPreferences,
|
||||
SteeringPreferences, StreamingCacheBudget,
|
||||
DiagnosticPreferences, ExecutionPreferences, GIB, GenerationPreferences, KvCachePreferences,
|
||||
ReasoningMode, RuntimePreferences, SpeculativePreferences, SsdPreferences, SteeringPreferences,
|
||||
StreamingCacheBudget,
|
||||
};
|
||||
use iced::widget::{markdown, scrollable, text_input};
|
||||
use iced::{Size, Subscription, Task, keyboard, mouse, window};
|
||||
@@ -53,7 +54,7 @@ pub(crate) struct App {
|
||||
pub(super) native_edit_commands: crate::native_edit::EditCommandQueue,
|
||||
database: Option<Database>,
|
||||
projects: Vec<ProjectWithSessions>,
|
||||
preferences: AppPreferences,
|
||||
config: Config,
|
||||
preference_draft: PreferenceDraft,
|
||||
preferences_open: bool,
|
||||
preference_error: Option<String>,
|
||||
@@ -99,7 +100,7 @@ pub(crate) struct App {
|
||||
#[cfg(target_os = "macos")]
|
||||
active_titling: Option<generation::TitleRequest>,
|
||||
#[cfg(target_os = "macos")]
|
||||
runtime_preferences: Arc<RwLock<AppPreferences>>,
|
||||
runtime_config: Arc<RwLock<Config>>,
|
||||
#[cfg(target_os = "macos")]
|
||||
_endpoint: Option<crate::server::ServerHandle>,
|
||||
error: Option<String>,
|
||||
@@ -217,22 +218,24 @@ pub(crate) enum Message {
|
||||
|
||||
impl App {
|
||||
pub(crate) fn load(main_window: window::Id) -> Self {
|
||||
let config = match Config::load(&config_path()) {
|
||||
Ok(config) => config,
|
||||
Err(error) => return Self::failed(error, main_window),
|
||||
};
|
||||
let path = application_support_path().join("data.sqlite3");
|
||||
match Database::open(&path) {
|
||||
Ok(mut database) => match (database.load_projects(), database.load_preferences()) {
|
||||
(Ok(projects), Ok(preferences)) => {
|
||||
let preference_draft = match PreferenceDraft::from_saved(&preferences) {
|
||||
Ok(draft) => draft,
|
||||
Err(error) => return Self::failed(error, main_window),
|
||||
};
|
||||
Ok(mut database) => match database.load_projects() {
|
||||
Ok(projects) => {
|
||||
let preference_draft = PreferenceDraft::from_saved(&config);
|
||||
// Before the counters are taken, so they measure what is
|
||||
// actually reachable.
|
||||
sweep_orphan_checkpoints(&kv_cache_path(), &projects);
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::engine::sweep_transient_cache(&transient_cache_path());
|
||||
let context_limit = preferences.context_tokens.max(0) as u32;
|
||||
let context_limit = config.generation.context_tokens.max(0) as u32;
|
||||
// Reopen on the project we left, with a fresh draft chat.
|
||||
let last_project = preferences
|
||||
let last_project = config
|
||||
.interface
|
||||
.last_project_id
|
||||
.filter(|id| projects.iter().any(|item| item.project.id == *id));
|
||||
let drafts = last_project
|
||||
@@ -243,8 +246,8 @@ impl App {
|
||||
Arc::new(Metrics::new(&application_support_path().join("kv-cache")));
|
||||
let metrics_snapshot = metrics.snapshot();
|
||||
#[cfg(target_os = "macos")]
|
||||
let (runtime_preferences, generation_service, endpoint, service_error) =
|
||||
spawn_services(&preferences, Arc::clone(&metrics));
|
||||
let (runtime_config, generation_service, endpoint, service_error) =
|
||||
spawn_services(&config, Arc::clone(&metrics));
|
||||
Self {
|
||||
main_window,
|
||||
model_manager_window: None,
|
||||
@@ -255,7 +258,7 @@ impl App {
|
||||
native_edit_commands: crate::native_edit::command_queue(),
|
||||
database: Some(database),
|
||||
projects,
|
||||
preferences,
|
||||
config,
|
||||
preference_draft,
|
||||
preferences_open: false,
|
||||
preference_error: None,
|
||||
@@ -294,7 +297,7 @@ impl App {
|
||||
#[cfg(target_os = "macos")]
|
||||
active_titling: None,
|
||||
#[cfg(target_os = "macos")]
|
||||
runtime_preferences,
|
||||
runtime_config,
|
||||
#[cfg(target_os = "macos")]
|
||||
_endpoint: endpoint,
|
||||
error: {
|
||||
@@ -309,22 +312,27 @@ impl App {
|
||||
},
|
||||
}
|
||||
}
|
||||
(Err(error), _) | (_, Err(error)) => Self::failed(error, main_window),
|
||||
Err(error) => Self::failed(
|
||||
format!("Could not open the project database: {error}"),
|
||||
main_window,
|
||||
),
|
||||
},
|
||||
Err(error) => Self::failed(error, main_window),
|
||||
Err(error) => Self::failed(
|
||||
format!("Could not open the project database: {error}"),
|
||||
main_window,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn failed(error: String, main_window: window::Id) -> Self {
|
||||
let preferences = AppPreferences::default();
|
||||
let preference_draft = PreferenceDraft::from_saved(&preferences)
|
||||
.expect("default preferences must use a supported model");
|
||||
let context_limit = preferences.context_tokens.max(0) as u32;
|
||||
let config = Config::default();
|
||||
let preference_draft = PreferenceDraft::from_saved(&config);
|
||||
let context_limit = config.generation.context_tokens.max(0) as u32;
|
||||
let metrics = Arc::new(Metrics::new(&application_support_path().join("kv-cache")));
|
||||
let metrics_snapshot = metrics.snapshot();
|
||||
#[cfg(target_os = "macos")]
|
||||
let (runtime_preferences, generation_service, endpoint, service_error) =
|
||||
spawn_services(&preferences, Arc::clone(&metrics));
|
||||
let (runtime_config, generation_service, endpoint, service_error) =
|
||||
spawn_services(&config, Arc::clone(&metrics));
|
||||
#[cfg(target_os = "macos")]
|
||||
let startup_error = service_error;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
@@ -339,7 +347,7 @@ impl App {
|
||||
native_edit_commands: crate::native_edit::command_queue(),
|
||||
database: None,
|
||||
projects: Vec::new(),
|
||||
preferences,
|
||||
config,
|
||||
preference_draft,
|
||||
preferences_open: false,
|
||||
preference_error: None,
|
||||
@@ -378,14 +386,12 @@ impl App {
|
||||
#[cfg(target_os = "macos")]
|
||||
active_titling: None,
|
||||
#[cfg(target_os = "macos")]
|
||||
runtime_preferences,
|
||||
runtime_config,
|
||||
#[cfg(target_os = "macos")]
|
||||
_endpoint: endpoint,
|
||||
error: Some(match startup_error {
|
||||
Some(service_error) => {
|
||||
format!("Could not open the project database: {error}. {service_error}")
|
||||
}
|
||||
None => format!("Could not open the project database: {error}"),
|
||||
Some(service_error) => format!("{error} {service_error}"),
|
||||
None => error,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -453,29 +459,19 @@ impl App {
|
||||
Message::StartSidebarDrag => self.sidebar_drag = true,
|
||||
Message::DragSidebar(x) => {
|
||||
if self.sidebar_drag {
|
||||
self.preferences.sidebar_width =
|
||||
self.config.interface.sidebar_width =
|
||||
(x.round() as i32).clamp(MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH);
|
||||
}
|
||||
}
|
||||
Message::EndSidebarDrag => {
|
||||
if self.sidebar_drag {
|
||||
self.sidebar_drag = false;
|
||||
if let Some(database) = self.database.as_mut()
|
||||
&& let Err(error) =
|
||||
database.set_sidebar_width(self.preferences.sidebar_width)
|
||||
{
|
||||
self.error = Some(error);
|
||||
}
|
||||
self.store_config();
|
||||
}
|
||||
}
|
||||
Message::ToggleSidebar => {
|
||||
let collapsed = !self.preferences.sidebar_collapsed;
|
||||
self.preferences.sidebar_collapsed = collapsed;
|
||||
if let Some(database) = self.database.as_mut()
|
||||
&& let Err(error) = database.set_sidebar_collapsed(collapsed)
|
||||
{
|
||||
self.error = Some(error);
|
||||
}
|
||||
self.config.interface.sidebar_collapsed = !self.config.interface.sidebar_collapsed;
|
||||
self.store_config();
|
||||
}
|
||||
Message::MetricsTick => self.sample_metrics(),
|
||||
Message::PreferenceModelChanged(model) => {
|
||||
@@ -829,9 +825,9 @@ impl App {
|
||||
let _ = fs::remove_file(session_checkpoint_path(session_id));
|
||||
}
|
||||
self.drafts.remove(&project_id);
|
||||
if self.preferences.last_project_id == Some(project_id) {
|
||||
self.preferences.last_project_id = None;
|
||||
let _ = database.set_last_project(None);
|
||||
if self.config.interface.last_project_id == Some(project_id) {
|
||||
self.config.interface.last_project_id = None;
|
||||
let _ = self.config.save(&config_path());
|
||||
}
|
||||
if self.selected_project == Some(project_id) {
|
||||
self.selected_project = None;
|
||||
@@ -957,7 +953,7 @@ impl App {
|
||||
self.context_limit = if limit > 0 {
|
||||
limit as u32
|
||||
} else {
|
||||
self.preferences.context_tokens.max(0) as u32
|
||||
self.config.generation.context_tokens.max(0) as u32
|
||||
};
|
||||
self.tokens_per_second = tokens_per_second;
|
||||
self.error = None;
|
||||
@@ -1128,6 +1124,13 @@ impl App {
|
||||
self.finish_cache_change();
|
||||
}
|
||||
|
||||
/// Writes the configuration file, surfacing a failure in the error banner.
|
||||
fn store_config(&mut self) {
|
||||
if let Err(error) = self.config.save(&config_path()) {
|
||||
self.error = Some(error);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_cache_change(&mut self) {
|
||||
self.metrics.rescan_cache(&kv_cache_path());
|
||||
self.metrics_snapshot = self.metrics.snapshot();
|
||||
@@ -1135,12 +1138,7 @@ impl App {
|
||||
}
|
||||
|
||||
fn scan_kv_cache(&mut self) {
|
||||
let budget = self
|
||||
.preferences
|
||||
.runtime()
|
||||
.map_or(DEFAULT_KV_BUDGET_GIB * GIB, |runtime| {
|
||||
runtime.kv_cache.settings().budget_bytes
|
||||
});
|
||||
let budget = self.config.runtime.kv_cache.settings().budget_bytes;
|
||||
self.kv_cache_report = crate::metrics::kv_cache_report(&kv_cache_path(), budget);
|
||||
self.last_cache_scan = Instant::now();
|
||||
}
|
||||
@@ -1148,34 +1146,34 @@ impl App {
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn spawn_services(
|
||||
preferences: &AppPreferences,
|
||||
config: &Config,
|
||||
metrics: Arc<Metrics>,
|
||||
) -> (
|
||||
Arc<RwLock<AppPreferences>>,
|
||||
Arc<RwLock<Config>>,
|
||||
Option<GenerationService>,
|
||||
Option<crate::server::ServerHandle>,
|
||||
Option<String>,
|
||||
) {
|
||||
let runtime_preferences = Arc::new(RwLock::new(preferences.clone()));
|
||||
let runtime_config = Arc::new(RwLock::new(config.clone()));
|
||||
let generation = match GenerationService::spawn(Arc::clone(&metrics)) {
|
||||
Ok(generation) => generation,
|
||||
Err(error) => return (runtime_preferences, None, None, Some(error)),
|
||||
Err(error) => return (runtime_config, None, None, Some(error)),
|
||||
};
|
||||
let endpoint = preferences.endpoint_enabled.then(|| {
|
||||
let endpoint = config.endpoint.enabled.then(|| {
|
||||
crate::server::ServerHandle::spawn(
|
||||
generation.clone(),
|
||||
Arc::clone(&runtime_preferences),
|
||||
Arc::clone(&runtime_config),
|
||||
models_path(),
|
||||
transient_cache_path(),
|
||||
u16::try_from(preferences.endpoint_port).unwrap_or(4000),
|
||||
preferences.endpoint_cors,
|
||||
u16::try_from(config.endpoint.port).unwrap_or(4000),
|
||||
config.endpoint.cors,
|
||||
metrics,
|
||||
)
|
||||
});
|
||||
match endpoint {
|
||||
Some(Ok(endpoint)) => (runtime_preferences, Some(generation), Some(endpoint), None),
|
||||
Some(Err(error)) => (runtime_preferences, Some(generation), None, Some(error)),
|
||||
None => (runtime_preferences, Some(generation), None, None),
|
||||
Some(Ok(endpoint)) => (runtime_config, Some(generation), Some(endpoint), None),
|
||||
Some(Err(error)) => (runtime_config, Some(generation), None, Some(error)),
|
||||
None => (runtime_config, Some(generation), None, None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1219,6 +1217,11 @@ fn models_path() -> PathBuf {
|
||||
application_support_path().join("models")
|
||||
}
|
||||
|
||||
/// The settings file, beside the project database.
|
||||
fn config_path() -> PathBuf {
|
||||
application_support_path().join("config.yaml")
|
||||
}
|
||||
|
||||
pub(super) fn chat_scroll_id() -> scrollable::Id {
|
||||
scrollable::Id::new("chat-transcript")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user