feat(sync): Wire progress callbacks through sync pipeline stages
The sync command's stage spinners now show real-time aggregate progress
for each pipeline phase instead of static "syncing..." messages.
- Add `progress_callback` parameter to `run_embed` and
`run_generate_docs` so callers can receive `(processed, total)` updates
- Add `stage_bar` parameter to `run_ingest` for aggregate progress
across concurrently-ingested projects using shared AtomicUsize counters
- Update `stage_spinner` to use `{prefix}` for the `[N/M]` label,
allowing `{msg}` to be updated independently with progress details
- Thread `ProgressBar` clones into each concurrent project task so
per-entity progress (fetch, discussions, events) is reflected on the
aggregate spinner
- Pass `None` for progress callbacks at standalone CLI entry points
(handle_ingest, handle_generate_docs, handle_embed) to preserve
existing behavior when commands are run outside of sync
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,9 @@ pub struct SyncResult {
|
||||
}
|
||||
|
||||
/// Create a styled spinner for a sync stage.
|
||||
///
|
||||
/// Uses `{prefix}` for the `[N/M]` stage label so callers can update `{msg}`
|
||||
/// independently without losing the stage context.
|
||||
fn stage_spinner(stage: u8, total: u8, msg: &str, robot_mode: bool) -> ProgressBar {
|
||||
if robot_mode {
|
||||
return ProgressBar::hidden();
|
||||
@@ -47,11 +50,12 @@ fn stage_spinner(stage: u8, total: u8, msg: &str, robot_mode: bool) -> ProgressB
|
||||
let pb = crate::cli::progress::multi().add(ProgressBar::new_spinner());
|
||||
pb.set_style(
|
||||
ProgressStyle::default_spinner()
|
||||
.template("{spinner:.blue} {msg}")
|
||||
.template("{spinner:.blue} {prefix} {msg}")
|
||||
.expect("valid template"),
|
||||
);
|
||||
pb.enable_steady_tick(std::time::Duration::from_millis(80));
|
||||
pb.set_message(format!("[{stage}/{total}] {msg}"));
|
||||
pb.set_prefix(format!("[{stage}/{total}]"));
|
||||
pb.set_message(msg.to_string());
|
||||
pb
|
||||
}
|
||||
|
||||
@@ -112,6 +116,7 @@ pub async fn run_sync(
|
||||
options.force,
|
||||
options.full,
|
||||
ingest_display,
|
||||
Some(spinner.clone()),
|
||||
)
|
||||
.await?;
|
||||
result.issues_updated = issues_result.issues_upserted;
|
||||
@@ -136,6 +141,7 @@ pub async fn run_sync(
|
||||
options.force,
|
||||
options.full,
|
||||
ingest_display,
|
||||
Some(spinner.clone()),
|
||||
)
|
||||
.await?;
|
||||
result.mrs_updated = mrs_result.mrs_upserted;
|
||||
@@ -154,7 +160,15 @@ pub async fn run_sync(
|
||||
options.robot_mode,
|
||||
);
|
||||
info!("Sync stage {current_stage}/{total_stages}: generating documents");
|
||||
let docs_result = run_generate_docs(config, false, None)?;
|
||||
let docs_spinner = spinner.clone();
|
||||
let docs_cb: Box<dyn Fn(usize, usize)> = Box::new(move |processed, total| {
|
||||
if total > 0 {
|
||||
docs_spinner.set_message(format!(
|
||||
"Processing documents... ({processed}/{total})"
|
||||
));
|
||||
}
|
||||
});
|
||||
let docs_result = run_generate_docs(config, false, None, Some(docs_cb))?;
|
||||
result.documents_regenerated = docs_result.regenerated;
|
||||
spinner.finish_and_clear();
|
||||
} else {
|
||||
@@ -171,7 +185,13 @@ pub async fn run_sync(
|
||||
options.robot_mode,
|
||||
);
|
||||
info!("Sync stage {current_stage}/{total_stages}: embedding documents");
|
||||
match run_embed(config, options.full, false).await {
|
||||
let embed_spinner = spinner.clone();
|
||||
let embed_cb: Box<dyn Fn(usize, usize)> = Box::new(move |processed, total| {
|
||||
embed_spinner.set_message(format!(
|
||||
"Embedding documents... ({processed}/{total})"
|
||||
));
|
||||
});
|
||||
match run_embed(config, options.full, false, Some(embed_cb)).await {
|
||||
Ok(embed_result) => {
|
||||
result.documents_embedded = embed_result.embedded;
|
||||
spinner.finish_and_clear();
|
||||
|
||||
Reference in New Issue
Block a user