use super::*; use std::sync::RwLock; #[derive(Clone)] pub(super) struct PreferenceDraft { pub(super) model: ModelChoice, pub(super) default_reasoning_mode: ReasoningMode, pub(super) generation_model: ModelChoice, pub(super) generation_reasoning_mode: ReasoningMode, pub(super) acceleration_model: ModelChoice, generation_profiles: BTreeMap>, model_profiles: BTreeMap, pub(super) default_permission_mode: PermissionMode, pub(super) legacy_mtp_enabled: bool, pub(super) dspark_enabled: bool, pub(super) idle_timeout_minutes: String, pub(super) a2ui_enabled: bool, pub(super) endpoint_port: String, pub(super) endpoint_enabled: bool, pub(super) endpoint_cors: bool, pub(super) dev_brain_enabled: bool, pub(super) dev_brain_vault_path: String, pub(super) git_diff_layout: GitDiffLayout, pub(super) git_diff_algorithm: GitDiffAlgorithm, pub(super) git_context_lines: String, pub(super) git_interhunk_lines: String, pub(super) git_indent_heuristic: bool, pub(super) git_whitespace: GitDiffWhitespace, pub(super) git_ignore_blank_lines: bool, pub(super) context_tokens: String, pub(super) max_generated_tokens: String, pub(super) system_prompt: text_editor::Content, pub(super) temperature: String, pub(super) top_p: String, pub(super) min_p: String, pub(super) seed: String, pub(super) cpu_threads: String, pub(super) power_percent: String, pub(super) prefill_chunk: String, pub(super) quality: bool, pub(super) warm_weights: bool, pub(super) mtp_draft_tokens: String, pub(super) mtp_margin: String, pub(super) glm_mtp: bool, pub(super) glm_mtp_timing: bool, pub(super) dspark_confidence_threshold: String, pub(super) dspark_strict: bool, pub(super) dspark_exact_sampling: bool, pub(super) ssd_streaming: bool, pub(super) ssd_streaming_cold: bool, pub(super) ssd_cache: String, pub(super) ssd_full_layers: String, pub(super) ssd_preload_experts: String, pub(super) directional_steering_file: String, pub(super) directional_steering_ffn: String, pub(super) directional_steering_attn: String, pub(super) simulated_used_memory_gib: String, pub(super) expert_profile_path: String, pub(super) kv_budget_gib: String, pub(super) kv_min_tokens: String, pub(super) kv_cold_max_tokens: String, pub(super) kv_continued_interval_tokens: String, } impl PreferenceDraft { pub(super) fn from_saved(config: &Config) -> Self { let default_reasoning_mode = config.reasoning_mode(config.model); let generation = config.generation_for(config.model, default_reasoning_mode); let runtime = config.runtime_for(config.model); let execution = &runtime.execution; let speculative = &runtime.speculative; Self { model: config.model, default_reasoning_mode, generation_model: config.model, generation_reasoning_mode: default_reasoning_mode, acceleration_model: config.model, generation_profiles: config.generation_profiles.clone(), model_profiles: config.model_profiles.clone(), default_permission_mode: config.default_permission_mode, legacy_mtp_enabled: speculative.legacy_mtp_enabled, dspark_enabled: speculative.dspark_enabled, idle_timeout_minutes: config.idle_timeout_minutes.to_string(), a2ui_enabled: config.a2ui_enabled, endpoint_port: config.endpoint.port.to_string(), endpoint_enabled: config.endpoint.enabled, endpoint_cors: config.endpoint.cors, dev_brain_enabled: config.dev_brain.enabled, dev_brain_vault_path: config.dev_brain.vault_path.clone().unwrap_or_default(), git_diff_layout: config.git.diff_layout, git_diff_algorithm: config.git.diff_algorithm, git_context_lines: config.git.context_lines.to_string(), git_interhunk_lines: config.git.interhunk_lines.to_string(), git_indent_heuristic: config.git.indent_heuristic, git_whitespace: config.git.whitespace, git_ignore_blank_lines: config.git.ignore_blank_lines, context_tokens: generation.context_tokens.to_string(), max_generated_tokens: generation.max_generated_tokens.to_string(), system_prompt: text_editor::Content::with_text(&config.system_prompt), temperature: optional_string(generation.temperature), top_p: optional_string(generation.top_p), min_p: optional_string(generation.min_p), seed: optional_string(generation.seed), cpu_threads: optional_string(execution.cpu_threads), power_percent: optional_string(execution.power_percent), prefill_chunk: optional_string(execution.prefill_chunk), quality: execution.quality, warm_weights: execution.warm_weights, mtp_draft_tokens: speculative.mtp_draft_tokens.to_string(), mtp_margin: speculative.mtp_margin.to_string(), glm_mtp: speculative.glm_mtp, glm_mtp_timing: speculative.glm_mtp_timing, dspark_confidence_threshold: optional_string(speculative.dspark_confidence_threshold), dspark_strict: speculative.dspark_strict, dspark_exact_sampling: speculative.dspark_exact_sampling, ssd_streaming: runtime.ssd.enabled, ssd_streaming_cold: runtime.ssd.cold, ssd_cache: optional_string(runtime.ssd.cache), ssd_full_layers: optional_string(runtime.ssd.full_layers), ssd_preload_experts: optional_string(runtime.ssd.preload_experts), directional_steering_file: runtime.steering.file.clone().unwrap_or_default(), directional_steering_ffn: optional_string(runtime.steering.ffn_scale), directional_steering_attn: optional_string(runtime.steering.attention_scale), simulated_used_memory_gib: optional_string( runtime.diagnostics.simulated_used_memory_gib, ), expert_profile_path: runtime .diagnostics .expert_profile_path .clone() .unwrap_or_default(), kv_budget_gib: optional_string(runtime.kv_cache.budget_gib), kv_min_tokens: optional_string(runtime.kv_cache.min_tokens), kv_cold_max_tokens: optional_string(runtime.kv_cache.cold_max_tokens), kv_continued_interval_tokens: optional_string( runtime.kv_cache.continued_interval_tokens, ), } } pub(super) fn generation(&self) -> Result { let preferences = GenerationPreferences { context_tokens: parse_positive_i32("Context tokens", &self.context_tokens)?, max_generated_tokens: parse_positive_i32( "Maximum generated tokens", &self.max_generated_tokens, )?, system_prompt: self.system_prompt.text(), temperature: parse_optional_f32("Temperature", &self.temperature)?, top_p: parse_optional_f32("Top-p", &self.top_p)?, min_p: parse_optional_f32("Min-p", &self.min_p)?, seed: parse_optional_u64("Seed", &self.seed)?, reasoning_mode: self.generation_reasoning_mode, }; preferences.validate()?; Ok(preferences) } pub(super) fn git(&self) -> Result { Ok(GitConfig { diff_layout: self.git_diff_layout, diff_algorithm: self.git_diff_algorithm, context_lines: parse_u32("Git diff context lines", &self.git_context_lines)?, interhunk_lines: parse_u32("Git diff interhunk lines", &self.git_interhunk_lines)?, indent_heuristic: self.git_indent_heuristic, whitespace: self.git_whitespace, ignore_blank_lines: self.git_ignore_blank_lines, }) } pub(super) fn reset(&mut self) { *self = Self::from_saved(&Config::default()); } fn store_generation(&mut self) -> Result<(), String> { let generation = self.generation()?; self.generation_profiles .entry(self.generation_model) .or_default() .insert(self.generation_reasoning_mode, generation); Ok(()) } pub(super) fn load_generation(&mut self, model: ModelChoice, mode: ReasoningMode) { let mut generation = self .generation_profiles .get(&model) .and_then(|profiles| profiles.get(&mode)) .cloned() .unwrap_or_default(); generation.reasoning_mode = mode; self.generation_model = model; self.generation_reasoning_mode = mode; self.context_tokens = generation.context_tokens.to_string(); self.max_generated_tokens = generation.max_generated_tokens.to_string(); self.temperature = optional_string(generation.temperature); self.top_p = optional_string(generation.top_p); self.min_p = optional_string(generation.min_p); self.seed = optional_string(generation.seed); } fn select_generation(&mut self, model: ModelChoice, mode: ReasoningMode) -> Result<(), String> { self.store_generation()?; self.load_generation(model, mode); Ok(()) } pub(super) fn execution(&self) -> Result { Ok(ExecutionPreferences { cpu_threads: parse_optional_u32("CPU helper threads", &self.cpu_threads)?, power_percent: parse_optional_u8("GPU power", &self.power_percent)?, prefill_chunk: parse_optional_u32("Prefill chunk", &self.prefill_chunk)?, quality: self.quality, warm_weights: self.warm_weights, }) } pub(super) fn speculative(&self) -> Result { Ok(SpeculativePreferences { mtp_draft_tokens: parse_positive_i32("MTP draft tokens", &self.mtp_draft_tokens)?, mtp_margin: parse_f32("MTP margin", &self.mtp_margin)?, legacy_mtp_enabled: self.legacy_mtp_enabled, glm_mtp: self.glm_mtp, glm_mtp_timing: self.glm_mtp_timing, dspark_enabled: self.dspark_enabled, dspark_confidence_threshold: parse_optional_f32( "DSpark confidence", &self.dspark_confidence_threshold, )?, dspark_strict: self.dspark_strict, dspark_exact_sampling: self.dspark_exact_sampling, }) } fn acceleration(&self) -> Result<(SpeculativePreferences, SsdPreferences), String> { let speculative = self.speculative()?; let ssd = SsdPreferences { enabled: self.ssd_streaming, cold: self.ssd_streaming_cold, cache: parse_streaming_cache(&self.ssd_cache)?, full_layers: parse_optional_u32("SSD full-layer count", &self.ssd_full_layers)?, preload_experts: parse_optional_u32("SSD preload experts", &self.ssd_preload_experts)?, }; speculative.validate(self.acceleration_model)?; ssd.validate(self.acceleration_model)?; Ok((speculative, ssd)) } fn store_acceleration(&mut self) -> Result<(), String> { let (speculative, ssd) = self.acceleration()?; let profile = self .model_profiles .entry(self.acceleration_model) .or_default(); profile.speculative = speculative; profile.ssd = ssd; Ok(()) } fn load_acceleration(&mut self, model: ModelChoice) { let profile = self.model_profiles.get(&model).cloned().unwrap_or_default(); let speculative = profile.speculative; let ssd = profile.ssd; self.acceleration_model = model; self.legacy_mtp_enabled = speculative.legacy_mtp_enabled; self.dspark_enabled = speculative.dspark_enabled; self.mtp_draft_tokens = speculative.mtp_draft_tokens.to_string(); self.mtp_margin = speculative.mtp_margin.to_string(); self.glm_mtp = speculative.glm_mtp; self.glm_mtp_timing = speculative.glm_mtp_timing; self.dspark_confidence_threshold = optional_string(speculative.dspark_confidence_threshold); self.dspark_strict = speculative.dspark_strict; self.dspark_exact_sampling = speculative.dspark_exact_sampling; self.ssd_streaming = ssd.enabled; self.ssd_streaming_cold = ssd.cold; self.ssd_cache = optional_string(ssd.cache); self.ssd_full_layers = optional_string(ssd.full_layers); self.ssd_preload_experts = optional_string(ssd.preload_experts); } fn select_acceleration(&mut self, model: ModelChoice) -> Result<(), String> { self.store_acceleration()?; self.load_acceleration(model); Ok(()) } pub(super) fn effective_for( &self, model: ModelChoice, mode: ReasoningMode, ) -> Result { let mut draft = self.clone(); draft.store_generation()?; draft.store_acceleration()?; let mut generation = draft .generation_profiles .get(&model) .and_then(|profiles| profiles.get(&mode)) .cloned() .unwrap_or_default(); generation.system_prompt = draft.system_prompt.text(); generation.reasoning_mode = mode; let mut runtime = draft.runtime()?; if let Some(profile) = draft.model_profiles.get(&model) { runtime.speculative = profile.speculative.clone(); runtime.ssd = profile.ssd.clone(); } crate::settings::effective_settings(model, &generation, &runtime, &models_path()) } pub(super) fn reasoning_mode_for(&self, model: ModelChoice) -> ReasoningMode { self.model_profiles .get(&model) .map_or(ReasoningMode::default(), |profile| profile.reasoning_mode) } pub(super) fn runtime(&self) -> Result { Ok(RuntimePreferences { execution: self.execution()?, speculative: SpeculativePreferences::default(), ssd: SsdPreferences::default(), steering: SteeringPreferences { file: optional_text(&self.directional_steering_file), ffn_scale: parse_optional_f32( "Directional FFN scale", &self.directional_steering_ffn, )?, attention_scale: parse_optional_f32( "Directional attention scale", &self.directional_steering_attn, )?, }, diagnostics: DiagnosticPreferences { simulated_used_memory_gib: parse_optional_gib( "Simulated used memory", &self.simulated_used_memory_gib, )?, expert_profile_path: optional_text(&self.expert_profile_path), }, kv_cache: KvCachePreferences { budget_gib: parse_optional_gib("KV cache budget", &self.kv_budget_gib)?, min_tokens: parse_optional_u32("KV cache minimum tokens", &self.kv_min_tokens)?, cold_max_tokens: parse_optional_u32( "KV cache cold maximum", &self.kv_cold_max_tokens, )?, continued_interval_tokens: parse_optional_u32( "KV cache continued interval", &self.kv_continued_interval_tokens, )?, }, }) } } fn parse_positive_i32(name: &str, value: &str) -> Result { value .trim() .parse::() .ok() .filter(|value| *value > 0) .ok_or_else(|| format!("{name} must be a positive whole number.")) } fn parse_optional_f32(name: &str, value: &str) -> Result, String> { let value = value.trim(); if value.is_empty() { Ok(None) } else { value .parse() .map(Some) .map_err(|_| format!("{name} must be a number or left blank for the DS4 default.")) } } fn parse_f32(name: &str, value: &str) -> Result { value .trim() .parse() .map_err(|_| format!("{name} must be a number.")) } fn parse_optional_u64(name: &str, value: &str) -> Result, String> { let value = value.trim(); if value.is_empty() { Ok(None) } else { value .parse() .map(Some) .map_err(|_| format!("{name} must be a positive whole number or left blank.")) } } fn parse_optional_u32(name: &str, value: &str) -> Result, String> { parse_optional_number(name, value) } fn parse_u32(name: &str, value: &str) -> Result { value .trim() .parse() .map_err(|_| format!("{name} must be a non-negative whole number.")) } fn parse_optional_u8(name: &str, value: &str) -> Result, String> { parse_optional_number(name, value) } fn parse_optional_number( name: &str, value: &str, ) -> Result, String> { let value = value.trim(); if value.is_empty() { Ok(None) } else { value .parse() .map(Some) .map_err(|_| format!("{name} must be a positive whole number or left blank.")) } } pub(super) fn parse_streaming_cache(value: &str) -> Result, String> { let value = value.trim(); if value.is_empty() { return Ok(None); } value.parse().map(Some) } pub(super) fn parse_optional_gib(name: &str, value: &str) -> Result, String> { let value = value.trim(); if value.is_empty() { Ok(None) } else { parse_gib(name, value).map(Some) } } fn parse_gib(name: &str, value: &str) -> Result { let value = value .get(value.len().saturating_sub(2)..) .filter(|suffix| suffix.eq_ignore_ascii_case("gb")) .map_or(value, |_| &value[..value.len() - 2]); if !value.chars().all(|character| character.is_ascii_digit()) { return Err(format!("{name} must be a positive whole GiB value.")); } value .parse::() .ok() .filter(|value| *value > 0 && *value <= u64::MAX / GIB) .ok_or_else(|| format!("{name} must be a positive whole GiB value.")) } fn optional_text(value: &str) -> Option { let value = value.trim(); (!value.is_empty()).then(|| value.to_owned()) } /// An unset preference shows as an empty field. fn optional_string(value: Option) -> String { value.map_or_else(String::new, |value| value.to_string()) } pub(super) fn update_runtime_config(runtime_config: &RwLock, config: &Config) { *runtime_config .write() .unwrap_or_else(|poisoned| poisoned.into_inner()) = config.clone(); } impl App { pub(super) fn open_preferences(&mut self) -> Task { if let Some(id) = self.preferences_window { return window::gain_focus(id); } if self.database.is_none() || self.pending_project_path.is_some() || self.choosing_folder { return Task::none(); } self.preference_draft = PreferenceDraft::from_saved(&self.config); self.preference_error = None; self.restore_dev_brain_confirmation = false; let (id, open) = window::open(window::Settings { size: Size::new(920.0, 700.0), min_size: Some(Size::new(720.0, 480.0)), icon: Some(app_icon()), ..Default::default() }); self.preferences_window = Some(id); open.map(Message::PreferencesOpened) } pub(super) fn save_preferences(&mut self) { let Ok(idle_timeout_minutes) = self .preference_draft .idle_timeout_minutes .trim() .parse::() else { self.preference_error = Some("Idle timeout must be a whole number.".into()); return; }; let Ok(endpoint_port) = self.preference_draft.endpoint_port.trim().parse::() else { self.preference_error = Some("Endpoint port must be a whole number.".into()); return; }; if let Err(error) = self.preference_draft.store_generation() { self.preference_error = Some(error); return; } if let Err(error) = self.preference_draft.store_acceleration() { self.preference_error = Some(error); return; } let runtime = match self.preference_draft.runtime() { Ok(runtime) => runtime, Err(error) => { self.preference_error = Some(error); return; } }; let git = match self.preference_draft.git() { Ok(git) => git, Err(error) => { self.preference_error = Some(error); return; } }; self.preference_draft .model_profiles .entry(self.preference_draft.model) .or_default() .reasoning_mode = self.preference_draft.default_reasoning_mode; let config = Config { model: self.preference_draft.model, default_permission_mode: self.preference_draft.default_permission_mode, idle_timeout_minutes, a2ui_enabled: self.preference_draft.a2ui_enabled, endpoint: EndpointConfig { port: i32::from(endpoint_port), enabled: self.preference_draft.endpoint_enabled, cors: self.preference_draft.endpoint_cors, }, dev_brain: DevBrainConfig { enabled: self.preference_draft.dev_brain_enabled, vault_path: optional_text(&self.preference_draft.dev_brain_vault_path), }, system_prompt: self.preference_draft.system_prompt.text(), generation_profiles: self.preference_draft.generation_profiles.clone(), model_profiles: self.preference_draft.model_profiles.clone(), runtime, git, interface: self.config.interface.clone(), }; if let Err(error) = config.validate() { self.preference_error = Some(error); return; } if config.dev_brain.enabled { let projects = self .projects .iter() .map(|project| project.project.clone()) .collect::>(); if let Err(error) = crate::dev_brain::DevBrain::open(&config.dev_brain, &projects) { self.preference_error = Some(error); return; } } #[cfg(target_os = "macos")] let dev_brain_changed = self.config.dev_brain != config.dev_brain; #[cfg(target_os = "macos")] let endpoint_changed = self.config.endpoint != config.endpoint; #[cfg(target_os = "macos")] if endpoint_changed && self.config.endpoint.port == config.endpoint.port && self._endpoint.is_some() { self._endpoint = None; } #[cfg(target_os = "macos")] let pending_endpoint = if config.endpoint.enabled && (endpoint_changed || self._endpoint.is_none()) { let Some(generation) = &self.generation_service else { self.preference_error = Some("The model runtime is unavailable.".into()); return; }; match crate::server::ServerHandle::spawn( generation.clone(), Arc::clone(&self.runtime_config), models_path(), application_support_path().join("kv-cache").join("http"), endpoint_port, config.endpoint.cors, Arc::clone(&self.metrics), ) { Ok(endpoint) => Some(endpoint), Err(error) => { self.preference_error = Some(error); return; } } } else { None }; if let Err(error) = config.save(&config_path()) { self.preference_error = Some(error); return; } self.config = config; self.context_limit = self.config.active_generation().context_tokens.max(0) as u32; #[cfg(target_os = "macos")] { self.agent_tools = None; } #[cfg(target_os = "macos")] if dev_brain_changed { self.invalidate_dev_brain_context(); } #[cfg(target_os = "macos")] update_runtime_config(&self.runtime_config, &self.config); #[cfg(target_os = "macos")] if endpoint_changed { self._endpoint = pending_endpoint; } else if let Some(endpoint) = pending_endpoint { self._endpoint = Some(endpoint); } self.preference_draft = PreferenceDraft::from_saved(&self.config); self.preference_error = None; self.error = None; } } impl App { pub(super) fn update_preference_message( &mut self, message: Message, ) -> Result> { match message { Message::PreferenceModelChanged(model) => { if let Err(error) = self.preference_draft.store_generation() { self.preference_error = Some(error); return Err(Task::none()); } if let Err(error) = self.preference_draft.store_acceleration() { self.preference_error = Some(error); return Err(Task::none()); } let mode = self.preference_draft.reasoning_mode_for(model); self.preference_draft.model = model; self.preference_draft.default_reasoning_mode = mode; self.preference_draft.load_generation(model, mode); self.preference_draft.load_acceleration(model); if model == ModelChoice::Glm52 { self.preference_draft.power_percent.clear(); self.preference_draft.prefill_chunk.clear(); self.preference_draft.directional_steering_file.clear(); self.preference_draft.directional_steering_ffn.clear(); self.preference_draft.directional_steering_attn.clear(); } self.preference_error = None; } Message::PreferenceDefaultReasoningChanged(mode) => { if self.preference_draft.generation_model == self.preference_draft.model && let Err(error) = self .preference_draft .select_generation(self.preference_draft.model, mode) { self.preference_error = Some(error); return Err(Task::none()); } self.preference_draft.default_reasoning_mode = mode; self.preference_draft .model_profiles .entry(self.preference_draft.model) .or_default() .reasoning_mode = mode; self.preference_error = None; } Message::PreferenceGenerationModelChanged(model) => { let mode = self.preference_draft.reasoning_mode_for(model); self.preference_error = self.preference_draft.select_generation(model, mode).err(); } Message::PreferenceGenerationReasoningChanged(mode) => { self.preference_error = self .preference_draft .select_generation(self.preference_draft.generation_model, mode) .err(); } Message::PreferenceAccelerationModelChanged(model) => { self.preference_error = self.preference_draft.select_acceleration(model).err(); } Message::PreferencePermissionModeChanged(mode) => { self.preference_draft.default_permission_mode = mode; self.preference_error = None; } Message::PreferenceLegacyMtpChanged(enabled) => { self.preference_draft.legacy_mtp_enabled = self .preference_draft .acceleration_model .supports_legacy_mtp() && enabled; if self.preference_draft.legacy_mtp_enabled { self.preference_draft.dspark_enabled = false; self.preference_draft.dspark_confidence_threshold.clear(); self.preference_draft.dspark_strict = false; self.preference_draft.dspark_exact_sampling = false; } self.preference_error = None; } Message::PreferenceDsparkChanged(enabled) => { self.preference_draft.dspark_enabled = self.preference_draft.acceleration_model.supports_dspark() && enabled; if !self.preference_draft.dspark_enabled { self.preference_draft.dspark_confidence_threshold.clear(); self.preference_draft.dspark_strict = false; self.preference_draft.dspark_exact_sampling = false; } else { self.preference_draft.legacy_mtp_enabled = false; } self.preference_error = None; } Message::PreferenceTimeoutChanged(value) => { self.preference_draft.idle_timeout_minutes = value; self.preference_error = None; } Message::PreferenceA2uiChanged(enabled) => { self.preference_draft.a2ui_enabled = enabled; self.preference_error = None; } Message::PreferenceEndpointPortChanged(value) => { self.preference_draft.endpoint_port = value; self.preference_error = None; } Message::PreferenceEndpointEnabledChanged(enabled) => { self.preference_draft.endpoint_enabled = enabled; self.preference_error = None; } Message::PreferenceEndpointCorsChanged(enabled) => { self.preference_draft.endpoint_cors = enabled; self.preference_error = None; } Message::PreferenceDevBrainEnabledChanged(enabled) => { self.preference_draft.dev_brain_enabled = enabled; self.preference_error = None; } Message::PreferenceDevBrainVaultChanged(value) => { self.preference_draft.dev_brain_vault_path = value; self.preference_error = None; } Message::PreferenceGitDiffLayoutChanged(layout) => { self.preference_draft.git_diff_layout = layout; self.preference_error = None; } Message::PreferenceGitDiffAlgorithmChanged(algorithm) => { self.preference_draft.git_diff_algorithm = algorithm; self.preference_error = None; } Message::PreferenceGitContextLinesChanged(value) => { self.preference_draft.git_context_lines = value; self.preference_error = None; } Message::PreferenceGitInterhunkLinesChanged(value) => { self.preference_draft.git_interhunk_lines = value; self.preference_error = None; } Message::PreferenceGitIndentHeuristicChanged(enabled) => { self.preference_draft.git_indent_heuristic = enabled; self.preference_error = None; } Message::PreferenceGitWhitespaceChanged(whitespace) => { self.preference_draft.git_whitespace = whitespace; self.preference_error = None; } Message::PreferenceGitIgnoreBlankLinesChanged(enabled) => { self.preference_draft.git_ignore_blank_lines = enabled; self.preference_error = None; } Message::ChooseDevBrainVault => { return Err(Task::perform( async { AsyncFileDialog::new() .set_title("Choose an Obsidian vault") .pick_folder() .await .map(|folder| folder.path().to_path_buf()) }, Message::DevBrainVaultPicked, )); } Message::DevBrainVaultPicked(path) => { if let Some(path) = path { self.preference_draft.dev_brain_vault_path = path.to_string_lossy().into_owned(); self.preference_error = None; } } Message::RestoreDevBrainDefaultGuides => { self.preference_error = None; self.restore_dev_brain_confirmation = true; } Message::ConfirmRestoreDevBrainDefaultGuides => { self.restore_dev_brain_confirmation = false; let projects = self .projects .iter() .map(|project| project.project.clone()) .collect::>(); self.preference_error = crate::dev_brain::restore_default_guides( Path::new(&self.preference_draft.dev_brain_vault_path), &projects, ) .err(); } Message::CancelRestoreDevBrainDefaultGuides => { self.restore_dev_brain_confirmation = false; } Message::PreferenceContextChanged(value) => { self.preference_draft.context_tokens = value; self.preference_error = None; } Message::PreferenceMaxTokensChanged(value) => { self.preference_draft.max_generated_tokens = value; self.preference_error = None; } Message::PreferenceSystemPromptAction(action) => { self.preference_draft.system_prompt.perform(action); self.preference_error = None; } Message::PreferenceTemperatureChanged(value) => { self.preference_draft.temperature = value; self.preference_error = None; } Message::PreferenceTopPChanged(value) => { self.preference_draft.top_p = value; self.preference_error = None; } Message::PreferenceMinPChanged(value) => { self.preference_draft.min_p = value; self.preference_error = None; } Message::PreferenceSeedChanged(value) => { self.preference_draft.seed = value; self.preference_error = None; } Message::PreferenceCpuThreadsChanged(value) => { self.preference_draft.cpu_threads = value; self.preference_error = None; } Message::PreferencePowerChanged(value) => { self.preference_draft.power_percent = value; self.preference_error = None; } Message::PreferencePrefillChunkChanged(value) => { self.preference_draft.prefill_chunk = value; self.preference_error = None; } Message::PreferenceQualityChanged(value) => { self.preference_draft.quality = value; self.preference_error = None; } Message::PreferenceWarmWeightsChanged(value) => { self.preference_draft.warm_weights = value; self.preference_error = None; } Message::PreferenceMtpDraftChanged(value) => { self.preference_draft.mtp_draft_tokens = value; self.preference_error = None; } Message::PreferenceMtpMarginChanged(value) => { self.preference_draft.mtp_margin = value; self.preference_error = None; } Message::PreferenceGlmMtpChanged(value) => { self.preference_draft.glm_mtp = self.preference_draft.acceleration_model == ModelChoice::Glm52 && value; if !self.preference_draft.glm_mtp { self.preference_draft.glm_mtp_timing = false; } self.preference_error = None; } Message::PreferenceGlmMtpTimingChanged(value) => { self.preference_draft.glm_mtp_timing = self.preference_draft.acceleration_model == ModelChoice::Glm52 && value; if self.preference_draft.glm_mtp_timing { self.preference_draft.glm_mtp = true; } self.preference_error = None; } Message::PreferenceDsparkConfidenceChanged(value) => { self.preference_draft.dspark_confidence_threshold = value; if self.preference_draft.acceleration_model.supports_dspark() && !self .preference_draft .dspark_confidence_threshold .trim() .is_empty() { self.preference_draft.dspark_enabled = true; self.preference_draft.legacy_mtp_enabled = false; } self.preference_error = None; } Message::PreferenceDsparkStrictChanged(value) => { self.preference_draft.dspark_strict = self.preference_draft.acceleration_model.supports_dspark() && value; if self.preference_draft.dspark_strict { self.preference_draft.dspark_enabled = true; self.preference_draft.legacy_mtp_enabled = false; } self.preference_error = None; } Message::PreferenceDsparkExactSamplingChanged(value) => { self.preference_draft.dspark_exact_sampling = self.preference_draft.acceleration_model.supports_dspark() && value; if self.preference_draft.dspark_exact_sampling { self.preference_draft.dspark_enabled = true; self.preference_draft.legacy_mtp_enabled = false; } self.preference_error = None; } Message::PreferenceSsdChanged(value) => { self.preference_draft.ssd_streaming = value; self.preference_error = None; } Message::PreferenceSsdColdChanged(value) => { self.preference_draft.ssd_streaming_cold = value; self.preference_error = None; } Message::PreferenceSsdCacheChanged(value) => { self.preference_draft.ssd_cache = value; self.preference_error = None; } Message::PreferenceSsdFullLayersChanged(value) => { if self.preference_draft.acceleration_model == ModelChoice::Glm52 { self.preference_draft.ssd_full_layers = value; } self.preference_error = None; } Message::PreferenceSsdPreloadChanged(value) => { self.preference_draft.ssd_preload_experts = value; self.preference_error = None; } Message::PreferenceSteeringFileChanged(value) => { if self.preference_draft.model != ModelChoice::Glm52 { self.preference_draft.directional_steering_file = value; } self.preference_error = None; } Message::PreferenceSteeringFfnChanged(value) => { if self.preference_draft.model != ModelChoice::Glm52 { self.preference_draft.directional_steering_ffn = value; } self.preference_error = None; } Message::PreferenceSteeringAttnChanged(value) => { if self.preference_draft.model != ModelChoice::Glm52 { self.preference_draft.directional_steering_attn = value; } self.preference_error = None; } Message::PreferenceSimulatedMemoryChanged(value) => { self.preference_draft.simulated_used_memory_gib = value; self.preference_error = None; } Message::PreferenceExpertProfileChanged(value) => { self.preference_draft.expert_profile_path = value; self.preference_error = None; } Message::PreferenceKvBudgetChanged(value) => { self.preference_draft.kv_budget_gib = value; self.preference_error = None; } Message::PreferenceKvMinTokensChanged(value) => { self.preference_draft.kv_min_tokens = value; self.preference_error = None; } Message::PreferenceKvColdMaxChanged(value) => { self.preference_draft.kv_cold_max_tokens = value; self.preference_error = None; } Message::PreferenceKvContinuedIntervalChanged(value) => { self.preference_draft.kv_continued_interval_tokens = value; self.preference_error = None; } Message::ResetPreferences => { self.preference_draft.reset(); self.preference_error = None; } Message::SavePreferences => { self.save_preferences(); if self.preference_error.is_none() && let Some(id) = self.preferences_window { return Err(window::close(id)); } } message => return Ok(message), } Err(Task::none()) } } #[cfg(test)] mod tests { use super::*; #[test] fn multiline_system_prompt_round_trips_through_the_editor() { let draft = PreferenceDraft::from_saved(&Config::default()); assert_eq!( draft.generation().unwrap().system_prompt, crate::settings::DEFAULT_SYSTEM_PROMPT ); } #[test] fn selectors_rehydrate_their_generation_and_model_profiles() { let mut draft = PreferenceDraft::from_saved(&Config::default()); draft.context_tokens = "123".into(); draft.ssd_streaming = true; draft .select_generation(ModelChoice::Glm52, ReasoningMode::Direct) .unwrap(); assert_eq!(draft.context_tokens, "32768"); draft.context_tokens = "456".into(); draft .select_generation(ModelChoice::DeepSeekV4Flash0731, ReasoningMode::High) .unwrap(); assert_eq!(draft.context_tokens, "123"); draft.select_acceleration(ModelChoice::Glm52).unwrap(); assert!(!draft.ssd_streaming); draft .select_acceleration(ModelChoice::DeepSeekV4Flash0731) .unwrap(); assert!(draft.ssd_streaming); } #[test] fn runtime_config_updates_after_lock_poisoning() { let runtime = Arc::new(RwLock::new(Config::default())); let poisoned = Arc::clone(&runtime); let _ = std::thread::spawn(move || { let _guard = poisoned.write().unwrap(); panic!("poison configuration lock"); }) .join(); let config = Config { endpoint: EndpointConfig { port: 4567, ..EndpointConfig::default() }, ..Config::default() }; update_runtime_config(&runtime, &config); assert_eq!( runtime .read() .unwrap_or_else(|poisoned| poisoned.into_inner()) .endpoint .port, 4567 ); } }