Compare commits

..

11 Commits

7 changed files with 2155 additions and 726 deletions

2
.gitignore vendored
View File

@@ -23,3 +23,5 @@ __pycache__/
.playwright-mcp/ .playwright-mcp/
.playwright-cli/ .playwright-cli/
ui-*.png ui-*.png
artifacts/
.memory/screenshots/

View File

@@ -1,6 +1,6 @@
# 项目接力 # 项目接力
- 生成时间May 30, 2026 at 16:22 - 生成时间May 31, 2026 at 18:30
- 项目AI玩具专利生成工作流 - 项目AI玩具专利生成工作流
- 路径:/Users/kangwan/Projects/code/20260518-ai-toy-patent-workflow - 路径:/Users/kangwan/Projects/code/20260518-ai-toy-patent-workflow
- 状态active - 状态active
@@ -9,7 +9,7 @@
## 最近助手会话概览 ## 最近助手会话概览
- Claude9827598a-67e8-4581-a39c-b89f12dedee8 · 时间未知 - Claude9827598a-67e8-4581-a39c-b89f12dedee8 · 时间未知
- Codex019e52d9-22f1-77d3-9897-146889dd551a · 时间未知 - Codex019e77fa-5990-7621-874c-2a05f076d02c · 时间未知
## Claude 最近会话 ## Claude 最近会话
@@ -53,47 +53,47 @@
## Codex 最近会话 ## Codex 最近会话
- Session ID019e52d9-22f1-77d3-9897-146889dd551a - Session ID019e77fa-5990-7621-874c-2a05f076d02c
- Transcript/Users/kangwan/.codex/sessions/2026/05/23/rollout-2026-05-23T11-20-22-019e52d9-22f1-77d3-9897-146889dd551a.jsonl - Transcript/Users/kangwan/.codex/sessions/2026/05/30/rollout-2026-05-30T16-22-36-019e77fa-5990-7621-874c-2a05f076d02c.jsonl
- 工作目录:/Users/kangwan/Projects/code/20260518-ai-toy-patent-workflow - 工作目录:/Users/kangwan/Projects/code/20260518-ai-toy-patent-workflow
- 分支master - 分支master
- 敏感字段:已对 token / key / password / secret 做脱敏 - 敏感字段:已对 token / key / password / secret 做脱敏
### 最近用户要求 ### 最近用户要求
- # AGENTS.md instructions for /Users/kangwan/Projects/code/20260518-ai-toy-patent-workflow <INSTRUCTIONS> # AI玩具专利生成工作流 Agent Rules ## Must Read First - 全项目规则真源:`/Users/kangwan/Projects/code/20260317-rules-dashboard/RULES.md` - 全项目元数据规范:`/Users/kangwan/Projects/code/20260317-rules-dashboard/SCHEMA.md` - `.project.json` 是机器真源:公网链接、快捷登录、凭证引用都以它为准 - `RULES.md` 是人工规则和部署事实:启动命令、平台、域名、注意事项都写这里 - 不允许编造不存在的域名、账号、密码;未知就保持空白并明确标记待补充 ## Deployment Metadata Contract - 任何任务只要新增、删除或修改公网地址,必须在同一次任务里更新 `.project.json` - `urls[]` 推荐显式写 `type``app``backend``docs``admin``repo` - 项目专属的网页登录信息,如果允许放进仓库,就写 … - 生图不用节约
- 你现在是在 Codex CLI 里的项目承接会话。 这不是对话立项台;项目目录已经存在,当前目标是接住上下文并等待用户下一步。 项目路径:/Users/kangwan/Projects/code/20260518-ai-toy-patent-workflow 承接步骤: - 先读取 `.memory/assistant-handoff.md` - 再阅读 `.project.json``RULES.md``AGENTS.md` - 结合当前仓库状态建立上下文 要求: - 不要要求用户重新粘贴 Claude / Codex / Cursor 的总结 - 先理解最近进度、未完成项和下一步候选 - 不要立即修改代码、继续上一个任务或自行执行命令 - 等待用户给出下一条明确指令后再开始动手 - 如果后续需要 Cursor提醒用户从看板打开 Cursor GUI 到当前项目目录 - 亥猪为c位做三个45秒的视频其他角色充当背景 主要是展示视频 触感细节 群体快闪 亥猪是这次的c位
- 你把这两天弄的 在桌面也打包一份 我要传给别人
### Codex 最近回复 ### Codex 最近回复
> 我看到当前交接文件比它自己记录的状态更新:现在 `.memory/assistant-handoff.md` 和 `.memory/worklog.json` 都是本地修改。再补一轮只读检查最近提交、diff 概览和全局规则真源,避免只信旧交接文本 > 素材已整理好214 个文件缺失为 0。现在压成桌面 zip压缩包会保留完整目录结构和交付说明
> 接住上下文,当前只做了只读同步,没有改代码或续跑旧任务。 > 经打包到桌面:
> >
> 项目当前状态:`master` 与 `origin/master` 对齐在 `f38c524 docs: record video panel deploy`,前一条功能提交是 `7abbb7d fix: dedupe suffixed video tasks`。最新部署事实是 2026-05-22 的视频面板修复:`video_turntable_60s` 这类真实成片会映射回默认视频卡,不再重复显示不可播放空卡。生产入口是 `https://ai-toy.kang-kang.com`,本地固定端口 `4560`,推荐 `npm run docker:up`。 > [有你家族-两日成果交付包-20260531.zip](/Users/kangwan/Desktop/有你家族-两日成果交付包-20260531.zip)
> >
> 当前未提交变更只有接力/日志类文件: > 包大小约 `508M`,共整理 `214` 个文件,缺失文件 `0`,我也做了压缩包完整性测试:`No errors detected`。
> >
> 里面包含三组:
>
> - `01_生肖幻装系列_50cm_亥猪C位`12 款专利六视图、18 张宣发图、3 条 45 秒视频成片、文字资料、QA 总览图
> - `02_亥猪MEEY机甲陪伴机器人_40cm`专利、配件、生产、宣发、文字资料、7 条视频成片
> - `03_糯糯猪智能陪伴毛绒玩具`:专利、配件、生产、宣发图;视频是本地分段草稿,未作为最终成片交付
>
> 包内有 `交付说明.md` 和 `交付文件索引.csv`,可以直接发给别人。
>
> `git status -sb`
> ```text > ```text
> ## master...origin/master > ## master...origin/master
> M .memory/assistant-handoff.md
> M .memory/worklog.json > M .memory/worklog.json
> ``` > ```
>
> 下一步候选我先不执行:可以验证线上/本地当前页面、继续查视频播放问题、处理 Claude 交接里提到的本地 dev server chunk 问题或做路线里的导出专利包、ZIP/PDF、Seedance 轮询 UI。后续如果需要 Cursor请从看板打开 Cursor GUI 到当前项目根目录 `/Users/kangwan/Projects/code/20260518-ai-toy-patent-workflow`,不要打开父目录。
>
> <oai-mem-citation>
> <citation_entries>
> MEMORY.md:503-511|note=[handoff read order and no-recap protocol]
> MEMORY.md:517-520|note=[AI toy repo runtime and provider caveats]
> MEMORY.md:49-58|note=[AI toy continuation scope and prior rollout pointer]
>
## 当前仓库状态 ## 当前仓库状态
- 当前分支master - 当前分支master
- 未提交变更1 项 - 未提交变更1 项
- 最近提交:docs: add orchestration logic overview (PDF + HTML source) - 最近提交:feat: add zodiac fantasy pig video templates
- 变更文件: - 变更文件:
- M .memory/worklog.json - M .memory/worklog.json

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,10 @@
## 部署事实 ## 部署事实
- 平台:个人 VPS `76.13.31.179`Docker Compose接入现有 Coolify Traefik - 平台:个人 VPS `76.13.31.179`Docker Compose接入现有 Coolify Traefik
- 发布状态VPS 生产已发布,仅个人使用 - 发布状态VPS 生产已发布,仅个人使用
- 最近生产数据同步2026-05-31新增两条补充展示视频并同步到 VPS `data/``有你家族 · 糯糯猪` session `s_mps3u047_48e383` 新增 45 秒 `nuonuo_pig_motion_feedback_showcase`,重点展示主动耳朵、三套以上眼睛反馈、灵巧尾巴和四肢小范围互动动作;`有你家族 · 亥猪 MEEY 机甲陪伴机器人` session `s_mpsn5ef3_edc352` 新增 30 秒 `meey_face_display_dynamic_30s`,快节奏聚焦完整无遮挡面部显示屏、互动表情、智能对话情绪反馈和演唱会视频播放。
- 最近生产数据同步2026-05-31`有你家族 · 亥猪 MEEY 机甲陪伴机器人` session `s_mpsn5ef3_edc352` 追加 1 条 45 秒“有你家族 MEEY 屏幕脸展示视频”并同步到 VPS `data/`,任务 `youni_family_meey_screen_showcase` 当前指向 `family-screen45v2` 成片v2 使用 `scripts/seedance-60s-compose.mjs --no-reference` 降低旧参考图 visor 影响,重点要求面部是一整块完整无遮挡显示屏,展示喜怒哀乐/爱心等表情包,以及屏幕播放演唱会舞台画面的智能陪伴场景。
- 最近生产数据同步2026-05-31新增 3 条 45 秒互动展示成片并已同步到 VPS `data/``有你家族 · 生肖幻装系列` session `s_zodiac_fantasy_20260531_main` 新增 `zodiac_fantasy_interaction_showcase`,重点展示亥猪屏幕脸、喜怒哀乐表情包、人机互动和视频播放,且额头猪鼻子可不显示;`有你家族 · 亥猪 MEEY 机甲陪伴机器人` session `s_mpsn5ef3_edc352` 新增 `hai_pig_meey_interaction_showcase`,重点展示面部显示器表情包、语音互动和视频播放;`有你家族 · 糯糯猪` session `s_mps3u047_48e383` 新增 `nuonuo_pig_interaction_showcase`,重点展示自主行动、语音互动,以及猪鼻子、猪耳朵、猪尾巴、猪眼睛的触摸反馈。本轮使用 `scripts/seedance-60s-compose.mjs``interaction45` 分段拼接流程生成,每条成片 3 段拼接约 45 秒。
- 最近生产数据同步2026-05-31`有你家族 · 生肖幻装系列` session `s_zodiac_fantasy_20260531_main` 已同步到 VPS `data/`,包含 12 张专业投影六视图专利图、18 张系列/单款宣发图、6 份专业文字资产、3 条亥猪 C 位 45 秒 Seedance 分段拼接视频(展示视频、触感细节、群体快闪);产品尺度统一按 50cm+ 具身 AI 智能陪伴机器人处理。本轮使用 `scripts/generate-zodiac-fantasy-series-assets.mjs` 以桌面参考图 `1400a0c9-6501-4a8f-942a-59d5e82edacd.png` 为视觉锚点生成图片,视频使用 `scripts/seedance-60s-compose.mjs``zodiac_fantasy_pig_*` 模板生成。
- 最近生产部署2026-05-31`有你家族 · 亥猪` 模板约束已发布并完成生产:视频任务统一改为 45 秒;图片包模板移除默认动物鼻子、尾巴、耳朵等提示,改为 40cm+ AI 陪伴机器人摆件、正面宽约 28cm、侧面深约 22cm、软壳/短绒触感但不改变基础机甲结构。生产 session `s_mpsn5ef3_edc352` 已完成 64 张基础图片、4 张十二生肖装甲组合图、5 条 45 秒亥猪视频和 2 条 30 秒十二生肖集合视频。 - 最近生产部署2026-05-31`有你家族 · 亥猪` 模板约束已发布并完成生产:视频任务统一改为 45 秒;图片包模板移除默认动物鼻子、尾巴、耳朵等提示,改为 40cm+ AI 陪伴机器人摆件、正面宽约 28cm、侧面深约 22cm、软壳/短绒触感但不改变基础机甲结构。生产 session `s_mpsn5ef3_edc352` 已完成 64 张基础图片、4 张十二生肖装甲组合图、5 条 45 秒亥猪视频和 2 条 30 秒十二生肖集合视频。
- 上一轮生产部署2026-05-30视频 provider 改为默认 Seedance`VIDEO_PROVIDER=seedance`OpenAI Sora 仅作为可选回退;实测 Ark / Seedance `doubao-seedance-2-0` R2V 不接受 `duration=60`,当前 Seedance 单任务按 15 秒提交。若仍需 60 秒成片,需要分段拼接,或回退 OpenAI Sora 的延展链路。 - 上一轮生产部署2026-05-30视频 provider 改为默认 Seedance`VIDEO_PROVIDER=seedance`OpenAI Sora 仅作为可选回退;实测 Ark / Seedance `doubao-seedance-2-0` R2V 不接受 `duration=60`,当前 Seedance 单任务按 15 秒提交。若仍需 60 秒成片,需要分段拼接,或回退 OpenAI Sora 的延展链路。
- 最近生产数据同步2026-05-30`有你家族 · 糯糯猪` session `s_mps3u047_48e383` 已同步到 VPS `data/`,包含专利包、配件包、生产打样包、宣发包共 64 张图片Seedance 生产环境 Key 已换新并作为当前默认视频 provider。 - 最近生产数据同步2026-05-30`有你家族 · 糯糯猪` session `s_mps3u047_48e383` 已同步到 VPS `data/`,包含专利包、配件包、生产打样包、宣发包共 64 张图片Seedance 生产环境 Key 已换新并作为当前默认视频 provider。

View File

@@ -12,6 +12,7 @@
"docker:logs": "docker compose logs -f web", "docker:logs": "docker compose logs -f web",
"resources:index": "node scripts/build-resource-index.mjs data", "resources:index": "node scripts/build-resource-index.mjs data",
"styles:previews": "node scripts/generate-style-previews.mjs", "styles:previews": "node scripts/generate-style-previews.mjs",
"images:zodiac-fantasy": "node scripts/generate-zodiac-fantasy-series-assets.mjs",
"videos:seedance": "node scripts/seedance-60s-compose.mjs", "videos:seedance": "node scripts/seedance-60s-compose.mjs",
"videos:seedance60": "node scripts/seedance-60s-compose.mjs --target-seconds 60" "videos:seedance60": "node scripts/seedance-60s-compose.mjs --target-seconds 60"
}, },

View File

@@ -0,0 +1,790 @@
#!/usr/bin/env node
import { readFileSync } from 'node:fs';
import fs from 'node:fs/promises';
import path from 'node:path';
import { randomBytes } from 'node:crypto';
import { execFile as execFileCb, spawn } from 'node:child_process';
import { promisify } from 'node:util';
import { fileURLToPath } from 'node:url';
const execFile = promisify(execFileCb);
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const args = parseArgs(process.argv.slice(2));
const sourcePath = path.resolve(args.source || '/Users/kangwan/Desktop/1400a0c9-6501-4a8f-942a-59d5e82edacd.png');
const env = {
...readEnvFile(path.join(root, args.env || '.env.local')),
...process.env,
};
const apiKey = env.OPENAI_API_KEY;
const apiBase = env.GPT_API_BASE || 'https://api.openai.com/v1';
const model = env.GPT_IMAGE_EDIT_MODEL || env.GPT_IMAGE_MODEL || 'gpt-image-2';
const sessionId = args.session || `s_zodiac_fantasy_${new Date().toISOString().slice(0, 10).replaceAll('-', '')}_${randomHex(3)}`;
const sessionPath = path.join(root, 'data', 'sessions', `${sessionId}.json`);
const uploadDir = path.join(root, 'data', 'uploads');
const packDir = path.join(root, 'data', 'packs');
const exportDir = path.join(root, 'data', 'exports');
const artifactDir = path.join(root, 'artifacts', 'screenshots', 'zodiac-fantasy-series', sessionId);
const prepareOnly = Boolean(args['prepare-only']);
const phase = args.phase || 'all';
const limit = args.limit ? Number(args.limit) : Infinity;
const only = String(args.only || '').split(',').map(item => item.trim()).filter(Boolean);
const fresh = Boolean(args.fresh);
const VARIANTS = [
{
key: 'rat',
zodiac: '子鼠',
sku: 'Rat',
color: '雾蓝 / 银白 / 冰青 visor',
crop: { x: 40, y: 130, w: 300, h: 300 },
traits: '圆形鼠耳幻装、雾蓝短绒外套、蓝灰斜挎带、冰青情绪 visor、圆润安全的侧面模块',
},
{
key: 'ox',
zodiac: '丑牛',
sku: 'Ox',
color: '象牙白 / 奶咖 / 暖金 visor',
crop: { x: 380, y: 130, w: 300, h: 300 },
traits: '柔性牛角幻装、奶咖短绒、棕色能量肩带、暖金 visor、稳重可靠的低重心体态',
},
{
key: 'tiger',
zodiac: '寅虎',
sku: 'Tiger',
color: '琥珀橙 / 黑纹 / 暖橙 visor',
crop: { x: 720, y: 130, w: 300, h: 300 },
traits: '虎纹短绒头罩、圆形耳部幻装、琥珀橙功能色、黑色肩带、精神但不攻击的守护者气质',
},
{
key: 'rabbit',
zodiac: '卯兔',
sku: 'Rabbit',
color: '薰衣草紫 / 月白 / 粉紫 visor',
crop: { x: 1060, y: 130, w: 330, h: 300 },
traits: '柔软兔耳幻装、淡紫短绒、蝴蝶结局部点缀、粉紫 visor、女性向温柔陪伴气质',
},
{
key: 'dragon',
zodiac: '辰龙',
sku: 'Dragon',
color: '青瓷绿 / 珠白 / 青蓝 visor',
crop: { x: 40, y: 425, w: 320, h: 310 },
traits: '东方龙鬃幻装、云纹柔性模块、青瓷绿短绒、深绿肩带、青蓝 visor、灵动但圆润安全',
},
{
key: 'snake',
zodiac: '巳蛇',
sku: 'Snake',
color: '薄荷绿 / 奶白 / 青蓝 visor',
crop: { x: 380, y: 425, w: 310, h: 310 },
traits: '鳞片纹短绒头罩、柔和蛇形纹样、薄荷绿外壳、深绿肩带、青蓝 visor、安静聪明的陪伴感',
},
{
key: 'horse',
zodiac: '午马',
sku: 'Horse',
color: '珊瑚粉 / 赤金 / 粉橙 visor',
crop: { x: 720, y: 425, w: 310, h: 310 },
traits: '马鬃幻装冠盖、珊瑚粉短绒、赤金马蹄形胸扣、粉橙 visor、活力轻快但保持机器人结构',
},
{
key: 'goat',
zodiac: '未羊',
sku: 'Goat',
color: '奶白 / 青灰 / 青蓝 visor',
crop: { x: 1060, y: 425, w: 330, h: 310 },
traits: '卷曲羊角柔性幻装、羊毛纹短绒、奶白体块、青灰肩带、青蓝 visor、柔软治愈的家庭陪伴气质',
},
{
key: 'monkey',
zodiac: '申猴',
sku: 'Monkey',
color: '燕麦米 / 深咖 / 暖金 visor',
crop: { x: 40, y: 735, w: 310, h: 310 },
traits: '圆耳猴系幻装、燕麦短绒、咖色肩带、暖金 visor、聪明俏皮但不过度卡通',
},
{
key: 'rooster',
zodiac: '酉鸡',
sku: 'Rooster',
color: '奶白 / 珊瑚红 / 红粉 visor',
crop: { x: 380, y: 735, w: 310, h: 310 },
traits: '柔性鸡冠幻装、小翅状侧面装饰、珊瑚红肩带、红粉 visor、明快醒目的系列识别',
},
{
key: 'dog',
zodiac: '戌狗',
sku: 'Dog',
color: '浅蓝 / 奶白 / 冰蓝 visor',
crop: { x: 720, y: 735, w: 310, h: 310 },
traits: '垂耳狗系幻装、浅蓝短绒、爪印冠盖纹样、蓝色肩带、冰蓝 visor、忠诚可靠的智能伙伴感',
},
{
key: 'pig',
zodiac: '亥猪',
sku: 'Pig',
color: '樱粉 / 奶白 / 粉紫 visor',
crop: { x: 1060, y: 735, w: 330, h: 310 },
traits: '粉色猪系幻装、柔软耳部外轮廓、鼻形胸扣而非真实突出猪鼻、粉紫 visor、主角型温暖陪伴感',
},
];
const MARKETING_ASSETS = [
{
templateId: 'zodiac_fantasy_collection_kv',
view: 'collection-kv',
title: '生肖幻装系列主视觉',
description: '12 款 50cm+ 智能陪伴机器人集合主视觉,面向宣发和招商展示。',
prompt: 'premium hero key visual of all twelve variants, clean studio, Hai Pig as one of the twelve, no video',
},
{
templateId: 'zodiac_fantasy_retail_grid',
view: 'retail-grid',
title: '十二款零售陈列图',
description: '用于电商首屏、招商页和产品目录的 12 款整列展示。',
prompt: 'retail catalog grid, twelve 50cm companion robots, unified scale and spacing',
},
{
templateId: 'zodiac_fantasy_material_board',
view: 'material-board',
title: '毛绒软壳材质宣发板',
description: '短绒、软壳、visor、肩带、徽章和扣件的材质卖点展示。',
prompt: 'material close-up board, plush textile, soft shell, emotional visor, strap buckle, M badge',
},
{
templateId: 'zodiac_fantasy_female_lifestyle',
view: 'lifestyle',
title: '女性向家庭空间场景',
description: '面向女性用户和家庭陪伴场景的空间化宣发图。',
prompt: 'lifestyle scene for female users, home space, one 50cm companion robot displayed at real scale',
},
{
templateId: 'zodiac_fantasy_sku_system',
view: 'sku-system',
title: '十二生肖 SKU 色彩系统',
description: '十二款配色、纹样和幻装模块的系列化设计板。',
prompt: 'SKU color system board, twelve palette swatches and front silhouettes, professional design system',
},
{
templateId: 'zodiac_fantasy_patent_overview',
view: 'patent-overview',
title: '系列专利申报总览',
description: '12 款外观专利申报时可用的系列总览图。',
prompt: 'design patent overview board, twelve front views at same scale, simple dimensions, white background',
},
];
const SKU_MARKETING_ASSETS = VARIANTS.map(variant => ({
key: variant.key,
zodiac: variant.zodiac,
templateId: `zodiac_fantasy_${variant.key}_sku_card`,
view: `${variant.key}-sku-card`,
title: `${variant.zodiac} · 单款 SKU 宣发卡`,
description: `${variant.zodiac}款 50cm+ 生肖幻装 AI 陪伴机器人单品宣发图。`,
prompt: `single product SKU hero card for ${variant.zodiac}, 50cm+ real product scale, premium catalog composition`,
variant,
}));
await fs.mkdir(uploadDir, { recursive: true });
await fs.mkdir(packDir, { recursive: true });
await fs.mkdir(exportDir, { recursive: true });
await fs.mkdir(path.dirname(sessionPath), { recursive: true });
await fs.mkdir(artifactDir, { recursive: true });
await assertFile(sourcePath);
const sourceFilename = `${sessionId}_source_zodiac_fantasy.png`;
const sourceUploadPath = path.join(uploadDir, sourceFilename);
await fs.copyFile(sourcePath, sourceUploadPath);
const cropDir = path.join(uploadDir, `${sessionId}_zodiac_crops`);
await fs.mkdir(cropDir, { recursive: true });
await makeVariantCrops(sourcePath, cropDir);
const contactSheet = path.join(artifactDir, 'zodiac-crops-contact-sheet.jpg');
await makeContactSheet(cropDir, contactSheet);
let session = await loadOrCreateSession();
syncTextAssets(session);
await saveSession(session);
if (prepareOnly) {
await writeTextExport(session);
console.log(JSON.stringify({
sessionId,
source: `/api/img/uploads/${sourceFilename}`,
cropDir,
contactSheet,
sessionPath,
prepareOnly: true,
}, null, 2));
process.exit(0);
}
if (!apiKey) fail('OPENAI_API_KEY missing');
const generated = [];
if (phase === 'all' || phase === 'patent') {
const patentPack = ensurePack(session, 'patent', '专利六视图包');
const pending = filterItems(VARIANTS, variant => ({
templateId: `patent_${variant.key}_six_view`,
pack: patentPack,
})).slice(0, limit);
for (const variant of pending) {
const templateId = `patent_${variant.key}_six_view`;
if (hasAsset(patentPack, templateId) && !fresh) {
generated.push({ templateId, status: 'skipped' });
continue;
}
const imagePath = path.join(cropDir, `${variant.key}.png`);
const dataUrl = await generateEdit({
imagePath,
prompt: patentPrompt(variant),
size: args.size || '1536x1024',
});
const asset = await writeAsset({
pack: patentPack,
templateId,
kind: 'patent',
view: `${variant.key}-six-view`,
title: `${variant.zodiac} · 专业投影六视图`,
description: `${variant.zodiac}款 50cm+ 智能陪伴机器人幻装外观,正/背/左/右/俯/仰六视图。`,
prompt: patentPrompt(variant),
anchorImageUrl: `/api/img/uploads/${sessionId}_zodiac_crops/${variant.key}.png`,
dataUrl,
meta: { zodiac: variant.zodiac, sku: variant.sku, color: variant.color, generatedBy: 'generate-zodiac-fantasy-series-assets' },
});
upsertAsset(patentPack, asset);
generated.push({ templateId, status: 'generated', url: asset.url });
await saveSession(session);
}
}
if (phase === 'all' || phase === 'marketing') {
const marketingPack = ensurePack(session, 'marketing', '系列宣发包');
const marketingItems = [...MARKETING_ASSETS, ...SKU_MARKETING_ASSETS];
const pending = filterItems(marketingItems, spec => ({
templateId: spec.templateId,
pack: marketingPack,
})).slice(0, limit);
for (const spec of pending) {
if (hasAsset(marketingPack, spec.templateId) && !fresh) {
generated.push({ templateId: spec.templateId, status: 'skipped' });
continue;
}
const dataUrl = await generateEdit({
imagePath: spec.variant ? path.join(cropDir, `${spec.variant.key}.png`) : sourceUploadPath,
prompt: marketingPrompt(spec),
size: args.size || '1536x1024',
});
const asset = await writeAsset({
pack: marketingPack,
templateId: spec.templateId,
kind: 'marketing',
view: spec.view,
title: spec.title,
description: spec.description,
prompt: marketingPrompt(spec),
anchorImageUrl: spec.variant ? `/api/img/uploads/${sessionId}_zodiac_crops/${spec.variant.key}.png` : `/api/img/uploads/${sourceFilename}`,
dataUrl,
meta: { generatedBy: 'generate-zodiac-fantasy-series-assets', zodiac: spec.variant?.zodiac, color: spec.variant?.color },
});
upsertAsset(marketingPack, asset);
generated.push({ templateId: spec.templateId, status: 'generated', url: asset.url });
await saveSession(session);
}
}
syncTextAssets(session);
await saveSession(session);
await writeTextExport(session);
await writeSeriesManifest(session);
console.log(JSON.stringify({
sessionId,
generated,
gallery: `/api/gallery/${sessionId}`,
textExport: `/api/export/${sessionId}_text_assets.json`,
manifest: `/api/export/${sessionId}_series_manifest.json`,
cropContactSheet: contactSheet,
}, null, 2));
function patentPrompt(variant) {
return [
`Create a professional design-patent orthographic projection sheet for ONE product variant: 有你家族 · 生肖幻装系列 · ${variant.zodiac}.`,
`Use the reference crop as the exact visual anchor. Preserve the variant identity: ${variant.traits}. Preserve its palette: ${variant.color}.`,
'Hard product scale constraint: this is a 50cm+ embodied AI companion robot for home spaces, NOT a small blind-box toy, NOT a keychain, NOT a desktop mini figure. Body volume, stable feet, and low center of gravity must feel like a real 50cm+ consumer robot.',
'Show exactly six orthographic views of the same product at the same scale: front view, back view, left side view, right side view, top view, bottom view. Arrange them in a clean 3 by 2 technical board. Use strict orthographic projection, no perspective distortion, no dramatic camera angle.',
'Fixed shared base must remain across every view: large rounded helmet head, short body, rounded limbs, black/dark graphite face base, curved emotional visor screen, round side modules, circular M family badge on chest, diagonal energy shoulder strap, central buckle, short stable legs and dark sole pads.',
'Material expression: plush fantasy outfit over a soft-shell robot body; short-pile textile and soft matte shell, safe rounded edges, female-friendly, warm technology, premium product design.',
'Patent clarity requirements: white or very light gray background, thin gray projection guide lines, clear silhouette, no props, no humans, no packaging, no scene, no accessories beside the robot, no shadows that hide contours.',
'Mandatory dimension marker: leave clear left margin in the FRONT view and draw a clean vertical dimension line labeled "约50cm+" or "50cm+" beside the robot. This is a hard acceptance requirement; an image without the height ruler fails. Do not omit, crop out, or hide this height marker. Keep width/depth believable for a 50cm+ robot.',
'Text rule: use only concise technical labels such as 主视图/FRONT, 后视图/BACK, 左视图/LEFT, 右视图/RIGHT, 顶视图/TOP, 底视图/BOTTOM, 约50cm+. No random text, no watermark, no price, no logo other than the product M badge visible on the robot.',
].join('\n');
}
function marketingPrompt(spec) {
if (spec.variant) {
return [
`Create a premium single-product marketing SKU card for 有你家族 · 生肖幻装系列 · ${spec.variant.zodiac}.`,
`Use the reference crop as the exact visual anchor. Preserve the character identity: ${spec.variant.traits}. Preserve palette: ${spec.variant.color}.`,
'Hard scale rule: this is a 50cm+ embodied AI companion robot for home spaces, not a small toy, keychain, blind box, or mini figurine. Make the body volume and product presence credible.',
'Preserve the shared family design language: rounded AI companion robot base, curved visor emotion screen, M badge, diagonal shoulder strap, side circular modules, short stable limbs, plush fantasy outfit over a soft-shell robot body.',
'Visual composition: one hero front or three-quarter front product render, one small orthographic side/back inset if helpful, soft studio lighting, clean premium catalog layout, subtle 50cm+ dimension cue, female-friendly warm technology feeling.',
'No humans, no celebrities, no third-party IP, no price tags, no watermark, no weapons. Avoid excessive text; if text appears, keep it concise and professional.',
`Visual task: ${spec.prompt}.`,
].join('\n');
}
return [
'Create a professional marketing visual for 有你家族 · 生肖幻装系列.',
'Reference image defines the exact 12 variants. Preserve the shared family design language: rounded AI companion robot base, curved visor emotion screen, M badge, diagonal shoulder strap, side circular modules, 50cm+ real product scale, plush fantasy zodiac outfits.',
'This series is for female users and family spaces: premium, warm technology, soft plush texture, clean modern lifestyle, not childish, not combat mech, not tiny blind-box toy.',
'Hard scale rule: all robots are 50cm+ intelligent companion robots. Make the body volume and environmental scale credible; never make them appear as small figurines.',
'Keep all twelve zodiac variants recognizable: 子鼠 blue mouse, 丑牛 ivory ox, 寅虎 amber tiger, 卯兔 lavender rabbit, 辰龙 mint dragon, 巳蛇 green snake, 午马 coral horse, 未羊 cream goat, 申猴 oatmeal monkey, 酉鸡 coral rooster, 戌狗 blue dog, 亥猪 pink pig.',
`Visual task: ${spec.prompt}.`,
'Use clean premium lighting and real product rendering. Avoid excessive text. No third-party IP, no celebrities, no watermarks, no price tags, no weapons.',
].join('\n');
}
function syncTextAssets(sessionValue) {
const now = Date.now();
const assets = [
{
templateId: 'series_positioning_brief',
kind: 'project',
title: '系列定位说明',
description: '生肖幻装系列的产品定位、用户对象和设计边界。',
outputFormat: 'paragraph',
content: `“有你家族 · 生肖幻装系列”定位为 50cm+ 具身 AI 智能陪伴机器人系列。十二生肖不是小挂件、盲盒或静态公仔,而是同一机器人家族基础结构上的十二套毛绒幻装外观。产品面向女性用户、家庭空间、礼赠场景和陪伴型智能硬件市场,核心气质为温暖科技、柔软可亲、系列收藏感和可落地的实体产品感。所有款式都必须保留情绪 visor、M 家族徽章、斜挎能量肩带、圆润低重心机身和 50cm+ 尺寸感。`,
},
{
templateId: 'design_system_rule',
kind: 'project',
title: '外观系统设计规则',
description: '固定资产、可变幻装区和十二款统一规则。',
outputFormat: 'bullets',
content: [
'固定核心:大头短身、圆润站立比例、情绪显示 visor、深色面部底层、胸前 M 徽章、斜挎肩带、中央功能扣、圆形侧面模块、短手短脚、稳定脚底。',
'可变区域:头部毛绒幻装、耳/角/鬃/冠等生肖识别件、肩带配色、胸扣造型、手臂纹样、腿脚边缘色、短绒表面纹理。',
'尺寸规则:每款高度按 50cm+ 产品处理,六视图和宣发图都要有真实体量,不允许表现成 10-20cm 小玩具。',
'材质规则:外部可采用短绒毛绒、软胶、哑光 ABS/PC、半透 visor 和织物肩带的复合表达,避免纯硬塑料的冷感。',
'系列规则:十二款可以有强生肖识别,但不能破坏同一机器人家族基型,不能变成十二个完全无关的动物玩偶。',
].join('\n'),
},
{
templateId: 'twelve_sku_design_matrix',
kind: 'marketing',
title: '十二款 SKU 设计矩阵',
description: '十二生肖款式命名、配色和核心卖点。',
outputFormat: 'table',
content: [
'| 款式 | 主色 | 识别元素 | 宣发关键词 |',
'| --- | --- | --- | --- |',
...VARIANTS.map(v => `| ${v.zodiac} | ${v.color} | ${v.traits} | 50cm+ AI 陪伴机器人,毛绒幻装,情绪 visor家庭空间陪伴 |`),
].join('\n'),
},
{
templateId: 'patent_filing_notes',
kind: 'patent',
title: '外观专利申报要点',
description: '专利六视图生成和申报时需要锁定的专业表达。',
outputFormat: 'bullets',
content: [
'建议按 12 个独立外观设计分别准备申报文件,每款包含正视图、后视图、左视图、右视图、俯视图、仰视图。',
'六视图应保持同一比例、同一站立中轴、同一投影逻辑,避免透视角度、场景光影和额外道具影响轮廓判断。',
'共同保护点包括情绪 visor 轮廓、胸前圆形 M 徽章、斜挎肩带、圆形侧面模块、短胖低重心结构和毛绒幻装与机器人基体的组合关系。',
'差异保护点包括每个生肖的头部幻装轮廓、表面纹样、配色组合、胸扣造型和局部毛绒材质分区。',
'尺寸文案统一为 50cm+ 智能陪伴机器人,避免在专利和宣发资料里出现小尺寸手办、小挂件或盲盒比例误导。',
].join('\n'),
},
{
templateId: 'launch_copy_package',
kind: 'marketing',
title: '宣发文案包',
description: '品牌主张、短句、详情页首屏和招商页文字。',
outputFormat: 'paragraph',
content: [
'主标题:有你家族 · 生肖幻装系列',
'副标题50cm+ 具身 AI 智能陪伴机器人,把十二生肖穿成可以回应你的温暖角色。',
'核心卖点:十二款生肖幻装、情绪 visor 互动、毛绒软壳触感、家庭空间级 50cm+ 尺寸、家族化 M 徽章与斜挎能量肩带、适合陪伴、礼赠、收藏和空间陈列。',
'详情页首屏:不是小玩偶,而是一位可以进入家庭空间的 AI 陪伴伙伴。每一款都保留有你家族的圆润机甲基型,并通过毛绒幻装、颜色和局部模块呈现专属生肖性格。',
'招商表述:该系列以统一基础结构降低产品化复杂度,以十二生肖外观系统拉开 SKU 阵列,兼具 IP 延展、节日礼赠、女性用户审美和智能硬件差异化表达。',
].join('\n'),
},
{
templateId: 'future_video_direction',
kind: 'video',
title: '后续视频方向预案',
description: '本轮视频暂缓,仅保留后续 30 秒集合片方向。',
outputFormat: 'script',
content: [
'片一十二生肖集合亮相。0-5s 情绪 visor 点亮5-15s 十二款按生肖顺序依次转身15-24s 50cm+ 家庭空间比例展示24-30s 全员定格,有你家族 · 生肖幻装系列。',
'片二亥猪主角带队。0-6s 亥猪 visor 呼吸光启动6-18s 镜头扫过其他十一款幻装18-26s 毛绒、肩带、徽章、侧面模块细节快切26-30s 亥猪站在前景,全系列在后景列阵。',
].join('\n'),
},
].map((asset, index) => ({
id: `${asset.templateId}_${index + 1}`,
templateId: asset.templateId,
kind: asset.kind,
title: asset.title,
description: asset.description,
outputFormat: asset.outputFormat,
content: asset.content,
prompt: 'manual professional text asset for zodiac fantasy series',
status: 'complete',
provider: 'mock',
model: 'manual',
createdAt: now + index,
}));
const previous = sessionValue.textAssets ?? [];
const ids = new Set(assets.map(asset => asset.templateId));
sessionValue.textAssets = [
...previous.filter(asset => !ids.has(asset.templateId)),
...assets,
];
}
async function loadOrCreateSession() {
const existing = await readJson(sessionPath);
if (existing) return existing;
const now = Date.now();
const uploadUrl = `/api/img/uploads/${sourceFilename}`;
return {
id: sessionId,
createdAt: now,
prompt: '有你家族 · 生肖幻装系列。12 款 50cm+ 具身 AI 智能陪伴机器人,毛绒幻装外观,按十二生肖展开;本轮只生成专业专利六视图、系列宣发图和文字资产,视频暂缓。',
refImages: [uploadUrl],
count: 1,
inputMode: 'replicate',
uploadedImages: [{
id: 'upload_zodiac_fantasy_source',
url: uploadUrl,
filename: sourceFilename,
originalFilename: path.basename(sourcePath),
mimeType: 'image/png',
uploadedAt: now,
role: 'subject',
needsCleanup: false,
}],
images: [{
id: 'source_zodiac_fantasy_board',
url: uploadUrl,
prompt: '有你家族 · 生肖幻装系列参考图',
status: 'selected',
meta: { sourcePath, role: 'series-reference-board' },
}],
characterSpec: {
name: '有你家族 · 生肖幻装系列',
oneLiner: '12 款 50cm+ 具身 AI 智能陪伴机器人,以统一家族基型承载十二生肖毛绒幻装。',
targetUser: '女性用户、家庭空间、礼赠用户、智能陪伴产品用户',
speciesShape: '圆润短胖 AI 机器人基型,外覆十二生肖毛绒幻装',
bodyRatio: '大头、小身体、短四肢、低重心站立,高度 50cm+',
faceFeatures: '黑色面部底层与弧形情绪 visor 显示屏',
colorPalette: ['奶白', '深灰', '生肖主题色', '高亮 visor 色', '织物肩带色'],
materials: ['短绒毛绒', '软壳 ABS/PC', '半透 visor', '织物肩带', '软胶扣件'],
accessories: ['斜挎能量肩带', '中央功能扣', '圆形侧面模块', '胸前 M 家族徽章'],
signatureElements: ['情绪 visor', 'M 徽章', '斜挎肩带', '圆润低重心', '十二生肖幻装'],
manufacturingNotes: ['高度 50cm+', '外层短绒需可清洁', '所有突出装饰必须软化圆角', '底部需稳定站立'],
patentFocus: ['十二款独立外观六视图', '统一机器人基型', '生肖幻装差异区', '50cm+ 产品比例'],
marketingAngle: ['温暖科技', '女性向陪伴', '家庭空间陈列', '生肖礼赠', '系列收藏'],
negativePrompt: '小挂件、小盲盒、小手办、低龄玩具化、重装战斗机甲、尖锐危险装饰、第三方 IP、水印、价格',
sourceImageId: 'source_zodiac_fantasy_board',
sourceImageUrl: uploadUrl,
cleanReferenceImageUrl: uploadUrl,
lockedAt: now,
},
packs: [],
textAssets: [],
videoTasks: [],
exports: [],
};
}
function ensurePack(sessionValue, kind, label) {
let pack = sessionValue.packs?.find(item => item.kind === kind);
if (pack) return pack;
const now = Date.now();
pack = {
id: `pack_${kind}_${now.toString(36)}_${randomHex(3)}`,
kind,
sessionId,
sourceImageId: 'source_zodiac_fantasy_board',
sourceImageUrl: `/api/img/uploads/${sourceFilename}`,
characterSpec: sessionValue.characterSpec,
assets: [],
manifestId: `manifest_${kind}_${now.toString(36)}`,
createdAt: now,
version: 'v01',
status: 'complete',
meta: { label },
};
sessionValue.packs = [...(sessionValue.packs || []), pack];
return pack;
}
function filterItems(items, getTarget) {
return items.filter(item => {
if (only.length && !only.includes(item.key) && !only.includes(item.templateId) && !only.includes(item.zodiac)) return false;
const { templateId, pack } = getTarget(item);
return fresh || !hasAsset(pack, templateId);
});
}
function hasAsset(pack, templateId) {
return Boolean(pack.assets?.some(asset => asset.templateId === templateId));
}
function upsertAsset(pack, asset) {
const index = pack.assets.findIndex(item => item.templateId === asset.templateId);
if (index >= 0) pack.assets[index] = asset;
else pack.assets.push(asset);
}
async function writeAsset(opts) {
const assetId = `${opts.templateId}_${randomHex(3)}`;
const filename = `${opts.pack.id}_${assetId}.png`;
const filePath = path.join(packDir, filename);
await writeDataUrl(opts.dataUrl, filePath);
return {
id: assetId,
templateId: opts.templateId,
kind: opts.kind,
view: opts.view,
title: opts.title,
description: opts.description,
url: `/api/img/packs/${filename}`,
prompt: opts.prompt,
status: 'draft',
version: 'v01',
aspectRatio: '16:9',
required: true,
createdAt: Date.now(),
anchorImageUrl: opts.anchorImageUrl,
derivationLevel: 2,
meta: { provider: 'gpt', model, ...opts.meta },
};
}
async function makeVariantCrops(inputPath, outputDir) {
for (const variant of VARIANTS) {
const output = path.join(outputDir, `${variant.key}.png`);
if (!fresh && await exists(output)) continue;
const geometry = `${variant.crop.w}x${variant.crop.h}+${variant.crop.x}+${variant.crop.y}`;
await execFile('magick', [inputPath, '-crop', geometry, '+repage', output]);
}
}
async function makeContactSheet(inputDir, outputPath) {
const tiles = [];
for (const variant of VARIANTS) {
const input = path.join(inputDir, `${variant.key}.png`);
const tile = path.join(path.dirname(outputPath), `tile_${variant.key}.png`);
await execFile('magick', [
input,
'-resize', '220x220',
'-background', 'white',
'-gravity', 'center',
'-extent', '240x240',
tile,
]);
tiles.push(tile);
}
const rows = [];
for (let index = 0; index < tiles.length; index += 4) {
const row = path.join(path.dirname(outputPath), `row_${index / 4}.png`);
await execFile('magick', [...tiles.slice(index, index + 4), '+append', row]);
rows.push(row);
}
await execFile('magick', [...rows, '-append', outputPath]);
}
async function generateEdit(opts) {
const rawText = await curlImageEdit(opts);
const raw = JSON.parse(rawText);
const first = raw.data?.[0];
if (first?.b64_json) return `data:image/png;base64,${first.b64_json}`;
if (first?.url) {
const tempPath = path.join(packDir, `tmp_${Date.now()}_${randomHex(2)}.png`);
await execFile('curl', [
'-sS',
'-L',
'--retry', '2',
'--connect-timeout', '20',
'--max-time', '300',
'-o', tempPath,
first.url,
], { maxBuffer: 1024 * 1024 });
const buffer = await fs.readFile(tempPath);
await fs.unlink(tempPath);
return `data:image/png;base64,${buffer.toString('base64')}`;
}
throw new Error('GPT image edit response missing image');
}
async function curlImageEdit(opts) {
const tempDir = path.join(root, 'artifacts', 'tmp');
await fs.mkdir(tempDir, { recursive: true });
const responsePath = path.join(tempDir, `response_${Date.now()}_${randomHex(3)}.json`);
const curlConfig = [
'silent',
'show-error',
'request = "POST"',
`url = "${apiBase}/images/edits"`,
`header = "Authorization: Bearer ${apiKey}"`,
'retry = 2',
'connect-timeout = 20',
'max-time = 600',
`output = "${responsePath}"`,
`write-out = "%{http_code}"`,
`form = "model=${model}"`,
`form = "size=${opts.size || '1536x1024'}"`,
`form = "prompt=${escapeCurlConfig(opts.prompt)}"`,
`form = "image=@${opts.imagePath};type=image/png;filename=${path.basename(opts.imagePath)}"`,
].join('\n');
try {
const stdout = await runCurlConfig(curlConfig);
const rawText = await fs.readFile(responsePath, 'utf8');
const status = Number(String(stdout).trim().slice(-3));
if (!Number.isFinite(status) || status < 200 || status >= 300) {
throw new Error(`GPT image edit ${stdout}: ${rawText}`);
}
return rawText;
} finally {
await fs.rm(responsePath, { force: true });
}
}
async function runCurlConfig(configText) {
return new Promise((resolve, reject) => {
const child = spawn('curl', ['--config', '-'], {
stdio: ['pipe', 'pipe', 'pipe'],
});
const stdout = [];
const stderr = [];
child.stdout.on('data', chunk => stdout.push(chunk));
child.stderr.on('data', chunk => stderr.push(chunk));
child.on('error', reject);
child.on('close', code => {
if (code === 0) {
resolve(Buffer.concat(stdout).toString('utf8'));
} else {
reject(new Error(`curl exited ${code}: ${Buffer.concat(stderr).toString('utf8')}`));
}
});
child.stdin.end(configText);
});
}
function escapeCurlConfig(value) {
return String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\r?\n/g, '\\n');
}
async function writeTextExport(sessionValue) {
await fs.writeFile(
path.join(exportDir, `${sessionId}_text_assets.json`),
`${JSON.stringify({
sessionId,
title: '有你家族 · 生肖幻装系列文字资产',
generatedAt: new Date().toISOString(),
textAssets: sessionValue.textAssets ?? [],
}, null, 2)}\n`,
);
}
async function writeSeriesManifest(sessionValue) {
await fs.writeFile(
path.join(exportDir, `${sessionId}_series_manifest.json`),
`${JSON.stringify({
sessionId,
title: '有你家族 · 生肖幻装系列',
source: `/api/img/uploads/${sourceFilename}`,
gallery: `/api/gallery/${sessionId}`,
counts: {
images: sessionValue.images?.length ?? 0,
packs: sessionValue.packs?.length ?? 0,
packAssets: (sessionValue.packs ?? []).reduce((sum, pack) => sum + pack.assets.length, 0),
textAssets: sessionValue.textAssets?.length ?? 0,
},
variants: VARIANTS.map(({ crop, ...variant }) => variant),
packs: sessionValue.packs ?? [],
}, null, 2)}\n`,
);
}
async function saveSession(sessionValue) {
await fs.writeFile(sessionPath, `${JSON.stringify(sessionValue, null, 2)}\n`);
}
async function readJson(filePath) {
try {
return JSON.parse(await fs.readFile(filePath, 'utf8'));
} catch {
return null;
}
}
async function writeDataUrl(dataUrl, filePath) {
const match = dataUrl.match(/^data:[^;]+;base64,(.+)$/);
if (!match) throw new Error('invalid image data URL');
await fs.writeFile(filePath, Buffer.from(match[1], 'base64'));
}
async function assertFile(filePath) {
const stat = await fs.stat(filePath).catch(() => null);
if (!stat?.isFile()) fail(`Source image not found: ${filePath}`);
}
async function exists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
function readEnvFile(filePath) {
try {
const raw = readFileSync(filePath, 'utf8');
return Object.fromEntries(raw
.split(/\r?\n/)
.map(line => line.trim())
.filter(line => line && !line.startsWith('#') && line.includes('='))
.map(line => {
const index = line.indexOf('=');
const key = line.slice(0, index).trim();
const value = line.slice(index + 1).trim().replace(/^['"]|['"]$/g, '');
return [key, value];
}));
} catch {
return {};
}
}
function parseArgs(argv) {
const parsed = { _: [] };
for (let i = 0; i < argv.length; i += 1) {
const item = argv[i];
if (!item.startsWith('--')) {
parsed._.push(item);
continue;
}
const key = item.slice(2);
const next = argv[i + 1];
if (!next || next.startsWith('--')) {
parsed[key] = true;
} else {
parsed[key] = next;
i += 1;
}
}
return parsed;
}
function randomHex(bytes) {
return randomBytes(bytes).toString('hex');
}
function fail(message) {
console.error(message);
process.exit(1);
}

View File

@@ -15,6 +15,9 @@ if (!sessionId) fail('Usage: node scripts/seedance-60s-compose.mjs run --session
const SEGMENT_SECONDS = clampNumber(args['segment-seconds'], 15, 3, 15); const SEGMENT_SECONDS = clampNumber(args['segment-seconds'], 15, 3, 15);
const TARGET_SECONDS = clampNumber(args['target-seconds'] || args.seconds, 45, SEGMENT_SECONDS, 180); const TARGET_SECONDS = clampNumber(args['target-seconds'] || args.seconds, 45, SEGMENT_SECONDS, 180);
const SEGMENT_COUNT = Math.ceil(TARGET_SECONDS / SEGMENT_SECONDS); const SEGMENT_COUNT = Math.ceil(TARGET_SECONDS / SEGMENT_SECONDS);
const PRODUCT_CONTEXT = productContextForSession(sessionId);
const PRODUCT_SIZE_LABEL = args['product-size'] || PRODUCT_CONTEXT.sizeLabel;
const PRODUCT_SIZE_TEXT = args['product-size-text'] || PRODUCT_CONTEXT.sizeText;
const TEMPLATE_IDS = [ const TEMPLATE_IDS = [
'video_turntable', 'video_turntable',
'video_unboxing', 'video_unboxing',
@@ -24,6 +27,7 @@ const TEMPLATE_IDS = [
]; ];
const runLabel = safe(args['run-label'] || `seedance${TARGET_SECONDS}`); const runLabel = safe(args['run-label'] || `seedance${TARGET_SECONDS}`);
const freshRun = Boolean(args.fresh); const freshRun = Boolean(args.fresh);
const noReferenceImages = Boolean(args['no-reference']);
const selectedTemplateIds = String(args.templates || '') const selectedTemplateIds = String(args.templates || '')
.split(',') .split(',')
.map(item => item.trim()) .map(item => item.trim())
@@ -73,6 +77,60 @@ const VIDEO_TEMPLATE_BLUEPRINTS = {
ratio: '16:9', ratio: '16:9',
prompt: character => `生成 ${TARGET_SECONDS} 秒十二生肖装甲走秀短片:${character}. 以亥猪基型为领队,十二套外观像产品发布走秀一样在白色科技展台上轮换展示。颜色可以更丰富,但基础形象不能变:橙色 visor 情绪屏、胸前 M 徽章、斜挎黑色能量肩带、灰橙扣件和短胖站姿全部保留。每个生肖只通过可换装甲区域、色彩符号和表面短绒/软壳材料区分,禁止真实动物器官、禁止重装战斗化。镜头要有节奏感、旋转台、局部特写和集合收束,音乐为原创韩流感电子鼓点,不使用真实歌曲。`, prompt: character => `生成 ${TARGET_SECONDS} 秒十二生肖装甲走秀短片:${character}. 以亥猪基型为领队,十二套外观像产品发布走秀一样在白色科技展台上轮换展示。颜色可以更丰富,但基础形象不能变:橙色 visor 情绪屏、胸前 M 徽章、斜挎黑色能量肩带、灰橙扣件和短胖站姿全部保留。每个生肖只通过可换装甲区域、色彩符号和表面短绒/软壳材料区分,禁止真实动物器官、禁止重装战斗化。镜头要有节奏感、旋转台、局部特写和集合收束,音乐为原创韩流感电子鼓点,不使用真实歌曲。`,
}, },
zodiac_fantasy_pig_showcase: {
title: '亥猪 C 位展示视频',
description: `${TARGET_SECONDS} 秒,亥猪作为 C 位主角,其它 11 款生肖幻装作为背景阵列。`,
ratio: '16:9',
prompt: character => `生成 ${TARGET_SECONDS} 秒“有你家族 · 生肖幻装系列 · 亥猪 C 位展示视频”:${character}. 亥猪必须始终是画面 C 位和视觉焦点,粉色猪系毛绒幻装、粉紫情绪 visor、M 家族徽章、斜挎能量肩带、圆润机甲基型必须清晰保留。其它 11 款生肖机器人只能作为背景阵列、舞台两侧、虚化陈列或后景灯箱出现,不抢主角。镜头结构:开场亥猪 visor 点亮,随后 360 度慢旋展示正面、侧面、背面和顶部,再用展台灯光扫过背景十二生肖阵列,最后亥猪回到正中定格。硬性尺度:全系列都是 50cm+ 具身 AI 智能陪伴机器人,不是小手办。画面风格酷炫、干净、女性向高级感,原创韩流感电子节奏,不使用真实受版权保护歌曲。`,
},
zodiac_fantasy_pig_touch_detail: {
title: '亥猪 C 位触感细节',
description: `${TARGET_SECONDS}展示亥猪毛绒软壳、visor、徽章、肩带和脚底细节。`,
ratio: '9:16',
prompt: character => `生成 ${TARGET_SECONDS} 秒“亥猪 C 位触感细节视频”:${character}. 竖版近景,亥猪是唯一主角,其他生肖只作为远处柔焦背景或镜面反射,不抢画面。重点展示粉色短绒毛绒外层、软壳机甲基体、粉紫 visor 情绪屏呼吸光、胸前 M 徽章、斜挎肩带、猪系胸扣、侧面圆形模块、圆润手臂、稳定脚底。需要有手部轻触、指尖划过短绒、按压软壳边缘、visor 亮起回应的细节,但手部比例必须证明这是 50cm+ 大体量陪伴机器人。禁止把亥猪拍成小玩偶、钥匙扣或桌面小公仔。音乐是原创韩流电子节奏和干净鼓点,不使用真实歌曲。`,
},
zodiac_fantasy_pig_group_flash: {
title: '亥猪 C 位群体快闪',
description: `${TARGET_SECONDS} 秒,十二生肖群体快闪,亥猪领舞/领队/C 位收束。`,
ratio: '16:9',
prompt: character => `生成 ${TARGET_SECONDS} 秒“十二生肖群体快闪视频”:${character}. 亥猪必须是 C 位领队,先单独登场,再由其它 11 款生肖幻装机器人从左右和后方快闪入场形成阵列。其它角色只做背景节奏、队形变化和颜色补充,不改变亥猪主角身份。镜头节奏:快速灯光点亮、生肖角色依次闪现、亥猪向前一步、全员 visor 同步呼吸光、群体 3 排阵列收束。所有角色都是 50cm+ 具身 AI 智能陪伴机器人,体量接近家居摆件/陪伴设备,不是盲盒。风格要酷炫、潮流、女性向高级,原创韩流感电子鼓点,不使用真实受版权保护歌曲。`,
},
zodiac_fantasy_interaction_showcase: {
title: '生肖幻装系列互动展示视频',
description: `${TARGET_SECONDS} 秒,亥猪作为系列代表展示屏幕脸、表情包、视频播放和人机互动。`,
ratio: '16:9',
prompt: character => `生成 ${TARGET_SECONDS} 秒“有你家族 · 生肖幻装系列互动展示视频”:${character}. 以系列中的亥猪为主要展示角色,其他生肖幻装机器人可以作为背景阵列、舞台两侧陈列或远景队列。亥猪面部必须是电子显示屏/情绪 visor屏幕上清楚变化喜、怒、哀、乐、害羞、惊喜等表情包并出现与用户语音互动、点头回应、灯效呼吸、播放短视频画面的场景。额头上的猪鼻子这条视频可以不要显示不要把猪鼻子做成突出的额头装饰保留粉色猪系幻装、M 家族徽章、斜挎能量肩带和圆润 50cm+ 具身 AI 陪伴机器人比例。镜头结构:开场系列展台点亮,亥猪屏幕脸从待机图标切换成表情包;中段展示用户说话、亥猪屏幕回应并播放小视频;后段其它生肖作为背景一起亮屏,最后回到亥猪正面定格。风格干净高级、亲和、有科技感,原创电子节奏,不使用真实受版权保护歌曲。`,
},
hai_pig_meey_interaction_showcase: {
title: '亥猪 MEEY 互动展示视频',
description: `${TARGET_SECONDS} 秒,展示屏幕脸、表情包、视频播放和陪伴交互。`,
ratio: '16:9',
prompt: character => `生成 ${TARGET_SECONDS} 秒“有你家族 · 亥猪 MEEY 机甲陪伴机器人互动展示视频”:${character}. 主角是 40cm+ 亥猪 MEEY 机甲陪伴机器人白色圆润机甲头盔、橙色弧形显示屏脸、深灰面部底层、MEEY 顶部识别条、胸前 M 徽章、斜挎能量肩带和灰橙功能扣必须稳定一致。面部显示器要清楚呈现喜、怒、哀、乐、撒娇、确认、睡眠等表情包切换,并展示用户靠近说话、机器人屏幕文字/表情回应、播放家庭短视频或动画片段、用灯效和轻微身体动作反馈的场景。镜头结构:开场桌面/客厅真实比例亮相;中段重点拍屏幕脸表情包和视频播放;后段成人手部或儿童在旁边互动,机器人转头/屏幕回应;最后产品完整正面收束。不要变成真实猪、毛绒动物或小手办。风格温暖科技、适合新品展示,原创电子节奏,不使用真实受版权保护歌曲。`,
},
youni_family_meey_screen_showcase: {
title: '有你家族 MEEY 屏幕脸展示视频',
description: `${TARGET_SECONDS} 秒,面部无遮挡显示屏,展示表情包、演唱会视频播放和有你家族陪伴主题。`,
ratio: '16:9',
prompt: character => `生成 ${TARGET_SECONDS} 秒“有你家族 · MEEY 机甲陪伴机器人屏幕脸展示视频”:${character}. 主题是“有你家族”,主角是亥猪 MEEY 机甲陪伴机器人但画面表达要更像品牌家族成员的智能陪伴产品发布片。最重要硬性要求机器人的面部就是一整块完整电子显示屏屏幕必须无遮挡、无护目镜遮挡、无金属横条挡住、无口罩、无外壳压住显示区域正面镜头要让观众清楚看见完整屏幕边界和屏幕内容。屏幕内容要不断变化开心、委屈、撒娇、惊喜、害羞、睡眼、爱心、确认等表情包中段必须出现播放演唱会视频的场景屏幕里有舞台灯光、歌手剪影、观众光棒或音乐可视化但不要使用真实明星肖像、真实歌曲名、真实品牌标识或可识别版权演唱会。机身保持白色圆润头盔、MEEY 顶部识别条、胸前 M 徽章、斜挎能量肩带、灰橙功能扣、短胖 40cm+ 陪伴机器人比例。镜头结构:开场“有你家族”温暖家庭空间中产品亮屏;中段大特写展示无遮挡全屏表情包切换和演唱会播放;后段用户语音点播音乐会,机器人屏幕回应并播放舞台画面;最后回到正面完整屏幕露出微笑表情收束。风格温暖、科技、家庭陪伴、品牌发布感,原创电子音乐氛围,不使用真实受版权保护歌曲。`,
},
meey_face_display_dynamic_30s: {
title: 'MEEY 面部屏幕动态展示',
description: `${TARGET_SECONDS} 秒,快节奏展示面部显示屏、互动表情、演唱会播放和智能对话情绪反馈。`,
ratio: '16:9',
prompt: character => `生成 ${TARGET_SECONDS} 秒“有你家族 · 亥猪 MEEY 机甲陪伴机器人面部屏幕动态展示”:${character}. 这是快节奏产品功能展示,核心只围绕面部显示屏展开。面部必须是一整块完整电子显示屏,无遮挡、无窄条 visor、无护目镜、无口罩、无金属横条遮住屏幕镜头要多次贴近完整屏幕边界。屏幕动态要丰富且切换快开心、惊喜、害羞、撒娇、认真倾听、思考中、确认收到、困困眼、爱心、音乐律动等互动表情快速轮换当用户说话时屏幕出现语音波形、听懂后的确认表情、情绪反馈动画当用户说“看演唱会”时屏幕立即播放演唱会视频效果画面里有舞台灯光、歌手剪影、观众光棒、音浪可视化和节奏跳动但不要出现真实明星肖像、真实歌曲名、真实品牌标识或可识别版权演唱会。镜头结构开场 0-5 秒快速亮屏与品牌氛围;中段 5-18 秒连续屏幕特写,表情包和语音互动快速切换;后段 18-27 秒屏幕播放演唱会并跟随节奏闪动;最后 27-30 秒回到完整正面屏幕用温暖笑脸和“陪伴感”收束。机身保持白色圆润机甲身体、MEEY 顶部识别条、胸前 M 徽章、斜挎能量肩带、40cm+ 陪伴机器人比例。整体节奏比 45 秒版本更快,镜头更密集,重点是屏幕动态效果,不是全身慢展示。原创电子节奏氛围,不使用真实受版权保护歌曲。`,
},
nuonuo_pig_interaction_showcase: {
title: '糯糯猪互动展示视频',
description: `${TARGET_SECONDS} 秒,展示自主行动、语音互动和猪鼻/耳朵/尾巴/眼睛触摸反馈。`,
ratio: '16:9',
prompt: character => `生成 ${TARGET_SECONDS} 秒“有你家族 · 糯糯猪智能陪伴毛绒玩具互动展示视频”:${character}. 主角是约 45cm 大尺寸浅粉色长绒毛糯糯猪,圆胖坐姿、黑亮圆眼睛、粉色立体猪鼻、下垂猪耳朵、短卷尾巴、金色挂绳项圈和爱心吊牌必须保持一致。视频必须体现它不是普通静态毛绒玩具,而是能自主行动、能和人语音互动的智能陪伴玩具:可以轻微前进/转身/抬头点头,听到用户说话后用软萌语音、眼部灯效、鼻尖呼吸光、耳朵轻动、尾巴摇动回应。必须安排人机互动镜头:用户摸猪鼻子时鼻尖发光并发出回应;摸耳朵时耳朵轻摆并听懂语音;摸尾巴时尾巴卷动或抖动反馈;看向眼睛时眼睛亮起、眨眼或显示情绪光。镜头结构:开场从沙发/儿童房自主走近;中段语音问答与触摸反馈;后段拥抱陪伴、睡前故事或亲子互动;最后糯糯猪坐回中心微笑收束。不要改成机甲机器人、不要失去毛绒小猪身份,不要变成小挂件。风格温暖治愈、真实家庭使用场景,原创轻快音乐,不使用真实受版权保护歌曲。`,
},
nuonuo_pig_motion_feedback_showcase: {
title: '糯糯猪动作反馈展示视频',
description: `${TARGET_SECONDS} 秒,重点展示主动耳朵、三套以上眼睛反馈、灵巧尾巴和四肢小动作。`,
ratio: '16:9',
prompt: character => `生成 ${TARGET_SECONDS} 秒“有你家族 · 糯糯猪动作反馈展示视频”:${character}. 主角必须是约 45cm 大尺寸浅粉色长绒毛糯糯猪,圆胖可拥抱坐姿、黑亮圆眼睛、粉色立体猪鼻、下垂猪耳朵、短卷尾巴、短小四肢、金色挂绳项圈和爱心吊牌保持一致。核心主题是“身体反馈比上一条更丰富”:猪耳朵不是被动晃动,而是会主动抬起、轻摆、左右倾听、听到用户声音后有节奏地扇动;眼睛要有三套以上明确反馈效果,包括眨眼/星星眼/爱心光/困困眼/开心月牙眼/听懂后的确认光圈等,至少出现三种不同状态;尾巴要灵巧摆动,可以卷尾轻摇、左右摆、开心时快速抖动、被呼唤时像小天线一样回应;四肢也要小范围动起来,前肢可以抬手打招呼、轻轻抱住用户手指,后肢可以配合身体小步前进或坐姿调整,但动作要符合软萌毛绒玩具,不要变成硬质机器人。镜头结构:开场糯糯猪听到“有你家族”呼唤后耳朵主动竖动并小步靠近;中段连续展示眼睛三种以上反馈、尾巴灵巧摆动、前肢打招呼和语音互动;后段用户轻摸耳朵/鼻子/尾巴后分别触发不同灯效和动作反馈;最后糯糯猪坐在沙发或儿童房中央,用眼睛爱心光和尾巴轻摇温暖收束。不要失去毛绒猪身份,不要变成机甲机器人、小挂件或四足宠物。风格温暖治愈、亲子陪伴、真实家庭使用场景,原创轻快音乐,不使用真实受版权保护歌曲。`,
},
}; };
const env = { const env = {
@@ -83,7 +141,7 @@ const apiKey = env.SEEDANCE_API_KEY;
if (!apiKey) fail('SEEDANCE_API_KEY missing'); if (!apiKey) fail('SEEDANCE_API_KEY missing');
const apiBase = env.SEEDANCE_API_BASE || 'https://ark.cn-beijing.volces.com/api/v3'; const apiBase = env.SEEDANCE_API_BASE || 'https://ark.cn-beijing.volces.com/api/v3';
const model = env.SEEDANCE_MODEL || 'doubao-seedance-2-0-260128'; const model = env.SEEDANCE_MODEL || 'doubao-seedance-2-0-260128';
const publicAppUrl = env.PUBLIC_APP_URL || env.NEXT_PUBLIC_APP_URL || 'https://ai-toy.kang-kang.com'; const publicAppUrl = args['public-app-url'] || env.PUBLIC_APP_URL || env.NEXT_PUBLIC_APP_URL || 'https://ai-toy.kang-kang.com';
const sessionPath = path.join(root, 'data', 'sessions', `${sessionId}.json`); const sessionPath = path.join(root, 'data', 'sessions', `${sessionId}.json`);
const trackerDir = path.join(root, 'data', 'video-segments'); const trackerDir = path.join(root, 'data', 'video-segments');
const trackerPath = path.join(trackerDir, `${sessionId}-${runLabel}.json`); const trackerPath = path.join(trackerDir, `${sessionId}-${runLabel}.json`);
@@ -176,7 +234,7 @@ async function ensureTracker() {
} }
const anchor = findAnchor(session); const anchor = findAnchor(session);
const referenceUrls = findVideoReferenceUrls(session, anchor); const referenceUrls = noReferenceImages ? [] : findVideoReferenceUrls(session, anchor);
const character = characterSummary(session); const character = characterSummary(session);
for (const templateId of activeTemplateIds) { for (const templateId of activeTemplateIds) {
const task = (session.videoTasks || []).find(item => item.templateId === templateId) || buildDefaultVideoTask(session, templateId, character, anchor); const task = (session.videoTasks || []).find(item => item.templateId === templateId) || buildDefaultVideoTask(session, templateId, character, anchor);
@@ -278,7 +336,7 @@ async function submitSegment(entry, part) {
const body = { const body = {
model, model,
content: [ content: [
{ type: 'text', text: segmentPrompt(entry.prompt, part) }, { type: 'text', text: segmentPrompt(entry, part) },
...entry.referenceUrls.map(url => ({ ...entry.referenceUrls.map(url => ({
type: 'image_url', type: 'image_url',
image_url: { url: publicUrl(url) }, image_url: { url: publicUrl(url) },
@@ -498,21 +556,71 @@ function taskStatus(entry) {
return 'processing'; return 'processing';
} }
function segmentPrompt(prompt, part) { function segmentPrompt(entry, part) {
const fullScreenFaceTemplate = entry.templateId === 'youni_family_meey_screen_showcase'
|| entry.templateId === 'meey_face_display_dynamic_30s';
const fullScreenFaceConstraints = fullScreenFaceTemplate
? [
'本条视频必须把“脸部是一整块完整显示屏”作为最高优先级,覆盖参考图中的橙色弧形 visor 造型;不要生成窄条 visor、护目镜、眼罩、头盔眉檐压住屏幕、外壳横条遮挡、口罩或任何挡住屏幕的结构。',
'每个正面或近景镜头都要能看见完整矩形或大圆角矩形屏幕边界,屏幕面积占脸部主体,大屏里显示表情包或演唱会画面;屏幕外壳只能作为薄边框。',
'可以保留白色圆润机甲身体、MEEY 顶部识别条、胸前 M 徽章、斜挎能量肩带和 40cm+ 陪伴机器人比例,但不要保留旧参考图的橙色眯眼 visor 脸。',
]
: entry.templateId === 'nuonuo_pig_motion_feedback_showcase'
? [
...PRODUCT_CONTEXT.constraints,
'本条视频的动作反馈优先级最高:耳朵必须主动动,眼睛必须出现三套以上不同反馈效果,尾巴必须灵巧摆动,四肢必须做小范围互动动作。',
'眼睛反馈至少包含三种清晰状态,例如眨眼、星星眼、爱心光、开心月牙眼、困困眼、确认光圈或彩色情绪光;每一种都要在镜头中可辨认。',
'动作必须保持柔软毛绒玩具的可信度:耳朵、尾巴和四肢可以有内置机芯驱动的小动作,但不能变成硬质机器人、四足宠物或夸张卡通变形。',
]
: PRODUCT_CONTEXT.constraints;
return [ return [
prompt.trim(), entry.prompt.trim(),
'', '',
`这是 Seedance 分段生成的第 ${part}/${SEGMENT_COUNT} 段,每段 ${SEGMENT_SECONDS} 秒,最终会拼成 ${TARGET_SECONDS} 秒完整视频。`, `这是 Seedance 分段生成的第 ${part}/${SEGMENT_COUNT} 段,每段 ${SEGMENT_SECONDS} 秒,最终会拼成 ${TARGET_SECONDS} 秒完整视频。`,
partCue(part), partCue(part, entry.templateId),
'硬性尺寸约束:主角始终是“有你家族 · 亥猪”40cm+ AI 陪伴机甲摆件,高度必须超过 40cm正面宽约 28cm侧面深约 22cm。', `硬性尺寸约束:${PRODUCT_SIZE_TEXT}`,
'必须明显是 40cm 以上的实体产品:可用成人双手、包装盒、展台、桌面或家居物件证明比例;不能像掌心小玩偶、桌面迷你摆件、挂件或钥匙扣。', `必须明显是 ${PRODUCT_SIZE_LABEL} 以上的实体产品:${PRODUCT_CONTEXT.scaleProof}`,
'参考图里的中文和数字只用于理解尺寸比例;成片画面中不要生成任何数字、厘米文字、箭头尺寸标注或文字海报,避免出现错误读数。', '参考图里的中文和数字只用于理解尺寸比例;成片画面中不要生成任何数字、厘米文字、箭头尺寸标注或文字海报,避免出现错误读数。',
'必须保留白色圆润头盔、橙色弧形 visor 情绪屏、深灰面部底层、头顶 MEEY 竖条、胸前 M 徽章、斜挎黑色能量肩带、灰橙功能扣、侧面圆形模块、短胖站立比例。', ...fullScreenFaceConstraints,
'外部可呈现亲肤短绒、软壳或软硅胶复合触感,但不能改变基础机甲设计。禁止猪鼻子、猪尾巴、写实猪耳、猪蹄、四足身体、其它动物主体、武器和攻击性重装机甲。', ].filter(Boolean).join('\n');
].join('\n');
} }
function partCue(part) { function partCue(part, templateId) {
if (templateId === 'youni_family_meey_screen_showcase') {
if (part === 1) return '第 1 段:建立“有你家族”家庭陪伴场景,机器人正面亮屏登场,脸部必须是无遮挡大显示屏,先展示微笑、爱心、欢迎等表情包。';
if (part === 2) return '第 2 段:重点展示完整屏幕脸播放演唱会视频,屏幕里有舞台灯光、歌手剪影、观众光棒或音乐可视化;屏幕边界必须完整可见。';
if (part === SEGMENT_COUNT) return '最后一段:用户语音点播,机器人用完整屏幕切换表情包并继续播放音乐会画面,最后以无遮挡大屏微笑正面收束。';
return `${part} 段:保持完整大屏脸和有你家族陪伴主题,增加表情包、音乐播放或人机互动,不要回到窄条 visor。`;
}
if (templateId === 'meey_face_display_dynamic_30s') {
if (part === 1) return '第 1 段:快节奏亮屏开场,正面完整大屏无遮挡,快速切换开心、惊喜、害羞、认真倾听等互动表情。';
if (part === 2 || part === SEGMENT_COUNT) return '最后一段:用户点播演唱会,完整面部屏幕播放舞台灯光、歌手剪影、观众光棒和音乐可视化,同时穿插智能对话情绪反馈并用笑脸收束。';
return `${part} 段:保持完整大屏脸,密集展示表情包、语音波形、确认反馈和演唱会播放效果,节奏要比 45 秒版本更快。`;
}
if (templateId === 'nuonuo_pig_motion_feedback_showcase') {
if (part === 1) return '第 1 段:建立家庭场景,糯糯猪听到“有你家族”呼唤后耳朵主动抬起和左右轻摆,身体小步靠近,展示 45cm 可拥抱体量。';
if (part === 2) return '第 2 段:集中展示眼睛三套以上反馈效果,例如眨眼、星星眼、爱心光、开心月牙眼或确认光圈,并配合语音互动。';
if (part === SEGMENT_COUNT) return '最后一段:重点展示尾巴灵巧摆动和四肢小动作,前肢打招呼或抱住用户手指,后肢小步调整坐姿,温暖收束。';
return `${part} 段:保持同一只糯糯猪和同一尺寸,继续增加耳朵、眼睛、尾巴、四肢的互动反馈细节。`;
}
if (PRODUCT_CONTEXT.kind === 'nuonuo') {
if (part === 1) return '第 1 段:建立家庭使用场景,糯糯猪从沙发、床边或儿童房角落自主行动到用户身边,展示 45cm 大尺寸毛绒体量。';
if (part === 2) return '第 2 段:重点展示语音互动和触摸反馈,猪鼻子、耳朵、眼睛、尾巴分别被触发并给出灯效、声音或动作回应。';
if (part === SEGMENT_COUNT) return '最后一段:进入亲子陪伴或睡前故事场景,糯糯猪被拥抱后仍有轻微眨眼、耳朵摆动和尾巴反馈,温暖收束。';
return `${part} 段:保持同一只糯糯猪和同一尺寸,增加真实家庭人机互动、语音问答或柔软触感细节。`;
}
if (PRODUCT_CONTEXT.kind === 'zodiac-fantasy') {
if (part === 1) return '第 1 段:建立系列展台和亥猪主角,亥猪屏幕脸点亮,先展示整体体量、粉色幻装、胸前徽章和斜挎肩带;额头猪鼻子可以不出现。';
if (part === 2) return '第 2 段:继续同一只亥猪,展示显示屏脸切换喜怒哀乐表情包、语音互动回应和播放视频画面,其它生肖只做背景。';
if (part === SEGMENT_COUNT) return '最后一段:背景十二生肖阵列亮屏,亥猪回到 C 位,屏幕脸用温暖表情收束,明确 50cm+ 具身 AI 陪伴机器人尺度。';
return `${part} 段:保持亥猪主角身份和 50cm+ 尺度,增加人机互动、局部特写或系列阵列变化,不改变基础设计。`;
}
if (PRODUCT_CONTEXT.kind === 'hai-pig-meey') {
if (part === 1) return '第 1 段:建立亥猪 MEEY 在客厅或展台中的真实比例展示屏幕脸、MEEY 顶部识别条、胸前徽章和斜挎肩带。';
if (part === 2) return '第 2 段:重点拍面部显示器,连续切换喜怒哀乐表情包,并展示屏幕播放视频与用户语音互动。';
if (part === SEGMENT_COUNT) return '最后一段:成人手部或儿童与机器人互动,机器人用屏幕表情、灯效和轻微转头回应,完整产品正面收束。';
return `${part} 段:保持同一台亥猪 MEEY 和 40cm+ 尺寸,增加陪伴交互、局部特写或多媒体播放场景。`;
}
if (SEGMENT_COUNT <= 2) { if (SEGMENT_COUNT <= 2) {
return part === 1 return part === 1
? '第 1 段:建立系列世界观和主角登场,亥猪先出现,镜头给足正面、肩带、徽章、情绪屏和 40cm+ 体量。' ? '第 1 段:建立系列世界观和主角登场,亥猪先出现,镜头给足正面、肩带、徽章、情绪屏和 40cm+ 体量。'
@@ -524,6 +632,46 @@ function partCue(part) {
return `${part} 段:保持同一角色和同一尺寸,增加使用场景、局部特写、触感互动或系列装甲转换,不改变基础设计。`; return `${part} 段:保持同一角色和同一尺寸,增加使用场景、局部特写、触感互动或系列装甲转换,不改变基础设计。`;
} }
function productContextForSession(value) {
if (value.includes('s_mps3u047') || value.includes('nuonuo')) {
return {
kind: 'nuonuo',
sizeLabel: '45cm',
sizeText: '主角始终是“有你家族 · 糯糯猪”约 45cm 大尺寸智能陪伴毛绒玩具,必须明显是可拥抱的家庭陪伴产品体量;可用儿童/成人手部、沙发、床、地毯、抱枕或包装盒衬托比例,不能像掌心小玩偶、钥匙扣、小挂件或桌面迷你摆件。',
scaleProof: '可用儿童/成人手部、沙发、床、地毯、抱枕或包装盒证明约 45cm 可拥抱体量;不能像掌心小玩偶、桌面迷你摆件、挂件或钥匙扣。',
constraints: [
'必须保留浅粉色长绒毛圆胖坐姿小猪形象、黑亮圆眼睛、粉色立体猪鼻、下垂猪耳朵、短卷尾巴、短小四肢、金色挂绳项圈和爱心吊牌。',
'必须体现智能互动能力:自主轻微移动、语音问答、触摸传感反馈、眼部灯效、鼻尖呼吸光、耳朵轻摆、尾巴卷动或抖动。反馈可以温柔夸张,但不能变成硬质机甲机器人。',
'外观可以暗示内置可拆卸智能机芯和安全电池仓,但主体必须是亲肤长绒毛绒玩具。禁止第三方 IP、水印、文字广告、真实品牌标识和错误尺寸标注。',
],
};
}
if (value.includes('zodiac_fantasy')) {
return {
kind: 'zodiac-fantasy',
sizeLabel: '50cm+',
sizeText: '主角始终是“有你家族 · 生肖幻装系列”中的亥猪 50cm+ 具身 AI 智能陪伴机器人,必须明显是家庭空间级真实产品体量;可用成人手部、沙发、展台、包装盒或其他生肖机器人衬托比例,不能像桌面小摆件、掌心玩偶、挂件或盲盒。',
scaleProof: '可用成人双手、包装盒、展台、沙发或其他生肖机器人证明家庭空间级体量;不能像掌心小玩偶、桌面迷你摆件、挂件或盲盒。',
constraints: [
'必须保留亥猪作为系列代表:粉色猪系毛绒幻装、电子显示屏/情绪 visor 脸、M 家族徽章、斜挎能量肩带、圆润机甲基型和 50cm+ 陪伴机器人比例。',
'额头上的猪鼻子在本条互动展示中可以不显示;不要把猪鼻子做成突出的额头装饰。面部重点是显示屏脸、表情包、喜怒哀乐和视频播放。',
'其它生肖幻装机器人只能作为背景阵列、远景陈列或辅助队列,不能抢走亥猪主角。禁止真实动物身体、四足化、攻击性重装、武器、水印和错误文字。',
],
};
}
return {
kind: 'hai-pig-meey',
sizeLabel: '40cm+',
sizeText: '主角始终是“有你家族 · 亥猪 MEEY 机甲陪伴机器人”40cm+ AI 陪伴机甲摆件,高度必须超过 40cm正面宽约 28cm侧面深约 22cm。',
scaleProof: '可用成人双手、包装盒、展台、桌面或家居物件证明比例;不能像掌心小玩偶、桌面迷你摆件、挂件或钥匙扣。',
constraints: [
'必须保留白色圆润头盔、橙色弧形显示屏脸/visor、深灰面部底层、头顶 MEEY 竖条、胸前 M 徽章、斜挎黑色能量肩带、灰橙功能扣、侧面圆形模块、短胖站立比例。',
'面部显示器必须可以切换喜怒哀乐、撒娇、确认、睡眠等表情包,并可以播放视频或动画片段;人机互动要通过屏幕表情、灯效、语音回应和轻微身体动作体现。',
'外部可呈现亲肤短绒、软壳或软硅胶复合触感,但不能改变基础机甲设计。禁止变成真实猪、毛绒动物、四足身体、武器和攻击性重装机甲。',
],
};
}
function normalizeStatus(status) { function normalizeStatus(status) {
if (status === 'succeeded' || status === 'success' || status === 'completed') return 'succeeded'; if (status === 'succeeded' || status === 'success' || status === 'completed') return 'succeeded';
if (status === 'failed' || status === 'error') return 'failed'; if (status === 'failed' || status === 'error') return 'failed';
@@ -552,7 +700,20 @@ function findAnchor(session) {
} }
function findVideoReferenceUrls(session, fallbackUrl) { function findVideoReferenceUrls(session, fallbackUrl) {
if (session.id?.includes('zodiac_fantasy') || sessionId.includes('zodiac_fantasy')) {
return [
findAssetUrl(session, 'zodiac_fantasy_pig_sku_card'),
findAssetUrl(session, 'patent_pig_six_view'),
fallbackUrl,
].filter(Boolean).filter((url, index, urls) => urls.indexOf(url) === index);
}
const preferredTemplateIds = [ const preferredTemplateIds = [
'zodiac_fantasy_pig_sku_card',
'zodiac_fantasy_collection_kv',
'zodiac_fantasy_retail_grid',
'zodiac_fantasy_patent_overview',
'zodiac_fantasy_female_lifestyle',
'patent_pig_six_view',
'zodiac_hero_lineup', 'zodiac_hero_lineup',
'zodiac_armor_grid', 'zodiac_armor_grid',
'zodiac_material_palette', 'zodiac_material_palette',