feat(recipes): skip rate-limited batch-import items and register download 429s (#1085)

Phase 2 of docs/plans/issue-1085-rate-limit-design.md:

- Batch import: items that fail due to vendor rate limiting are now
  SKIPPED with a "re-run the import later" hint instead of FAILED, so a
  transient 429 no longer pollutes failure accounting; the progress
  broadcast carries a rate_limited flag.
- Batch import UI: show a one-time "rate limited — slowing down" toast
  and swap the running status text while rate_limited; i18n keys synced
  to all locales.
- Downloader: download_file / download_to_memory / get_response_headers
  register 429 cooldowns with the RateLimitCoordinator, so subsequent
  API calls queue behind a download-triggered rate-limit window.
This commit is contained in:
Will Miao
2026-08-27 10:08:32 +08:00
parent c2a2048c8b
commit df34efafbc
16 changed files with 273 additions and 9 deletions
+9 -1
View File
@@ -427,6 +427,12 @@ export class BatchImportManager {
this.progress = progress;
this.updateProgressUI(progress);
// Surface vendor rate limiting once per import (#1085): requests are
// being paced and some items may be skipped rather than failed.
if (progress.rate_limited && !(prev && prev.rate_limited)) {
showToast('toast.recipes.batchImportRateLimited', {}, 'warning');
}
// Only log when something actually changed (and on the first update),
// so per-second polling does not spam the console with identical lines.
const changed =
@@ -495,7 +501,9 @@ export class BatchImportManager {
const statusText = document.getElementById('batchStatusText');
if (statusText) {
if (progress.status === 'running') {
statusText.textContent = translate('recipes.batchImport.importing', {}, 'Importing...');
statusText.textContent = progress.rate_limited
? translate('recipes.batchImport.rateLimitedSlowdown', {}, 'Rate limited — slowing down...')
: translate('recipes.batchImport.importing', {}, 'Importing...');
} else if (progress.status === 'completed') {
statusText.textContent = translate('recipes.batchImport.completed', {}, 'Import completed');
} else if (progress.status === 'cancelled') {