Fixed agent UI
This commit is contained in:
@@ -19,6 +19,12 @@ pub async fn run(state: Arc<AppState>) {
|
||||
Ok(count) => tracing::info!(count, "processed new files"),
|
||||
Err(e) => tracing::error!(?e, "inbox scan failed"),
|
||||
}
|
||||
// Re-process pending tracks (e.g. retried from admin UI)
|
||||
match reprocess_pending(&state).await {
|
||||
Ok(0) => {}
|
||||
Ok(count) => tracing::info!(count, "re-processed pending tracks"),
|
||||
Err(e) => tracing::error!(?e, "pending re-processing failed"),
|
||||
}
|
||||
tokio::time::sleep(interval).await;
|
||||
}
|
||||
}
|
||||
@@ -61,6 +67,137 @@ async fn scan_inbox(state: &Arc<AppState>) -> anyhow::Result<usize> {
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Re-process pending tracks from DB (e.g. tracks retried via admin UI).
|
||||
/// These already have raw metadata and path hints stored — just need RAG + LLM.
|
||||
async fn reprocess_pending(state: &Arc<AppState>) -> anyhow::Result<usize> {
|
||||
let pending = db::list_pending_for_processing(&state.pool, 10).await?;
|
||||
if pending.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let mut count = 0;
|
||||
for pt in &pending {
|
||||
tracing::info!(id = %pt.id, title = pt.raw_title.as_deref().unwrap_or("?"), "Re-processing pending track");
|
||||
|
||||
db::update_pending_status(&state.pool, pt.id, "processing", None).await?;
|
||||
|
||||
// Build raw metadata and hints from stored DB fields
|
||||
let raw_meta = metadata::RawMetadata {
|
||||
title: pt.raw_title.clone(),
|
||||
artist: pt.raw_artist.clone(),
|
||||
album: pt.raw_album.clone(),
|
||||
track_number: pt.raw_track_number.map(|n| n as u32),
|
||||
year: pt.raw_year.map(|n| n as u32),
|
||||
genre: pt.raw_genre.clone(),
|
||||
duration_secs: pt.duration_secs,
|
||||
};
|
||||
|
||||
let hints = db::PathHints {
|
||||
title: pt.path_title.clone(),
|
||||
artist: pt.path_artist.clone(),
|
||||
album: pt.path_album.clone(),
|
||||
year: pt.path_year,
|
||||
track_number: pt.path_track_number,
|
||||
};
|
||||
|
||||
// RAG lookup
|
||||
let artist_query = raw_meta.artist.as_deref()
|
||||
.or(hints.artist.as_deref())
|
||||
.unwrap_or("");
|
||||
let album_query = raw_meta.album.as_deref()
|
||||
.or(hints.album.as_deref())
|
||||
.unwrap_or("");
|
||||
|
||||
let similar_artists = if !artist_query.is_empty() {
|
||||
db::find_similar_artists(&state.pool, artist_query, 5).await.unwrap_or_default()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let similar_albums = if !album_query.is_empty() {
|
||||
db::find_similar_albums(&state.pool, album_query, 5).await.unwrap_or_default()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// LLM normalization
|
||||
match normalize::normalize(state, &raw_meta, &hints, &similar_artists, &similar_albums).await {
|
||||
Ok(normalized) => {
|
||||
let confidence = normalized.confidence.unwrap_or(0.0);
|
||||
let status = if confidence >= state.config.confidence_threshold {
|
||||
"approved"
|
||||
} else {
|
||||
"review"
|
||||
};
|
||||
|
||||
tracing::info!(
|
||||
id = %pt.id,
|
||||
norm_artist = normalized.artist.as_deref().unwrap_or("-"),
|
||||
norm_title = normalized.title.as_deref().unwrap_or("-"),
|
||||
confidence,
|
||||
status,
|
||||
"Re-processing complete"
|
||||
);
|
||||
|
||||
db::update_pending_normalized(&state.pool, pt.id, status, &normalized, None).await?;
|
||||
|
||||
if status == "approved" {
|
||||
let artist = normalized.artist.as_deref().unwrap_or("Unknown Artist");
|
||||
let album = normalized.album.as_deref().unwrap_or("Unknown Album");
|
||||
let title = normalized.title.as_deref().unwrap_or("Unknown Title");
|
||||
let source = std::path::Path::new(&pt.inbox_path);
|
||||
let ext = source.extension().and_then(|e| e.to_str()).unwrap_or("flac");
|
||||
let track_num = normalized.track_number.unwrap_or(0);
|
||||
|
||||
let dest_filename = if track_num > 0 {
|
||||
format!("{:02} - {}.{}", track_num, sanitize_filename(title), ext)
|
||||
} else {
|
||||
format!("{}.{}", sanitize_filename(title), ext)
|
||||
};
|
||||
|
||||
// Check if already moved
|
||||
let dest = state.config.storage_dir
|
||||
.join(sanitize_filename(artist))
|
||||
.join(sanitize_filename(album))
|
||||
.join(&dest_filename);
|
||||
|
||||
let storage_path = if dest.exists() && !source.exists() {
|
||||
dest.to_string_lossy().to_string()
|
||||
} else if source.exists() {
|
||||
match mover::move_to_storage(
|
||||
&state.config.storage_dir, artist, album, &dest_filename, source,
|
||||
).await {
|
||||
Ok(p) => p.to_string_lossy().to_string(),
|
||||
Err(e) => {
|
||||
tracing::error!(id = %pt.id, ?e, "Failed to move file");
|
||||
db::update_pending_status(&state.pool, pt.id, "error", Some(&e.to_string())).await?;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::error!(id = %pt.id, "Source file missing: {:?}", source);
|
||||
db::update_pending_status(&state.pool, pt.id, "error", Some("Source file missing")).await?;
|
||||
continue;
|
||||
};
|
||||
|
||||
match db::approve_and_finalize(&state.pool, pt.id, &storage_path).await {
|
||||
Ok(track_id) => tracing::info!(id = %pt.id, track_id, "Track finalized"),
|
||||
Err(e) => tracing::error!(id = %pt.id, ?e, "Failed to finalize"),
|
||||
}
|
||||
}
|
||||
|
||||
count += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(id = %pt.id, ?e, "LLM normalization failed");
|
||||
db::update_pending_status(&state.pool, pt.id, "error", Some(&e.to_string())).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Recursively remove empty directories inside the inbox.
|
||||
/// Does not remove the inbox root itself.
|
||||
async fn cleanup_empty_dirs(dir: &std::path::Path) -> bool {
|
||||
|
||||
Reference in New Issue
Block a user