Feature insertion- File importing

This commit is contained in:
sriram
2026-08-28 07:47:44 +05:30
parent e1247ce1b2
commit e073d68d60
3 changed files with 583 additions and 2 deletions

View File

@@ -131,6 +131,52 @@ async function upload(path, file, { fieldName = 'file' } = {}) {
return res.json();
}
/**
* Post several files under one field name.
*
* Separate from `upload` rather than a flag on it: `upload` is used by six
* call sites that send exactly one file, and widening its signature would put
* an array check in front of all of them for the benefit of one.
*
* FastAPI reads `files: List[UploadFile]` from repeated parts sharing a name,
* which is why every file is appended under the same key. As with `upload`,
* Content-Type is deliberately left unset so the browser writes the multipart
* boundary itself.
*/
async function uploadMany(path, files, { fieldName = 'files' } = {}) {
const formData = new FormData();
Array.from(files || []).forEach((file) => formData.append(fieldName, file));
let res;
try {
res = await fetch(`${BASE}${path}`, {
method: 'POST',
headers: authHeaders(),
body: formData,
});
} catch {
throw new ApiError(
`Could not reach the API at ${BASE || '(same origin)'}${path}. Is the backend running ` +
`and reachable?`,
0
);
}
if (!res.ok) {
let detail = `Upload failed (${res.status})`;
try {
const body = await res.json();
detail = body.detail || JSON.stringify(body);
} catch {
/* ignore parse errors, keep generic message */
}
if (res.status === 401 && onUnauthorized) onUnauthorized(detail);
throw new ApiError(detail, res.status);
}
return res.json();
}
function qs(params = {}) {
const usp = new URLSearchParams();
Object.entries(params).forEach(([k, v]) => {
@@ -304,6 +350,36 @@ export const api = {
getStoreCatalogJob: (jobId) =>
request(`/api/admin/store-catalog/jobs/${encodeURIComponent(jobId)}`),
// --- Admin: batch catalog ingestion (many spreadsheets -> one batch) ---
// The multi-file sibling of the block above. See
// app/api/routers/batch_catalog.py.
//
// NOTE the flag defaults are the OPPOSITE of ingestStoreCatalog's. One file
// with image search on is a considered trade; twenty files is thousands of
// outbound requests and a Playwright subprocess per row, on a single-vCPU
// container that is also serving the API. A batch opts in.
previewCatalogBatch: (files) =>
uploadMany('/api/admin/catalog-batch/preview', files),
ingestCatalogBatch: (files, { use_llm = false, fetch_images = false } = {}) =>
uploadMany(`/api/admin/catalog-batch/ingest${qs({ use_llm, fetch_images })}`, files),
getCatalogBatch: (batchId) =>
request(`/api/admin/catalog-batch/batches/${encodeURIComponent(batchId)}`),
listCatalogBatches: (limit = 20) =>
request(`/api/admin/catalog-batch/batches${qs({ limit })}`),
resumeCatalogBatch: (batchId) =>
request(`/api/admin/catalog-batch/batches/${encodeURIComponent(batchId)}/resume`, {
method: 'POST',
}),
cancelCatalogBatch: (batchId) =>
request(`/api/admin/catalog-batch/batches/${encodeURIComponent(batchId)}/cancel`, {
method: 'POST',
}),
// --- User workspace: single + batch product entry ---
// Guarded by `add_product` / `upload_batch_products` - see
// app/api/routers/user_products.py.