ai-chatbot/lib/detail-ui-design-skill.ts
Your Name 3101f5966d
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: enhance canvas agent UI customization
2026-04-23 13:25:05 +08:00

232 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { DetailGenerationMode } from "@/lib/detail-ui-protocol";
export const DETAIL_UI_DESIGN_SKILL_VERSION = "detail-ui-design-skill/v1";
export interface DetailUiDesignSkillSpec {
version: typeof DETAIL_UI_DESIGN_SKILL_VERSION;
name: string;
source: string;
language: "zh" | "en";
generationMode: DetailGenerationMode;
canvas: {
width: number;
height: number;
grid: string;
maxOverlayCoverage: string;
};
visualDirection: string[];
typography: string[];
layout: string[];
imageFusion: string[];
dslContract: string[];
antiPatterns: string[];
reviewChecklist: string[];
metadata: {
screenCount: number;
hasReferenceImages: boolean;
hasModelImage: boolean;
hasBundleImages: boolean;
productName?: string;
brandTone?: string;
};
}
export interface DetailUiDesignSkillSummary {
version: typeof DETAIL_UI_DESIGN_SKILL_VERSION;
name: string;
source: string;
visualDirection: string[];
reviewChecklist: string[];
}
function normalizeText(value: unknown) {
return String(value || "").replace(/\s+/g, " ").trim();
}
export function buildDetailUiDesignSkillSpec(params: {
language: "zh" | "en";
generationMode: DetailGenerationMode;
screenCount: number;
hasReferenceImages: boolean;
hasModelImage: boolean;
hasBundleImages: boolean;
productName?: string;
brandTone?: string;
}): DetailUiDesignSkillSpec {
const isZh = params.language === "zh";
return {
version: DETAIL_UI_DESIGN_SKILL_VERSION,
name: isZh ? "详情 UI 设计技能" : "Detail UI Design Skill",
source: "Google Stitch DESIGN.md pattern + Apple product-showcase restraint + Linear precision + Figma editable-layer semantics",
language: params.language,
generationMode: params.generationMode,
canvas: {
width: 790,
height: 1300,
grid: isZh ? "8px 基础栅格,左右安全边距 56px,主内容按 12 栅格对齐" : "8px base grid, 56px side safe margins, main content aligned to a 12-column rhythm",
maxOverlayCoverage: isZh ? "可编辑 UI 覆盖面积建议 18%-38%,最高不超过 45%" : "Editable UI overlay should cover 18%-38% of the canvas, never above 45%",
},
visualDirection: isZh
? [
"产品/素材主体优先,主体轮廓不能被大面积 UI 卡片遮挡。",
"一屏只建立一个主视觉焦点,文字层服务焦点,不把画面做成模板拼贴说明页。",
"整体气质克制、精密、可上线:少装饰,靠比例、留白、对比和真实素材质感建立高级感。",
"强调色只用于 CTA、重点徽章或关键对比,不能把整屏染成单一蓝紫渐变。",
]
: [
"Prioritize the product or reference asset; never bury the silhouette under large UI cards.",
"Create one dominant focal point per screen. Text supports the focal point instead of turning the frame into a collage.",
"Use restrained production-grade polish: proportion, whitespace, contrast and material realism over decoration.",
"Reserve accent color for CTA, proof badge, or key comparison only; avoid one-note blue/purple gradients.",
],
typography: isZh
? [
"主标题 52-64px、字重 650-750、行高 1.06-1.15,禁止负字距,最多 2 行。",
"副标题 26-34px、字重 550-650,必须是一句结果句,不写成长段说明。",
"正文 18-24px、字重 450-600、行高 1.34-1.5,单块最多 4 行。",
"同一套详情页只能使用一个字体家族和三个清晰层级:标题/副标题/正文。",
]
: [
"Hero title 52-64px, weight 650-750, line-height 1.06-1.15, no negative tracking, max 2 lines.",
"Subtitle 26-34px, weight 550-650, one result sentence only.",
"Body 18-24px, weight 450-600, line-height 1.34-1.5, max 4 lines per block.",
"Use one font family and exactly three readable levels: title, subtitle, body.",
],
layout: isZh
? [
"首屏必须是封面海报/KV:单主体、强焦点、清晰标题安全区,禁止多卡片堆叠。",
"卡片只用于承载真实信息模块;卡片圆角 8-12px,阴影轻,边框低透明。",
"不要把卡片套卡片;一屏最多 1 个主面板 + 1 个辅助网格/证据条。",
"UI 区域必须避开商品脸部、Logo、关键结构和手持/使用动作。",
]
: [
"First screen must feel like a cover/KV poster: single subject, strong focus, clean headline safe zone.",
"Use cards only for real information modules; radius 8-12px, light shadow, low-opacity border.",
"No card nesting. Max one main panel plus one supporting grid/proof strip per screen.",
"UI regions must avoid product face, logo, critical structure, and usage action.",
],
imageFusion: isZh
? [
params.hasReferenceImages
? "已给参考图:生成画面必须复用参考素材的结构、颜色、材质、比例和关键识别特征。"
: "未给参考图:画面主体仍需清晰、完整、可识别,不用抽象氛围图代替产品。",
params.hasModelImage
? "已给模特图:涉及人物时保持同一人物身份,不替换脸型、发型、体型和服装风格。"
: "未给模特图:不要依赖人物脸部作为主卖点。",
params.hasBundleImages
? "搭配图只服务一屏关联销售,其余屏不要把搭配品抢成主角。"
: "未给搭配图:不要凭空制造搭配商品主叙事。",
"AGENT排版模式下,生图提示词只生成干净画面层,不生成任何可见文字、假按钮、假 UI。",
]
: [
params.hasReferenceImages
? "Reference images are provided: preserve structure, color, material, proportion, and key identity cues."
: "No reference image: keep the product subject clear and inspectable; avoid abstract atmosphere as a substitute.",
params.hasModelImage
? "Model image is provided: preserve the same identity when humans appear."
: "No model image: do not rely on a human face as the main selling point.",
params.hasBundleImages
? "Bundle products serve exactly one cross-sell screen and must not steal the main subject elsewhere."
: "No bundle refs: do not invent a bundle-product narrative.",
"In agent-layout mode, image prompts generate clean background only: no visible text, fake buttons, or fake UI.",
],
dslContract: isZh
? [
"dslV2Screens 必须语义化命名 id,例如 hero-title、proof-strip、feature-grid。",
"region 使用 0-1 归一化坐标;宽高和位置必须像设计工具的 frame,而不是随意漂浮。",
"nodes 只使用允许的 kind,优先 panel/stack/row/heading/body/badge/proof/cta 的少量组合。",
"每个文本节点必须可编辑、短句化、层级明确,不把长说明塞进 heading。",
]
: [
"dslV2Screens must use semantic ids such as hero-title, proof-strip, feature-grid.",
"Use normalized 0-1 regions like design-tool frames, not arbitrary floating boxes.",
"Use only allowed node kinds, with a sparse combination of panel/stack/row/heading/body/badge/proof/cta.",
"Every text node must be editable, concise, and hierarchy-aware; no long explanations in headings.",
],
antiPatterns: isZh
? [
"禁止大面积玻璃拟态卡片盖住主体。",
"禁止蓝紫渐变、发光球、装饰块堆满画面。",
"禁止主标题写“核心价值/场景痛点/第X屏/本屏展示”等规划话术。",
"禁止同屏 4 个以上信息模块或超过 2 种强调色。",
"禁止可见文字由生图模型生成,文字必须走可编辑 UI 层。",
]
: [
"No large glassmorphism cards covering the subject.",
"No blue/purple gradient wash, glowing blobs, or decorative block clutter.",
"No planning phrases in visible copy such as Screen X or This frame shows.",
"No more than 4 information modules or 2 accent colors in one screen.",
"No image-model rendered visible text; text must live in editable UI layers.",
],
reviewChecklist: isZh
? [
"主体是否完整、清晰、没有被 UI 遮挡?",
"首屏是否像封面海报,而不是拼贴说明页?",
"字体层级是否只有标题/副标题/正文三层,且统一?",
"UI 覆盖面积是否控制在 45% 内,并避开关键素材?",
"是否没有负字距、超大圆角、重阴影、卡片套卡片?",
"visualPrompt 是否完全禁止生成可见文字?",
]
: [
"Is the subject complete, clear, and unobstructed by UI?",
"Does the first screen feel like a cover poster rather than a collage?",
"Are there only title/subtitle/body levels, consistently applied?",
"Is UI coverage under 45% and away from critical asset details?",
"Are negative tracking, huge radii, heavy shadows, and nested cards avoided?",
"Does visualPrompt fully forbid visible text generation?",
],
metadata: {
screenCount: Math.max(1, Math.round(params.screenCount || 1)),
hasReferenceImages: params.hasReferenceImages,
hasModelImage: params.hasModelImage,
hasBundleImages: params.hasBundleImages,
productName: normalizeText(params.productName) || undefined,
brandTone: normalizeText(params.brandTone) || undefined,
},
};
}
export function summarizeDetailUiDesignSkill(
spec: DetailUiDesignSkillSpec
): DetailUiDesignSkillSummary {
return {
version: spec.version,
name: spec.name,
source: spec.source,
visualDirection: spec.visualDirection.slice(0, 4),
reviewChecklist: spec.reviewChecklist.slice(0, 6),
};
}
export function buildDetailUiDesignSkillFlowStep(
spec: DetailUiDesignSkillSpec
): string {
return spec.language === "zh"
? "详情 UI Design Skill:按 DESIGN.md 方式锁定视觉语言、素材融合、安全区、可编辑 DSL 与反俗套自检。"
: "Detail UI Design Skill: lock visual language, asset fusion, safe zones, editable DSL, and anti-slop review using DESIGN.md-style rules.";
}
export function buildDetailUiDesignSkillPrompt(
spec: DetailUiDesignSkillSpec
): string {
const section = (title: string, items: string[]) =>
`${title}\n${items.map((item) => `- ${item}`).join("\n")}`;
const title = spec.language === "zh"
? `【${spec.name} ${spec.version}】`
: `[${spec.name} ${spec.version}]`;
return [
title,
`${spec.language === "zh" ? "来源" : "Source"}: ${spec.source}`,
`${spec.language === "zh" ? "画布" : "Canvas"}: ${spec.canvas.width}x${spec.canvas.height}; ${spec.canvas.grid}; ${spec.canvas.maxOverlayCoverage}`,
section(spec.language === "zh" ? "视觉方向" : "Visual Direction", spec.visualDirection),
section(spec.language === "zh" ? "字体层级" : "Typography", spec.typography),
section(spec.language === "zh" ? "布局规则" : "Layout Rules", spec.layout),
section(spec.language === "zh" ? "图片素材融合" : "Image Fusion", spec.imageFusion),
section(spec.language === "zh" ? "DSL 输出契约" : "DSL Contract", spec.dslContract),
section(spec.language === "zh" ? "禁止项" : "Anti-patterns", spec.antiPatterns),
section(spec.language === "zh" ? "生成前自检" : "Preflight Checklist", spec.reviewChecklist),
].join("\n");
}