Store selected picture candidates in scrape DB

This commit is contained in:
2026-08-06 00:02:25 +02:00
parent 05338a062a
commit a8e3e62dbd
9 changed files with 697 additions and 16 deletions
+122 -6
View File
@@ -856,21 +856,62 @@ function renderSourceCandidates(items) {
}
function renderPictureCandidates(items) {
const rows = [];
let position = 0;
for (const item of items) {
for (const candidate of item.candidates || []) {
for (const picture of candidate.pictures || []) {
const row = document.createElement("a");
position += 1;
const target = buildPictureTargetInfo();
const row = document.createElement("div");
row.className = "picture-candidate";
row.href = picture;
row.target = "_blank";
row.rel = "noreferrer";
const preview = document.createElement("a");
preview.href = picture;
preview.target = "_blank";
preview.rel = "noreferrer";
preview.className = "picture-candidate-preview";
const image = document.createElement("img");
image.src = picture;
image.alt = candidate.rawTitle || item.source?.name || "Product picture";
image.loading = "lazy";
preview.append(image);
const details = document.createElement("div");
details.className = "picture-candidate-meta";
const label = document.createElement("span");
label.textContent = item.source?.name || item.source?.key || "Source";
row.append(image, label);
label.className = "picture-candidate-title";
label.textContent = `${item.source?.name || item.source?.key || "Source"} picture #${position}`;
const dimensions = document.createElement("span");
dimensions.className = "picture-candidate-dimensions";
dimensions.textContent = "Size: loading...";
image.addEventListener("load", () => {
dimensions.textContent = image.naturalWidth && image.naturalHeight
? `Size: ${image.naturalWidth} x ${image.naturalHeight}px`
: "Size: unknown";
});
image.addEventListener("error", () => {
dimensions.textContent = "Size: image preview failed";
});
const assignment = document.createElement("span");
assignment.className = "picture-candidate-target";
assignment.textContent = target.label;
const duplicateHint = document.createElement("span");
duplicateHint.className = "picture-candidate-duplicate";
duplicateHint.textContent = "DB: not saved";
details.append(label, dimensions, assignment, duplicateHint);
const action = document.createElement("button");
action.type = "button";
action.className = "source-picture-action picture-candidate-save";
action.textContent = "Save to DB";
action.addEventListener("click", () => savePictureCandidateToDb({
picture,
item,
candidate,
image,
position,
target,
action,
duplicateHint,
}));
row.append(preview, details, action);
rows.push(row);
}
}
@@ -884,6 +925,81 @@ function renderPictureCandidates(items) {
}
sourceList.replaceChildren(...rows);
}
function buildPictureTargetInfo() {
const supplierColorGroup = getFirstSupplierColorGroup();
const firstSupplierRow = supplierColorGroup?.supplierRows?.[0] || currentCombinations[0] || {};
const color = supplierColorGroup?.color || formatValue(firstSupplierRow.color);
const ean13 = supplierColorGroup?.supplierEan || firstSupplierRow.ean13 || "";
if (color) {
return {
strategy: "color",
color,
sizeValue: "",
combi: "",
ean13,
idProductCatalog: firstSupplierRow.idProductCatalog || currentProduct?.idProductCatalog || "",
label: `Target: color ${color}${ean13 ? ` / EAN ${ean13}` : ""}`,
};
}
return {
strategy: "unknown",
color: "",
sizeValue: "",
combi: "",
ean13,
idProductCatalog: firstSupplierRow.idProductCatalog || currentProduct?.idProductCatalog || "",
label: ean13 ? `Target: unknown / EAN ${ean13}` : "Target: needs manual assignment",
};
}
async function savePictureCandidateToDb({ picture, item, candidate, image, position, target, action, duplicateHint, }) {
action.disabled = true;
action.textContent = "Saving...";
duplicateHint.textContent = "DB: saving...";
try {
const payload = {
sourceKey: item.source?.key || "",
sourceName: item.source?.name || "",
sourceType: item.source?.type || "",
imageUrl: picture,
pageUrl: candidate.url || item.searchUrl || "",
sourcePosition: position,
manufacturerId: manufacturerSelect.value,
idProductCatalog: currentProduct?.idProductCatalog || target.idProductCatalog || "",
idProduct: currentProduct?.idProduct || "",
supplierReference: currentProduct?.supplierReference || "",
ean13: target.ean13 || "",
color: target.color || "",
sizeValue: target.sizeValue || "",
combi: target.combi || "",
targetStrategy: target.strategy,
targetColor: target.color || "",
targetSize: target.sizeValue || "",
targetCombi: target.combi || "",
targetEan13: target.ean13 || "",
targetIdProductCatalog: target.idProductCatalog || currentProduct?.idProductCatalog || "",
matchConfidence: target.strategy === "color" ? 70 : 0,
width: image.naturalWidth || "",
height: image.naturalHeight || "",
};
const response = await fetch("/api/catalog-maker/save-picture", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload),
});
const data = await response.json();
if (!response.ok)
throw new Error(data.message || "Picture save failed.");
action.textContent = "Saved";
duplicateHint.textContent = data.duplicateStatus === "exact_duplicate"
? `DB: duplicate of #${data.duplicateOfCandidateId || "known"}`
: `DB: saved #${data.candidateId}`;
}
catch (error) {
action.disabled = false;
action.textContent = "Save to DB";
duplicateHint.textContent = `DB: ${error.message || "save failed"}`;
}
}
function collectPictureUrls(items) {
return items.flatMap((item) => (item.candidates || []).flatMap((candidate) => candidate.pictures || [])).filter((url, index, all) => all.indexOf(url) === index);
}