Prefs: owner defaults (model, backend, persona_id, reel span/length)
Closes #127. Grows user_ai_prefs with five nullable owner-default columns and makes them the server-side fallback layer: request param -> stored pref -> hard default, at insight generation (agentic + standard), all three insight-chat turn variants, and reel creation. - PUT /prefs accepts the fields with the existing three-state partial update semantics (TriState generalized to TriState<T> for the numeric reel_max_segments); validation (canonical BackendKind::parse, MemoriesSpan::from_str) runs before any write. - GET /prefs returns the new fields. - capture_prefs reads the existing row and carries the AI owner-defaults through unchanged (the upsert overwrites all columns), mirroring only the effective reel span/segments. - MemoriesSpan gains Display + FromStr so stored spans round-trip losslessly. 1463/0 tests (+27), clippy clean.
This commit is contained in:
1 parent
aa3c58f051
commit
a699c940b8
10 files changed
+863
-85
No files matched your search
@@ -579,6 +579,19 @@ pub struct UserAiPrefs {
|
||||
/// default (`camera_tz::DEFAULT_DISPLAY_TZ`). The legacy
|
||||
/// `tz_offset_minutes` column stays frozen for rollback.
|
||||
pub display_tz: Option<String>,
|
||||
/// Last/explicit insight + chat model choice (local id or OpenRouter id).
|
||||
/// NULL = current per-request / env default behavior.
|
||||
pub default_model: Option<String>,
|
||||
/// `"local"` | `"hybrid"` — insight/chat backend. NULL = per-request /
|
||||
/// hard default ("local").
|
||||
pub default_backend: Option<String>,
|
||||
/// Active persona for persona chat. NULL = hard default ("default").
|
||||
pub default_persona_id: Option<String>,
|
||||
/// Reel span: `"day"` | `"week"` | `"month"`. NULL = per-request /
|
||||
/// hard default (day).
|
||||
pub reel_span: Option<String>,
|
||||
/// Reel segment cap. NULL = per-request / hard default.
|
||||
pub reel_max_segments: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||
@@ -589,4 +602,9 @@ pub struct UpsertUserAiPrefs {
|
||||
pub library: Option<String>,
|
||||
pub updated_at: i64,
|
||||
pub display_tz: Option<String>,
|
||||
pub default_model: Option<String>,
|
||||
pub default_backend: Option<String>,
|
||||
pub default_persona_id: Option<String>,
|
||||
pub reel_span: Option<String>,
|
||||
pub reel_max_segments: Option<i32>,
|
||||
}
|
||||
@@ -293,6 +293,11 @@ diesel::table! {
|
||||
library -> Nullable<Text>,
|
||||
updated_at -> BigInt,
|
||||
display_tz -> Nullable<Text>,
|
||||
default_model -> Nullable<Text>,
|
||||
default_backend -> Nullable<Text>,
|
||||
default_persona_id -> Nullable<Text>,
|
||||
reel_span -> Nullable<Text>,
|
||||
reel_max_segments -> Nullable<Integer>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,11 @@ impl UserAiPrefsDao for SqliteUserAiPrefsDao {
|
||||
dsl::tz_offset_minutes.eq(&prefs.tz_offset_minutes),
|
||||
dsl::library.eq(&prefs.library),
|
||||
dsl::display_tz.eq(&prefs.display_tz),
|
||||
dsl::default_model.eq(&prefs.default_model),
|
||||
dsl::default_backend.eq(&prefs.default_backend),
|
||||
dsl::default_persona_id.eq(&prefs.default_persona_id),
|
||||
dsl::reel_span.eq(&prefs.reel_span),
|
||||
dsl::reel_max_segments.eq(&prefs.reel_max_segments),
|
||||
dsl::updated_at.eq(&prefs.updated_at),
|
||||
))
|
||||
.execute(connection.deref_mut())
|
||||
@@ -147,6 +152,11 @@ mod tests {
|
||||
library: Some("1".to_string()),
|
||||
updated_at: now,
|
||||
display_tz: Some("America/New_York".to_string()),
|
||||
default_model: Some("llama3.2:3b".to_string()),
|
||||
default_backend: Some("hybrid".to_string()),
|
||||
default_persona_id: Some("cabin".to_string()),
|
||||
reel_span: Some("week".to_string()),
|
||||
reel_max_segments: Some(24),
|
||||
};
|
||||
dao.upsert_prefs(&ctx(), &prefs).unwrap();
|
||||
|
||||
@@ -156,6 +166,11 @@ mod tests {
|
||||
assert_eq!(row.tz_offset_minutes, Some(-480));
|
||||
assert_eq!(row.library, Some("1".to_string()));
|
||||
assert_eq!(row.display_tz, Some("America/New_York".to_string()));
|
||||
assert_eq!(row.default_model, Some("llama3.2:3b".to_string()));
|
||||
assert_eq!(row.default_backend, Some("hybrid".to_string()));
|
||||
assert_eq!(row.default_persona_id, Some("cabin".to_string()));
|
||||
assert_eq!(row.reel_span, Some("week".to_string()));
|
||||
assert_eq!(row.reel_max_segments, Some(24));
|
||||
assert_eq!(row.updated_at, now);
|
||||
}
|
||||
|
||||
@@ -171,6 +186,11 @@ mod tests {
|
||||
library: Some("1".to_string()),
|
||||
updated_at: now1,
|
||||
display_tz: Some("America/New_York".to_string()),
|
||||
default_model: Some("llama3.2:3b".to_string()),
|
||||
default_backend: Some("local".to_string()),
|
||||
default_persona_id: None,
|
||||
reel_span: Some("week".to_string()),
|
||||
reel_max_segments: Some(24),
|
||||
};
|
||||
dao.upsert_prefs(&ctx(), &prefs1).unwrap();
|
||||
|
||||
@@ -180,6 +200,11 @@ mod tests {
|
||||
library: None,
|
||||
updated_at: now2,
|
||||
display_tz: None,
|
||||
default_model: None,
|
||||
default_backend: None,
|
||||
default_persona_id: None,
|
||||
reel_span: None,
|
||||
reel_max_segments: None,
|
||||
};
|
||||
dao.upsert_prefs(&ctx(), &prefs2).unwrap();
|
||||
|
||||
@@ -188,6 +213,11 @@ mod tests {
|
||||
assert_eq!(row.tz_offset_minutes, Some(-300));
|
||||
assert!(row.library.is_none());
|
||||
assert!(row.display_tz.is_none());
|
||||
// Owner-default columns are overwritten to NULL like the rest.
|
||||
assert!(row.default_model.is_none());
|
||||
assert!(row.default_backend.is_none());
|
||||
assert!(row.reel_span.is_none());
|
||||
assert!(row.reel_max_segments.is_none());
|
||||
assert_eq!(row.updated_at, now2);
|
||||
}
|
||||
|
||||
@@ -202,6 +232,11 @@ mod tests {
|
||||
library: None,
|
||||
updated_at: now,
|
||||
display_tz: Some("Europe/Berlin".to_string()),
|
||||
default_model: None,
|
||||
default_backend: None,
|
||||
default_persona_id: Some("default".to_string()),
|
||||
reel_span: Some("month".to_string()),
|
||||
reel_max_segments: None,
|
||||
};
|
||||
dao.upsert_prefs(&ctx(), &prefs).unwrap();
|
||||
|
||||
@@ -210,5 +245,9 @@ mod tests {
|
||||
assert!(row.voice.is_none());
|
||||
assert!(row.library.is_none());
|
||||
assert_eq!(row.display_tz, Some("Europe/Berlin".to_string()));
|
||||
assert!(row.default_model.is_none());
|
||||
assert_eq!(row.default_persona_id, Some("default".to_string()));
|
||||
assert_eq!(row.reel_span, Some("month".to_string()));
|
||||
assert!(row.reel_max_segments.is_none());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user