Add typed generation preferences

This commit is contained in:
Georg Bauer
2026-07-24 15:26:15 +02:00
parent ad7167e84a
commit bccce697af
9 changed files with 529 additions and 28 deletions

View File

@@ -5,6 +5,7 @@ use std::fs;
use std::path::Path;
use crate::schema::{preferences, projects, sessions};
use crate::settings::{GenerationPreferences, ReasoningMode};
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
@@ -16,6 +17,14 @@ pub struct AppPreferences {
pub selected_model: String,
pub dspark_enabled: bool,
pub idle_timeout_minutes: i32,
pub context_tokens: i32,
pub max_generated_tokens: i32,
pub system_prompt: String,
pub temperature: Option<f32>,
pub top_p: Option<f32>,
pub min_p: Option<f32>,
pub seed: Option<String>,
pub reasoning_mode: String,
}
impl Default for AppPreferences {
@@ -25,16 +34,55 @@ impl Default for AppPreferences {
selected_model: "deepseek-v4-flash".into(),
dspark_enabled: false,
idle_timeout_minutes: 10,
context_tokens: 32_768,
max_generated_tokens: 50_000,
system_prompt: "You are a helpful assistant".into(),
temperature: None,
top_p: None,
min_p: None,
seed: None,
reasoning_mode: "high".into(),
}
}
}
impl AppPreferences {
pub(crate) fn generation(&self) -> Result<GenerationPreferences, String> {
let preferences = GenerationPreferences {
context_tokens: self.context_tokens,
max_generated_tokens: self.max_generated_tokens,
system_prompt: self.system_prompt.clone(),
temperature: self.temperature,
top_p: self.top_p,
min_p: self.min_p,
seed: self
.seed
.as_deref()
.map(str::parse)
.transpose()
.map_err(|_| "Saved seed is not a valid positive whole number.".to_owned())?,
reasoning_mode: ReasoningMode::from_id(&self.reasoning_mode)
.ok_or_else(|| format!("Unsupported reasoning mode: {}", self.reasoning_mode))?,
};
preferences.validate()?;
Ok(preferences)
}
}
#[derive(AsChangeset)]
#[diesel(table_name = preferences)]
struct PreferenceChanges<'a> {
selected_model: &'a str,
dspark_enabled: bool,
idle_timeout_minutes: i32,
context_tokens: i32,
max_generated_tokens: i32,
system_prompt: &'a str,
temperature: Option<f32>,
top_p: Option<f32>,
min_p: Option<f32>,
seed: Option<&'a str>,
reasoning_mode: &'a str,
}
#[derive(Clone, Debug, Identifiable, Queryable, Selectable)]
@@ -137,12 +185,23 @@ impl Database {
selected_model: &str,
dspark_enabled: bool,
idle_timeout_minutes: i32,
generation: &GenerationPreferences,
) -> Result<AppPreferences, String> {
generation.validate()?;
let seed = generation.seed.map(|seed| seed.to_string());
diesel::update(preferences::table.find(1))
.set(PreferenceChanges {
selected_model,
dspark_enabled,
idle_timeout_minutes,
context_tokens: generation.context_tokens,
max_generated_tokens: generation.max_generated_tokens,
system_prompt: &generation.system_prompt,
temperature: generation.temperature,
top_p: generation.top_p,
min_p: generation.min_p,
seed: seed.as_deref(),
reasoning_mode: generation.reasoning_mode.id(),
})
.returning(AppPreferences::as_returning())
.get_result(&mut self.connection)
@@ -202,13 +261,26 @@ mod tests {
assert_eq!(preferences.selected_model, "deepseek-v4-flash");
assert!(!preferences.dspark_enabled);
assert_eq!(preferences.idle_timeout_minutes, 10);
assert!(database.update_preferences("glm-5.2", true, 30).is_err());
let generation = GenerationPreferences::default();
assert!(
database
.update_preferences("deepseek-v4-flash", false, 0)
.update_preferences("glm-5.2", true, 30, &generation)
.is_err()
);
database.update_preferences("glm-5.2", false, 30).unwrap();
assert!(
database
.update_preferences("deepseek-v4-flash", false, 0, &generation)
.is_err()
);
let generation = GenerationPreferences {
top_p: Some(0.7),
seed: Some(u64::MAX),
reasoning_mode: ReasoningMode::Max,
..generation
};
database
.update_preferences("glm-5.2", false, 30, &generation)
.unwrap();
let project = database.create_project("DS4", "/tmp/ds4").unwrap();
let first = database
@@ -229,6 +301,7 @@ mod tests {
let preferences = reopened.load_preferences().unwrap();
assert_eq!(preferences.selected_model, "glm-5.2");
assert_eq!(preferences.idle_timeout_minutes, 30);
assert_eq!(preferences.generation().unwrap(), generation);
drop(reopened);
fs::remove_file(path).unwrap();
}