ai-chatbot/lib/ai/detail-replacement-inputs.ts

147 lines
4.9 KiB
TypeScript

export type DetailReplacementGenerationInputRole =
| "product"
| "model"
| "supplement"
| "layout";
export function selectDetailReplacementGenerationInputs(params: {
productImageUrls: string[];
modelImageUrl?: string | null;
includeModelImage?: boolean;
templateReferenceImageUrls?: string[];
rerunReferenceImageUrls?: string[];
referenceImageUrl: string;
maxImages?: number;
}) {
const normalizeUrls = (values: Array<string | null | undefined>) =>
Array.from(
new Set(
values
.map((value) => String(value || "").trim())
.filter((value) => /^https?:\/\//i.test(value)),
),
);
const referenceImageUrl = String(params.referenceImageUrl || "").trim();
const productImageUrls = normalizeUrls(params.productImageUrls).filter(
(url) => url !== referenceImageUrl,
);
const modelImageUrl = params.includeModelImage
? normalizeUrls([params.modelImageUrl]).find((url) => url !== referenceImageUrl) || null
: null;
const rerunReferenceImageUrls = normalizeUrls(
params.rerunReferenceImageUrls || [],
).filter((url) => url !== referenceImageUrl);
const templateReferenceImageUrls = normalizeUrls(
params.templateReferenceImageUrls || [],
).filter((url) => url !== referenceImageUrl);
const maxImages = Math.max(2, Math.floor(params.maxImages || 3));
const contentCapacity = maxImages - 1;
const selectedProductImageUrls: string[] = [];
const selectedTemplateImageUrls: string[] = [];
const selectedRerunImageUrls: string[] = [];
let selectedModelImageUrl: string | null = null;
if (productImageUrls[0]) selectedProductImageUrls.push(productImageUrls[0]);
if (modelImageUrl && selectedProductImageUrls.length < contentCapacity) {
selectedModelImageUrl = modelImageUrl;
}
const usedCount = () =>
selectedProductImageUrls.length +
selectedTemplateImageUrls.length +
selectedRerunImageUrls.length +
(selectedModelImageUrl ? 1 : 0);
const addSupplement = (url: string | undefined, kind: "template" | "rerun") => {
if (!url || usedCount() >= contentCapacity) return;
const selectedSupplementImageUrls = [
...selectedTemplateImageUrls,
...selectedRerunImageUrls,
];
if (
selectedProductImageUrls.includes(url) ||
selectedSupplementImageUrls.includes(url) ||
selectedModelImageUrl === url
) {
return;
}
if (kind === "template") selectedTemplateImageUrls.push(url);
else selectedRerunImageUrls.push(url);
};
if (!selectedModelImageUrl) {
addSupplement(rerunReferenceImageUrls[0], "rerun");
if (usedCount() < contentCapacity && productImageUrls[1]) {
selectedProductImageUrls.push(productImageUrls[1]);
}
addSupplement(templateReferenceImageUrls[0], "template");
} else if (selectedProductImageUrls.length === 0) {
addSupplement(rerunReferenceImageUrls[0], "rerun");
addSupplement(templateReferenceImageUrls[0], "template");
}
while (usedCount() < contentCapacity) {
const nextProduct = productImageUrls.find(
(url) => !selectedProductImageUrls.includes(url),
);
if (nextProduct) {
selectedProductImageUrls.push(nextProduct);
continue;
}
const nextRerun = rerunReferenceImageUrls.find(
(url) =>
!selectedRerunImageUrls.includes(url) &&
!selectedTemplateImageUrls.includes(url) &&
!selectedProductImageUrls.includes(url) &&
selectedModelImageUrl !== url,
);
if (nextRerun) {
selectedRerunImageUrls.push(nextRerun);
continue;
}
const nextTemplate = templateReferenceImageUrls.find(
(url) =>
!selectedTemplateImageUrls.includes(url) &&
!selectedRerunImageUrls.includes(url) &&
!selectedProductImageUrls.includes(url) &&
selectedModelImageUrl !== url,
);
if (!nextTemplate) break;
selectedTemplateImageUrls.push(nextTemplate);
}
const entries: Array<{
url: string;
role: DetailReplacementGenerationInputRole;
}> = [
...selectedProductImageUrls.map((url) => ({ url, role: "product" as const })),
...(selectedModelImageUrl
? [{ url: selectedModelImageUrl, role: "model" as const }]
: []),
...selectedTemplateImageUrls.map((url) => ({
url,
role: "supplement" as const,
})),
...selectedRerunImageUrls.map((url) => ({
url,
role: "supplement" as const,
})),
{ url: referenceImageUrl, role: "layout" as const },
].filter((entry) => /^https?:\/\//i.test(entry.url));
return {
entries,
imageUrls: entries.map((entry) => entry.url),
inputImageRoleByUrl: Object.fromEntries(
entries.map((entry) => [entry.url, entry.role]),
) as Record<string, DetailReplacementGenerationInputRole>,
productImageUrls: selectedProductImageUrls,
modelImageUrl: selectedModelImageUrl,
supplementImageUrls: [
...selectedTemplateImageUrls,
...selectedRerunImageUrls,
],
templateImageUrls: selectedTemplateImageUrls,
rerunImageUrls: selectedRerunImageUrls,
};
}