feat: nightly agentic pre-generation of memory reels

Implement end-to-end nightly pre-generation of memory reels with agentic
scripting that grounds narration in calendar, location, messages, and RAG.

Sections A-E from the plan:

A. Extract produce_reel pipeline core from run_reel_job with
   ScripterMode::Fast/Agentic and progress callbacks.

B. Agentic scripter: factor run_readonly_tool_loop from the insight
   generator, build read-only tool gate, prompt builder with GPS, and
   generate_script_agentic with fallback to fast path.

C. Precomputed reels ledger (SQLite table + DAO), GET /reels/precomputed
   handler with validity gate, GET /reels/by-key/{key}/video streaming,
   and normalize_library_key helper.

D. Nightly scheduler: spawn_pregen_scheduler with configurable hour,
   run_pregen_batch (day/week/month spans), pregen_one with dedup and
   disk-check, secs_until_next_run_hour time math.

E. user_ai_prefs passive mirror table + DAO for param capture in
   create_reel_handler and replay in the scheduler.

Also fixes resolve_library_param signature to take &[Library] and adds
resolve_library_param_state wrapper for AppState callers.

New files: migrations/2026-06-13-000000_add_precomputed_reels/,
  migrations/2026-06-13-000010_add_user_ai_prefs/,
  src/database/precomputed_reel_dao.rs,
  src/database/user_ai_prefs_dao.rs
This commit is contained in:
Cameron Cordes committed 2026-06-13 14:29:34 -04:00
1 parent b30c8c16d0
commit f707353807
26 files changed
+1825 -153

No files matched your search

+55 -1
View File
@@ -1,6 +1,7 @@
use crate::database::schema::{
entities, entity_facts, entity_photo_links, favorites, image_exif, insight_generation_jobs,
libraries, personas, photo_insights, users, video_preview_clips,
libraries, personas, photo_insights, precomputed_reels, user_ai_prefs, users,
video_preview_clips,
};
use serde::Serialize;
@@ -505,3 +506,56 @@ pub struct InsightGenerationJob {
pub result_insight_id: Option<i32>,
pub error_message: Option<String>,
}
// --- Precomputed reels -------------------------------------------------------
#[derive(Insertable)]
#[diesel(table_name = precomputed_reels)]
pub struct InsertablePrecomputedReel {
pub span: String,
pub library_key: String,
pub cache_key: String,
pub output_path: String,
pub title: String,
pub media_count: i32,
pub render_version: i32,
pub tz_offset_minutes: i32,
pub voice: Option<String>,
pub generated_at: i64,
}
#[derive(Serialize, Queryable, Clone, Debug)]
pub struct PrecomputedReel {
pub id: i32,
pub span: String,
pub library_key: String,
pub cache_key: String,
pub output_path: String,
pub title: String,
pub media_count: i32,
pub render_version: i32,
pub tz_offset_minutes: i32,
pub voice: Option<String>,
pub generated_at: i64,
}
// --- User AI preferences (Section E) ----------------------------------------
#[derive(Queryable, Insertable, Debug, Clone, serde::Deserialize, serde::Serialize)]
#[diesel(table_name = user_ai_prefs)]
pub struct UserAiPrefs {
pub id: i32,
pub voice: Option<String>,
pub tz_offset_minutes: Option<i32>,
pub library: Option<String>,
pub updated_at: i64,
}
#[derive(Insertable, Debug, Clone, serde::Deserialize, serde::Serialize)]
#[diesel(table_name = user_ai_prefs)]
pub struct UpsertUserAiPrefs {
pub voice: Option<String>,
pub tz_offset_minutes: Option<i32>,
pub library: Option<String>,
pub updated_at: i64,
}