feat(sources): real-time file progress for sync and upload — background upload with success toast
This commit is contained in:
+42
-2
@@ -51,7 +51,12 @@ decisions):
|
||||
run aborts in the ``failed`` state before this step.
|
||||
|
||||
Status is in memory: a restart mid-sync loses the running state
|
||||
(accepted — the next click re-syncs idempotently).
|
||||
(accepted — the next click re-syncs idempotently). The status also
|
||||
carries the phase-64 per-file progress — ``current_file`` (the
|
||||
``source/relative/path`` the import is processing right now) plus
|
||||
``files_done`` / ``files_total`` — null/0/0 before the import starts
|
||||
(clone/pull reports no file yet) and in terminal states, which clear
|
||||
``current_file`` but keep the run's final counts.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -107,6 +112,13 @@ class SyncStatus:
|
||||
``running``, ``success``, ``failed``. Terminal states carry the run's
|
||||
``detail`` (success) or ``error`` (failure) so the UI can render the
|
||||
last result after a page reload (task 02's re-attach behavior).
|
||||
|
||||
Phase 64 (task 02) progress fields: ``current_file`` is the
|
||||
``source/relative/path`` the import is processing right now (null
|
||||
outside the import phase — clone/pull first, terminal states
|
||||
after); ``files_done`` / ``files_total`` carry the hook's
|
||||
done/total position and survive a terminal state (the run's last
|
||||
position is useful context next to the error).
|
||||
"""
|
||||
|
||||
state: Literal["idle", "running", "success", "failed"] = "idle"
|
||||
@@ -114,6 +126,11 @@ class SyncStatus:
|
||||
finished_at: datetime | None = None
|
||||
detail: dict[str, Any] = field(default_factory=dict)
|
||||
error: str | None = None
|
||||
# Phase 64 (task 02): per-file progress — the file the import is
|
||||
# processing right now and the hook's done/total position.
|
||||
current_file: str | None = None
|
||||
files_done: int = 0
|
||||
files_total: int = 0
|
||||
|
||||
|
||||
_status = SyncStatus()
|
||||
@@ -125,6 +142,10 @@ def sync_status() -> dict[str, Any]:
|
||||
"""Current sync state (the UI polls this every 2 s — task 02).
|
||||
|
||||
``started_at`` / ``finished_at`` are ISO-8601 strings or null.
|
||||
``current_file`` (phase 64) is the ``source/relative/path`` the
|
||||
import is processing right now — null during the clone/pull phase
|
||||
and in terminal states; ``files_done`` / ``files_total`` carry the
|
||||
hook's position (0/0 idle).
|
||||
"""
|
||||
return {
|
||||
"state": _status.state,
|
||||
@@ -132,6 +153,9 @@ def sync_status() -> dict[str, Any]:
|
||||
"finished_at": _status.finished_at.isoformat() if _status.finished_at else None,
|
||||
"detail": _status.detail,
|
||||
"error": _status.error,
|
||||
"current_file": _status.current_file,
|
||||
"files_done": _status.files_done,
|
||||
"files_total": _status.files_total,
|
||||
}
|
||||
|
||||
|
||||
@@ -165,6 +189,11 @@ async def _run_sync() -> None:
|
||||
_status.finished_at = None
|
||||
_status.detail = {}
|
||||
_status.error = None
|
||||
# Phase 64 (task 02): the progress fields reset with the run — no
|
||||
# current file until the import starts (the clone/pull phase).
|
||||
_status.current_file = None
|
||||
_status.files_done = 0
|
||||
_status.files_total = 0
|
||||
try:
|
||||
settings = get_settings()
|
||||
# Step 1 (phase 41): fail fast — verify both models the sync
|
||||
@@ -208,7 +237,16 @@ async def _run_sync() -> None:
|
||||
if not path.is_dir():
|
||||
raise GitSyncError(f"local source missing: {path}")
|
||||
sources.append(path)
|
||||
summary: ImportSummary = await import_sources(sources, llm, prune=True)
|
||||
# Phase 64 (task 02): the per-file progress hook — the status
|
||||
# endpoint reports the file being processed right now. The
|
||||
# closure captures the module ``_status`` exactly like the state
|
||||
# assignments above.
|
||||
def _hook(source: str, rel: str, done: int, total: int) -> None:
|
||||
_status.current_file = f"{source}/{rel}"
|
||||
_status.files_done = done
|
||||
_status.files_total = total
|
||||
|
||||
summary: ImportSummary = await import_sources(sources, llm, prune=True, progress=_hook)
|
||||
overview = False
|
||||
if summary.added + summary.updated > 0:
|
||||
overview = await regenerate_overview(llm)
|
||||
@@ -234,6 +272,7 @@ async def _run_sync() -> None:
|
||||
db.close()
|
||||
_status.state = "success"
|
||||
_status.finished_at = datetime.now(UTC)
|
||||
_status.current_file = None # phase 64: keep the final counts
|
||||
_status.detail = {
|
||||
"files": summary.files,
|
||||
"added": summary.added,
|
||||
@@ -253,3 +292,4 @@ async def _run_sync() -> None:
|
||||
_status.state = "failed"
|
||||
_status.finished_at = datetime.now(UTC)
|
||||
_status.error = _sanitize_error(str(e))
|
||||
_status.current_file = None # phase 64: keep the final counts
|
||||
|
||||
Reference in New Issue
Block a user