export type DetailReplacementGenerationInputRole = | "product" | "model" | "supplement" | "layout"; const MODEL_IMAGE_TEXT_HINT = /自拍|合照|聚会|聚餐|围坐|用餐|试坐|承重|受力|拿手机|手持|人物场景|人物关系|真人演示|人物展示|人像展示|固定模特|用户模特|same\s+model|fixed\s+model|person|people|human/i; export function shouldIncludeDetailReplacementModelImage(scenePlan: { useModelImage?: boolean; peoplePolicy?: string; generationRoute?: string; pageKind?: string; modelImageReason?: string; routeReason?: string; sceneSummary?: string; cameraSummary?: string; placementSummary?: string; preserveSummary?: string; styleSummary?: string; avoidSummary?: string; generationPrompt?: string; } | null | undefined) { if (!scenePlan) return false; if (scenePlan.useModelImage) return true; if (scenePlan.peoplePolicy && scenePlan.peoplePolicy !== "none") return true; if (scenePlan.generationRoute === "people_relation_locked") return true; if (scenePlan.pageKind === "people_scene" || scenePlan.pageKind === "action_poster") return true; return MODEL_IMAGE_TEXT_HINT.test([ scenePlan.modelImageReason, scenePlan.routeReason, scenePlan.sceneSummary, scenePlan.cameraSummary, scenePlan.placementSummary, scenePlan.preserveSummary, scenePlan.styleSummary, scenePlan.avoidSummary, scenePlan.generationPrompt, ].filter(Boolean).join(" ")); } export function selectDetailReplacementGenerationInputs(params: { productImageUrls: string[]; modelImageUrl?: string | null; includeModelImage?: boolean; templateReferenceImageUrls?: string[]; rerunReferenceImageUrls?: string[]; referenceImageUrl: string; maxImages?: number; }) { const normalizeUrls = (values: Array) => 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, productImageUrls: selectedProductImageUrls, modelImageUrl: selectedModelImageUrl, supplementImageUrls: [ ...selectedTemplateImageUrls, ...selectedRerunImageUrls, ], templateImageUrls: selectedTemplateImageUrls, rerunImageUrls: selectedRerunImageUrls, }; }