Merge remote-tracking branch 'origin/master' into codex/yace

# Conflicts:
#	.hermes/shared-memory/pitfalls.md
This commit is contained in:
kdletters
2026-05-16 23:05:38 +08:00
64 changed files with 10425 additions and 1095 deletions

View File

@@ -158,6 +158,26 @@ const assetDefinitions = [
chromaKeyNote,
].join(''),
},
{
id: 'character-outline-only-v3',
output: 'picture-book-character-outline-v3.png',
sourceOutput: 'picture-book-character-outline-v2.png',
sourceDirectory: 'asset',
transparent: true,
localPostprocess: 'character-outline-only',
prompt:
'本地后处理资源:基于 character-outline-v2 提取用户角色外轮廓,只保留浅青白描边,中间完全透明,不保留原有半透明材质、填充和明暗变化。',
},
{
id: 'character-outline-white-v4',
output: 'picture-book-character-outline-v4.png',
sourceOutput: 'picture-book-character-outline-v2.png',
sourceDirectory: 'asset',
transparent: true,
localPostprocess: 'character-outline-white-thin',
prompt:
'本地后处理资源:基于 character-outline-v2 提取用户角色外轮廓,先弱化耳朵、手指、脚趾等细碎凸起,再输出更细的白色描边,中间完全透明。',
},
{
id: 'hud-strip',
output: 'picture-book-hud-strip-v2.png',
@@ -601,6 +621,16 @@ const assetDefinitions = [
chromaKeyNote,
].join(''),
},
{
id: 'wave-cat-body-guide-v7',
output: 'picture-book-wave-cat-body-guide-v7.png',
sourceOutput: 'picture-book-wave-cat-body-guide-v6.png',
sourceDirectory: 'asset',
transparent: true,
localPostprocess: 'remove-cat-body-shoulder-dots',
prompt:
'本地后处理资源:基于 wave-cat-body-guide-v6 去除身体左右两侧不协调的小圆点,保留猫头、身体、透明边界和整体水彩风格。',
},
{
id: 'wave-cat-arm-guide-v6',
output: 'picture-book-wave-cat-arm-guide-v6.png',
@@ -632,6 +662,37 @@ const assetDefinitions = [
chromaKeyNote,
].join(''),
},
{
id: 'wave-cat-arm-guide-v7',
output: 'picture-book-wave-cat-arm-guide-v7.png',
sourceOutput: 'picture-book-wave-cat-arm-guide-v7-source.png',
size: '1024x1024',
transparent: true,
transparencyCleanup: 'cat-guide',
useWaveCatHeadReference: true,
useWaveCatArmReference: true,
layoutNormalization: {
canvasWidth: 1024,
canvasHeight: 1024,
fit: 'contain',
fillWidth: 0.74,
fillHeight: 0.88,
anchorY: 'bottom',
padding: 20,
},
prompt: [
'请在参考手臂资源的基础上重新绘制儿童动作互动游戏猫咪挥手动画用的单侧手臂资源,严格作为可动纸偶拆件。只画一条橘白猫手臂:底部是肩膀连接端,向左上方弯曲,末端是一只简化圆猫手。',
'关键修改:末端圆猫爪必须正面对镜头,像在对观众挥手。圆爪正面轮廓要清楚可见,不要转成侧面,不要转向画面内侧或角色中心,不要画成握拳或背面。可以用浅奶油白圆形爪面、柔和高光和非常淡的短弧线表现正面对镜头。',
'猫手必须像多啦A梦式圆手或软玩具圆爪一个完整圆润圆爪不画分开的手指不画尖爪不画黑色或深色爪垫。若需要爪面细节只允许非常浅的桃色小圆面或柔和弧线不能变成真实动物爪垫。',
'手臂短而厚实,像小猫上肢,不要成人类长手臂。资源必须适合网页左右镜像复用和围绕肩部连接点旋转:肩膀连接端在画面底部偏内侧,圆手在画面上方,四周留透明空白。',
'颜色参考输入猫猫头和参考手臂:浅奶油白和淡橘色为主体,少量浅橘斑纹,柔和暖棕或浅橘棕描边;不要纯黑粗描边。',
'请避免粉色大背景、避免主体外侧彩色光晕,主体贴纸外轮廓之外必须直接是纯色背景。资源自身保持清晰不透明,半透明效果由网页 CSS 控制。',
'不要画猫头、躯干、另一只手臂、完整动物、腿、脚、文字、数字、按钮、面板、水印、真实照片质感、黑色、灰色、黑白毛、黑灰重阴影或深色大面积毛。',
styleReferenceNote,
noStretchNote,
chromaKeyNote,
].join(''),
},
];
const args = new Map();
@@ -811,6 +872,12 @@ function buildRequestBody(asset, size) {
path.join(assetDir, 'picture-book-wave-cat-head-guide-v2.png'),
);
}
if (asset.useWaveCatArmReference) {
pushReferenceImage(
body,
path.join(assetDir, 'picture-book-wave-cat-arm-guide-v6.png'),
);
}
return body;
}
@@ -864,6 +931,9 @@ function outputPathFor(asset) {
}
function sourceOutputPathFor(asset) {
if (asset.sourceDirectory === 'asset') {
return path.join(assetDir, asset.sourceOutput || asset.output);
}
return path.join(intermediateDir, asset.sourceOutput || asset.output);
}
@@ -1038,6 +1108,92 @@ function removeCharacterOutlineChromaKey(sourcePath, finalPath) {
}
}
function createCharacterOutlineOnlyIndicator(sourcePath, finalPath) {
const script = [
'from PIL import Image, ImageChops, ImageFilter',
'import sys',
'source, out = sys.argv[1], sys.argv[2]',
'im = Image.open(source).convert("RGBA")',
'alpha = im.getchannel("A")',
'mask = alpha.point(lambda v: 255 if v > 24 else 0)',
'mask = mask.filter(ImageFilter.MaxFilter(5)).filter(ImageFilter.MinFilter(5))',
'outer = mask.filter(ImageFilter.MaxFilter(47))',
'inner = mask.filter(ImageFilter.MinFilter(47))',
'stroke = ImageChops.subtract(outer, inner)',
'stroke = stroke.filter(ImageFilter.GaussianBlur(0.45))',
'glow = stroke.filter(ImageFilter.GaussianBlur(3.0)).point(lambda v: int(v * 0.34))',
'result = Image.new("RGBA", im.size, (0, 0, 0, 0))',
'glow_layer = Image.new("RGBA", im.size, (91, 205, 197, 0))',
'glow_layer.putalpha(glow)',
'line_layer = Image.new("RGBA", im.size, (224, 255, 247, 0))',
'line_layer.putalpha(stroke.point(lambda v: min(235, int(v * 0.92))))',
'result.alpha_composite(glow_layer)',
'result.alpha_composite(line_layer)',
'result.save(out)',
].join('\n');
const result = spawnSync('python', ['-c', script, sourcePath, finalPath], {
cwd: repoRoot,
encoding: 'utf8',
});
if (result.status !== 0) {
throw new Error(
`Failed to create outline-only character indicator: ${(result.stderr || result.stdout).trim()}`,
);
}
}
function createWhiteCharacterOutlineIndicator(sourcePath, finalPath) {
const script = [
'from pathlib import Path',
'import cv2',
'import numpy as np',
'from PIL import Image',
'import sys',
'source, out = Path(sys.argv[1]), Path(sys.argv[2])',
'rgba = np.array(Image.open(source).convert("RGBA"))',
'alpha = rgba[:, :, 3]',
'mask = (alpha > 24).astype(np.uint8) * 255',
'contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)',
'body = np.zeros_like(mask)',
'if contours:',
' largest = max(contours, key=cv2.contourArea)',
' cv2.drawContours(body, [largest], -1, 255, thickness=cv2.FILLED)',
'open_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (25, 25))',
'close_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (35, 35))',
'body = cv2.morphologyEx(body, cv2.MORPH_OPEN, open_kernel, iterations=1)',
'body = cv2.morphologyEx(body, cv2.MORPH_CLOSE, close_kernel, iterations=1)',
'body = cv2.GaussianBlur(body, (0, 0), 7.0)',
'_, body = cv2.threshold(body, 92, 255, cv2.THRESH_BINARY)',
'body = cv2.GaussianBlur(body, (0, 0), 1.4)',
'_, body = cv2.threshold(body, 64, 255, cv2.THRESH_BINARY)',
'line_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15))',
'outer = cv2.dilate(body, line_kernel, iterations=1)',
'inner = cv2.erode(body, line_kernel, iterations=1)',
'stroke = cv2.subtract(outer, inner)',
'stroke = cv2.GaussianBlur(stroke, (0, 0), 0.55)',
'glow = cv2.GaussianBlur(stroke, (0, 0), 2.2)',
'result = np.zeros((mask.shape[0], mask.shape[1], 4), dtype=np.uint8)',
'glow_alpha = np.clip(glow.astype(np.float32) * 0.22, 0, 70).astype(np.uint8)',
'line_alpha = np.clip(stroke.astype(np.float32) * 0.78, 0, 205).astype(np.uint8)',
'result[:, :, 0:3] = 255',
'result[:, :, 3] = np.maximum(glow_alpha, line_alpha)',
'Image.fromarray(result, "RGBA").save(out)',
].join('\n');
const result = spawnSync('python', ['-c', script, sourcePath, finalPath], {
cwd: repoRoot,
encoding: 'utf8',
});
if (result.status !== 0) {
throw new Error(
`Failed to create thin white character indicator: ${(result.stderr || result.stdout).trim()}`,
);
}
}
function removeCatGuideChromaKey(sourcePath, finalPath) {
const script = [
'from collections import deque',
@@ -1253,6 +1409,50 @@ function scrubChromaFringe(finalPath) {
}
}
function removeCatBodyShoulderDots(sourcePath, finalPath) {
const script = [
'from pathlib import Path',
'import cv2',
'import numpy as np',
'from PIL import Image',
'source, out = Path(__import__("sys").argv[1]), Path(__import__("sys").argv[2])',
'rgba = np.array(Image.open(source).convert("RGBA"))',
'rgb = rgba[:, :, :3].copy()',
'alpha = rgba[:, :, 3]',
'opaque = alpha > 10',
'known = opaque.astype(np.uint8)',
'unknown = (1 - known).astype(np.uint8)',
'_, labels = cv2.distanceTransformWithLabels(unknown, cv2.DIST_L2, 5, labelType=cv2.DIST_LABEL_PIXEL)',
'flat_known_indices = np.flatnonzero(known.reshape(-1))',
'filled_rgb = rgb.copy().reshape(-1, 3)',
'labels_flat = labels.reshape(-1)',
'unknown_flat = unknown.reshape(-1).astype(bool)',
'if flat_known_indices.size > 0 and unknown_flat.any():',
' nearest_known_flat_index = flat_known_indices[np.maximum(labels_flat[unknown_flat] - 1, 0)]',
' filled_rgb[unknown_flat] = filled_rgb[nearest_known_flat_index]',
'filled_rgb = filled_rgb.reshape(rgb.shape)',
'bgr = cv2.cvtColor(filled_rgb, cv2.COLOR_RGB2BGR)',
'mask = np.zeros(alpha.shape, dtype=np.uint8)',
'cv2.ellipse(mask, (383, 763), (23, 26), 0, 0, 360, 255, -1)',
'cv2.ellipse(mask, (648, 762), (23, 26), 0, 0, 360, 255, -1)',
'mask = cv2.bitwise_and(mask, opaque.astype(np.uint8) * 255)',
'repaired = cv2.inpaint(bgr, mask, 7, cv2.INPAINT_TELEA)',
'repaired_rgb = cv2.cvtColor(repaired, cv2.COLOR_BGR2RGB)',
'Image.fromarray(np.dstack([repaired_rgb, alpha]), "RGBA").save(out)',
].join('\n');
const result = spawnSync('python', ['-c', script, sourcePath, finalPath], {
cwd: repoRoot,
encoding: 'utf8',
});
if (result.status !== 0) {
throw new Error(
`Failed to remove cat body shoulder dots: ${(result.stderr || result.stdout).trim()}`,
);
}
}
function writeOpaquePng(sourcePath, outputPath) {
const result = spawnSync(
'python',
@@ -1291,6 +1491,54 @@ async function generateAsset(asset, env, size, force) {
}
if (args.has('--postprocess-only')) {
if (asset.localPostprocess === 'character-outline-white-thin') {
const sourcePath = sourceOutputPathFor(asset);
if (!existsSync(sourcePath)) {
throw new Error(`Missing source image for postprocess-only: ${sourcePath}`);
}
mkdirSync(assetDir, { recursive: true });
createWhiteCharacterOutlineIndicator(sourcePath, finalPath);
return {
id: asset.id,
ok: true,
file: finalPath,
sourceFile: sourcePath,
postprocessedOnly: true,
};
}
if (asset.localPostprocess === 'character-outline-only') {
const sourcePath = sourceOutputPathFor(asset);
if (!existsSync(sourcePath)) {
throw new Error(`Missing source image for postprocess-only: ${sourcePath}`);
}
mkdirSync(assetDir, { recursive: true });
createCharacterOutlineOnlyIndicator(sourcePath, finalPath);
return {
id: asset.id,
ok: true,
file: finalPath,
sourceFile: sourcePath,
postprocessedOnly: true,
};
}
if (asset.localPostprocess === 'remove-cat-body-shoulder-dots') {
const sourcePath = sourceOutputPathFor(asset);
if (!existsSync(sourcePath)) {
throw new Error(`Missing source image for postprocess-only: ${sourcePath}`);
}
mkdirSync(assetDir, { recursive: true });
removeCatBodyShoulderDots(sourcePath, finalPath);
return {
id: asset.id,
ok: true,
file: finalPath,
sourceFile: sourcePath,
postprocessedOnly: true,
};
}
if (!asset.transparent) {
return {
id: asset.id,
@@ -1328,6 +1576,54 @@ async function generateAsset(asset, env, size, force) {
};
}
if (asset.localPostprocess === 'character-outline-white-thin') {
const sourcePath = sourceOutputPathFor(asset);
if (!existsSync(sourcePath)) {
throw new Error(`Missing source image for local postprocess: ${sourcePath}`);
}
mkdirSync(assetDir, { recursive: true });
createWhiteCharacterOutlineIndicator(sourcePath, finalPath);
return {
id: asset.id,
ok: true,
file: finalPath,
sourceFile: sourcePath,
postprocessedOnly: true,
};
}
if (asset.localPostprocess === 'character-outline-only') {
const sourcePath = sourceOutputPathFor(asset);
if (!existsSync(sourcePath)) {
throw new Error(`Missing source image for local postprocess: ${sourcePath}`);
}
mkdirSync(assetDir, { recursive: true });
createCharacterOutlineOnlyIndicator(sourcePath, finalPath);
return {
id: asset.id,
ok: true,
file: finalPath,
sourceFile: sourcePath,
postprocessedOnly: true,
};
}
if (asset.localPostprocess === 'remove-cat-body-shoulder-dots') {
const sourcePath = sourceOutputPathFor(asset);
if (!existsSync(sourcePath)) {
throw new Error(`Missing source image for local postprocess: ${sourcePath}`);
}
mkdirSync(assetDir, { recursive: true });
removeCatBodyShoulderDots(sourcePath, finalPath);
return {
id: asset.id,
ok: true,
file: finalPath,
sourceFile: sourcePath,
postprocessedOnly: true,
};
}
const requestBody = buildRequestBody(asset, size);
const payloadText = await fetchWithTimeout(
buildVectorEngineImagesGenerationUrl(env.baseUrl),
@@ -1427,6 +1723,7 @@ function dryRun(selectedAssets, size) {
? sourceOutputPathFor(asset)
: undefined,
transparent: asset.transparent,
localPostprocess: asset.localPostprocess,
body: {
...body,
image: body.image ? ['<local style reference image>'] : undefined,

View File

@@ -1,5 +1,7 @@
import { Buffer } from 'node:buffer';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import http from 'node:http';
import https from 'node:https';
import path from 'node:path';
const repoRoot = process.cwd();
@@ -156,6 +158,12 @@ const handsConcepts = [
prompt:
'围绕“陶泥儿”Logo 方向 03 的“上下两只手”感觉做延展。设计一个无文字扁平矢量主标:上下一对抽象软手 / 软泥掌从两侧微微合捏中间形成一颗小圆珠或作品核。图形要像品牌符号不像手势教学图保留托举与成型的温柔感。禁止播放三角、聊天气泡、笑脸、眼睛、花朵、褐色主色、真实手指、复杂掌纹、碎小图标。风格flat vector, minimal, mainstream app logo, high contrast, iconic, friendly。配色莓红、奶白、薄荷青、少量深墨最多 3 色。',
},
{
id: 'taonier-hands-cradle-v2',
title: '托星软掌',
prompt:
'为“陶泥儿”设计无文字扁平矢量 Logo。图形是上下两片圆润软托托住中央一颗小星像把灵感轻轻捏成作品。不要画具体手指只保留抽象软掌感觉。适合 App icon简单、亲和、醒目、小尺寸清楚。配色珊瑚红、薄荷青、奶油白最多三色。不要播放三角、聊天气泡、笑脸、眼睛、花朵、褐色、文字、字母、3D、碎元素。',
},
{
id: 'taonier-hands-soft-bowl',
title: '创意托碗',
@@ -176,6 +184,464 @@ const handsConcepts = [
},
];
const broadConcepts = [
{
id: 'taonier-broad-clay-dot-crown',
title: '泥点皇冠',
prompt:
'为中文产品“陶泥儿”设计一个无文字扁平矢量 Logo 图标。产品是 AI UGC 创作与轻休闲互动内容平台,用户用“泥点”驱动 AI把一句脑洞、一张图或一个梗捏成小游戏和可分享作品。本方向把“泥点”做成核心品牌符号3 到 5 个圆润泥点自然聚合,形成一个像皇冠、火苗、作品星核之间的抽象主轮廓,表达很多灵感汇聚成精品作品。整体必须像成熟 App 主标亲和、明亮、可注册感强小尺寸清楚。避免播放三角、聊天气泡、笑脸、真实陶艺、褐色陶土主色、人物、手、复杂碎点。风格flat vector logo, bold simple silhouette, modern consumer app, warm, memorable, scalable, solid colors。配色珊瑚红、奶油白、青绿色、少量金色最多 4 色。无文字、无字母、无水印、无 3D、无厚阴影、无玻璃高光。',
},
{
id: 'taonier-broad-soft-portal',
title: '软泥入口',
prompt:
'为中文产品“陶泥儿”设计一个无文字扁平矢量 Logo 图标。产品把 AI 创作、UGC、小游戏、视觉小说、拼图和轻互动作品放在同一平台内核心感觉是“打开一个软软的创作入口进去就能造作品”。图形主体是一枚被捏开的柔软入口/门洞外轮廓像软泥被拉开中心留出干净负形作品核或小星点。图形要完整、抽象、主流不像播放器、不像聊天框、不像眼睛。风格flat vector brand mark, simple, iconic, friendly premium, strong silhouette, app icon ready。配色使用亮珊瑚、薄荷青、奶油白、深墨中的 3 色。禁止中文字、英文字母、真实门、真实陶土、3D、复杂纹理、碎小装饰、UI 按钮。',
},
{
id: 'taonier-broad-work-embryo',
title: '作品胚芽',
prompt:
'为中文产品“陶泥儿”设计一个无文字扁平矢量 Logo 图标。品牌隐喻不是传统陶艺,而是“灵感胚胎被 AI 塑形成可玩的作品”。图形主体是一颗圆润的作品胚芽:外形像软泥种子、游戏棋子和小宇宙的结合,内部只有一条柔软切面和一个小星点负形。整体高级、温柔、年轻,适合平台主 Logo 和 App icon。避免植物叶子过强、教育儿童感、播放按钮、聊天气泡、笑脸、循环箭头、褐色主色。风格flat vector, premium friendly app logo, minimal, bold, clear at 32px, solid colors。配色湖蓝或青绿主色珊瑚橙点缀奶白负形最多 3 色。无文字、无字母、无水印、无 3D、无照片质感。',
},
{
id: 'taonier-broad-game-mold',
title: '游戏模芯',
prompt:
'为中文产品“陶泥儿”设计一个无文字扁平矢量 Logo 图标。产品不是工具后台而是能把脑洞生成拼图、抓大鹅、视觉小说、文字游戏等互动作品的平台。本方向用“游戏模芯”做符号一个圆润软泥主形中嵌入极简十字方向键或小方块负形但不要画传统手柄不要出现播放三角。图形要表达可玩、轻休闲、低门槛创作同时保持品牌主标感。风格flat vector logo, simple geometric, friendly, playful but mature, app icon, high contrast。配色珊瑚红、青绿、奶油白、深墨最多 4 色。禁止文字、字母、水印、3D、复杂按钮、真实手柄、聊天气泡、笑脸、儿童玩具感。',
},
{
id: 'taonier-broad-tao-negative',
title: '陶字负形',
prompt:
'为中文产品“陶泥儿”设计一个无文字扁平矢量 Logo 图标。尝试从“陶”的结构提炼抽象负形,但不要直接写汉字,也不要让模型生成可读文字。图形主体是一枚圆润软泥徽标,内部用两到三块负形构成类似陶器开口、耳部、土块和作品核的抽象关系,让熟悉中文的人隐约感到“陶”,但第一眼仍是现代 App 标志。风格flat vector brand symbol, abstract Chinese-inspired, clean, iconic, friendly premium, scalable。配色深墨或莓红主形奶油白负形青绿小点缀。禁止真实汉字、书法、篆刻、传统印章、褐色陶艺、播放按钮、聊天气泡、人物、3D、水印。',
},
{
id: 'taonier-broad-soft-totem',
title: '软体图腾',
prompt:
'为中文产品“陶泥儿”设计一个无文字扁平矢量 Logo 图标。基于 Taonier / 陶泥儿 的品牌声母感觉做一个抽象软体图腾,但不要直接画英文字母 T也不要生成任何文字。图形由一笔连续的圆润软泥带形成稳定的竖向图腾顶部像被轻捏出的小角中心有一颗作品星核负形表达“捏、造、发布”。整体要比普通字母标更独特适合 App icon、favicon 和平台顶栏。风格flat vector logo, bold, simple, modern, friendly, memorable, solid colors。配色珊瑚红主形、奶油白负形、薄荷青小面积辅助。禁止文字、字母直出、播放三角、聊天气泡、笑脸、无限循环、褐色陶土、3D、复杂纹理。',
},
{
id: 'taonier-broad-creation-spark',
title: '开捏火花',
prompt:
'为中文产品“陶泥儿”设计一个无文字扁平矢量 Logo 图标。核心动作为“开捏”用户输入灵感AI 立刻生成可玩的作品。图形不要画真实手用两块极简软形挤压出中心火花火花不是爆炸特效而是一个稳定的四角作品星核。外轮廓要比上一轮左右括号更完整像一个独立品牌图腾。风格flat vector logo, iconic, minimal, high contrast, friendly, youthful, app icon ready。配色莓红或珊瑚红主形奶油白负形青绿中心点缀最多 3 色。禁止文字、字母、水印、播放三角、聊天气泡、笑脸、眼睛、真实手指、碎粒、3D、厚阴影。',
},
{
id: 'taonier-broad-content-orbit',
title: '作品星轨',
prompt:
'为中文产品“陶泥儿”设计一个无文字扁平矢量 Logo 图标。产品承载多种互动内容RPG、拼图、抓大鹅、视觉小说、文字游戏、儿童寓教于乐。图形用一个软泥圆核和两条极简短弧形成“作品星轨”表达一个灵感生成多个作品形态但整体必须是一个凝聚的主标不是天文图标。风格flat vector brand mark, simple, premium friendly, clean geometry, app icon, scalable。配色青绿主核、珊瑚红弧线、奶油白负形、深墨小轮廓可选。禁止文字、字母、水印、真实星球、复杂轨道、科技冷硬、播放键、聊天气泡、循环箭头、3D。',
},
];
const freshConcepts = [
{
id: 'taonier-fresh-wheel-imprint',
title: '陶轮印记',
prompt:
'为“陶泥儿”设计无文字扁平矢量 Logo。完全换方向俯视一个正在旋转的创作轮盘圆环被轻轻压出一处缺口像把灵感旋成作品。成熟消费级 App 主标几何、干净、有速度感。配色钴蓝、奶白、珊瑚红、少量深墨。不要软手、星核、聊天气泡、播放键、笑脸、真实陶艺、褐色、文字、字母、3D。',
},
{
id: 'taonier-fresh-mold-window',
title: '模具窗格',
prompt:
'为“陶泥儿”设计无文字扁平矢量 Logo。完全换方向一个圆角模具窗口内部是 2x2 的不规则负形窗格像多种小游戏和互动作品从同一个模具里生成。主流、简洁、品牌感强、小尺寸清楚。配色深墨主形、奶油白负形、亮青绿和珊瑚小点缀。不要软手、星星、播放键、聊天气泡、脸、真实陶土、文字、字母、3D。',
},
{
id: 'taonier-fresh-dot-dice',
title: '泥点骰面',
prompt:
'为“陶泥儿”设计无文字扁平矢量 Logo。完全换方向一枚圆润方形骰面或游戏牌面5 个泥点孔组成独特节奏表达泥点、玩法和随机脑洞。不要画立体骰子只要正面抽象符号。潮流、轻游戏、可注册。配色象牙白底、黑色主形、荧光青、珊瑚红。不要播放键、聊天气泡、笑脸、星星、软手、褐色、文字、字母、3D。',
},
{
id: 'taonier-fresh-pinwheel',
title: '灵感风车',
prompt:
'为“陶泥儿”设计无文字扁平矢量 Logo。完全换方向抽象纸风车由四片圆润色块围成旋转中心表达简单、轻松、人人能造内容。它要像品牌主标不像儿童玩具。配色莓红、天蓝、薄荷、奶白、深墨。不要软泥团、手、星核、播放键、聊天气泡、笑脸、花朵、文字、字母、3D、复杂渐变。',
},
{
id: 'taonier-fresh-pocket-world',
title: '口袋世界',
prompt:
'为“陶泥儿”设计无文字扁平矢量 Logo。完全换方向一个抽象口袋形徽标口袋里露出一小块世界切片或舞台切片表示把脑洞装进口袋随手开玩。现代、亲和、平台感强。配色青绿色主形、奶白负形、珊瑚红小块、深墨轮廓。不要软手、星核、播放键、聊天气泡、笑脸、地图图钉、真实口袋、文字、字母、3D。',
},
{
id: 'taonier-fresh-builder-blocks',
title: '创作积木',
prompt:
'为“陶泥儿”设计无文字扁平矢量 Logo。完全换方向三块圆角积木以不对称方式咬合形成一个稳定主轮廓表达 UGC 搭建、模板生成和小游戏创作。不要儿童玩具感要成熟、潮流、清晰。配色黑色或深紫主轮廓珊瑚、青绿、奶白填色。不要软手、星星、播放键、聊天气泡、笑脸、褐色、文字、字母、3D。',
},
{
id: 'taonier-fresh-stage-window',
title: '叙事舞台窗',
prompt:
'为“陶泥儿”设计无文字扁平矢量 Logo。完全换方向一个极简舞台窗或小剧场窗口左右两片抽象幕布形成负形中心代表视觉小说、RPG 和互动叙事。它要是 App icon 主标不是插画。配色深墨、珊瑚红、奶油白、少量湖蓝。不要播放键、聊天气泡、笑脸、软手、星核、真实舞台、文字、字母、3D。',
},
{
id: 'taonier-fresh-ribbon-knot',
title: '灵感绳结',
prompt:
'为“陶泥儿”设计无文字扁平矢量 Logo。完全换方向一条圆润彩色泥条打成简洁绳结像把多个创意线索系成一个作品。形状必须凝聚成单个主标不能散。配色珊瑚、钴蓝、薄荷、奶白边缘干净。不要无限符号、软手、星核、播放键、聊天气泡、笑脸、褐色陶土、文字、字母、3D。',
},
{
id: 'taonier-fresh-folded-sticker',
title: '贴纸折角',
prompt:
'为“陶泥儿”设计无文字扁平矢量 Logo。完全换方向一张圆角贴纸或作品卡片右上角轻轻折起负形像一个小入口。表达 UGC、作品发布、随手开玩。成熟、潮流、极简。配色奶白、黑、珊瑚、青绿。不要播放键、聊天气泡、笑脸、手、星星、褐色、文字、字母、3D。',
},
{
id: 'taonier-fresh-punch-hole',
title: '印模孔洞',
prompt:
'为“陶泥儿”设计无文字扁平矢量 Logo。完全换方向一个圆润印模形状中间被冲出一个不规则圆孔像从泥板里取出作品。抽象、强轮廓、可注册、小尺寸清楚。配色黑色主形、奶白负形、荧光青小块、珊瑚红。不要播放键、聊天气泡、笑脸、手、星星、陶罐、文字、字母、3D。',
},
];
const punchReferencePath = path.join(
repoRoot,
'public',
'branding',
'taonier-logo-fresh-concepts',
'taonier-fresh-punch-hole.png',
);
const punch04ReferencePath = path.join(
repoRoot,
'public',
'branding',
'taonier-logo-punch-hole-concepts',
'taonier-punch-color-inlay.png',
);
const paletteRefineReferencePath = path.join(
repoRoot,
'public',
'branding',
'taonier-logo-ref04-palette-transfer',
'taonier-ref04-palette-transfer-warm-yellow-sparkle.png',
);
const paletteShapeReferencePath = path.join(
repoRoot,
'public',
'branding',
'taonier-logo-ref04-locked-color-concepts',
'taonier-ref04-locked-warm-ink.png',
);
const sparkleRefineReferencePath = path.join(
repoRoot,
'public',
'branding',
'taonier-logo-ref04-warm-sparkle-v2-concepts',
'taonier-ref04-warm-sparkle-terracotta.png',
);
const sparkleCropReferencePath = path.join(
repoRoot,
'public',
'branding',
'taonier-logo-ref04-palette-refine-concepts',
'taonier-sparkle-reference-crop.png',
);
const paletteRefineV2ReferencePath = path.join(
repoRoot,
'public',
'branding',
'taonier-logo-ref04-palette-refine-v2-concepts',
'taonier-ref04-palette-refine-v2-pale-cream.png',
);
const paletteRefineV4PaleButterReferencePath = path.join(
repoRoot,
'public',
'branding',
'taonier-logo-ref04-palette-refine-v4-concepts',
'taonier-ref04-palette-refine-v4-pale-butter.png',
);
const punchConcepts = [
{
id: 'taonier-punch-locked-shape',
title: '原型锁定微调',
referenceImages: [punchReferencePath],
prompt:
'为“陶泥儿”继续打磨参考图 06 印模孔洞 logo。必须保持参考图基本造型不变黑色圆润不规则环形主形、中央白色不规则孔洞、右上珊瑚红辅形、左下青蓝辅形。只优化比例、边缘、留白和小尺寸识别让它更像成熟 App icon。无文字、无字母、无播放键、无聊天气泡、无手、无星星、无3D。',
},
{
id: 'taonier-punch-stable-icon',
title: '稳定主标',
referenceImages: [punchReferencePath],
prompt:
'基于参考图 06 印模孔洞,为“陶泥儿”做无文字扁平矢量 logo 延展。保留黑色冲孔主形和中央不规则白洞但让外轮廓更稳定、更像长期品牌主标。右上珊瑚红和左下青蓝辅形更克制白底强轮廓小尺寸清楚。无文字、无字母、无播放键、无聊天气泡、无手、无星星、无3D。',
},
{
id: 'taonier-punch-hole-balance',
title: '孔洞比例',
referenceImages: [punchReferencePath],
prompt:
'基于参考图 06 印模孔洞,为“陶泥儿”延展一个更干净的无文字 logo。核心仍是黑色圆润印模环和中央不规则白色孔洞重点调整孔洞大小、厚薄关系和负形节奏让黑形更有张力。珊瑚红、青蓝只作为小面积辅形。白底。无文字、无字母、无播放键、无聊天气泡、无手、无星星、无3D。',
},
{
id: 'taonier-punch-color-inlay',
title: '彩色嵌合',
referenceImages: [punchReferencePath],
prompt:
'基于参考图 06 印模孔洞,为“陶泥儿”做彩色嵌合版 logo。黑色主环保持冲孔感右上珊瑚红和左下青蓝两块辅形与主形更自然嵌合像从泥板里取出的两片作品碎片。造型简洁、可注册、App icon 友好。无文字、无字母、无播放键、无聊天气泡、无手、无星星、无3D。',
},
{
id: 'taonier-punch-mono-test',
title: '单色测试',
referenceImages: [punchReferencePath],
prompt:
'基于参考图 06 印模孔洞,为“陶泥儿”做单色极简版 logo。只保留黑色圆润冲孔主形和中央白色不规则孔洞去掉彩色辅形。强调强轮廓、可注册、小尺寸识别和品牌符号感。白底。无文字、无字母、无播放键、无聊天气泡、无手、无星星、无3D。',
},
{
id: 'taonier-punch-app-token',
title: '应用图标',
referenceImages: [punchReferencePath],
prompt:
'基于参考图 06 印模孔洞,为“陶泥儿”延展一个更完整的 App icon 核心图形。黑色不规则冲孔主形更饱满中央白洞更清晰珊瑚红与青蓝辅形保持年轻感但不抢主体。整体像可长期使用的品牌符号不像插画。白底。无文字、无字母、无播放键、无聊天气泡、无手、无星星、无3D。',
},
];
const punch04Concepts = [
{
id: 'taonier-punch04-warm-ink-core',
title: '暖墨填芯',
referenceImages: [punch04ReferencePath],
prompt:
'基于参考图“04 彩色嵌合”为“陶泥儿”继续做 logo 延展。保持原有基本结构不变:一个圆润不规则环形主形,右上珊瑚红嵌合块,左下青蓝嵌合块,中央不规则孔洞。重点调整配色:中间黑色主形改为温暖深墨灰,不要纯黑;中央孔洞内部加入一枚很简洁的奶油色软泥种子/作品核填充不要填满保留留白呼吸。扁平矢量、品牌主标、小尺寸清楚。无文字、无字母、无播放键、无聊天气泡、无手、无星星、无3D。',
},
{
id: 'taonier-punch04-navy-game-core',
title: '靛蓝作品核',
referenceImages: [punch04ReferencePath],
prompt:
'基于参考图“04 彩色嵌合”为“陶泥儿”设计一版配色延展。保持黑环、右上红块、左下青块的基本结构和嵌合关系,但把主形从黑色改为深靛蓝或蓝黑色,整体更年轻、更像互联网 App。中央空心区域加入一个极简浅色作品核小圆角方块或软形小岛不能像播放键、不能像字母。白底扁平矢量干净可注册。无文字、无字母、无聊天气泡、无手、无星星、无3D。',
},
{
id: 'taonier-punch04-cream-window',
title: '奶油内窗',
referenceImages: [punch04ReferencePath],
prompt:
'基于参考图“04 彩色嵌合”为“陶泥儿”做一版更柔和的 logo。基本结构不变主环、右上珊瑚红、左下青蓝、中央孔洞都保留。把原黑色主环调整为柔和深紫灰或墨绿色降低硬度。中央孔洞不再是纯空白设计成奶油色内窗里面有两块极简小色面表达多个作品从同一模具生成。整体仍然极简不要复杂插画。无文字、无字母、无播放键、无聊天气泡、无手、无星星、无3D。',
},
{
id: 'taonier-punch04-clay-gradient-flat',
title: '陶盒彩芯',
referenceImages: [punch04ReferencePath],
prompt:
'基于参考图“04 彩色嵌合”为“陶泥儿”做配色与中孔设计。保持 04 的基本轮廓和红青嵌合块位置。主形不要纯黑,改成深陶紫、莓紫或炭灰紫,仍保持强轮廓。中央孔洞加入一个扁平的彩色泥芯,由珊瑚、青蓝、奶白三块圆润小面组成,像作品被捏出来的内核。不要渐变高光,不要立体,不要复杂细节。无文字、无字母、无播放键、无聊天气泡、无手、无星星。',
},
{
id: 'taonier-punch04-mint-shadow',
title: '薄荷深影',
referenceImages: [punch04ReferencePath],
prompt:
'基于参考图“04 彩色嵌合”为“陶泥儿”做一版更清爽的品牌 logo。保持 04 的三块嵌合结构不变。把中间黑色主形改成深青绿/墨绿右上红块更偏珊瑚左下青块更偏亮薄荷。中央空心处加入一枚小小的浅黄色或奶白圆角形像可玩的作品胚不要过大。整体强识别、轻休闲、App icon 友好。无文字、无字母、无播放键、无聊天气泡、无手、无星星、无3D。',
},
{
id: 'taonier-punch04-negative-tile',
title: '内嵌拼片',
referenceImages: [punch04ReferencePath],
prompt:
'基于参考图“04 彩色嵌合”为“陶泥儿”做一版中间内容更明确的 logo。保持外部基本结构和红青嵌合块位置不变。主形从纯黑改为深墨蓝灰。中央不规则孔洞内部放入一个极简拼片/圆角模块组合,表示拼图、小游戏、互动作品,但必须非常简洁,不能像 UI 图标堆叠。白底扁平矢量主标感强。无文字、无字母、无播放键、无聊天气泡、无手、无星星、无3D。',
},
];
const paletteRefineConcepts = [
{
id: 'taonier-ref04-palette-refine-butter',
title: '淡黄黄油',
referenceImages: [paletteRefineReferencePath, sparkleRefineReferencePath],
prompt:
'为“陶泥儿”继续调整 REF-04 配色迁移版。必须锁定参考图一的外轮廓和分区:主形、右上红块、左下青块和中间孔洞都保持不变;把中间主形改成温暖、低饱和、很淡的黄油黄或奶油黄,不要脏黄、土黄、芥末黄或偏橙黄。中间的星星必须保持参考图二的原样:四角闪光星,带短小光芒,不能拉伸成细长十字,不能变成五角星,不能加厚底托。整体要像成熟、干净、轻松的品牌 logo。无文字、无字母、无播放键、无聊天气泡、无手、无3D。',
},
{
id: 'taonier-ref04-palette-refine-cream',
title: '奶油淡黄',
referenceImages: [paletteRefineReferencePath, sparkleRefineReferencePath],
prompt:
'基于 REF-04 造型锁定版和四角闪光星参考图生成一版更高级的暖黄配色。保持图一的造型完全不变只把中间主形改成低饱和奶油淡黄颜色要轻、透、干净避免脏、沉、厚。中心星星完全沿用参考图二的四角闪光样式和短光芒不要拉伸不要变形不要变成五角星。红块和青块保持现有位置与比例。白底、扁平、品牌标志感。无文字、无字母、无手、无播放键、无3D。',
},
{
id: 'taonier-ref04-palette-refine-biscuit',
title: '饼干淡黄',
referenceImages: [paletteRefineReferencePath, sparkleRefineReferencePath],
prompt:
'继续基于 REF-04 造型锁定版做色彩优化。外轮廓、红青辅形、中孔边界全部锁住不变中间主形换成更淡的饼干黄、奶油黄或浅麦黄必须低饱和、暖而不脏。中心填充严格使用参考图二的四角闪光星和短光芒保持原样不许被拉长也不许改成几何五角星。整体要简洁、轻盈、专业。无文字、无字母、无聊天气泡、无3D、无复杂阴影。',
},
{
id: 'taonier-ref04-palette-refine-milk',
title: '牛奶暖黄',
referenceImages: [paletteRefineReferencePath, sparkleRefineReferencePath],
prompt:
'在 REF-04 锁形轮廓上做最后一轮暖黄微调。只改中间主形的颜色,把它变成接近牛奶、黄油、奶霜的浅暖黄,低饱和、柔和、干净,不要土气,不要发灰。中间星星必须保持参考图二的四角闪光星原型和短光芒,不能被拉伸,不能变瘦,不能加底托。红青两块辅形位置不动。白底,极简 logo。无文字、无字母、无手、无播放键、无3D。',
},
];
const paletteRefineV2Concepts = [
{
id: 'taonier-ref04-palette-refine-v2-soft-butter',
title: '柔和奶黄',
referenceImages: [
paletteShapeReferencePath,
paletteRefineReferencePath,
sparkleCropReferencePath,
],
prompt:
'为“陶泥儿”修正 REF-04 配色迁移版。严格锁定参考图一的造型和分区不改变外轮廓、不改变右上辅形、不改变左下辅形、不改变中央孔洞边界。只做两处调整1把中间主形改成温暖、低饱和、淡淡的奶油黄/黄油黄颜色要高级、轻、干净绝对不要土黄、脏黄、芥末黄、焦糖黄、偏橙黄2中心空洞里的星星必须使用参考图三的原始四角闪光星和短光芒保持饱满菱形闪光不要拉伸成十字不要变成五角星不要加底托。保持白底和扁平 logo。无文字、无字母、无手、无播放键、无聊天气泡、无3D。',
},
{
id: 'taonier-ref04-palette-refine-v2-pale-cream',
title: '浅奶油黄',
referenceImages: [
paletteShapeReferencePath,
paletteRefineReferencePath,
sparkleCropReferencePath,
],
prompt:
'基于三张参考图生成一版修正版 logo参考图一只用于锁定 REF-04 造型;参考图二只用于当前粉红与薄荷青位置;参考图三用于中心星星样式。中间主形颜色改为低饱和浅奶油黄,接近柔和奶霜,不要土气、不要脏、不要高饱和。中心星星必须照参考图三,四角闪光星带短光芒,比例自然饱满,不能被压扁或拉长。外轮廓和孔洞边界不变。白底、干净、成熟品牌 logo。无文字、无字母、无3D。',
},
{
id: 'taonier-ref04-palette-refine-v2-light-vanilla',
title: '香草淡黄',
referenceImages: [
paletteShapeReferencePath,
paletteRefineReferencePath,
sparkleCropReferencePath,
],
prompt:
'继续优化 REF-04 造型锁定 logo。必须保持参考图一的所有轮廓位置只把中间原黑色区域换成温暖低饱和的香草淡黄颜色像轻柔黄油、奶油纸、浅米黄不能像陶土、咖啡、焦糖或芥末。中心空洞填入参考图三的星星圆润四角闪光、短小光芒、自然比例不要变瘦不要拉伸不要五角星。粉红和薄荷青辅形沿用参考图二的气质。无文字、无字母、无播放键、无聊天气泡、无手、无3D。',
},
];
const paletteRefineV3Concepts = [
{
id: 'taonier-ref04-palette-refine-v3-butter-soft',
title: '淡奶油黄',
referenceImages: [
paletteShapeReferencePath,
paletteRefineV2ReferencePath,
sparkleCropReferencePath,
],
prompt:
'为“陶泥儿”继续修正 REF-04 的配色迁移版。锁定参考图一的外轮廓、红块、青块和孔洞边界不动;把中间主形调成更高级的淡奶油黄、黄油白黄或柔软黄米色,颜色要更淡一点、更轻一点、更透一点,不要土黄、脏黄、焦糖黄、芥末黄,也不要偏橙偏褐。中心空洞使用参考图三的星星:必须是饱满的四角闪光星,带短小光芒,不能被拉长成细十字,不能变成五角星,也不能出现厚底托。整体保持白底、扁平、品牌 logo 感。无文字、无字母、无3D、无聊天气泡。',
},
{
id: 'taonier-ref04-palette-refine-v3-milk-cream',
title: '奶霜淡黄',
referenceImages: [
paletteShapeReferencePath,
paletteRefineV2ReferencePath,
sparkleCropReferencePath,
],
prompt:
'基于三张参考图输出一版更轻的 REF-04 logo。第一张参考只负责锁定原始造型第二张参考只负责当前配色关系第三张参考只负责中心闪光星的样子。中间主形改成低饱和的奶霜淡黄颜色要轻柔、通透、像淡淡的黄油和牛奶混合不要土、不要厚、不要脏。星星保持参考图三的四角闪光星和短光芒不许拉伸不许变形不许五角星化。红块和青块位置固定。无文字、无字母、无手、无播放键、无3D。',
},
{
id: 'taonier-ref04-palette-refine-v3-soft-vanilla',
title: '香草奶黄',
referenceImages: [
paletteShapeReferencePath,
paletteRefineV2ReferencePath,
sparkleCropReferencePath,
],
prompt:
'继续保持 REF-04 的造型锁定做一次更安静的暖黄修正。中间主形变成香草奶黄或浅奶油黄必须是低饱和、柔和、高级的淡黄不要像土黄、咖喱黄、焦糖黄或偏橙黄。中心填充沿用参考图三的四角闪光星星体要圆润饱满旁边的短光芒保留但不能夸张不能拉长。外轮廓完全不动。白底、logo 感、扁平。无文字、无字母、无聊天气泡、无3D。',
},
];
const paletteRefineV4Concepts = [
{
id: 'taonier-ref04-palette-refine-v4-cream-paper',
title: '奶油纸淡黄',
referenceImages: [
paletteShapeReferencePath,
paletteRefineReferencePath,
sparkleCropReferencePath,
],
prompt:
'继续用 image-2 修正“陶泥儿” REF-04 logo。参考图一只用于锁定 REF-04 原型:外轮廓、右上粉红块、左下薄荷青块、中央孔洞边界都不要重新设计;参考图二只说明当前需要修正的版本;参考图三只用于中心星星。把中间原本土黄/脏黄的主形改成温暖、低饱和、淡淡的奶油纸黄色,接近 #F3E5B4 或 #F6E9C5颜色要轻、干净、高级不要陶土黄、芥末黄、咖喱黄、焦糖黄、橙黄、棕黄。中心孔洞里的星星必须保持参考图三原本的四角闪光星比例上下左右四个圆润尖角宽高自然不能被横向或纵向拉伸不能变成细十字不能变成五角星旁边短光芒也保持短小。白底、扁平品牌 logo。无文字、无字母、无播放键、无聊天气泡、无手、无3D。',
},
{
id: 'taonier-ref04-palette-refine-v4-warm-ivory',
title: '暖象牙淡黄',
referenceImages: [
paletteShapeReferencePath,
paletteRefineReferencePath,
sparkleCropReferencePath,
],
prompt:
'基于三张参考图输出一版 REF-04 精修 logo。第一张参考图的形状和分区必须优先主形轮廓不改、粉红块和薄荷青块位置不改、中间白色孔洞不改只把中间主形从现在偏土的黄改成暖象牙淡黄像很淡的黄油白、奶油米白、暖白纸低饱和、柔和、通透不要厚重和脏感。第三张参考图的四角闪光星需要原样放进中心星体不能被压扁、不能拉长、不能瘦成十字短光芒不要变多。整体保持成熟、干净、可做 App icon 的扁平 logo。无文字、无字母、无3D。',
},
{
id: 'taonier-ref04-palette-refine-v4-soft-champagne',
title: '淡香槟暖黄',
referenceImages: [
paletteShapeReferencePath,
paletteRefineV2ReferencePath,
sparkleCropReferencePath,
],
prompt:
'为“陶泥儿”做一版更高级的 REF-04 暖黄精修。参考图一锁定基本造型,不允许改成播放按钮、三角形、气泡或新图标;参考图二只参考淡黄的轻盈程度;参考图三锁定星星。中间主形使用低饱和淡香槟黄/奶霜黄颜色要非常淡、温暖、干净不能像泥土、咖喱、焦糖、芥末或橙棕。中心星星必须是参考图三那种饱满四角闪光保留短光芒按原始宽高比例绘制不能拉伸、不能变形、不能五角星化。粉红和薄荷青辅形保持克制。白底、扁平、品牌主标感。无文字、无字母、无3D、无复杂阴影。',
},
{
id: 'taonier-ref04-palette-refine-v4-pale-butter',
title: '淡黄油暖白',
referenceImages: [
paletteShapeReferencePath,
paletteRefineV2ReferencePath,
sparkleCropReferencePath,
],
prompt:
'继续调整 REF-04 配色版本,只修正颜色和中心星星,不重画 logo。外轮廓、三块嵌合关系、中央孔洞边界以参考图一为准中间主形换成淡黄油暖白像轻薄奶油、温暖米白、浅黄纸低饱和、不土、不脏、不橙、不褐。中心孔洞填入参考图三原样的四角闪光星星星要圆润饱满四个尖角长度均衡短光芒短而自然不能拉伸成细长十字。保留白底和扁平矢量 logo 气质。禁止文字、字母、五角星、播放键、聊天气泡、手、3D。',
},
];
const paletteRefineV5Concepts = [
{
id: 'taonier-ref04-palette-refine-v5-filled-centered-spark',
title: '填心居中亮星',
referenceImages: [
paletteRefineV4PaleButterReferencePath,
paletteShapeReferencePath,
sparkleCropReferencePath,
],
prompt:
'根据参考图修改“陶泥儿”04 图标保留右上粉红块、左下薄荷青块和整体软泥圆润气质。重点做三处修改1补全左侧外轮廓曲线让左侧从上到下形成连续、顺滑、饱满的弧线不能有缺口、锯齿、截断或不自然凹陷2把中央白色空心孔洞完全用主体同色的温暖低饱和淡奶油黄填平不能再出现白色中孔、白色环或内窗3把参考星星改成明亮的黄色四角闪光星放在整个淡黄主体的视觉中央星星清晰、圆润、比例自然不要五角星不要拉伸成十字。白底、扁平品牌 logo、干净高级。无文字、无字母、无播放键、无聊天气泡、无手、无3D。',
},
{
id: 'taonier-ref04-palette-refine-v5-smooth-left-small-spark',
title: '顺滑左弧小亮星',
referenceImages: [
paletteRefineV4PaleButterReferencePath,
paletteShapeReferencePath,
sparkleCropReferencePath,
],
prompt:
'继续精修“陶泥儿”04 图标。以参考图一的 04 配色和比例为基础但不要保留中央白洞。左侧外边缘需要补成更完整、更协调的连续曲线像一整块柔软陶泥的自然外轮廓中间原空心区域必须填成和主形一致的淡奶油黄色与主体融为一体。中心放一枚明亮黄色四角闪光星星星略小、居中、干净不带复杂底托不是五角星不是细长十字。粉红块和薄荷青块仍然分离在右上和左下白色间隔保持干净。无文字、无字母、无3D。',
},
{
id: 'taonier-ref04-palette-refine-v5-balanced-bright-spark',
title: '平衡亮星',
referenceImages: [
paletteRefineV4PaleButterReferencePath,
paletteShapeReferencePath,
sparkleCropReferencePath,
],
prompt:
'为“陶泥儿”输出一版更协调的 04 图标修改稿。主形是温暖、低饱和、淡淡的奶油黄色;请补齐左侧曲线,让左边外轮廓更圆润完整,整体重心更稳。中央空心区域不再留白,必须填平为同样的淡黄色主形。把四角闪光星改成更明亮、更清楚的黄色,准确放在图标中央,星体饱满,四个尖角均衡,可以有很短的小光芒但不要抢主体。保持扁平 logo 感和白底。禁止文字、字母、五角星、播放键、聊天气泡、手、3D。',
},
{
id: 'taonier-ref04-palette-refine-v5-solid-core-no-hole',
title: '实体主形亮星',
referenceImages: [
paletteRefineV4PaleButterReferencePath,
paletteShapeReferencePath,
sparkleCropReferencePath,
],
prompt:
'按用户参考图修改 04 logo把淡黄主形做成一个更完整的实体软泥形。左侧曲线补全并顺滑化外轮廓不要破碎原中央白色孔洞完全消失改成与主形同色的淡奶油黄实体面在实体面的正中央放一枚明亮黄色四角闪光星星星比主体颜色更亮有明确识别但不幼稚。保持右上粉红块和左下薄荷青块的年轻配色整体干净、轻盈、品牌主标感。无文字、无字母、无内孔、无白色中窗、无五角星、无播放键、无3D。',
},
];
const args = new Map();
for (let index = 2; index < process.argv.length; index += 1) {
const raw = process.argv[index];
@@ -201,6 +667,24 @@ const concepts =
? magicDotConcepts
: style === 'hands'
? handsConcepts
: style === 'broad'
? broadConcepts
: style === 'fresh'
? freshConcepts
: style === 'punch'
? punchConcepts
: style === 'punch04'
? punch04Concepts
: style === 'palette-refine'
? paletteRefineConcepts
: style === 'palette-refine-v2'
? paletteRefineV2Concepts
: style === 'palette-refine-v3'
? paletteRefineV3Concepts
: style === 'palette-refine-v4'
? paletteRefineV4Concepts
: style === 'palette-refine-v5'
? paletteRefineV5Concepts
: dimensionalConcepts;
const selectedOutputDir =
style === 'flat'
@@ -221,6 +705,69 @@ const selectedOutputDir =
'branding',
'taonier-logo-hands-concepts',
)
: style === 'broad'
? path.join(
repoRoot,
'public',
'branding',
'taonier-logo-broad-concepts',
)
: style === 'fresh'
? path.join(
repoRoot,
'public',
'branding',
'taonier-logo-fresh-concepts',
)
: style === 'punch'
? path.join(
repoRoot,
'public',
'branding',
'taonier-logo-punch-hole-concepts',
)
: style === 'punch04'
? path.join(
repoRoot,
'public',
'branding',
'taonier-logo-punch04-color-concepts',
)
: style === 'palette-refine'
? path.join(
repoRoot,
'public',
'branding',
'taonier-logo-ref04-palette-refine-concepts',
)
: style === 'palette-refine-v2'
? path.join(
repoRoot,
'public',
'branding',
'taonier-logo-ref04-palette-refine-v2-concepts',
)
: style === 'palette-refine-v3'
? path.join(
repoRoot,
'public',
'branding',
'taonier-logo-ref04-palette-refine-v3-concepts',
)
: style === 'palette-refine-v4'
? path.join(
repoRoot,
'public',
'branding',
'taonier-logo-ref04-palette-refine-v4-concepts',
)
: style === 'palette-refine-v5'
? path.join(
repoRoot,
'public',
'branding',
'taonier-logo-ref04-palette-refine-v5-concepts',
)
: outputDir;
function readDotenv(fileName) {
@@ -276,6 +823,82 @@ function buildUrl(baseUrl) {
: `${baseUrl}/v1/images/generations`;
}
function hasHeader(headers, targetName) {
return Object.keys(headers).some(
(name) => name.toLowerCase() === targetName.toLowerCase(),
);
}
async function requestBuffer(url, options, timeoutMs, redirectCount = 0) {
const body =
typeof options.body === 'string'
? Buffer.from(options.body)
: options.body || null;
const headers = { ...(options.headers || {}) };
if (body && !hasHeader(headers, 'content-length')) {
headers['Content-Length'] = String(body.length);
}
return new Promise((resolve, reject) => {
const parsedUrl = new URL(url);
const transport = parsedUrl.protocol === 'http:' ? http : https;
const request = transport.request(
parsedUrl,
{
method: options.method || 'GET',
headers,
},
(response) => {
const statusCode = response.statusCode || 0;
const location = response.headers.location;
if (
statusCode >= 300 &&
statusCode < 400 &&
location &&
redirectCount < 5
) {
response.resume();
const redirectedUrl = new URL(location, parsedUrl).toString();
const preserveBody = statusCode === 307 || statusCode === 308;
requestBuffer(
redirectedUrl,
preserveBody
? options
: {
method: 'GET',
headers: options.headers,
},
timeoutMs,
redirectCount + 1,
)
.then(resolve)
.catch(reject);
return;
}
const chunks = [];
response.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
response.on('end', () =>
resolve({
statusCode,
headers: response.headers,
bytes: Buffer.concat(chunks),
}),
);
},
);
request.setTimeout(timeoutMs, () => {
request.destroy(new Error(`request timed out after ${timeoutMs}ms`));
});
request.on('error', reject);
if (body) {
request.write(body);
}
request.end();
});
}
function collectStringsByKey(value, targetKey, output) {
if (Array.isArray(value)) {
value.forEach((entry) => collectStringsByKey(entry, targetKey, output));
@@ -331,45 +954,58 @@ function inferExtensionFromBytes(bytes) {
return 'png';
}
function imagePathToDataUrl(imagePath) {
if (!existsSync(imagePath)) {
throw new Error(`Reference image not found: ${imagePath}`);
}
const bytes = readFileSync(imagePath);
const extension = path.extname(imagePath).toLowerCase();
const mimeType =
extension === '.jpg' || extension === '.jpeg'
? 'image/jpeg'
: extension === '.webp'
? 'image/webp'
: 'image/png';
return `data:${mimeType};base64,${bytes.toString('base64')}`;
}
async function fetchJson(url, options, timeoutMs) {
const abortController = new AbortController();
const timer = setTimeout(() => abortController.abort(), timeoutMs);
try {
const response = await fetch(url, {
...options,
signal: abortController.signal,
});
const text = await response.text();
if (!response.ok) {
throw new Error(`VectorEngine ${response.status}: ${text.slice(0, 600)}`);
const response = await requestBuffer(url, options, timeoutMs);
const text = response.bytes.toString('utf8');
if (response.statusCode < 200 || response.statusCode >= 300) {
throw new Error(
`VectorEngine ${response.statusCode}: ${text.slice(0, 600)}`,
);
}
return JSON.parse(text);
} catch (error) {
if (error?.name === 'AbortError') {
throw new Error(`VectorEngine request timed out after ${timeoutMs}ms`);
if (String(error?.message || '').includes('timed out')) {
throw new Error(
`VectorEngine request timed out after ${timeoutMs}ms`,
{ cause: error },
);
}
throw error;
} finally {
clearTimeout(timer);
}
}
async function downloadUrl(url, timeoutMs) {
const abortController = new AbortController();
const timer = setTimeout(() => abortController.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: abortController.signal });
if (!response.ok) {
throw new Error(`download ${response.status}`);
const response = await requestBuffer(url, { method: 'GET' }, timeoutMs);
if (response.statusCode < 200 || response.statusCode >= 300) {
throw new Error(`download ${response.statusCode}`);
}
return Buffer.from(await response.arrayBuffer());
return response.bytes;
} catch (error) {
if (error?.name === 'AbortError') {
throw new Error(`Generated image download timed out after ${timeoutMs}ms`);
if (String(error?.message || '').includes('timed out')) {
throw new Error(
`Generated image download timed out after ${timeoutMs}ms`,
{ cause: error },
);
}
throw error;
} finally {
clearTimeout(timer);
}
}
@@ -380,6 +1016,9 @@ async function generateConcept(env, concept) {
n: 1,
size: '1024x1024',
};
if (concept.referenceImages?.length) {
requestBody.image = concept.referenceImages.map(imagePathToDataUrl);
}
const payload = await fetchJson(
buildUrl(env.baseUrl),
{
@@ -438,6 +1077,13 @@ if (dryRun) {
prompt: concept.prompt,
n: 1,
size: '1024x1024',
...(concept.referenceImages?.length
? {
image: concept.referenceImages.map((imagePath) =>
path.relative(repoRoot, imagePath),
),
}
: {}),
},
})),
},