feat: seed style pro index presets
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-05-16 16:24:01 +08:00
parent d6a19ad171
commit 6a47359d8e
2 changed files with 341 additions and 174 deletions

View File

@ -281,11 +281,11 @@ export function StyleProSettingsClient() {
<Button
onClick={() => {
setConfig(defaultStyleProConfig);
toast.info("已恢复默认配置,保存后生效");
toast.info("已恢复 index (25) 预设,保存后生效");
}}
variant="outline"
>
恢复默认
恢复 index (25) 预设
</Button>
<Button disabled={saving} onClick={save}>
<Save className="mr-2 h-4 w-4" />
@ -648,4 +648,3 @@ export function StyleProSettingsClient() {
</main>
);
}

View File

@ -72,6 +72,332 @@ const DEFAULT_PROMPT = [
"不要新增无关主体,不要改变产品类别,不要破坏原始构图逻辑。",
].join("\n");
function presetOption(
id: string,
label: string,
prompt: string,
jsonKey: string,
order: number
): StyleProOption {
return {
id,
label,
description: prompt.length > 52 ? `${prompt.slice(0, 52)}...` : prompt,
prompt,
jsonKey,
enabled: true,
order,
};
}
function presetCategory(params: {
id: string;
title: string;
description: string;
jsonKey: string;
required?: boolean;
minSelections?: number;
maxSelections?: number;
order: number;
options: Array<{
id: string;
label: string;
prompt: string;
}>;
}): StyleProCategory {
return {
id: params.id,
title: params.title,
description: params.description,
required: params.required ?? false,
minSelections: params.minSelections ?? 0,
maxSelections: params.maxSelections ?? 1,
enabled: true,
order: params.order,
options: params.options.map((option, index) =>
presetOption(option.id, option.label, option.prompt, params.jsonKey, (index + 1) * 10)
),
};
}
const INDEX_25_STYLE_PRO_CATEGORIES: StyleProCategory[] = [
presetCategory({
id: "scene-time",
title: "时点",
description: "来自 index (25).html 的时点与天气预设,用于控制时间边界、色温和环境光感。",
jsonKey: "场景类型",
required: true,
minSelections: 1,
maxSelections: 1,
order: 10,
options: [
{
id: "dawn_twilight",
label: "黎明",
prompt:
"时间边界:日出前 30 分钟至日出瞬间,太阳高度角处于 -6°~0°。地平线有暖橙红渐变,无直射太阳光,以大气层散射漫射光为主,暗部细节丰富,天空从暖红橙过渡到淡蓝青蓝。",
},
{
id: "sunrise",
label: "日出",
prompt:
"日出:时间边界为太阳上边缘接触地平线至完全跃出地平线的 1-3 分钟。太阳直射光强但不刺眼,红橙光占比高,色温 1800K-2500K,光线有明显体积感,长阴影、星芒和地平线光晕明显。",
},
{
id: "golden",
label: "黄金时刻",
prompt:
"黄金时刻:日出后或日落前约 1 小时,太阳低角度斜射,色温 2800K-4000K,方向明确但柔和,阴影绵长,高光不过曝,暖调质感、通透空气和高饱和度明显。",
},
{
id: "early_morning",
label: "清晨",
prompt:
"清晨:日出后 1 小时至正午前 3 小时,太阳高度逐步升高,直射光适中,阴影清晰但不生硬,色温 4000K-5200K,画面明暗对比适中、色彩还原准确、空间纵深感强。",
},
{
id: "day",
label: "日景",
prompt:
"日景:白天常态时段,稳定太阳直射光,色温 5200K-5800K,曝光充足,细节完整,色彩还原真实,空间感和立体感强,是最通用的日常场景。",
},
{
id: "sunset",
label: "日落",
prompt:
"日落:日落前约 1 小时,红橙色光更强,色温 2700K-3800K,空气湿度和体积光更明显,天空偏橙红或粉紫渐变,长阴影柔和,氛围浓烈。",
},
{
id: "blue",
label: "蓝调",
prompt:
"蓝调时刻:日出前或日落后 20-30 分钟,太阳完全低于地平线,仅有均匀冷蓝散射天光,色温 9000K-11000K,人工暖光形成强烈冷暖对比,画面干净通透。",
},
{
id: "night",
label: "夜景",
prompt:
"夜景:无自然直射光,以月光、星光、城市人工光为主,明暗反差强,暗部占比高,亮部集中在光源区域,暖黄、冷蓝、霓虹和月光形成丰富色彩层次。",
},
{
id: "sunny",
label: "晴天",
prompt:
"晴天:云量低、太阳完整可见,蓝天纯净,直射硬光强,阴影锐利,色温约 5500K,空气通透度高,高对比、高饱和、细节清晰。",
},
{
id: "overcast",
label: "全阴",
prompt:
"全阴:厚云完全覆盖,太阳轮廓不可见,光线完全漫射且无方向性,色温 6000K-7000K,整体微冷,低对比、低饱和、阴影细节丰富但层次较弱。",
},
{
id: "after_rain",
label: "雨霁",
prompt:
"雨霁:雨后云层散开,空气尘埃被清洗,通透度极高,地面积水形成镜面反射,色彩饱满,残云、彩虹、水面反光和体积光带来丰富层次。",
},
{
id: "morning_fog",
label: "晨雾",
prompt:
"晨雾:秋冬清晨低层雾气,日出前形成,日出后 1-2 小时消散。漫射柔光穿过雾气形成强散射和体积感,近实远虚,空间纵深强,易出现丁达尔效应。",
},
],
}),
presetCategory({
id: "sunlight",
title: "光照",
description: "来自 index (25).html 的太阳光照预设,用于控制直射、投影和特殊光学效果。",
jsonKey: "太阳光照",
required: true,
minSelections: 1,
maxSelections: 1,
order: 20,
options: [
{
id: "blocked",
label: "太阳遮挡",
prompt: "太阳存在但被建筑物或云层遮挡,无直射光线,仅保留环境光晕和柔和漫射光。",
},
{
id: "yes",
label: "有太阳",
prompt: "出现明确太阳直射光,太阳光源可感知,方向性强,阴影清晰,明亮暖调照明。",
},
{
id: "blinds",
label: "百叶光影",
prompt:
"太阳光穿过百叶窗、格栅或栏杆形成平行线性光束和等距明暗条纹,边界清晰,几何秩序强,可伴随丁达尔效应。",
},
{
id: "tree",
label: "树影交散",
prompt: "太阳光穿过树冠和枝叶,形成自然交错的叶影投影,阴影不规则、有轻微风动感,画面自然鲜活。",
},
{
id: "sunlight_dappled",
label: "日照光斑",
prompt:
"阳光穿过叶片缝隙形成针孔成像般的圆形光斑,亮度高于周围阴影,随机分布并形成光暗交织的自然斑驳效果。",
},
{
id: "silhouette",
label: "剪影投影",
prompt: "强逆光或低角度光线形成明确剪影,主体边缘清晰,地面或墙面有长投影,明暗对比强烈。",
},
{
id: "window_grid",
label: "窗格投影",
prompt: "阳光穿过窗框形成矩形网格投影,光影边界清晰,带有建筑结构秩序感和室内空间层次。",
},
{
id: "tyndall",
label: "丁达尔效应",
prompt: "空气中有雾气、尘埃或水汽,光束路径可见,形成强烈体积光和空间纵深,光线穿透感明显。",
},
{
id: "crepuscular",
label: "云隙光",
prompt: "太阳光从云层缝隙穿出,形成放射状光束,天空和地面出现强烈体积光,画面有神圣、戏剧化氛围。",
},
{
id: "glory",
label: "宝光",
prompt: "逆光雾气或云雾环境中出现环状彩色光晕,围绕观察方向形成柔和神秘的光学效果。",
},
],
}),
presetCategory({
id: "design-style",
title: "设计风格",
description: "来自 index (25).html 的设计风格预设,用于控制空间、产品与软装的审美方向。",
jsonKey: "设计风格",
required: false,
maxSelections: 1,
order: 30,
options: [
{ id: "new_chinese", label: "新中式", prompt: "新中式设计风格,东方秩序、现代比例、克制留白与雅致材质结合。" },
{ id: "french", label: "法式", prompt: "法式设计风格,优雅线条、精致雕花、浪漫柔和的空间气质。" },
{ id: "italian_minimal", label: "意式极简", prompt: "意式极简设计风格,低饱和高级材质、简洁线条、克制但有质感。" },
{ id: "modern", label: "现代", prompt: "现代设计风格,清晰结构、简洁界面、实用比例与干净视觉秩序。" },
{ id: "light_luxury", label: "轻奢风", prompt: "轻奢风设计,金属、石材、皮革等精致材质搭配,强调高级但不过度浮夸。" },
{ id: "premium_gray", label: "高级灰", prompt: "高级灰风格,低饱和灰阶体系,现代艺术感和沉稳空间气质。" },
{ id: "minimalist", label: "极简主义", prompt: "极简主义风格,减少装饰、强调留白、体块比例和材质本身。" },
{ id: "nordic", label: "北欧", prompt: "北欧设计风格,明亮自然、木质温度、轻盈家具和舒适生活感。" },
{ id: "cream", label: "奶油风", prompt: "奶油风设计,柔和暖白、圆润体块、低对比材质和温馨包裹感。" },
{ id: "wabi_sabi", label: "诧寂风", prompt: "侘寂风格,自然粗粝材质、不完美之美、东方禅意与静谧留白。" },
{ id: "japanese", label: "日式", prompt: "日式设计风格,木质、纸感、低矮尺度、清爽秩序和自然光。" },
{ id: "natural_wood", label: "原木风", prompt: "原木风设计,大面积木纹、自然肌理、温暖生活气息和简洁空间线条。" },
{ id: "american_vintage", label: "美式复古", prompt: "美式复古风格,厚重家具、复古色调、旧木质感与温暖怀旧氛围。" },
{ id: "loft", label: "Loft风", prompt: "Loft 风格,开放空间、工业结构、裸露管线、金属和混凝土材质。" },
{ id: "industrial", label: "工业风", prompt: "工业风设计,黑铁、混凝土、裸露结构、粗粝材质和强烈空间个性。" },
{ id: "art_deco", label: "Art Deco", prompt: "Art Deco 风格,几何装饰、金属线条、对称秩序和复古奢华感。" },
],
}),
presetCategory({
id: "atmosphere",
title: "氛围",
description: "来自 index (25).html 的氛围预设,用于控制整体情绪、色调和商业质感。",
jsonKey: "场景氛围",
required: false,
maxSelections: 1,
order: 40,
options: [
{ id: "clean", label: "干净通透", prompt: "干净通透,自然光充沛,空间明亮开阔。" },
{ id: "warm", label: "温暖柔和", prompt: "温暖柔和,暖色光源,生活气息浓厚。" },
{ id: "cloudy_soft", label: "阴天柔光", prompt: "阴天柔光,漫射天光透过窗户进入室内,整体光线均匀柔和,无明显阴影。" },
{ id: "cinematic", label: "电影感", prompt: "电影级布光,戏剧化光影,高动态光比。" },
{ id: "natural_real", label: "自然纪实", prompt: "自然纪实,真实光感,最少人工干预。" },
{ id: "commercial", label: "商业质感", prompt: "商业摄影,高对比强饱和,强调材质质感。" },
{ id: "film", label: "胶片感", prompt: "胶片感,轻微颗粒,柔和色调,温暖复古氛围。" },
{ id: "cold_gray", label: "冷调高级灰", prompt: "冷调高级灰,低饱和,现代科技感。" },
{ id: "dramatic", label: "戏剧光影", prompt: "戏剧性光影,明暗对比强烈,电影叙事感。" },
{ id: "dreamy", label: "梦幻朦胧", prompt: "梦幻朦胧,柔焦质感,浪漫唯美。" },
{ id: "cyber", label: "赛博朋克", prompt: "赛博朋克,霓虹灯光,强烈色彩对比。" },
{ id: "vintage", label: "复古怀旧", prompt: "复古怀旧,暖黄调,旧时光质感。" },
{ id: "minimal_white", label: "极简留白", prompt: "极简留白,纯净空间,大量留白。" },
{ id: "deep", label: "深邃静谧", prompt: "深邃静谧,暗色调为主,少量重点照明,神秘高级感。" },
],
}),
presetCategory({
id: "view",
title: "外景",
description: "来自 index (25).html 的外景预设,用于给窗外或环境补充地点记忆点和空间层次。",
jsonKey: "外景",
required: false,
maxSelections: 1,
order: 50,
options: [
{ id: "shanghai", label: "上海", prompt: "窗外可见上海陆家嘴、黄浦江与外滩建筑群,现代繁华,蓝调时刻灯光与江面反射交织。" },
{ id: "chongqing", label: "重庆", prompt: "窗外可见重庆洪崖洞、嘉陵江、千厮门大桥与渝中半岛天际线,传统烟火气与赛博城市感并存。" },
{ id: "beijing", label: "北京", prompt: "窗外可见北京故宫中轴线、红墙黄瓦、飞檐斗拱与景山视野,庄重宏大、古典秩序强。" },
{ id: "hangzhou", label: "杭州", prompt: "窗外可见杭州西湖、雷峰塔、苏堤白堤与远山水面,晨雾或夕阳下治愈、轻盈、诗意。" },
{ id: "hongkong", label: "香港", prompt: "窗外可见香港维多利亚港、中环天际线和海面反射,蓝调时刻霓虹璀璨,都市感强。" },
{ id: "tokyo", label: "东京", prompt: "窗外可见东京塔、晴空塔、东京湾或隅田川与现代都市群,日系现代城市氛围。" },
{ id: "paris", label: "巴黎", prompt: "窗外可见巴黎埃菲尔铁塔、塞纳河、亚历山大三世桥和奥斯曼建筑群,浪漫、优雅、金色光感。" },
{ id: "iceland", label: "冰岛", prompt: "窗外可见冰岛极光、冰川、黑沙滩或荒原地貌,冷调、空旷、自然奇观感强。" },
{ id: "sanya", label: "海岛", prompt: "窗外可见海岛、海面、沙滩与热带植被,明亮通透,度假感和松弛感强。" },
{ id: "bamboo", label: "竹林", prompt: "窗外可见竹林,竖向线条密集,自然绿意和东方静谧感明显,增加空间层次。" },
{ id: "snow_mountain", label: "雪山", prompt: "窗外可见雪山远景,冷白高光、蓝灰阴影和清澈空气,空间开阔且有自然史诗感。" },
{ id: "desert", label: "沙漠", prompt: "窗外可见沙漠或戈壁,暖色沙丘、低饱和大地色和宽广留白,画面安静、有地域感。" },
],
}),
presetCategory({
id: "lighting-system",
title: "照明系统",
description: "来自 index (25).html 的照明系统预设,可多选叠加灯具与光源类型。",
jsonKey: "照明系统",
required: false,
minSelections: 0,
maxSelections: 3,
order: 60,
options: [
{ id: "off", label: "无", prompt: "未选择人工照明系统,保持原始环境光,不额外添加灯具。" },
{ id: "ceiling_spot", label: "天花射灯", prompt: "嵌入式天花防眩射灯,3000K-4000K,中高显色指数,精准下照,光斑清晰但不过曝。" },
{ id: "ceiling_strip", label: "天花灯带", prompt: "悬浮吊顶嵌入式线性灯带,间接漫射洗顶,光线均匀无断点,与吊顶结构齐平隐藏安装。" },
{ id: "ceiling_pendant", label: "天花吊灯", prompt: "天花悬挂式主吊灯,现代极简或轻奢造型,柔光漫射,灯体结构清晰,空间核心区域照明均匀。" },
{ id: "cabinet", label: "柜体灯带", prompt: "柜体内嵌线性灯带,柔和照亮层板、展示物或收纳区域,见光不见灯,提升材质层次。" },
{ id: "curtain", label: "窗帘盒灯带", prompt: "窗帘盒隐藏灯带向下或向墙面漫射,营造柔和边界光和空间包裹感。" },
{ id: "table", label: "台灯", prompt: "桌面台灯或床头台灯,暖白柔光,形成局部重点照明和生活化氛围。" },
{ id: "magnetic", label: "磁吸轨道灯", prompt: "磁吸轨道灯系统,线性轨道和可调灯头,精准控光,现代感强。" },
{ id: "wall_wash", label: "洗墙灯", prompt: "洗墙灯均匀扫亮墙面,形成连续光带和立面层次,突出材质纹理。" },
{ id: "mirror", label: "镜前灯", prompt: "镜前灯高显色柔光,无面部阴影,无偏色,均匀覆盖镜面和近景区域。" },
{ id: "fireplace", label: "壁炉火焰光", prompt: "壁炉动态火焰光,2000K-2500K 暖橙低色温,柔和跳动光影,氛围感强。" },
{ id: "candle", label: "烛光", prompt: "自然烛火光,多根蜡烛低色温暖橙光,柔和漫射,高对比光影和温暖生活感。" },
{ id: "accent", label: "重点照明", prompt: "空间重点照明,可调角度窄光束射灯,聚焦装饰画、雕塑、软装摆件或端景墙,突出细节和层次。" },
],
}),
presetCategory({
id: "render-style",
title: "画面表现",
description: "来自 index (25).html 的表现风格预设,用于控制最终成片语言。",
jsonKey: "画面表现",
required: true,
minSelections: 1,
maxSelections: 1,
order: 70,
options: [
{ id: "natural_doc", label: "真实环境", prompt: "自然纪实风格,真实光感,最少后期干预,保留原生现场细节,标准人眼透视,轻微胶片颗粒。" },
{ id: "cinematic_real", label: "电影级写实", prompt: "写实室内摄影,质感通透,细节丰富,电影级后期调色,标准镜头视角,精准光影,真实材质质感。" },
{ id: "arch_viz", label: "建筑效果图", prompt: "建筑效果图标准,清晰真实,专业表达,无过度后期,准确空间比例,合理光影分布。" },
{ id: "hollywood", label: "好莱坞", prompt: "cinematography,Wide Color Gamut,高光柔和,阴影细节丰富,电影叙事感,戏剧性光影对比。" },
{ id: "cyber_film", label: "赛博朋克", prompt: "银翼杀手2049冷蓝调,高对比暗部,青色高光,赛博朋克氛围,霓虹灯反射。" },
{ id: "wes_anderson", label: "韦斯安德森风", prompt: "布达佩斯大饭店粉调,柔和粉彩色系,复古浪漫氛围,对称构图感,高度风格化的色彩配置。" },
{ id: "japan_fresh", label: "日式小清新", prompt: "日系小清新,偏高曝光,柔和暖色,治愈氛围,清透空气感。" },
{ id: "morandi", label: "莫兰迪风", prompt: "莫兰迪色系,中性灰调,低饱和,优雅克制,画廊级配色。" },
{ id: "commercial_mag", label: "商业杂志风", prompt: "高对比强饱和,杂志级商业效果图,精致时髦,封面级出片。" },
{ id: "japan_film", label: "日系胶片", prompt: "日系胶片感,轻微颗粒,柔和色调,温暖复古氛围,参考日本室内设计杂志。" },
{ id: "low_sat", label: "高级灰", prompt: "低饱和高级灰,高级感,当代艺术调色,参考 Architectural Digest。" },
{ id: "cream_warm", label: "奶油风", prompt: "奶油暖调色系,柔和质感,温馨氛围,参考奶油风设计杂志。" },
{ id: "korean", label: "韩式", prompt: "韩式风格,柔和莫兰迪粉调、极简留白、自然光渗透、干花与亚麻质感、生活方式杂志质感。" },
{ id: "wabi_sabi", label: "侘寂", prompt: "侘寂风格,不完美之美、自然材质老化肌理、手工粗粝感、非对称平衡和东方禅意静谧氛围。" },
{ id: "impressionist", label: "油画", prompt: "油画印象派风格,可见笔触肌理、莫奈式光色捕捉、朦胧柔焦和画布纹理。" },
],
}),
];
export const defaultStyleProConfig: StyleProConfig = {
enabled: true,
modelId: "",
@ -79,176 +405,7 @@ export const defaultStyleProConfig: StyleProConfig = {
negativePrompt: "低清晰度,畸变,文字乱码,主体变形,产品结构错误,多余物体,水印",
aspectRatio: "auto",
imageSize: "2K",
categories: [
{
id: "audience",
title: "人群选择",
description: "定义画面服务的人群与生活方式基调。",
required: true,
minSelections: 1,
maxSelections: 1,
enabled: true,
order: 10,
options: [
{
id: "young-family",
label: "有孩家庭",
description: "温暖、亲近、真实生活气息。",
prompt:
"目标人群为有孩家庭,画面需要温暖、亲近、可信,呈现真实生活场景中的舒适使用感。",
jsonKey: "audience",
enabled: true,
order: 10,
},
{
id: "young-couple",
label: "年轻情侣 / 夫妻",
description: "轻松、共享空间、现代审美。",
prompt:
"目标人群为年轻情侣或年轻夫妻,画面强调共享空间、轻松陪伴和现代审美。",
jsonKey: "audience",
enabled: true,
order: 20,
},
{
id: "solo-youth",
label: "独居青年",
description: "自我表达、松弛、精致小空间。",
prompt:
"目标人群为独居青年,画面强调自我表达、松弛感、精致小空间和个人生活方式。",
jsonKey: "audience",
enabled: true,
order: 30,
},
],
},
{
id: "style",
title: "主题风格",
description: "确定整体视觉方向。",
required: true,
minSelections: 1,
maxSelections: 1,
enabled: true,
order: 20,
options: [
{
id: "modern-natural",
label: "自然生活",
description: "自然光、柔和材质、干净舒服。",
prompt:
"主题风格为自然生活方式,使用自然光、柔和材质、干净舒适的空间层次,避免过度商业棚拍感。",
jsonKey: "style",
enabled: true,
order: 10,
},
{
id: "commercial-premium",
label: "商业高级",
description: "精致布光、高质感、成片感强。",
prompt:
"主题风格为商业高级视觉,画面需要精致布光、高质感材质、清晰主体和成熟广告成片感。",
jsonKey: "style",
enabled: true,
order: 20,
},
{
id: "retro-warm",
label: "复古暖调",
description: "胶片感、温暖、怀旧但干净。",
prompt:
"主题风格为复古暖调,保留干净商业画质,同时加入温暖胶片质感和轻微怀旧氛围。",
jsonKey: "style",
enabled: true,
order: 30,
},
],
},
{
id: "light",
title: "光氛选择",
description: "定义光线时间和明暗关系。",
required: true,
minSelections: 1,
maxSelections: 1,
enabled: true,
order: 30,
options: [
{
id: "sunrise-edge",
label: "日出边缘光",
description: "短暂临界时段,明暗对比强。",
prompt:
"时间效果:太阳上边缘接触地平线至完全跃出地平线的 1-3 分钟;光线特征:直射光强但不刺眼,红橙光占比高,色温约 1800K-2500K,带有明显体积感;视觉特征:以主体为核心视点,地面形成长阴影,画面暖调饱满,明暗对比强烈。",
jsonKey: "light",
enabled: true,
order: 10,
},
{
id: "soft-window",
label: "自然窗光",
description: "柔和、真实、生活感。",
prompt:
"光线为自然窗光,柔和漫射,保留真实阴影层次,画面舒适通透,避免硬阴影和过曝。",
jsonKey: "light",
enabled: true,
order: 20,
},
{
id: "cinematic",
label: "电影氛围光",
description: "方向明确,层次和空间感强。",
prompt:
"光线为电影氛围光,方向明确,明暗层次清晰,空间纵深强,主体边缘有细腻轮廓光。",
jsonKey: "light",
enabled: true,
order: 30,
},
],
},
{
id: "camera",
title: "机位选择",
description: "控制视角和镜头语言。",
required: false,
minSelections: 0,
maxSelections: 1,
enabled: true,
order: 40,
options: [
{
id: "wide-perspective",
label: "广角透视",
description: "空间开阔,纵深明显。",
prompt:
"机位使用广角透视,空间开阔,纵深明显,但主体比例保持自然,不产生夸张畸变。",
jsonKey: "camera",
enabled: true,
order: 10,
},
{
id: "eye-level",
label: "平视机位",
description: "稳定真实,适合商品主体。",
prompt:
"机位使用平视视角,画面稳定真实,主体呈现清晰完整,适合商业商品展示。",
jsonKey: "camera",
enabled: true,
order: 20,
},
{
id: "close-detail",
label: "近景细节",
description: "突出材质和光影细节。",
prompt:
"机位偏近景细节,突出材质、纹理、边缘光和主体工艺感,同时保留可识别的整体结构。",
jsonKey: "camera",
enabled: true,
order: 30,
},
],
},
],
categories: INDEX_25_STYLE_PRO_CATEGORIES,
};
function asRecord(value: unknown): Record<string, unknown> {
@ -280,6 +437,11 @@ function sortByOrder<T extends { order: number; id: string }>(items: T[]) {
return [...items].sort((a, b) => a.order - b.order || a.id.localeCompare(b.id));
}
function isLegacyDefaultCategories(categories: StyleProCategory[]) {
const ids = categories.map((category) => category.id).join(",");
return ids === "audience,style,light,camera";
}
export function normalizeStyleProConfig(value: unknown): StyleProConfig {
const raw = asRecord(value);
const fallback = defaultStyleProConfig;
@ -334,6 +496,10 @@ export function normalizeStyleProConfig(value: unknown): StyleProConfig {
})
.filter((category) => category.title && category.options.length > 0);
const sortedCategories = sortByOrder(
categories.length ? categories : fallback.categories
);
return {
enabled: typeof raw.enabled === "boolean" ? raw.enabled : fallback.enabled,
modelId: cleanString(raw.modelId, fallback.modelId, 160),
@ -341,7 +507,9 @@ export function normalizeStyleProConfig(value: unknown): StyleProConfig {
negativePrompt: cleanString(raw.negativePrompt, fallback.negativePrompt, 4000),
aspectRatio: cleanString(raw.aspectRatio, fallback.aspectRatio, 32),
imageSize: cleanString(raw.imageSize, fallback.imageSize, 32),
categories: sortByOrder(categories.length ? categories : fallback.categories),
categories: isLegacyDefaultCategories(sortedCategories)
? fallback.categories
: sortedCategories,
};
}