diff --git a/src/app.rs b/src/app.rs index d347d36..ab771b2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -185,7 +185,7 @@ pub(crate) enum Message { PreferenceEndpointCorsChanged(bool), PreferenceContextChanged(String), PreferenceMaxTokensChanged(String), - PreferenceSystemPromptChanged(String), + PreferenceSystemPromptAction(text_editor::Action), PreferenceTemperatureChanged(String), PreferenceTopPChanged(String), PreferenceMinPChanged(String), @@ -684,8 +684,8 @@ impl App { self.preference_draft.max_generated_tokens = value; self.preference_error = None; } - Message::PreferenceSystemPromptChanged(value) => { - self.preference_draft.system_prompt = value; + Message::PreferenceSystemPromptAction(action) => { + self.preference_draft.system_prompt.perform(action); self.preference_error = None; } Message::PreferenceTemperatureChanged(value) => { diff --git a/src/app/preferences.rs b/src/app/preferences.rs index d3b99cf..879721e 100644 --- a/src/app/preferences.rs +++ b/src/app/preferences.rs @@ -12,7 +12,7 @@ pub(super) struct PreferenceDraft { pub(super) endpoint_cors: bool, pub(super) context_tokens: String, pub(super) max_generated_tokens: String, - pub(super) system_prompt: String, + pub(super) system_prompt: text_editor::Content, pub(super) temperature: String, pub(super) top_p: String, pub(super) min_p: String, @@ -62,7 +62,7 @@ impl PreferenceDraft { endpoint_cors: config.endpoint.cors, context_tokens: generation.context_tokens.to_string(), max_generated_tokens: generation.max_generated_tokens.to_string(), - system_prompt: generation.system_prompt.clone(), + system_prompt: text_editor::Content::with_text(&generation.system_prompt), temperature: optional_string(generation.temperature), top_p: optional_string(generation.top_p), min_p: optional_string(generation.min_p), @@ -111,7 +111,7 @@ impl PreferenceDraft { "Maximum generated tokens", &self.max_generated_tokens, )?, - system_prompt: self.system_prompt.clone(), + 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)?, @@ -423,6 +423,16 @@ impl App { 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 runtime_config_updates_after_lock_poisoning() { let runtime = Arc::new(RwLock::new(Config::default())); diff --git a/src/app/view.rs b/src/app/view.rs index eacd859..a304eb1 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -18,8 +18,8 @@ use crate::settings::{GIB, REASONING_MODES}; use iced::theme::{Palette, palette}; use iced::widget::{ Button, Space, Svg, Tooltip, button, checkbox, column, container, image, markdown, mouse_area, - opaque, pick_list, progress_bar, row, rule, scrollable, slider, stack, svg, text, text_input, - tooltip, + opaque, pick_list, progress_bar, row, rule, scrollable, slider, stack, svg, text, text_editor, + text_input, tooltip, }; use iced::{Alignment, Background, Border, Color, Element, Length, Padding, Theme, window}; use std::collections::VecDeque; diff --git a/src/app/view/preferences.rs b/src/app/view/preferences.rs index 9a233ef..a22a2fd 100644 --- a/src/app/view/preferences.rs +++ b/src/app/view/preferences.rs @@ -173,12 +173,11 @@ impl App { text("System prompt").size(13), "Standing instruction sent before every conversation: persona, tone, house rules. Leave it empty to send no system message at all.", ), - text_input( - "You are a helpful assistant", - &self.preference_draft.system_prompt, - ) - .on_input(Message::PreferenceSystemPromptChanged) - .padding(9), + text_editor(&self.preference_draft.system_prompt) + .placeholder("Additional system instructions…") + .height(140) + .on_action(Message::PreferenceSystemPromptAction) + .padding(9), Space::new().height(4), text("SAMPLING & REASONING").size(11).color(muted_text()), preference_input_row( diff --git a/src/settings.rs b/src/settings.rs index 93ae91a..7aeecf2 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -537,12 +537,14 @@ pub(crate) struct GenerationPreferences { pub(crate) reasoning_mode: ReasoningMode, } +pub(crate) const DEFAULT_SYSTEM_PROMPT: &str = "You are an expert coding assistant operating inside DS4Server, a local coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nGuidelines:\n- Be concise in your responses\n- Show file paths clearly when working with files"; + impl Default for GenerationPreferences { fn default() -> Self { Self { context_tokens: 32_768, max_generated_tokens: 50_000, - system_prompt: "You are a helpful assistant".into(), + system_prompt: DEFAULT_SYSTEM_PROMPT.into(), temperature: None, top_p: None, min_p: None, @@ -667,6 +669,8 @@ mod tests { #[test] fn effective_settings_preserve_unset_model_defaults() { let defaults = GenerationPreferences::default(); + assert_eq!(defaults.system_prompt, DEFAULT_SYSTEM_PROMPT); + assert!(defaults.system_prompt.contains("\n\nGuidelines:\n-")); let cache = KvCachePreferences::default().settings(); let deepseek = defaults.turn_settings(ModelChoice::DeepSeekV4Flash, cache); assert_eq!(