Add typed generation preferences
This commit is contained in:
151
src/app.rs
151
src/app.rs
@@ -4,6 +4,7 @@ pub(crate) use view::app_theme;
|
||||
|
||||
use crate::database::{AppPreferences, Database, ProjectWithSessions};
|
||||
use crate::model::{self, DownloadOutcome, DownloadProgress, ManagedArtifactId, ModelChoice};
|
||||
use crate::settings::{GenerationPreferences, ReasoningMode};
|
||||
use iced::{Size, Subscription, Task, keyboard, window};
|
||||
use rfd::AsyncFileDialog;
|
||||
use std::fs;
|
||||
@@ -21,18 +22,109 @@ struct PreferenceDraft {
|
||||
model: ModelChoice,
|
||||
dspark_enabled: bool,
|
||||
idle_timeout_minutes: String,
|
||||
context_tokens: String,
|
||||
max_generated_tokens: String,
|
||||
system_prompt: String,
|
||||
temperature: String,
|
||||
top_p: String,
|
||||
min_p: String,
|
||||
seed: String,
|
||||
reasoning_mode: ReasoningMode,
|
||||
}
|
||||
|
||||
impl PreferenceDraft {
|
||||
fn from_saved(preferences: &AppPreferences) -> Result<Self, String> {
|
||||
let model = ModelChoice::from_id(&preferences.selected_model)
|
||||
.ok_or_else(|| format!("Unsupported model: {}", preferences.selected_model))?;
|
||||
let generation = preferences.generation()?;
|
||||
Ok(Self {
|
||||
model,
|
||||
dspark_enabled: model.supports_dspark() && preferences.dspark_enabled,
|
||||
idle_timeout_minutes: preferences.idle_timeout_minutes.to_string(),
|
||||
context_tokens: generation.context_tokens.to_string(),
|
||||
max_generated_tokens: generation.max_generated_tokens.to_string(),
|
||||
system_prompt: generation.system_prompt,
|
||||
temperature: generation
|
||||
.temperature
|
||||
.map_or_else(String::new, |value| value.to_string()),
|
||||
top_p: generation
|
||||
.top_p
|
||||
.map_or_else(String::new, |value| value.to_string()),
|
||||
min_p: generation
|
||||
.min_p
|
||||
.map_or_else(String::new, |value| value.to_string()),
|
||||
seed: generation
|
||||
.seed
|
||||
.map_or_else(String::new, |value| value.to_string()),
|
||||
reasoning_mode: generation.reasoning_mode,
|
||||
})
|
||||
}
|
||||
|
||||
fn generation(&self) -> Result<GenerationPreferences, String> {
|
||||
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.clone(),
|
||||
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.reasoning_mode,
|
||||
};
|
||||
preferences.validate()?;
|
||||
Ok(preferences)
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
let defaults = GenerationPreferences::default();
|
||||
self.model = ModelChoice::default();
|
||||
self.dspark_enabled = false;
|
||||
self.idle_timeout_minutes = "10".into();
|
||||
self.context_tokens = defaults.context_tokens.to_string();
|
||||
self.max_generated_tokens = defaults.max_generated_tokens.to_string();
|
||||
self.system_prompt = defaults.system_prompt;
|
||||
self.temperature.clear();
|
||||
self.top_p.clear();
|
||||
self.min_p.clear();
|
||||
self.seed.clear();
|
||||
self.reasoning_mode = defaults.reasoning_mode;
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_positive_i32(name: &str, value: &str) -> Result<i32, String> {
|
||||
value
|
||||
.trim()
|
||||
.parse::<i32>()
|
||||
.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<Option<f32>, 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_optional_u64(name: &str, value: &str) -> Result<Option<u64>, 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(crate) struct App {
|
||||
@@ -96,6 +188,15 @@ pub(crate) enum Message {
|
||||
PreferenceModelChanged(ModelChoice),
|
||||
PreferenceDsparkChanged(bool),
|
||||
PreferenceTimeoutChanged(String),
|
||||
PreferenceContextChanged(String),
|
||||
PreferenceMaxTokensChanged(String),
|
||||
PreferenceSystemPromptChanged(String),
|
||||
PreferenceTemperatureChanged(String),
|
||||
PreferenceTopPChanged(String),
|
||||
PreferenceMinPChanged(String),
|
||||
PreferenceSeedChanged(String),
|
||||
PreferenceReasoningChanged(ReasoningMode),
|
||||
ResetPreferences,
|
||||
SavePreferences,
|
||||
DownloadArtifact(ManagedArtifactId),
|
||||
ValidateArtifact(ManagedArtifactId),
|
||||
@@ -237,6 +338,42 @@ impl App {
|
||||
self.preference_draft.idle_timeout_minutes = value;
|
||||
self.preference_error = None;
|
||||
}
|
||||
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::PreferenceSystemPromptChanged(value) => {
|
||||
self.preference_draft.system_prompt = value;
|
||||
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::PreferenceReasoningChanged(value) => {
|
||||
self.preference_draft.reasoning_mode = value;
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::ResetPreferences => {
|
||||
self.preference_draft.reset();
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::SavePreferences => self.save_preferences(),
|
||||
Message::DownloadArtifact(artifact) => {
|
||||
self.start_model_operation(artifact, ModelOperation::Download)
|
||||
@@ -464,13 +601,25 @@ impl App {
|
||||
self.preference_error = Some("Idle timeout must be between 1 and 1440 minutes.".into());
|
||||
return;
|
||||
}
|
||||
let generation = match self.preference_draft.generation() {
|
||||
Ok(generation) => generation,
|
||||
Err(error) => {
|
||||
self.preference_error = Some(error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let model = self.preference_draft.model;
|
||||
let dspark_enabled = model.supports_dspark() && self.preference_draft.dspark_enabled;
|
||||
let Some(database) = &mut self.database else {
|
||||
return;
|
||||
};
|
||||
match database.update_preferences(model.id(), dspark_enabled, idle_timeout_minutes) {
|
||||
match database.update_preferences(
|
||||
model.id(),
|
||||
dspark_enabled,
|
||||
idle_timeout_minutes,
|
||||
&generation,
|
||||
) {
|
||||
Ok(preferences) => {
|
||||
self.preferences = preferences;
|
||||
self.preference_draft = PreferenceDraft::from_saved(&self.preferences)
|
||||
|
||||
Reference in New Issue
Block a user