93 lines
3 KiB
Markdown
93 lines
3 KiB
Markdown
|
|
---
|
||
|
|
convoy: scanner-desktop-layout
|
||
|
|
brief_number: 5
|
||
|
|
depends_on: []
|
||
|
|
recommended_model: composer-2.5-fast
|
||
|
|
model_tier: fast
|
||
|
|
files:
|
||
|
|
- lib/scanner-batch-identify.js
|
||
|
|
- test/lib/scanner-batch-identify.test.js
|
||
|
|
cross_brief_commitments:
|
||
|
|
- brief: 6
|
||
|
|
description: |
|
||
|
|
Brief 6 calls `runSequentialGalleryIdentify(files, identifyFn, options)`
|
||
|
|
from the Batch Scan multi-file picker. On per-file failure Brief 6 enqueues
|
||
|
|
a queue row with `identifyFailed: true` and `identifyError` message; cancel
|
||
|
|
via `cancelRef.current = true` keeps completed rows (IA-locked).
|
||
|
|
---
|
||
|
|
|
||
|
|
# Brief 5: Sequential batch identify helper
|
||
|
|
|
||
|
|
## Goal (1 sentence)
|
||
|
|
|
||
|
|
Add a small library helper that runs `identifyFromGalleryFile` sequentially over multiple files with progress, cancel, and per-file failure continuation.
|
||
|
|
|
||
|
|
## Files in scope (do not edit anything else)
|
||
|
|
|
||
|
|
- `lib/scanner-batch-identify.js`
|
||
|
|
- `test/lib/scanner-batch-identify.test.js`
|
||
|
|
|
||
|
|
## Conventions to follow
|
||
|
|
|
||
|
|
- **No new API routes** — caller passes `identifyFn` (Brief 6 binds
|
||
|
|
`identification.identifyFromGalleryFile`).
|
||
|
|
- **Sequential only** — one `await identifyFn(file)` at a time; no `Promise.all`.
|
||
|
|
- Respect identify rate limits implicitly (sequential pacing).
|
||
|
|
- Pure JS module — no React.
|
||
|
|
|
||
|
|
## Implementation shape
|
||
|
|
|
||
|
|
```js
|
||
|
|
/**
|
||
|
|
* @typedef {{ current: number, total: number, file: File }} BatchProgress
|
||
|
|
*/
|
||
|
|
|
||
|
|
export async function runSequentialGalleryIdentify(files, identifyFn, options = {}) {
|
||
|
|
const {
|
||
|
|
onProgress,
|
||
|
|
onFileSuccess,
|
||
|
|
onFileError,
|
||
|
|
cancelRef = { current: false },
|
||
|
|
} = options;
|
||
|
|
|
||
|
|
const list = Array.from(files || []);
|
||
|
|
const total = list.length;
|
||
|
|
const results = [];
|
||
|
|
|
||
|
|
for (let i = 0; i < list.length; i++) {
|
||
|
|
if (cancelRef.current) break;
|
||
|
|
const file = list[i];
|
||
|
|
onProgress?.({ current: i + 1, total, file });
|
||
|
|
|
||
|
|
try {
|
||
|
|
await identifyFn(file);
|
||
|
|
results.push({ file, ok: true });
|
||
|
|
onFileSuccess?.({ file, index: i });
|
||
|
|
} catch (error) {
|
||
|
|
results.push({ file, ok: false, error });
|
||
|
|
onFileError?.({ file, index: i, error });
|
||
|
|
// continue — IA forbids stopping the batch
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return { results, cancelled: cancelRef.current };
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
`identifyFn` may not throw today — helper still catches for forward-compat.
|
||
|
|
If `identifyFromGalleryFile` only reports via console, Brief 6 may wrap it to
|
||
|
|
throw on hard failures.
|
||
|
|
|
||
|
|
## Acceptance criteria
|
||
|
|
|
||
|
|
- [ ] Processes files one-at-a-time in order
|
||
|
|
- [ ] `onProgress` fires before each file with `{ current, total, file }`
|
||
|
|
- [ ] `cancelRef.current = true` stops remaining files but returns partial `results`
|
||
|
|
- [ ] Per-file errors invoke `onFileError` and continue loop
|
||
|
|
- [ ] tests cover success path, mid-batch cancel, and error continuation
|
||
|
|
- [ ] no scope expansion (do not edit files outside `files:` above)
|
||
|
|
|
||
|
|
## Rationale (≤3 sentences)
|
||
|
|
|
||
|
|
Batch Scan (C1) needs orchestration without touching `use-scanner-identification.js` identify logic. A 60-line pure helper is testable and keeps the page brief focused on UI wiring. Sequential execution avoids Gemini rate-limit storms explicitly forbidden in scope.
|