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:
1 parent
b30c8c16d0
commit
f707353807
26 files changed
+1825
-153
No files matched your search
+29
-3
@@ -8,9 +8,10 @@ use crate::ai::turn_registry::TurnRegistry;
|
||||
use crate::ai::{InsightGenerator, OllamaClient, SmsApiClient};
|
||||
use crate::database::{
|
||||
CalendarEventDao, DailySummaryDao, ExifDao, InsightDao, InsightGenerationJobDao, KnowledgeDao,
|
||||
LocationHistoryDao, SearchHistoryDao, SqliteCalendarEventDao, SqliteDailySummaryDao,
|
||||
SqliteExifDao, SqliteInsightDao, SqliteInsightGenerationJobDao, SqliteKnowledgeDao,
|
||||
SqliteLocationHistoryDao, SqliteSearchHistoryDao, connect,
|
||||
LocationHistoryDao, PrecomputedReelDao, SearchHistoryDao, SqliteCalendarEventDao,
|
||||
SqliteDailySummaryDao, SqliteExifDao, SqliteInsightDao, SqliteInsightGenerationJobDao,
|
||||
SqliteKnowledgeDao, SqliteLocationHistoryDao, SqlitePrecomputedReelDao, SqliteSearchHistoryDao,
|
||||
SqliteUserAiPrefsDao, UserAiPrefsDao, connect,
|
||||
};
|
||||
use crate::database::{PreviewDao, SqlitePreviewDao};
|
||||
use crate::faces;
|
||||
@@ -88,6 +89,14 @@ pub struct AppState {
|
||||
pub clip_client: ClipClient,
|
||||
pub insight_job_dao: Arc<Mutex<Box<dyn InsightGenerationJobDao>>>,
|
||||
pub insight_job_handles: Arc<Mutex<HashMap<i32, tokio::task::AbortHandle>>>,
|
||||
/// Ledger for precomputed memory reels. Written by the nightly agentic
|
||||
/// job (Section D); read by `GET /reels/precomputed` (Section C).
|
||||
#[allow(dead_code)]
|
||||
pub precomputed_reel_dao: Arc<Mutex<Box<dyn PrecomputedReelDao>>>,
|
||||
/// User AI preferences (voice, timezone, library). Mirrored by the
|
||||
/// client; read by the nightly pre-generation scheduler.
|
||||
#[allow(dead_code)]
|
||||
pub user_ai_prefs_dao: Arc<Mutex<Box<dyn UserAiPrefsDao>>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -101,6 +110,7 @@ impl AppState {
|
||||
self.libraries.iter().find(|l| l.id == id)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn library_by_name(&self, name: &str) -> Option<&Library> {
|
||||
self.libraries.iter().find(|l| l.name == name)
|
||||
}
|
||||
@@ -129,6 +139,8 @@ impl AppState {
|
||||
clip_client: ClipClient,
|
||||
insight_job_dao: Arc<Mutex<Box<dyn InsightGenerationJobDao>>>,
|
||||
insight_job_handles: Arc<Mutex<HashMap<i32, tokio::task::AbortHandle>>>,
|
||||
precomputed_reel_dao: Arc<Mutex<Box<dyn PrecomputedReelDao>>>,
|
||||
user_ai_prefs_dao: Arc<Mutex<Box<dyn UserAiPrefsDao>>>,
|
||||
) -> Self {
|
||||
assert!(
|
||||
!libraries_vec.is_empty(),
|
||||
@@ -187,6 +199,8 @@ impl AppState {
|
||||
clip_client,
|
||||
insight_job_dao,
|
||||
insight_job_handles,
|
||||
precomputed_reel_dao,
|
||||
user_ai_prefs_dao,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,6 +281,14 @@ impl Default for AppState {
|
||||
let insight_job_handles: Arc<Mutex<HashMap<i32, tokio::task::AbortHandle>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
|
||||
// Initialize precomputed reel DAO (nightly pre-generation ledger)
|
||||
let precomputed_reel_dao: Arc<Mutex<Box<dyn PrecomputedReelDao>>> =
|
||||
Arc::new(Mutex::new(Box::new(SqlitePrecomputedReelDao::new())));
|
||||
|
||||
// Initialize user AI preferences DAO (Section E)
|
||||
let user_ai_prefs_dao: Arc<Mutex<Box<dyn UserAiPrefsDao>>> =
|
||||
Arc::new(Mutex::new(Box::new(SqliteUserAiPrefsDao::new())));
|
||||
|
||||
// Load base path and ensure the primary library row reflects it.
|
||||
let base_path = env::var("BASE_PATH").expect("BASE_PATH was not set in the env");
|
||||
let mut seed_conn = connect();
|
||||
@@ -344,6 +366,8 @@ impl Default for AppState {
|
||||
clip_client,
|
||||
insight_job_dao,
|
||||
insight_job_handles,
|
||||
precomputed_reel_dao,
|
||||
user_ai_prefs_dao,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -553,6 +577,8 @@ impl AppState {
|
||||
ClipClient::new(None), // disabled in test
|
||||
Arc::new(Mutex::new(Box::new(SqliteInsightGenerationJobDao::new()))), // placeholder for test
|
||||
Arc::new(Mutex::new(HashMap::new())), // placeholder for test
|
||||
Arc::new(Mutex::new(Box::new(SqlitePrecomputedReelDao::new()))), // placeholder for test
|
||||
Arc::new(Mutex::new(Box::new(SqliteUserAiPrefsDao::new()))), // placeholder for test
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user