feat: async insight generation with SQLite job tracking
- Add insight_generation_jobs table migration and DAO - Implement job lifecycle: create_or_get_active, complete, fail, cancel - Refactor POST /insights/generate and /agentic to async spawn with timeout - Add GET /insights/generation/status endpoint with job_id and file_path lookup - Use String for enum fields in Diesel models to avoid private Bound type - Add from_str() helpers on InsightJobStatus and InsightGenerationType - Fix update_training_messages to return Result<usize, DbError> - 7/7 DAO unit tests passing
This commit is contained in:
+15
-4
@@ -6,10 +6,10 @@ use crate::ai::llamacpp::LlamaCppClient;
|
||||
use crate::ai::openrouter::OpenRouterClient;
|
||||
use crate::ai::{InsightGenerator, OllamaClient, SmsApiClient};
|
||||
use crate::database::{
|
||||
CalendarEventDao, DailySummaryDao, ExifDao, InsightDao, KnowledgeDao, LocationHistoryDao,
|
||||
SearchHistoryDao, SqliteCalendarEventDao, SqliteDailySummaryDao, SqliteExifDao,
|
||||
SqliteInsightDao, SqliteKnowledgeDao, SqliteLocationHistoryDao, SqliteSearchHistoryDao,
|
||||
connect,
|
||||
CalendarEventDao, DailySummaryDao, ExifDao, InsightDao, InsightGenerationJobDao, KnowledgeDao,
|
||||
LocationHistoryDao, SearchHistoryDao, SqliteCalendarEventDao, SqliteDailySummaryDao,
|
||||
SqliteExifDao, SqliteInsightDao, SqliteInsightGenerationJobDao, SqliteKnowledgeDao,
|
||||
SqliteLocationHistoryDao, SqliteSearchHistoryDao, connect,
|
||||
};
|
||||
use crate::database::{PreviewDao, SqlitePreviewDao};
|
||||
use crate::faces;
|
||||
@@ -86,6 +86,8 @@ pub struct AppState {
|
||||
/// Same disabled semantics as `face_client`: unset env → no-op
|
||||
/// backlog drain, /photos/search returns an empty result.
|
||||
pub clip_client: ClipClient,
|
||||
/// Tracks async insight generation jobs (spawned by generate endpoints).
|
||||
pub insight_job_dao: Arc<Mutex<Box<dyn InsightGenerationJobDao>>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -124,6 +126,7 @@ impl AppState {
|
||||
preview_dao: Arc<Mutex<Box<dyn PreviewDao>>>,
|
||||
face_client: FaceClient,
|
||||
clip_client: ClipClient,
|
||||
insight_job_dao: Arc<Mutex<Box<dyn InsightGenerationJobDao>>>,
|
||||
) -> Self {
|
||||
assert!(
|
||||
!libraries_vec.is_empty(),
|
||||
@@ -165,6 +168,7 @@ impl AppState {
|
||||
insight_chat,
|
||||
face_client,
|
||||
clip_client,
|
||||
insight_job_dao,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +257,10 @@ impl Default for AppState {
|
||||
let face_dao: Arc<Mutex<Box<dyn faces::FaceDao>>> =
|
||||
Arc::new(Mutex::new(Box::new(faces::SqliteFaceDao::new())));
|
||||
|
||||
// Initialize insight generation job DAO (async generation tracking)
|
||||
let insight_job_dao: Arc<Mutex<Box<dyn InsightGenerationJobDao>>> =
|
||||
Arc::new(Mutex::new(Box::new(SqliteInsightGenerationJobDao::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();
|
||||
@@ -319,6 +327,7 @@ impl Default for AppState {
|
||||
preview_dao,
|
||||
face_client,
|
||||
clip_client,
|
||||
insight_job_dao,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -389,6 +398,7 @@ fn parse_llamacpp_allowed_models() -> Vec<String> {
|
||||
impl AppState {
|
||||
/// Creates an AppState instance for testing with temporary directories
|
||||
pub fn test_state() -> Self {
|
||||
use crate::database::insight_generation_job_dao::SqliteInsightGenerationJobDao;
|
||||
use actix::Actor;
|
||||
// Create a base temporary directory
|
||||
let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
|
||||
@@ -502,6 +512,7 @@ impl AppState {
|
||||
preview_dao,
|
||||
FaceClient::new(None), // disabled in test
|
||||
ClipClient::new(None), // disabled in test
|
||||
Arc::new(Mutex::new(Box::new(SqliteInsightGenerationJobDao::new()))), // placeholder for test
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user