{#if jobStatus === 'running' || jobStatus === 'compiling_video' || jobStatus === 'cancelling'}
@@ -114,6 +135,18 @@
margin-bottom: 1rem;
}
+ .header-left {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ }
+
+ .person-name {
+ font-size: 0.875rem;
+ color: #e0e0e0;
+ font-weight: 500;
+ }
+
.status {
font-weight: 600;
font-size: 0.875rem;
@@ -142,11 +175,33 @@
color: #f87171;
}
+ .header-right {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ }
+
.count {
font-size: 0.875rem;
color: #888;
}
+ .cancel-btn {
+ padding: 0.375rem 0.75rem;
+ background: #dc2626;
+ border: none;
+ border-radius: 4px;
+ color: #fff;
+ font-size: 0.75rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background 0.15s ease;
+ }
+
+ .cancel-btn:hover {
+ background: #b91c1c;
+ }
+
.progress-bar {
height: 8px;
background: #333;
diff --git a/src/job/mod.rs b/src/job/mod.rs
index 5dc2d35..1ab2fd4 100644
--- a/src/job/mod.rs
+++ b/src/job/mod.rs
@@ -97,18 +97,20 @@ pub async fn run_job(state: AppState, params: JobParams, cancel_token: Cancellat
// Clear the cancellation token when done
state.clear_cancel_token().await;
- // Get final skip stats from progress
- let final_skip_stats = state.progress.read().await.skip_stats.clone();
+ // Get final state from progress (skip stats and person info)
+ let final_progress = state.progress.read().await.clone();
match result {
Ok(output_path) => {
state
.update_progress(Progress {
status: JobStatus::Completed,
- completed: 0,
- total: 0,
+ completed: final_progress.completed,
+ total: final_progress.total,
message: Some(format!("Video saved to: {}", output_path.display())),
- skip_stats: final_skip_stats,
+ skip_stats: final_progress.skip_stats,
+ person_id: final_progress.person_id,
+ person_name: final_progress.person_name,
})
.await;
}
@@ -116,10 +118,12 @@ pub async fn run_job(state: AppState, params: JobParams, cancel_token: Cancellat
state
.update_progress(Progress {
status: JobStatus::Cancelled,
- completed: 0,
- total: 0,
+ completed: final_progress.completed,
+ total: final_progress.total,
message: Some("Job cancelled by user".to_string()),
- skip_stats: final_skip_stats,
+ skip_stats: final_progress.skip_stats,
+ person_id: final_progress.person_id,
+ person_name: final_progress.person_name,
})
.await;
}
@@ -128,10 +132,12 @@ pub async fn run_job(state: AppState, params: JobParams, cancel_token: Cancellat
state
.update_progress(Progress {
status: JobStatus::Error(e.to_string()),
- completed: 0,
- total: 0,
+ completed: final_progress.completed,
+ total: final_progress.total,
message: Some(e.to_string()),
- skip_stats: final_skip_stats,
+ skip_stats: final_progress.skip_stats,
+ person_id: final_progress.person_id,
+ person_name: final_progress.person_name,
})
.await;
}
@@ -193,6 +199,8 @@ async fn run_job_inner(
total: 0,
message: Some("Fetching assets from Immich...".to_string()),
skip_stats: SkipStats::default(),
+ person_id: Some(params.person_id.clone()),
+ person_name: params.person_name.clone(),
})
.await;
@@ -244,6 +252,8 @@ async fn run_job_inner(
total,
message: Some(format!("Processing {} images...", total)),
skip_stats: SkipStats::default(),
+ person_id: Some(params.person_id.clone()),
+ person_name: params.person_name.clone(),
})
.await;
@@ -267,6 +277,10 @@ async fn run_job_inner(
let mut handles = Vec::with_capacity(assets_with_faces.len());
+ // Clone person info for use in spawned tasks
+ let task_person_id = params.person_id.clone();
+ let task_person_name = params.person_name.clone();
+
for (asset, face_data) in assets_with_faces {
// Check for cancellation before spawning more tasks
if cancel_token.is_cancelled() {
@@ -295,6 +309,10 @@ async fn run_job_inner(
let skip_decode_failed = skip_decode_failed.clone();
let skip_crop_failed = skip_crop_failed.clone();
+ // Clone person info for this task
+ let person_id = task_person_id.clone();
+ let person_name = task_person_name.clone();
+
let handle = tokio::spawn(async move {
// Check cancellation at start of task
if task_cancel_token.is_cancelled() {
@@ -353,6 +371,8 @@ async fn run_job_inner(
total,
message: Some(format!("Processing images... ({}/{})", done, total)),
skip_stats: current_skip_stats,
+ person_id: Some(person_id.clone()),
+ person_name: person_name.clone(),
})
.await;
}
@@ -419,6 +439,8 @@ async fn run_job_inner(
total,
message: Some("Processing complete, preparing video...".to_string()),
skip_stats: skip_stats.clone(),
+ person_id: Some(params.person_id.clone()),
+ person_name: params.person_name.clone(),
})
.await;
@@ -442,6 +464,8 @@ async fn run_job_inner(
total: successful as u32,
message: Some("Compiling video...".to_string()),
skip_stats: skip_stats.clone(),
+ person_id: Some(params.person_id.clone()),
+ person_name: params.person_name.clone(),
})
.await;
diff --git a/src/web/handlers.rs b/src/web/handlers.rs
index 022eb1c..b84de5b 100644
--- a/src/web/handlers.rs
+++ b/src/web/handlers.rs
@@ -258,6 +258,8 @@ struct ProgressResponse {
total: u32,
message: Option
,
skip_stats: SkipStatsResponse,
+ person_id: Option,
+ person_name: Option,
}
async fn get_progress(State(state): State) -> Json {
@@ -292,6 +294,8 @@ async fn get_progress(State(state): State) -> Json {
crop_failed: skip_stats.crop_failed,
total: skip_stats.total(),
},
+ person_id: progress.person_id.clone(),
+ person_name: progress.person_name.clone(),
})
}
@@ -322,7 +326,7 @@ async fn start_processing(
}
}
- // Reset progress
+ // Reset progress with person info
state
.update_progress(Progress {
status: JobStatus::Running,
@@ -330,6 +334,8 @@ async fn start_processing(
total: 0,
message: Some("Starting...".to_string()),
skip_stats: SkipStats::default(),
+ person_id: Some(request.person_id.clone()),
+ person_name: request.person_name.clone(),
})
.await;
diff --git a/src/web/state.rs b/src/web/state.rs
index f672419..9cbe299 100644
--- a/src/web/state.rs
+++ b/src/web/state.rs
@@ -54,6 +54,9 @@ pub struct Progress {
pub total: u32,
pub message: Option,
pub skip_stats: SkipStats,
+ /// The person being processed (for display when resuming)
+ pub person_id: Option,
+ pub person_name: Option,
}
impl Default for Progress {
@@ -64,6 +67,8 @@ impl Default for Progress {
total: 0,
message: None,
skip_stats: SkipStats::default(),
+ person_id: None,
+ person_name: None,
}
}
}