| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- import { createHash } from "node:crypto";
- import { DEFAULT_PAGE_TEMPLATE_IDS, pageTemplate } from "./reference-first-page-template-registry.mjs";
- import { resolvePlatformDesignProfile } from "./platform-design-profile-registry.mjs";
- const text = (value, fallback = "") => typeof value === "string" && value.trim() ? value.trim() : fallback;
- const array = (value) => Array.isArray(value) ? value.map((item) => text(item)).filter(Boolean) : [];
- const digest = (value) => createHash("sha256").update(String(value)).digest("hex");
- function normalizeCreativeBrief(value = {}) {
- const result = {
- category: text(value.category, "未明确类别商品"),
- form: text(value.form, "以三视图可见完整形态为准"),
- colors: array(value.colors),
- material_cues: array(value.material_cues),
- identity_cues: array(value.identity_cues),
- visible_selling_point_candidates: array(value.visible_selling_point_candidates),
- natural_support_and_use: array(value.natural_support_and_use),
- human_interactions: array(value.human_interactions),
- commercial_direction: text(value.commercial_direction, "真实商业产品摄影"),
- creative_audience_hypothesis: text(value.creative_audience_hypothesis),
- unsupported_copy_facts: array(value.unsupported_copy_facts),
- };
- if (result.colors.length === 0) result.colors.push("颜色严格以三视图为准");
- if (result.material_cues.length === 0) result.material_cues.push("材质和光泽严格以三视图为准");
- if (result.identity_cues.length === 0) result.identity_cues.push("商品轮廓、比例、Logo和包装原有识别元素");
- if (result.visible_selling_point_candidates.length < 2) throw new Error("CREATIVE_BRIEF_TWO_VISIBLE_SELLING_POINTS_REQUIRED");
- if (result.natural_support_and_use.length === 0) throw new Error("CREATIVE_BRIEF_NATURAL_USE_REQUIRED");
- return result;
- }
- function normalizePage(page = {}, index) {
- const id = String(index + 1).padStart(2, "0");
- const templateId = text(page.template_id, DEFAULT_PAGE_TEMPLATE_IDS[index]);
- const template = pageTemplate(templateId);
- return {
- id,
- role: template.family,
- template_id: template.id,
- selection_reason: text(page.selection_reason, `该配方用于完成${template.page_goal}`),
- page_job: text(page.page_job, template.page_goal),
- scene: text(page.scene, index === 0 ? "纯白无缝背景" : "依据视觉设定板选择与商品自然使用关系一致的具体物理空间"),
- product_pose: text(page.product_pose, index === 0 ? "完整商品自然稳定居中,无遮挡" : template.relation),
- camera: text(page.camera, template.camera_language),
- human_relation: text(page.human_relation, template.family === "usage_scene" ? "根据商品自然使用方式建立明确人物关系" : "人物仅在有助于本页任务时出现"),
- props: array(page.props),
- composition: text(page.composition, template.composition),
- text_safe_zone: text(page.text_safe_zone, template.safe_zone),
- difference_from_peer: text(page.difference_from_peer, index === 0 ? "白底基准页无同定位页面" : "与同定位另一页采用不同任务、镜头、关系和构图"),
- };
- }
- export function validateSetBlueprint(blueprint) {
- if (blueprint?.schema_version !== "2.0") throw new Error("SET_BLUEPRINT_VERSION_UNSUPPORTED");
- if (!Array.isArray(blueprint.pages) || blueprint.pages.length !== 7) throw new Error("SET_BLUEPRINT_SEVEN_PAGES_REQUIRED");
- const expected = ["white_background", "selling_point", "selling_point", "usage_scene", "usage_scene", "purchase_decision", "purchase_decision"];
- if (blueprint.pages.some((page, index) => page.id !== String(index + 1).padStart(2, "0") || page.role !== expected[index])) {
- throw new Error("SET_BLUEPRINT_ROLE_ALLOCATION_INVALID");
- }
- if (new Set(blueprint.pages.map((page) => page.template_id)).size !== 7) throw new Error("SET_BLUEPRINT_TEMPLATE_REPEATED");
- for (const page of blueprint.pages) {
- for (const key of ["page_job", "scene", "product_pose", "camera", "composition", "text_safe_zone", "difference_from_peer"]) {
- if (!text(page[key])) throw new Error(`SET_BLUEPRINT_PAGE_FIELD_REQUIRED:${page.id}:${key}`);
- }
- }
- for (const pair of [[1, 2], [3, 4], [5, 6]]) {
- const left = blueprint.pages[pair[0]];
- const right = blueprint.pages[pair[1]];
- const differing = ["template_id", "page_job", "camera", "human_relation", "composition", "text_safe_zone"]
- .filter((key) => JSON.stringify(left[key]) !== JSON.stringify(right[key])).length;
- if (differing < 4) throw new Error(`SET_BLUEPRINT_PEER_DIFFERENCE_INSUFFICIENT:${left.role}`);
- }
- if (!blueprint.platform_strategy?.resolved_profile) throw new Error("SET_BLUEPRINT_PLATFORM_PROFILE_REQUIRED");
- return blueprint;
- }
- export function compileSetBlueprint({ runId, generatedReferences, brief = {}, creativePlan = {} } = {}) {
- if (!text(runId)) throw new Error("SET_BLUEPRINT_RUN_ID_REQUIRED");
- const references = array(generatedReferences);
- if (references.length !== 2) throw new Error("SET_BLUEPRINT_TWO_REFERENCES_REQUIRED");
- const platform = resolvePlatformDesignProfile(brief.platform);
- const creativeBrief = normalizeCreativeBrief(creativePlan.product_creative_brief || creativePlan.product || {});
- const rawPages = Array.isArray(creativePlan.pages) ? creativePlan.pages : [];
- if (rawPages.length !== 7) throw new Error("CREATIVE_PLAN_SEVEN_PAGES_REQUIRED");
- const pages = rawPages.map(normalizePage);
- const profile = platform.profile;
- const blueprint = {
- schema_version: "2.0",
- run_id: runId,
- input_asset_sha256: references.map(digest),
- instruction: text(brief.instruction, "生成适合电商详情页的中文七图"),
- platform_strategy: {
- requested_platform: platform.requested_platform,
- resolved_profile: platform.resolved_profile,
- fallback_used: platform.fallback_used,
- profile_version: profile.version,
- profile_snapshot: profile,
- },
- product_creative_brief: creativeBrief,
- visual_bible: {
- commercial_style: text(creativePlan.visual_bible?.commercial_style, creativeBrief.commercial_direction),
- palette: array(creativePlan.visual_bible?.palette).length ? array(creativePlan.visual_bible.palette) : creativeBrief.colors,
- lighting: text(creativePlan.visual_bible?.lighting, profile.color_lighting),
- prop_language: text(creativePlan.visual_bible?.prop_language, "道具克制且只解释商品关系"),
- background_materials: array(creativePlan.visual_bible?.background_materials),
- material_source: "商品形态、颜色、材质、纹理与光泽以产品三视图为唯一商品基准",
- setting_board_role: "产品视觉设定板只用于场景、氛围、构图与使用关系参考",
- },
- pages,
- difference_matrix: pages.slice(1).map((page) => ({ page_id: page.id, role: page.role, template_id: page.template_id, peer_difference: page.difference_from_peer })),
- copy_policy: {
- generation: "image_model_adds_no_new_text",
- verified_sources: ["current_user_prompt", "user_supplied_official_material"],
- inferred_product_facts_forbidden: true,
- },
- };
- return validateSetBlueprint(blueprint);
- }
- export function creativePlannerInstruction({ brief = {}, platformResolution = resolvePlatformDesignProfile(brief.platform) } = {}) {
- return `你是电商套图创意规划器,不是商品审核器。请根据输入的第一张产品三视图和第二张产品视觉设定板,为本次需求生成结构化设计方案。
- 用户需求:${text(brief.instruction, "生成适合电商详情页的中文七图")}
- 目标平台:${platformResolution.profile.label}
- 平台设计方向:${platformResolution.profile.rhythm};${platformResolution.profile.composition};${platformResolution.profile.scene_human_weight}。
- 只输出JSON对象,不要Markdown。结构必须为:
- {
- "product_creative_brief": {
- "category":"",
- "form":"",
- "colors":[""],
- "material_cues":[""],
- "identity_cues":[""],
- "visible_selling_point_candidates":["至少两个仅根据可见画面提出的卖点候选"],
- "natural_support_and_use":[""],
- "human_interactions":[""],
- "commercial_direction":"",
- "creative_audience_hypothesis":"",
- "unsupported_copy_facts":["不能从两张图证明的尺寸、性能、成分、功效等"]
- },
- "visual_bible": {"commercial_style":"","palette":[""],"lighting":"","prop_language":"","background_materials":[""]},
- "pages":[七个页面对象]
- }
- 七个页面按顺序必须使用以下定位与可选配方:
- 01 white_background:white_complete
- 02-03 selling_point:从 selling_form、selling_material、selling_operation、selling_brand 中选择两个不同配方
- 04-05 usage_scene:从 scene_core_environment、scene_human_interaction、scene_lifestyle、scene_mobile_outdoor、scene_installed 中选择两个不同配方
- 06-07 purchase_decision:从 decision_scale_fit、decision_usage_threshold、decision_context_match、decision_value_summary 中选择两个不同配方
- 每个页面对象必须包含:template_id、selection_reason、page_job、scene、product_pose、camera、human_relation、props数组、composition、text_safe_zone、difference_from_peer。
- 具体场景和动作必须适合当前商品;不要硬套不合理桌面、户外、穿戴或手持关系。六张非白底图不能只是同一模板换背景。同定位两页必须在任务、镜头、主体比例、人物/空间关系、视觉证据和文字区域中形成明确差异。图片后续独立生成,因此每页描述必须自足。不要把推断尺寸、材质、功效、成分、性能或包装清单写成商品事实。`;
- }
|