373 lines
17 KiB
TypeScript
373 lines
17 KiB
TypeScript
|
|
import type { EcommerceTemplateManifestItem } from "../../api/ecommerceTemplateClient";
|
|||
|
|
import type { EcommerceHistoryRecord } from "./utils/clonePersistence";
|
|||
|
|
import { normalizeEcommerceHistoryRecord } from "./utils/clonePersistence";
|
|||
|
|
import type { ProductSetOutputKey } from "./utils/platformRules";
|
|||
|
|
import type { CloneSetCountKey, CloneVideoQualityKey, CloneReplicateLevelKey } from "./utils/clonePersistence";
|
|||
|
|
import type {
|
|||
|
|
CommerceDefaultImageScenarioKey,
|
|||
|
|
CommerceDefaultIntent,
|
|||
|
|
CommerceScenarioKey,
|
|||
|
|
CommerceScenarioTemplate,
|
|||
|
|
} from "./ecommerceTypes";
|
|||
|
|
import { commerceScenarioOptions } from "./ecommerceJsxConstants";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 模块级纯常量与纯函数(无 React / 无 I/O),从 EcommercePage.tsx 抽出。
|
|||
|
|
* 含 JSX 的常量(sideTools/commerceScenarioOptions/renderPlatformLogo)见 ecommerceConstants.tsx。
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const smartCutoutColorPresets = [
|
|||
|
|
"#ffffff",
|
|||
|
|
"#111111",
|
|||
|
|
"#ff3131",
|
|||
|
|
"#ff7a1a",
|
|||
|
|
"#f7c600",
|
|||
|
|
"#29b34a",
|
|||
|
|
"#25a9e0",
|
|||
|
|
"#438df5",
|
|||
|
|
"#9029d9",
|
|||
|
|
"#8aa3ad",
|
|||
|
|
"#6b7b86",
|
|||
|
|
"#f46f7b",
|
|||
|
|
"#ff9451",
|
|||
|
|
"#f7d34f",
|
|||
|
|
"#55c66f",
|
|||
|
|
"#73c7f3",
|
|||
|
|
"#6dabf5",
|
|||
|
|
"#b45adb",
|
|||
|
|
"#bcc8ce",
|
|||
|
|
"#aeb7bd",
|
|||
|
|
"#ffbec4",
|
|||
|
|
"#ffd1ac",
|
|||
|
|
"#f8e69d",
|
|||
|
|
"#91de9e",
|
|||
|
|
"#b7e5fb",
|
|||
|
|
"#b9d9fb",
|
|||
|
|
"#d7abe8",
|
|||
|
|
"#dfe5e8",
|
|||
|
|
"#d7dde0",
|
|||
|
|
"#ffe2e4",
|
|||
|
|
"#ffe5d1",
|
|||
|
|
"#f8efcf",
|
|||
|
|
"#c9efcf",
|
|||
|
|
"#d8f0fb",
|
|||
|
|
"#d8eafa",
|
|||
|
|
"#ead2f1",
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const smartCutoutSizeOptions = [
|
|||
|
|
{ key: "original", label: "原尺寸", icon: "image", frameWidth: "min(520px, 78%)", frameAspect: "auto", imageMaxWidth: "78%", imageMaxHeight: "310px" },
|
|||
|
|
{ key: "trim", label: "裁剪到边缘", icon: "crop", frameWidth: "min(420px, 70%)", frameAspect: "auto", imageMaxWidth: "92%", imageMaxHeight: "360px" },
|
|||
|
|
{ key: "one-inch", label: "一寸头像", sizeLabel: "295*413", icon: "portrait", frameWidth: "min(290px, 50%)", frameAspect: "295 / 413", imageMaxWidth: "86%", imageMaxHeight: "86%", outputWidth: 295, outputHeight: 413 },
|
|||
|
|
{ key: "two-inch", label: "二寸头像", sizeLabel: "413*579", icon: "portrait", frameWidth: "min(320px, 54%)", frameAspect: "413 / 579", imageMaxWidth: "86%", imageMaxHeight: "86%", outputWidth: 413, outputHeight: 579 },
|
|||
|
|
{ key: "taobao-1-1", label: "淘宝1:1主图", sizeLabel: "800*800", icon: "shop", frameWidth: "min(430px, 72%)", frameAspect: "800 / 800", imageMaxWidth: "82%", imageMaxHeight: "82%", outputWidth: 800, outputHeight: 800 },
|
|||
|
|
{ key: "taobao-3-4", label: "淘宝3:4主图", sizeLabel: "750*1000", icon: "shop", frameWidth: "min(330px, 56%)", frameAspect: "750 / 1000", imageMaxWidth: "82%", imageMaxHeight: "82%", outputWidth: 750, outputHeight: 1000 },
|
|||
|
|
{ key: "pdd-main", label: "拼多多主图", sizeLabel: "800*800", icon: "pdd", frameWidth: "min(430px, 72%)", frameAspect: "800 / 800", imageMaxWidth: "82%", imageMaxHeight: "82%", outputWidth: 800, outputHeight: 800 },
|
|||
|
|
{ key: "xiaohongshu-cover", label: "小红书封面", sizeLabel: "1242*1660", icon: "text", frameWidth: "min(330px, 56%)", frameAspect: "1242 / 1660", imageMaxWidth: "82%", imageMaxHeight: "82%", outputWidth: 1242, outputHeight: 1660 },
|
|||
|
|
{ key: "ratio-1-1", label: "1:1", icon: "square", frameWidth: "min(430px, 72%)", frameAspect: "1 / 1", imageMaxWidth: "82%", imageMaxHeight: "82%" },
|
|||
|
|
{ key: "ratio-3-2", label: "3:2", icon: "landscape", frameWidth: "min(520px, 78%)", frameAspect: "3 / 2", imageMaxWidth: "82%", imageMaxHeight: "82%" },
|
|||
|
|
{ key: "ratio-2-3", label: "2:3", icon: "portrait-ratio", frameWidth: "min(330px, 56%)", frameAspect: "2 / 3", imageMaxWidth: "82%", imageMaxHeight: "82%" },
|
|||
|
|
{ key: "ratio-4-3", label: "4:3", icon: "landscape", frameWidth: "min(520px, 78%)", frameAspect: "4 / 3", imageMaxWidth: "82%", imageMaxHeight: "82%" },
|
|||
|
|
{ key: "ratio-3-4", label: "3:4", icon: "portrait-ratio", frameWidth: "min(330px, 56%)", frameAspect: "3 / 4", imageMaxWidth: "82%", imageMaxHeight: "82%" },
|
|||
|
|
{ key: "ratio-16-9", label: "16:9", icon: "wide", frameWidth: "min(560px, 82%)", frameAspect: "16 / 9", imageMaxWidth: "82%", imageMaxHeight: "82%" },
|
|||
|
|
{ key: "ratio-9-16", label: "9:16", icon: "phone", frameWidth: "min(260px, 46%)", frameAspect: "9 / 16", imageMaxWidth: "82%", imageMaxHeight: "82%" },
|
|||
|
|
] as const;
|
|||
|
|
|
|||
|
|
type SmartCutoutSizeKey = (typeof smartCutoutSizeOptions)[number]["key"];
|
|||
|
|
|
|||
|
|
const ecommerceInspirationTabs = ["最近打开", "一键同款", "海报模板", "热门", "商品图", "模特穿戴"];
|
|||
|
|
|
|||
|
|
// 把灵感卡片的标题 + 卖点要点合成一段可直接填入指令栏的提示词。
|
|||
|
|
const buildInspirationPrompt = (title: string, meta: string): string => {
|
|||
|
|
const points = meta
|
|||
|
|
.split(/[·、,,]/)
|
|||
|
|
.map((part) => part.trim())
|
|||
|
|
.filter(Boolean);
|
|||
|
|
const base = title.trim();
|
|||
|
|
return points.length ? `${base}。风格要点:${points.join("、")}。` : `${base}。`;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const getPlatformLogoText = (value: string) => {
|
|||
|
|
const normalized = value.toLowerCase();
|
|||
|
|
if (value.includes("淘宝") || value.includes("天猫")) return "淘";
|
|||
|
|
if (value.includes("京东")) return "京";
|
|||
|
|
if (value.includes("拼多多") || value.includes("鎷煎澶")) return "拼";
|
|||
|
|
if (value.includes("抖音")) return "抖";
|
|||
|
|
if (normalized.includes("amazon")) return "a";
|
|||
|
|
if (normalized.includes("shopee")) return "S";
|
|||
|
|
if (normalized.includes("lazada")) return "L";
|
|||
|
|
if (normalized.includes("instagram")) return "IG";
|
|||
|
|
if (value.includes("速卖通") || value.includes("閫熷崠閫")) return "AE";
|
|||
|
|
if (normalized.includes("ebay")) return "eB";
|
|||
|
|
if (normalized.includes("tiktok")) return "♪";
|
|||
|
|
return value.trim().slice(0, 1).toUpperCase() || "商";
|
|||
|
|
};
|
|||
|
|
const getPlatformLogoVariant = (value: string) => {
|
|||
|
|
const normalized = value.toLowerCase();
|
|||
|
|
if (value.includes("淘宝") || value.includes("天猫")) return "taobao";
|
|||
|
|
if (value.includes("京东")) return "jd";
|
|||
|
|
if (value.includes("拼多多") || value.includes("鎷煎澶")) return "pdd";
|
|||
|
|
if (value.includes("抖音")) return "douyin";
|
|||
|
|
if (normalized.includes("amazon")) return "amazon";
|
|||
|
|
if (normalized.includes("shopee")) return "shopee";
|
|||
|
|
if (normalized.includes("lazada")) return "lazada";
|
|||
|
|
if (normalized.includes("instagram")) return "instagram";
|
|||
|
|
if (value.includes("速卖通") || value.includes("閫熷崠閫")) return "aliexpress";
|
|||
|
|
if (normalized.includes("ebay")) return "ebay";
|
|||
|
|
if (normalized.includes("tiktok")) return "tiktok";
|
|||
|
|
return "default";
|
|||
|
|
};
|
|||
|
|
const getPlatformLogoMarks = (value: string) => {
|
|||
|
|
if (value.includes("淘宝") || value.includes("天猫")) return ["淘", "猫"];
|
|||
|
|
return [getPlatformLogoText(value)];
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const primaryCommerceScenarioKeys: CommerceScenarioKey[] = ["popular", "poster", "mainImage", "model"];
|
|||
|
|
const scenarioSettingsKeys: CommerceScenarioKey[] = ["poster", "mainImage", "model", "scene", "festival", "salesVideo"];
|
|||
|
|
const scenarioAdvancedSettingsKeys: CommerceScenarioKey[] = ["model", "salesVideo"];
|
|||
|
|
const commerceScenarioOutputMap: Record<Exclude<CommerceScenarioKey, "popular">, ProductSetOutputKey> = {
|
|||
|
|
poster: "set",
|
|||
|
|
mainImage: "set",
|
|||
|
|
scene: "set",
|
|||
|
|
festival: "set",
|
|||
|
|
model: "model",
|
|||
|
|
background: "set",
|
|||
|
|
retouch: "set",
|
|||
|
|
salesVideo: "video",
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const ecommerceTemplateCategoryMap: Record<string, Exclude<CommerceScenarioKey, "popular">> = {
|
|||
|
|
poster: "poster",
|
|||
|
|
"main-image": "mainImage",
|
|||
|
|
"scene-image": "scene",
|
|||
|
|
"festival-image": "festival",
|
|||
|
|
"model-image": "model",
|
|||
|
|
"background-replace": "background",
|
|||
|
|
retouch: "retouch",
|
|||
|
|
"sales-video": "salesVideo",
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const getTemplateMediaType = (template: EcommerceTemplateManifestItem): "image" | "video" => {
|
|||
|
|
const extension = template.preview?.extension?.toLowerCase() || template.preview?.url?.split("?")[0].split(".").pop()?.toLowerCase() || "";
|
|||
|
|
return extension.includes("mp4") || extension.includes("webm") || extension.includes("mov") ? "video" : "image";
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const mapRemoteTemplateToScenarioTemplate = (template: EcommerceTemplateManifestItem): CommerceScenarioTemplate | null => {
|
|||
|
|
const scenario = ecommerceTemplateCategoryMap[String(template.categorySlug || "").trim()];
|
|||
|
|
const mediaUrl = template.preview?.url?.trim();
|
|||
|
|
if (!scenario || !template.id || !mediaUrl) return null;
|
|||
|
|
|
|||
|
|
const title = template.templateName?.trim() || template.templateSlug?.trim() || template.id;
|
|||
|
|
const prompt = template.prompt?.trim() || title;
|
|||
|
|
const sourceAssets = (template.assets || [])
|
|||
|
|
.filter((asset) => typeof asset.url === "string" && asset.url.trim())
|
|||
|
|
.map((asset, index) => {
|
|||
|
|
const url = asset.url!.trim();
|
|||
|
|
const extension = asset.extension?.replace(/^\./, "") || url.split("?")[0].split(".").pop() || "png";
|
|||
|
|
return {
|
|||
|
|
url,
|
|||
|
|
name: asset.fileName?.trim() || `${title}-素材${asset.assetIndex || index + 1}.${extension}`,
|
|||
|
|
ossKey: asset.ossKey,
|
|||
|
|
mimeType: extension.toLowerCase() === "jpg" || extension.toLowerCase() === "jpeg" ? "image/jpeg" : "image/png",
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
id: template.id,
|
|||
|
|
scenario,
|
|||
|
|
output: commerceScenarioOutputMap[scenario],
|
|||
|
|
title,
|
|||
|
|
desc: template.category?.trim() || commerceScenarioOptions.find((option) => option.key === scenario)?.desc || "",
|
|||
|
|
badge: template.category?.trim() || commerceScenarioOptions.find((option) => option.key === scenario)?.label || title,
|
|||
|
|
prompt,
|
|||
|
|
mediaUrl,
|
|||
|
|
mediaType: getTemplateMediaType(template),
|
|||
|
|
sourceAssets,
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const defaultCommerceIntentFallback: CommerceDefaultIntent = { kind: "image", scenario: "mainImage" };
|
|||
|
|
|
|||
|
|
const normalizeDefaultCommerceIntent = (value: unknown): CommerceDefaultIntent => {
|
|||
|
|
if (!value || typeof value !== "object") return defaultCommerceIntentFallback;
|
|||
|
|
const record = value as Record<string, unknown>;
|
|||
|
|
const kind = record.kind === "video" ? "video" : "image";
|
|||
|
|
const scenario = typeof record.scenario === "string" ? record.scenario : "";
|
|||
|
|
if (kind === "video" || scenario === "salesVideo") return { kind: "video", scenario: "salesVideo" };
|
|||
|
|
const imageScenarios: CommerceDefaultImageScenarioKey[] = ["poster", "mainImage", "scene", "festival", "model", "background", "retouch"];
|
|||
|
|
return imageScenarios.includes(scenario as CommerceDefaultImageScenarioKey)
|
|||
|
|
? { kind: "image", scenario: scenario as CommerceDefaultImageScenarioKey }
|
|||
|
|
: defaultCommerceIntentFallback;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const commerceScenarioGenerationKind = (scenario: CommerceDefaultImageScenarioKey): "singleImage" | "imageEdit" =>
|
|||
|
|
scenario === "background" || scenario === "retouch" ? "imageEdit" : "singleImage";
|
|||
|
|
|
|||
|
|
const cloneSetCountOptions: Array<{
|
|||
|
|
key: CloneSetCountKey;
|
|||
|
|
title: string;
|
|||
|
|
desc: string;
|
|||
|
|
}> = [
|
|||
|
|
{ key: "selling", title: "卖点图", desc: "展示商品核心卖点和细节特写" },
|
|||
|
|
{ key: "white", title: "白底图", desc: "白底主图,多角度呈现商品细节" },
|
|||
|
|
{ key: "scene", title: "场景图", desc: "展示商品生活使用场景和人物搭配" },
|
|||
|
|
];
|
|||
|
|
const cloneSetCountKeys = cloneSetCountOptions.map((option) => option.key);
|
|||
|
|
const minCloneSetTotal = 1;
|
|||
|
|
const maxCloneSetTotal = 16;
|
|||
|
|
const maxCloneProductImages = 10;
|
|||
|
|
const maxCloneReferenceImages = 20;
|
|||
|
|
const cloneVideoDurationMin = 5;
|
|||
|
|
const cloneVideoDurationMax = 45;
|
|||
|
|
const composerDurationOptions = [5, 10, 15];
|
|||
|
|
const cloneVideoQualityOptions: Array<{ key: CloneVideoQualityKey; label: string; desc: string }> = [
|
|||
|
|
{ key: "standard", label: "标准", desc: "快速出片" },
|
|||
|
|
{ key: "high", label: "高清", desc: "推荐" },
|
|||
|
|
{ key: "ultra", label: "超清", desc: "细节增强" },
|
|||
|
|
];
|
|||
|
|
const cloneReplicateLevelOptions: Array<{ key: CloneReplicateLevelKey; title: string; desc: string }> = [
|
|||
|
|
{ key: "style", title: "参考风格", desc: "参考整体风格和结构,自动调整色彩和重构场景。" },
|
|||
|
|
{ key: "high", title: "高度复刻", desc: "参考视觉结构替换产品和文案,保留主要场景细节。" },
|
|||
|
|
];
|
|||
|
|
const tryOnRatioOptions = ["3:4", "1:1", "9:16"];
|
|||
|
|
const tryOnScenes = ["纯色棚拍", "都市街头", "街角咖啡", "自然草坪", "度假海滩", "温馨居家", "艺术展馆"];
|
|||
|
|
const normalizeCloneModelSceneSelection = (scenes: string[] | null | undefined) => {
|
|||
|
|
const validScenes = (scenes ?? []).filter((scene) => typeof scene === "string" && scene.trim());
|
|||
|
|
const latestScene = validScenes[validScenes.length - 1];
|
|||
|
|
return latestScene ? [latestScene] : [];
|
|||
|
|
};
|
|||
|
|
const tryOnModelOptions = {
|
|||
|
|
gender: ["女", "男"],
|
|||
|
|
age: ["青年", "少年", "中年"],
|
|||
|
|
ethnicity: ["欧美白人", "亚洲人", "拉美裔", "非洲裔"],
|
|||
|
|
body: ["标准", "高挑", "微胖", "运动"],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const detailTypeOptions = ["普通A+", "品牌A+", "标准详情页", "移动端长图"];
|
|||
|
|
const detailModules = [
|
|||
|
|
{ id: "hero", title: "首页焦点图", desc: "集中呈现核心利益点" },
|
|||
|
|
{ id: "selling", title: "卖点强化图", desc: "放大产品优势" },
|
|||
|
|
{ id: "usage", title: "使用情境图", desc: "还原实际使用画面" },
|
|||
|
|
{ id: "angle", title: "外观角度图", desc: "展示不同视角造型" },
|
|||
|
|
{ id: "scene", title: "氛围场景图", desc: "营造产品应用环境" },
|
|||
|
|
{ id: "detail", title: "细节特写图", desc: "突出材质和做工" },
|
|||
|
|
{ id: "story", title: "品牌理念图", desc: "表达品牌主张" },
|
|||
|
|
{ id: "size", title: "规格尺寸图", desc: "说明尺寸容量尺码" },
|
|||
|
|
{ id: "compare", title: "效果对照图", desc: "呈现前后差异" },
|
|||
|
|
{ id: "spec", title: "参数信息表", desc: "整理商品关键数据" },
|
|||
|
|
{ id: "craft", title: "工艺流程图", desc: "说明制作与处理步骤" },
|
|||
|
|
{ id: "gift", title: "清单配件图", desc: "展示包装内全部内容" },
|
|||
|
|
{ id: "series", title: "SKU组合图", desc: "呈现颜色款式组合" },
|
|||
|
|
{ id: "ingredient", title: "成分材质图", desc: "说明配方或材料构成" },
|
|||
|
|
{ id: "service", title: "保障说明图", desc: "传达质保退换承诺" },
|
|||
|
|
{ id: "tips", title: "使用提示图", desc: "提醒操作与保养要点" },
|
|||
|
|
];
|
|||
|
|
const defaultDetailModuleIds: string[] = [];
|
|||
|
|
const maxDetailModuleSelection = 6;
|
|||
|
|
const cloneDetailModules = detailModules;
|
|||
|
|
|
|||
|
|
function getImageFileFormat(file: File) {
|
|||
|
|
const mimeFormat = file.type.split("/")[1]?.replace("jpeg", "jpg").toUpperCase();
|
|||
|
|
if (mimeFormat) return mimeFormat;
|
|||
|
|
return file.name.split(".").pop()?.toUpperCase() ?? "";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getRemoteImageFormat(mimeType: string, imageUrl: string) {
|
|||
|
|
const mimeFormat = mimeType.split("/")[1]?.replace("jpeg", "jpg").toUpperCase();
|
|||
|
|
if (mimeFormat) return mimeFormat;
|
|||
|
|
return imageUrl.split("?")[0].split(".").pop()?.toUpperCase() ?? "IMAGE";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getRemoteImageName(imageUrl: string, fallback: string) {
|
|||
|
|
try {
|
|||
|
|
const parsed = new URL(imageUrl);
|
|||
|
|
const filename = decodeURIComponent(parsed.pathname.split("/").filter(Boolean).pop() || "");
|
|||
|
|
return filename || fallback;
|
|||
|
|
} catch {
|
|||
|
|
return fallback;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function readImageDimensions(src: string): Promise<{ width: number; height: number }> {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const image = new Image();
|
|||
|
|
image.onload = () => resolve({ width: image.naturalWidth, height: image.naturalHeight });
|
|||
|
|
image.onerror = reject;
|
|||
|
|
image.src = src;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const blobToDataUrl = (blob: Blob): Promise<string> =>
|
|||
|
|
new Promise((resolve, reject) => {
|
|||
|
|
const reader = new FileReader();
|
|||
|
|
reader.onload = () => resolve(String(reader.result || ""));
|
|||
|
|
reader.onerror = () => reject(reader.error || new Error("文件读取失败"));
|
|||
|
|
reader.readAsDataURL(blob);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
function clampCloneVideoDuration(value: number) {
|
|||
|
|
return Math.min(cloneVideoDurationMax, Math.max(cloneVideoDurationMin, Math.round(value)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function mergeEcommerceHistoryRecords(...recordGroups: EcommerceHistoryRecord[][]): EcommerceHistoryRecord[] {
|
|||
|
|
const recordsById = new Map<string, EcommerceHistoryRecord>();
|
|||
|
|
for (const records of recordGroups) {
|
|||
|
|
for (const record of records) {
|
|||
|
|
const normalized = normalizeEcommerceHistoryRecord(record);
|
|||
|
|
const existing = recordsById.get(normalized.id);
|
|||
|
|
if (!existing || normalized.createdAt >= existing.createdAt || normalized.turns?.length !== existing.turns?.length) {
|
|||
|
|
recordsById.set(normalized.id, normalized);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return Array.from(recordsById.values()).sort((a, b) => b.createdAt - a.createdAt).slice(0, 30);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export {
|
|||
|
|
smartCutoutColorPresets,
|
|||
|
|
smartCutoutSizeOptions,
|
|||
|
|
type SmartCutoutSizeKey,
|
|||
|
|
ecommerceInspirationTabs,
|
|||
|
|
buildInspirationPrompt,
|
|||
|
|
getPlatformLogoText,
|
|||
|
|
getPlatformLogoVariant,
|
|||
|
|
getPlatformLogoMarks,
|
|||
|
|
primaryCommerceScenarioKeys,
|
|||
|
|
scenarioSettingsKeys,
|
|||
|
|
scenarioAdvancedSettingsKeys,
|
|||
|
|
commerceScenarioOutputMap,
|
|||
|
|
ecommerceTemplateCategoryMap,
|
|||
|
|
getTemplateMediaType,
|
|||
|
|
mapRemoteTemplateToScenarioTemplate,
|
|||
|
|
defaultCommerceIntentFallback,
|
|||
|
|
normalizeDefaultCommerceIntent,
|
|||
|
|
commerceScenarioGenerationKind,
|
|||
|
|
cloneSetCountOptions,
|
|||
|
|
cloneSetCountKeys,
|
|||
|
|
minCloneSetTotal,
|
|||
|
|
maxCloneSetTotal,
|
|||
|
|
maxCloneProductImages,
|
|||
|
|
maxCloneReferenceImages,
|
|||
|
|
cloneVideoDurationMin,
|
|||
|
|
cloneVideoDurationMax,
|
|||
|
|
composerDurationOptions,
|
|||
|
|
cloneVideoQualityOptions,
|
|||
|
|
cloneReplicateLevelOptions,
|
|||
|
|
tryOnRatioOptions,
|
|||
|
|
tryOnScenes,
|
|||
|
|
normalizeCloneModelSceneSelection,
|
|||
|
|
tryOnModelOptions,
|
|||
|
|
detailTypeOptions,
|
|||
|
|
detailModules,
|
|||
|
|
defaultDetailModuleIds,
|
|||
|
|
maxDetailModuleSelection,
|
|||
|
|
cloneDetailModules,
|
|||
|
|
getImageFileFormat,
|
|||
|
|
getRemoteImageFormat,
|
|||
|
|
getRemoteImageName,
|
|||
|
|
readImageDimensions,
|
|||
|
|
blobToDataUrl,
|
|||
|
|
clampCloneVideoDuration,
|
|||
|
|
mergeEcommerceHistoryRecords,
|
|||
|
|
};
|