开发手册
这一本讲为什么这么做、怎么改、怎么用。具体数值参数不在这里 —— 那些在
docs/长期规范速查.md;逐轮改了什么不在这里 —— 那些在 docs/修改记录.md。
本手册只做总纲和索引,不复制那两份文档的内容,避免同一件事在三个地方各写一遍、改一处漏两处。
1去哪儿查什么
四份文档分工明确,先看这张表就不会找错地方。
| 想知道 | 去哪 | 说明 |
|---|---|---|
| 某个数值参数是多少 | docs/长期规范速查.md | 唯一权威。影 / 尺寸 / 抠图 / 真值 / 详情方块 / 色名 / 工具坑都在里面 |
| 某一轮改了什么、为什么 | docs/修改记录.md | 逐轮流水,最全最长 |
| 影的原理与新方案 | docs/影-新方案(从原片提影).md | 影为什么必须从原片提 |
| 加新灯型时怎么标定影 | docs/灯型影标定卡.md | 加灯型只读这一段 |
| 原理 / 流程 / 怎么用 | 本手册 | 总纲与索引 |
| 最近几轮我这边定了什么规矩 | .workbuddy/memory/MEMORY.md | 硬规则索引 + 当日日志 |
2系统架构
四层,边界很硬:前端负责画和排版,Node 只做转发,云库只存配置,自建模型只做抠图。
为什么是「纯前端 + 薄代理」
整站唯一的服务端是 server.js,零第三方依赖,只做三件事:读 PORT 绑 0.0.0.0、把 /api/* 转发到上游、把 /api/cutout 交给本地 Python 推理。这样部署沙箱只需要一个公开端口,不需要数据库;代价是发版形态必须是 Node 服务,发成静态站 /api/* 会全挂(见 发版)。
图片的存储纪律
- 进 DOM 的
img一律走缩略图(长边 720);导出、合成、送模型用原图。 - 大于 2MB 的原图存 IndexedDB,不进 localStorage。
- 模型返回的 URL 交给 canvas 前,先
fetchAsDataURL转成本地,否则跨源图会污染画布、toDataURL直接抛Tainted canvas。
3一张图的流水线
从点「生成」到拿到成品,中间经过这些站。每一站的产物和判据都能在浏览器控制台读到。
每站的判据
| 站 | 判据(控制台读) | 不合格长什么样 |
|---|---|---|
| ③ 转本地 | 画布不抛 Tainted canvas | 导出直接报错 |
| ④ 抠图 | __heroCutSource = ai 或 field | 长期 field ⇒ 抠图服务在掉链子 |
| ⑤ 剥影 | __heroShadeCut / __heroShadeCutReject | Reject 非空 ⇒ 硬闸挡住了(这是保护,不是 bug) |
| ⑥ 贴底板 | 成品背景行带 vs 底板行带,逐灰阶差 | 差 > 2 ⇒ 背景没对齐 |
| ⑦ 定位 | __heroRect 的 w / 中心 | 宽度不在档位、中心偏离重心线 |
| ⑧ 排版 | __blockNorm(方块尺寸归一化) | 各块宽度差一大截 |
4三条像素纪律
整个项目最贵的三条规矩,违反了任何一条,出来的东西「看着差不多」但就是不能用。
① 背景 = 固定底板,逐像素不改
底板是 assets/bg-1x1.jpg(方)/ bg-3x4.jpg(长)。规范是中性灰纵向渐变
#e9eaea → #f4f3f3(跨度 11 灰阶)。实测差 1 灰阶就是 JPEG 量化的锅,别为了「对齐规范」去动底板本身。
要让模型画对渐变,唯一有效的办法是把底板当「样张」一起喂给模型;提示词里写死 RGB 没用。代价是产品会被模型重画 —— 见 §9。
② 产品默认取上传原片的像素
产品不是「生成」的,是「搬」的。只有两处例外,且各自有开关可以一键回退:吸顶灯主图第 1 张与详情 2 的方块整张交模型(见 §9)。
为什么抠图要 fail-closed:合成失败宁可报错也不退回「让模型重画一张」—— 那等于悄悄换掉客户的货。
③ 阴影必须来自原片里那道真实影,绝不许模型画
真值库里只有 49% 的宽扁吸顶灯画影,所以影的处理得按灯型自适应,别按类型硬分档。模型画的影实测只有真值的 1/8。
量影之前先自证尺子 —— 别拿阈值去定影的顶边,剖面只取上沿顶弧那一段列。
5抠图:两路 + 兜底
抠图是整条流水线里唯一有「算法不确定」的一环,所以它有两条路和一堆保险。
两路
| 路 | 触发 | 产物 | 读数 |
|---|---|---|---|
| AI 抠图 | /api/cutout 正常 | ONNX 概率图 → 掩膜 | __heroCutSource = 'ai' |
| 规则退路 | 接口失败 / 连续失败进冷却 | 背景建模 + 连通块 | __heroCutSource = 'field' |
为什么必须有退路
抠图服务吃内存(几百 MB 级推理),偶尔会被杀掉。退路能跑,只是质量差一些。关键:退路会把「贴着产品的那团影」连成一块收下,于是成品看着体积大、灯罩下沿被切碎。所以退路上多跑一步「剥影」。
剥影的判据演进(两次踩坑,结论写死)
- v246 排除法 —— 错。 只定义「什么算产品」(明显比背景亮 / 光比不对),其余一律默认算影。 深色或中性色产品正落在「说不清」的中间带里,直接被泛洪吃掉。实测:中性色灯罩被剥 85%,成品里灯罩缺一半。
- v247 正判法 —— 对。 只有「明确像影」的才剥:影 = 背景的等比压暗,同时满足压暗量够、等比关系成立、亮度比在合理区间。 中间带既不算产品也不算影,一个像素都不动。
- v247b 去掉压暗上限。 影贴着产品那层压暗很浓,设上限会把浓影全挡在外面,成品里影残留。去掉后影剥得更干净,产品读数一字未变。
两道硬闸(宁可影剥不干净,也绝不吃产品)
- 剥完剩余不足基准的 35% ⇒ 一像素不动;
- 剥完剩余的最大连通块不足剥之前的 50% ⇒ 一像素不动。
再加两道结构性保护:贴着产品 6px 内不剥;细杆件(横段窄 + 竖段长)= 吊线不剥 —— 吊线颜色跟影几乎一样,只能靠形状救,不保护会被剥掉一大半。
剥影只在退路上跑
代码里有开关:AI 掩膜一个字节都不动。这是为了保住 AI 那条路已有的好成绩,别为了「统一」把好路径也改了。
常见坑
- 量掩膜 / 产品框之前先画到图上肉眼验,别信纯数值。
- 别用「只保留最大连通域」当通用手段 —— 会把吸顶式带下挂小熊那类造型切掉挂件。
- 本地跑
node server.js起不了子进程(spawnSync … EBUSY)⇒ 本地抠图必然走退路。要验 AI 路径的读数,以线上为准。
docs/长期规范速查.md → 抠图6阴影:从原片提
影是唯一「看起来能画、其实不能画」的东西 —— 一画就假。
现役口径
- 影 = 搬影(PDS):从上传原片里那道真实影按比例搬过来,默认开。
- 搬影后面接一小段本机尾,让影的收尾自然一点。
- 灯罩本身不许有投影 —— 影只在天花板那一片,不许糊到灯体上。
量影的纪律
- 先自证尺子:先量「免对齐尺」(不依赖阈值)确认测量本身可信,再谈数值。
- 别用阈值定影的顶边 —— 原片是渐变,阈值一动整条都变。
- 剖面只取上沿顶弧那一段列,别整条列都取。
已知未对齐(记着,别以为已经好了)
搬影在均匀灰带上偏弱:同一张原片上逐列的跨度,真值是 189,我们只有 70 ⇒ 说明我们把横向反差抹平了。影该浓的地方还不够浓。
docs/影-新方案(从原片提影).md;加新灯型见 docs/灯型影标定卡.md7版本与缓存
改完不升版本号 = 用户看到的是缓存。这是最容易「我改了怎么没变」的原因。
版本三件套(一起动)
- 缓存版本
?v=Nindex.html里 18 处引用。改了任何 js/css 都要 +1。 - 更新日志
changelog.js版本号 +1,条目写用户能懂的短句。 CUT_VER只改抠图逻辑时才 +1。抠图产物按这个号缓存,抠图逻辑变了却没升 = 用户拿旧抠图结果。
更新日志的三条禁令
- 不许出现安全机制 / 参数 / 密钥 / 口令 / 门禁这类字样(含注释)。
- 提示词里永远不许出现
**(markdown 星号加粗)。 - 不许写「优化了体验」这种没有信息量的话。
自检
tools/yuanhui-selfcheck.js 是发版前的闸门,必须全绿。它不只是「跑一下」—— 它里面钉着几十条否定断言(比如某个写法必须已经消失),防止旧实现悄悄回来。
自检有个坑
否定断言跑的是未剥注释的源码 ⇒ 你在注释里写出那个被否定的字面串,自检就会红。改完记得跑。
8访问门与密钥
除了管理员那台电脑,任何电脑都必须手输口令才能进。这是硬要求,不接受例外。
三种入场方式
| 方式 | 说明 |
|---|---|
| 主口令 | 只存 PBKDF2 哈希,源码里看不到明文。config.js 里烤了一份兜底哈希,云端被覆盖也能进。 |
| 生成口令 | 管理员自定义 / 自动生成,可设有效期、随时撤销。给别人临时用就走这条。 |
| 管理员设备 | 本机被标记后永久免口令,换口令也不受影响。 |
设计前提:fail-closed
遮罩默认可见,只有访问门模块明确放行才隐藏。加载失败 / 抛异常 / 建不出遮罩 —— 一律保持上锁,绝不放行。「邀请链接自动入场」这条路已下线。
模型配置(密钥)
用户自己的 API Key 存在浏览器本地 + 云库。面板里改完下发到各设备(见 js/team.js)。口令就是提货凭证 —— 知道口令才能取到配置。
怎么把口令给新设备
- 在管理员电脑上打开网站免口令直接进。
- 密钥 → 🔒 访问安全面板里生成一个口令,可设天数。
- 把口令给对方新设备打开网址 → 口令页 → 输入 → 进入。
js/gate.js;自检里有关于它的守卫条目9两个全局开关
有两个「整块交给模型还是本机做」的分叉点。搞清它们能省掉很多来回。
开关 A:主图第 1 张走哪条路
| 口径 | 背景 | 产品 | 阴影 | 位置 / 宽度 |
|---|---|---|---|---|
| 整张交模型 当前 |
模型画 | 模型画 | 模型画(柔和) | 模型定 |
| 本机归位 备选 |
贴固定底板 | 上传原片像素 | 本机搬影 | 本机统一档 |
取舍很清楚:整张交模型换来柔和自然的影,代价是背景色值和产品的形状要交给模型;本机归位换来背景逐像素对齐、产品一个像素不改,代价是本机那道影边缘偏硬。选哪条是审美决定,不是技术优劣。
开关 B:详情 2 的方块怎么画
- 整张交模型(当前):每款方块各叫一次模型画一张 1:1,本机只排版。因为每款单独生成,各块尺寸天然会不一致 ⇒ 本机额外做一次尺寸归一化(量每块最宽行,统一缩放到规范档,只缩不放)。
- 本机画:留作兜底,某一款模型没出图时接管。
js/skills.js 顶部注释;字段名 CEILING_HERO_ALLMODEL / SHOW_BLOCK_ALLMODEL10改一个参数的完整闭环
这是最高频的工作。七步,一步都不能省 —— 跳过第 3 步和第 6 步是绝大多数「改了没生效」的原因。
- 先量,别先改写个脚本从真实日志 / 真实图片里读现状。改之前的数是唯一能证明改动有没有用的东西。
- 写进权威文件数值参数只写
docs/长期规范速查.md,代码里只写注释指向它。不要两处都写。 - 改代码中文一律用编辑器工具改,不要用 shell heredoc(会把中文写花)。改完查行尾,必须全 LF。
- 升版本三件套见 §7。改抠图逻辑还要升
CUT_VER。 - 自检必须全绿并把旧守卫按新写法更新 —— 别为了让自检过而删断言。
- 跑实测本地端到端跑一次,把关键读数(宽度、色差、剥了多少、0 报错)打出来。不合格项要能一条条数出来。
- 目视把产物抠出来用眼睛看。断言全绿不等于东西好看 —— 这一步是唯一能发现「数据对、结果难看」的办法。
改中文文件的三条工具坑
- 不要用 shell heredoc 改含中文的文件 —— 会把中文写花,严重时文件变 0 字节。
- 编辑工具会把行尾从 LF 改成 CRLF —— 写完必须查
CRLF=0。 - shell 双引号里的反引号会被执行 —— 先写
.py文件再跑,别在命令行里拼含反引号的代码。
11加一个新技能 / 新灯型
加灯型 = 新增一个系列,界面按系列自动分组,不用改任何渲染代码。
- 建系列新增一个
series(比如「台灯」「落地灯」)。底栏下拉和节点库卡片会自动出现一个分组。 - 定主图套系方图 1440×1440 六张 + 长图 1440×1920 五张。1:1 和 3:4 各一套独立提示词,不能共用。
- 定详情套系张数与产出见 技能清单。产品展示那张是整张交模型 + 本机尺寸归一化,排版位置按真值墨迹反解,不许凭感觉调字号。
- 标定影只读
docs/灯型影标定卡.md。别套用别的灯型的影参数 —— 真值库里只有 49% 的宽扁吸顶灯画影。 - 提示词守三条不许出现
**;不写死 RGB(对模型无效);场景图那档要把「产品不许变」写成最强的一条。 - 加进技能清单本手册 技能清单 加一行;更新记录 §16 追一条。
尺寸口径别凭感觉
产品占画面多少、中心线在哪,都是从真值成品里量出来的,不是设计出来的。新灯型必须先量真值再写参数 —— 哪怕只取两三张。
12发版
这个项目发过一次错版本,导致线上接口全挂、生图抠图对话全线不可用。所以这一步的参数是死规定。
四个参数一个都不能省
- 运行语言 = Node(不许
auto) - 启动命令 =
node server.js - 安装命令 = 空(零依赖)
- 根目录必须有
package.json,且scripts.start = "node server.js"
发成静态站的后果:被判成 python3 -m http.server,/api/* 全部失效(POST 501 / GET 404),生图、抠图、对话全部不能用。
发完必做(三件事)
- 打真接口线上探针打一次生成接口。返回 401 类 = 正常(用的是无效 Key,意思是「链路通、上游已应答」);返回 501/404 = 没起来。
- 核对版本直接拉线上的 js,确认
?v=、开关值、更新日志号都是本次的。 - 线上再跑一遍端到端本地跑过不等于线上跑过。桩住接口的自检永远测不出「发成静态站」这条。
发版前的完整闭环
语法检查 → 自检必须全绿 → 各专项体检 → 与线上逐文件比对(有没有不是我改的差异)
→ 发版(Node 形态)→ 线上核对版本 → 线上真接口探针 → 线上端到端再跑一次
多人同时改一个项目时的纪律
部署前先逐文件比对线上版本。若出现不是我改的的差异 ⇒ 先停下来报告,不要默默覆盖掉别人刚发的东西。
13排查手册
按症状查。第一嫌疑永远是「你那个页面是改动之前打开的」。
「我改了,怎么没变」
- 先让对方按 F5 刷新。版本号变了浏览器才会重新拉 —— 这是第一嫌疑,占绝大多数。
- 确认线上那个文件确实是新版本号(拉一下 js 看
?v=)。 - 看是不是产物本身被缓存了:抠图产物按
CUT_VER缓存,逻辑改了但没升版本号 = 还是旧结果。 - 节点里已有的旧图不会自动重算,要点「生成」重新跑。
「产品不见了 / 抠图不完整」
- 看
__heroCutSource:是field说明抠图服务在掉链子,先查服务。 - 若是退路:看
__heroShadeCut与__heroShadeCutReject。剥影吃产品时读数上会出现「剥掉的比例远超影区」。 - 量化做法:量成品里产品的外接框宽,和正常档位比。掉一半就是被剥了。
- 判据演进见 §5 —— 回到正判法 + 双硬闸。
「阴影不对」
- 先问是哪一种:影太硬 / 影太淡 / 影位置错 / 影糊到灯体上。
- 影太硬、有明显直边 ⇒ 多半走的是本机搬影那条路(见 §9 开关 A)。
- 影糊到灯体上 ⇒ 灯罩的投影没关干净,对应「灯罩不许有投影」那条纪律。
- 影整体偏淡 ⇒ 已知问题:横向反差被抹平了,影该浓的地方不够浓。
「背景色值不对」
- 量成品背景行带 vs 底板行带,逐灰阶比。差 1 是 JPEG 量化,正常;差 10 以上是背景没对齐。
- 如果这张走的是「整张交模型」⇒ 背景本来就是模型画的,色值会飘,这是该口径的固有代价。
- 底板文件本身别动 —— 差 1 灰阶去改底板会越改越错。
「产品大小不一致」
- 先量每一块的产品宽度占画面宽度的百分比,量化差距,别凭眼睛。
- 如果是每款各生成一次的档 ⇒ 尺寸天然不统一,需要本机归一化(只缩不放)。
- 如果是本机归位的档 ⇒ 宽度应走统一档;不统一说明归位没跑到,检查
__heroRect.w。
体检脚本红了但不确定是不是真问题
- 有脚本断言全过却不退出(进程挂着)⇒ 会伪装成超时,超时不算红。
- 有脚本静默吞异常,把「没测到」伪装成「测出问题」⇒ 遇到可疑的红先单独复测一次。
- 线上宿主的导航偶发超时 ⇒ 重试即过,导航超时不算产品红。
docs/长期规范速查.md → 工具坑14操作说明
使用者视角,不涉及代码。
第一次用
- 打开网址新设备会先看到口令页;管理员那台电脑免口令。
- 进「密钥」填模型配置生图和生视频用的 Key、模型、地址。填完下发到你的其他设备。
- 选套系底部「技能」里选,比如「卧室吸顶灯主图套系」。
- 传原片 + 参考图图1 = 产品原片(必给);图2 = 背景样张(这一档要固定背景时才用)。
- 点发送等生成。节点上会出图。
微调成品
选中节点会出现调位面板:方向键挪位置,−/+ 每次 5% 改大小(有上下限,不会缩到离谱)。调完的值会记住。
详情页文字
标题、英文副题、色名都能在底部「标题」面板逐字改。字号和位置锁死(按真值实测反解),面板只能改文字内容,改不了大小和位置 —— 这是有意的,避免改坏版式。
导出
白底那张存 PNG,其余按节点上的格式走。导出用原图,不用缩略图。
一个必须知道的事
已经生成过的图不会自动跟着改动更新。改了任何参数,都要重新点一次「生成」。
15技能清单
当前有 23 个技能:2 个通用技能 + 7 个产品系列,每个产品系列都是「主图套系 + 详情套系 + SKU」三件套。加新技能时在下面加一行。
| 技能 / 系列 | 产出 | 要点 |
|---|---|---|
| 主图视频 | 3:4 竖版短视频 1 条 | 秒数 4~30 逐秒一档,清晰度 480P / 720P / 1080P;图生视频(主图当第一帧) |
| 生成买家秀 | 8 宫格实拍图 | 普通买家模特、非专业感、同一模特多角度 |
| 小吊灯(主图 / 详情 / SKU) | 方图 6 张(含白底 PNG)+ 长图 5 张;详情 11 张 | 有吊线,吊线的形态是单独一条死规定 |
| 卧室吸顶灯(主图 / 详情 / SKU) | 方图 6 张 + 长图 5 张;详情 11 张 | 必须展示吸顶盘、灯光朝下;第 4 张是场景图 |
| 客厅大吊灯 / 餐厅长吊灯(各三件套) | 同上(方 6 + 长 5;详情 11) | 吊装悬挂、悬空不画影;场景分别是客厅沙发区、餐厅长桌 |
| 床头壁灯(三件套) | 同上 | 贴墙安装,只有贴墙那一圈接触影;场景以床头为主(床头墙 / 床头自然光 / 床头柜侧视角) |
| 床头台灯 / 客厅落地灯(各三件套) | 同上 | 必须落在台面 / 地面上,只有底座下一小片接触影 |
后五个系列由「灯型档案 + 套系生成器」产出:加新灯型 = 在档案里加一条,主图与详情张数、提示词骨架、界面分组都自动生成,不用复制整段提示词。各套系张数与提示词口径见 js/skills.js;单张的具体要求见 docs/长期规范速查.md。
16更新记录
每次发版在这里加一条,最新的在最上面。写清楚「改了什么」和「为什么」,一行一条,不重复正文里已有的细节。
- 此前只有小吊灯、卧室吸顶灯两个系列有技能入口;另外 5 个灯型只有重心线与宽度档,界面上根本点不到,等于做不了。
- 做法上没有复制 5 份提示词(那是 110 张文案,必然走样),而是把「随灯型变化的那几项」抽成灯型档案:安装方式、吊线与吸顶盘的有无、影的类型、场景空间、重心线、宽度档;骨架沿用已验收的那套,按档案逐张生成。
- 每个灯型自动得到主图 11 张(方 6 + 长 5)、详情 11 张、SKU 一张。界面按系列自动分组,渲染代码没有改。
- 三条按灯型分流的硬约束:安装方式(吊装 / 贴墙 / 台面 / 落地,互不许混)、影(吊灯悬空不画影、壁灯只有贴墙那道、台灯落地灯只有底座下一小片)、位置口径(重心线接近画面底边的灯型,那条线是「落地点」不是「产品中心」,按中心摆会被切掉一半)。
- 第 1 张是固定渐变底,安装约束只锁姿态不要求出现台面 / 地面,否则会和背景规范打架。
- 顺手解除两处灯型硬编码:详情方块的提示词改成按节点灯型找对应套系;详情标题按钮改成按技能类型判断。以前每加一个灯型都要回来改这两处。
- 密钥面板里可以同时存在多条语言模型配置;「当前使用」如果指向其中一条还没填的空配置,助手就会拿着空配置报「未配置」,用户明明接好了却用不了。
- 新口径:选中那条没有配好就自动回退到第一条配好的;一条都没有才报未配置。
- 助手对话失败时多显示一行「本次使用:名称 · 模型 · 地址」—— 面板里躺着好几条时,不看这一行根本不知道用的是哪条。永不显示 Key。
- 排查路径:助手报错 ⇒ 先看这一行是不是他以为的那条 ⇒ 不是就去「密钥」里点「当前使用」。
- boss 截图:方图第 2、3 张的产品缩成画面中间一小块,四周一圈硬边方框。
- 根因是上一版的「缩小 + 铺底」:整张图(连背景)缩进画面中间,四周靠近似底色补,而补色与原背景不是同一条渐变 ⇒ 一眼就是贴上去的;再加上同一张图被处理两次时缩放会累积。
- 新口径:只放大不缩小。产品偏小 ⇒ 按目标宽度放大(图像溢出画面,四周天然无缝);产品偏大 ⇒ 保持模型原图不动(提示词负责收)。填边代码整套删除。
- 同一张图处理过就记下来,绝不处理第二次(缩放是乘法的,跑两次就毁)。
- 体检口加了失败原因(
__ceilAlignWhy),以后认不出产品能直接看到是哪一闸拦的,不用猜。
- 位置归位后 boss 又报「第 2/3 张的产品高度没达到图 1 的标准」——同一个病根的另一半:大小仍然靠提示词,模型不照做。
- 做法与位置一致:出图后本机量产品外接框的左右跨度,按「目标 = 画面宽的 66%(与第 1 张主图同档)」整张等比缩放。宽度到位,高度按同一比例自然到位。
- 为什么锚宽度而不是高度:灯体宽扁,横向跨度由整排灯罩/水晶决定,比纵向那条(吸顶盘与灯罩交界处容易断行)稳得多。
- 缩放围绕产品中心做,产品不会被顺带挪位;缩小方向四周会露空,用「逐行取左右边缘平均色」的纯背景铺底(拿单列像素拉伸会把墙面噪点拉成竖纹,实测踩过)。
- 提示词同步:第 2 张不再单独给一档更小的百分比,方/长图的第 2、3 张全部与第 1 张同档;「远景感」只许靠背景留白与景深,不许靠缩小产品。
- 安全闸:量不到可信跨度、跨度接近满宽(认错东西)、单次缩放超过 45%、缩放幅度不到 4%,任一成立就保持模型原图。
- 连续两轮改提示词都没挪动位置,原因是这四张(方/长图的第 2、3 张)走的是「拿实拍原图做精修」——模型对精修的理解就是照搬原图构图,写进提示词的像素坐标它不跟。
- 改成出图后本机量一次产品外接框的垂直中心,偏多少就把整张图平移多少;空出来的一条用原图最靠边的真实墙面行做纵向拉伸填上,看不出接缝。
- 三道安全闸:检测不到可信外接框、偏移太小、平移会让产品顶到画面边,任一成立就一个像素都不动(保留模型原图)。
- 透明底那张另外改了两处:产品宽度从百分比改成绝对像素区间(百分比模型换算不来),并把「必须展示吸顶盘」改成「照原片,原片没有就不许补」——那句要求正是凭空多出一个吸顶盘的来源。
- 上一版写的是「灯罩最宽处所在的水平中线」落在重心线上;而吸顶灯最宽的那一行在整只灯的上半部分 —— 把最宽行对在这条线上,整只灯就整体下坠。本机合成一直按「产品外接框中心」对齐,提示词跟它不是一个口径。
- 现在全套改成「整只灯外接框的垂直中心」对齐重心线,并明写「不是灯罩最宽的那一行」+「上方留白约为下方留白的 0.6~0.8 倍」这条可量判据。
- 第 1 张原来把 630 写死在方/长共用的那段里,长图那句本应是 710 ⇒ 两句话互相打架;现在数值只在各画幅里出现一次。
- 长图第 2 张单独抬一档(竖画幅比方图高,同样的百分比看着更小);方图第 6 张透明底收一档,并补「只做抠图不做重绘」的可核对判据。
- 病根不是模型画不准,是每一张的尺寸口径各写各的:第1张给区间、第2张写「偏小、不要放大」、第3张写「比第2张更近」、第5/6张写「与第1张一致」——而第1张本身也是模型画的,等于没有依据。
- 改成全套共用
ceilGeo(),按张分级给绝对百分比:远景 48~54%、中景与主图 62~68%、白底/透明底同上;重心线仍走 630 / 710。 - 另加两条通用句:四周留白不少于画面宽度的 8%(治「顶到边」)、上方留白必须小于下方留白(治「靠下」)。
- 顺带修掉一处互相打架:形状锁里原来写「位置大小与图1 完全一致」,与「按规范重排大小」冲突,已改为「位置大小只认本张规范」。
- 位置:吸顶灯重心线 720/815 是画面正中,而 96 个成品实测中位是 629/711 ⇒ 改成 630/710(方/长)。其余 6 个灯型不动。
- 自相矛盾:同一张提示词前半段写「720px = 约 42% 高度」、后半段写「720px = 约 50%」,还有一句「上方留白充足」在往下推 ⇒ 统一成一个值,并把「上方留白 < 下方留白」写成可核对的一句。
- 尺寸:宽度区间 40%~75% 宽了 35 个百分点,模型自然取上限 ⇒ 收到 62%~68%(以 66% 为准),并加「原片在区间内就照原片、绝不放大」兜底。
- 形状:从形容词换成可核对判据 —— 轮廓剪影必须与原片重合;不许画得更规整更对称;第 2/3/5/6 张加构图锁;负面词补 12 项。
他的原话:「方图第四张不是固定了是根据原图生成场景图吗?我看生成出来的还是原图」。
查下来不是模型不行,是提示词自己打架:通用段落里写「第 2 / 3 / 4 张 …… 保持我原图里的背景、
一律不许重造场景」,而第 4 张的画面要求写的是「生成一个场景图」——
两条指令正面对撞,模型只会听更保守的那条,于是把原图原样交回来。
另一个帮凶:第 4 张原来写着「优先用实拍,如果我给了第 4 张实拍原图就完全照它来」,
而实际只给图1 时,模型会把「照它来」理解成「照图1 来」。
正解:① 第 4 张单独走「场景分支」,通用段落按张分流(第 1 张固定底板 / 第 2·3 张保持原图 /
第 4 张新建场景 / 第 5 张纯白底),一个分支只说一件事;
② 去掉「优先用实拍」这类条件句,改成写死;
③ 给模型一个可自检的判据(「把产品去掉之后如果还像原图那面空墙,就说明场景没生成,必须重做」)。
教训:提示词里不能出现互相否定的两句话——尤其是「不许重造场景」和「生成场景」这种。
同一份提示词里,同一件事只能有一种说法。
他反复说过产品被改了样貌。原来那句「不得改变形状和样式」太笼统,模型会自己决定什么算「改变」。
现在改成逐项对照 + 可量阈值:结构(瓣数 / 沟槽数 / 开孔 / 五金件数量与位置)一处不许差、
外接框宽高比偏差与局部偏移给出量化上限、视角与颜色材质同样锁死、只许动背景、出图前自检。
顺带补上一个漏洞:吸顶灯第 1 张走「整张交模型」,它的提示词是自成一篇、
不拼通用段落的 ⇒ 形状硬保护整段漏掉了,而这一张恰恰最容易改产品。现在单独补进这一张。
「上传的图莫名变空白、参考图一发就报参数错误」查到底,是两个 bug 叠在一起,都在图片仓库模块里 ——
而它从来没进过自检清单,所以连着几个版本都没拦住。
① 收图时把占位符自己存了进去:先把字段换成占位符、再把「字段」推进收集表(推到的已经是占位符),
于是真正被取来当图片用的是一个占位符对象;它被转成字符串当成网址去请求,服务端老老实实回了首页
⇒ 仓库里存的一直是同一份首页文件,所有图共用一个 key,读回来当然解不出图。
② 存盘前那份「副本」是假的:只给「带图片数组」的节点做了拷贝,而图片节点没有那个数组 ⇒
返回的仍是画布本体,抽图时就地把他正在编辑的那张图换成了引用
⇒ 节点当场空白,参考图也变成非法地址发给上游(上游只回「某个参数不合法」,看不出是哪个)。
教训一:凡是「我要改一份副本」的代码,必须能证明副本与原件不共享任何可变结构;只拷一部分等于没拷。
教训二:模块要自己保证不改调用方的数据(进来先深拷贝),别指望调用方每次都记得先拷贝。
教训三:自检的文件清单要覆盖全部模块 —— 漏掉一个,那个模块里多大的坑都测不出来。
视频冻结那版我只断言了「冻结标记有没有加上/清掉」就上线,却没断言「图片有没有被盖住」, 结果 CSS 选择器写太宽(图片节点与视频节点共用一个类),拖一下全画布变黑。 凡有全局副作用的改动,断言里必须有一条「不该被影响的东西确实没被影响」。
之前定位到「保存时序列化整张画布」只对了一半。真用真实鼠标拖拽、给
IDBObjectStore.put 单独打点后量到:60 节点画布每次自动保存卡 409~455 ms,
其中 99.7% 花在这一行。根因是 IndexedDB 写入前要对整个对象做一次 structured clone,
而我们塞进去的是含几十 MB 图片数据的整张画布。修法见 §13。
原来 SKU 参数是全局一份,所有节点共用,填一次只够一个规格,改第二次第一个就错。 改成参数分两级:标题是 SKU 级(多行,一行一个,写几行出几张), 光色/显指/材质/工艺是 商品级(共用一份)。规格名按「颜色-直径-光色」自动拆解; 原片按文件名里的颜色字样自动配对,配不上退回第一张且不报错。见 §15。
画布上「图片不见了」的真根因:模型给的是临时远程地址,过期或被防盗链拦就加载失败, 浏览器画破图图标。现在生成完立刻转成本地数据(永不过期), 并加了失败兜底(打不开也只提示重新生成,不再出现破图图标)。
播放中的视频会每帧重新合成,多个视频时拖拽掉到 30 fps。改成交互期间冻结、解冻后 恢复播放进度。过程中两次踩坑:① 冻结时别把视频隐藏(画面会变黑,而 pause 本就够用); ② 冻结态可能残留,必须加定时/失焦兜底。见 §21。
原来所有代码类资源都 no-store,导致版本号形同虚设、每次开页面重下 1.5 MB,
且不压缩。现在带版本号的资源走长期缓存、文本类加 gzip(实测首屏 1539 KB → 535 KB,
再次打开≈0),网页本身仍不缓存以保住「F5 必得新版」。见 §7。
视频原先没有金额,根因是秒数与清晰度根本没存下来(而价格完全由这两项决定)。 现在生成时存回节点,老视频从元数据反推,读不到就宁可不算。另把金额胶囊改成 居中靠上常显(原先藏在 hover 才出现的按钮条里,等于看不见)。见 §9。
之前只量到「保存时拼字符串」那 98 ms。真用真实鼠标拖、再给数据库写入单独打点才发现: 真凶是 IndexedDB 写入前对整个对象做同步深拷贝,而存档里装着几十 MB 图片数据 (60 节点实测每次松手卡 409~455 ms,其中 99.7% 花在这一行)。 现在图片按内容指纹单独存进独立仓库,存档只留短引用 ⇒ 实测卡顿归零、画质零损失 (字节原样搬,不重编码)。见 §13。
把整个项目里「悄悄把东西剔掉」的写法扫了一遍(filter(Boolean) 共 14 处),
揪出底板样张被静默剔掉——那正是「提示词说两张、实际只发一张」被拒的原因。
现在不静默剔、只记不丢,并在发请求前加一道通用核对:
提示词声明几张 vs 实际发几张,不一致就明着说(对所有技能生效)。
同时说明为什么不逐个改那 93 处空 catch——改动大、易引新 bug,
不如在最后一个关口拦住。见 §10。
上一条的「自动修复坏图」里,我写了「拆不出来就置空」—— 结果把老板的图真的删掉了,且补不回来。 现在拆不出来一律原样保留,并加了两条守卫:敢删就报红。 自愈的职责是「修好」,不是「修不好就删」;真要剔除,只能发生在 发往后端之前那一道。宁可让用户看见坏数据、能自己重传, 也绝不替用户删。见 §21。
「上传的图不显示」与「生成报 400」其实是同一个病:参考图里混进了一个 对象而不是图片地址。靠读代码猜了三轮全错(猜 blob、猜空数组、猜提示词太长), 最后是前一天加的「把每个字段的形状记下来」的诊断口一次看清。 修法分三道:入口只收字符串、生成前一律剔除非字符串、 页面启动时自动修复画布里已坏的图(不用重新上传)。见 §10。
对话报 /api/v3/responses/chat/completions 找不到 —— 服务端只认一种地址写法,
而用户填了另一种,于是拼了两次路径。同时发现界面上写着「填完整地址也兼容」,
那句话是假的 —— 用户是照着它填错的。
改法:服务端认得常见几种端点写法,已含就直接用、不重复拼;提示改成
「填到 /api/v3 就停」。教训:提示与实现必须一致,
不一致的提示比没有提示更糟。见 §11。
上游报「某个参数不合法」却不说是哪个,靠读代码猜命中率是 0(连猜三次都没中)。 于是先做诊断再修 bug:转发前把每一个字段的键名 / 类型 / 长度记下来 (不含明文、不含 Key),并新增一个带 key 保护的诊断接口 —— 下一次报错就能一次定位。见 §10。
今天这一串 bug(入口死结 / 只读没撤 / 填不了)静态检查全绿,只有真跑才炸。
于是把运行时体检做成固定脚本(tools/audit-runtime.py):
真点一遍所有入口、真敲字保存、往浏览器里塞 8 种坏数据看会不会崩、
真鼠标拖拽缩放、API 全挂、窄屏溢出。
并补了三条守卫,把「权限判定不许出现两处各说各话」「配置数要走官方读法」
「保存后立刻重算」钉死。见 §10。
入口放行「零配置」后,只读判定还只看管理员标记 ⇒ 人进得来却打不了字。 现在两处用同一套条件:有配置且非管理员 ⇒ 才只读。 另补两处:保存后立刻重算入口、配置计数走官方读法(自己解析会漏旧格式迁移)。 教训:改一条权限/可见性规则,要先 grep 出所有判定点一起改, 同一件事出现两处判定就一定会打架。见 §8。
规则就一句:有配置且本机不是管理员 ⇒ 入口隐藏(与以前一致,外人看不到面板); 一条配置都没有时例外放行 —— 没有配置就没有配置可看, 让人能把第一份填上,填完立刻恢复隐藏。 这样既守住了原来的严格性,又不会「新设备永远配不回来」。见 §8。
旧写法把「密钥入口」绑在本机管理员标记上,而标记只存在浏览器本地 —— 于是换浏览器 ⇒ 入口被藏 ⇒ 配不了密钥 ⇒ 永远提示「暂无模型」, 管理员想恢复自己反而被自己的标记锁在门外;更糟的是自检还守着这条, 把缺陷固化成规则。现在:过了门就永远看得见入口,非管理员只读 (密钥仍打码 + 控件禁用 + 增删与导入导出隐藏 + 事件层再拦一道)。 教训:自检守住的规则也可能是错的规则,报红时先问「这条规则本身对不对」。见 §8。
之前手册只存在本机磁盘的一个目录里,而那个目录恰好是某个软件的程序安装目录,
软件一升级整个目录被重建,手册就打不开了。现在手册跟着网站一起发布,
顶栏一键打开,不依赖任何本机目录。
手册本体仍以 docs/开发手册.html 为准,每次维护后同步到线上副本。见 §7。
参考图里的 blob 地址会经过两个处理点,之前只堵了第一个
(收集参考图),第二个(压缩/准备参考图)没堵,坏数据照样流到请求里 ⇒ 上游 400。
现在两处都处理:能本地还原的还原成 dataURL,还原不了的明确报错。
教训:堵住一条路不等于堵住一类问题 —— 同一个坏数据会流经多个处理点,
改数据产生处之外,还要把它的每个消费点都走一遍。见 §21。
症状是上游只说一句「某个参数不合法」,不说是哪个,极难查。真因是一条链:
图片存档曾把地址换成 blob: → 拦截函数把 blob 变空串 →
过滤后参考图数组变成空 → 仍然发出去(把图生图悄悄退化成文生图)→ 上游 400。
现在宁可明确报错也不静默发空数组,并在页面启动时自动把残留地址修好、并存回。
教训:兜底要么保住语义,要么就该报错;返回空值是最坏的一种。见 §21。
发现「代码 / 提示词正文 / 节点标签 / 自检守卫」四处的重心线数字可能各说各话 (历史上 v163 与 v232 改过两轮口径)。本轮按老板给的权威表一次改全四处, 并让自检守卫按新值卡死 —— 以后任何一处再漂移,自检立刻报红。 锚点定义仍是「产品外接框中心」。见 §9。
图片存档上线后,读档时内部换成了 blob: 地址(省内存),而节点图片要作为参考图
发给火山方舟 ⇒ 后端不认 blob: ⇒ 线上 400。
现在存档仍然只有 14 KB 引用(卡顿不回弹),但读出来还原成 dataURL ——
卡顿的收益来自「存档小」,与读出的格式无关,两者不必二选一。
教训:浏览器内部地址(blob:/objectURL)绝不能出现在要出网的数据里。见 §13。
实测发现模型把上传原片里天花板的光照一起画进背景(上部偏暗 12~24 灰阶、左侧偏暗 47)。 病根不是色值写错,而是只写了一句「上 #e9e9e9 → 下 #f4f4f4 均匀过渡」,模型没有分段参照可核对。 现在给成五段可逐点核对的数值,并明写「背景与原片光照无关、最上一行不许暗 20 灰阶以上」, 负面里再补「背景顶部压暗 / 偏色 / 有光斑 / 把原片光照带进来」。见 §9。
套系右上角从只显示积分,改成积分 + 约多少元。金额不是用积分换算的(两者没有固定比例),
而是按平台官方刊例价实算:图片按输出像素分档、视频按 token 单价 × token 用量。
价格表独立成 js/price-table.js,改价只动那一个文件。抠图和本机排版不算钱。见 §9。
按 10 个品类推导出 10 种风格与 12 类场景,每条关系标三档强度:核心 / 适配 / 慎用。
「慎用」这一档最要紧 —— 它不是不行,是容易翻车(比例不对 / 亮度不对 / 风格打架),接单前要先确认。
数据在 data/light_map.py(唯一数据源),页面由 tools/build-light-map.py 生成 ⇒
以后加品类或调强度只改数据、不动页面。
3:4 竖版图生视频。秒数 4~30 逐秒一档(27 档)、清晰度 480P / 720P / 1080P。
选中技能时底栏自动切到「秒数 / 清晰度」、把图片专用的「尺寸 / 格式 / 张数」收起来、比例锁 3:4、引擎落到视频那档。
这两个值以前在代码里是写死的(恒为 5 秒 / 720P),界面上根本选不了。
模型选择:发送栏的引擎下拉本来就有「图片生成 / 视频生成」两个 tab;「调整后再生成」弹窗里的模型下拉原来只列图片模型,
视频节点在里面会被当成图片重生成 —— 已按 optgroup 分成「图片生成 / 视频生成」两组。
见 技能清单 · 见 §19 视频任务
长图第 4 张原本只写「产品一模一样」,比方图松 ⇒ 补上完整枚举 + AI 感约束。同一条提示词还被详情第 7 张复用,一处受益两处。
把原理、流程、操作从参数文档和修改记录里抽成总纲,并定下「一条事实只写一处」的维护规则。
更早的改动在哪里
docs/修改记录.md(按轮次分节)。本页只放最近几条,保持可读。17术语表
全站术语只在这里定义一次,别处直接用。
- 秒数 / 清晰度
- 视频任务的两个可选参数,见 §19。范围在
js/models.js的两个常量里。 - 主图
- 1:1 方图 1440×1440 六张 + 3:4 长图 1440×1920 五张。电商首图区用的那批。
- 详情套系
- 详情页 11 张,1440 宽。
- 方图 / 长图
- 方图 = 1:1;长图 = 3:4。两套提示词各自独立,不共用。
- 固定底板
- 中性灰纵向渐变的背景图,
assets/bg-1x1.jpg/bg-3x4.jpg。逐像素不改。 - 归位
- 模型出完图之后,本机把产品抠出来、贴到固定底板上、按规范摆到该在的位置。
- 整张交模型
- 不做归位,模型出的整张图直接用。背景、产品、影都是模型画的。
- 搬影 / PDS
- 从上传原片里把那道真实影按比例搬过来。影不许模型画。
- AI 抠图
- ONNX 模型本地推理出的掩膜。
- 规则退路
- 抠图服务不可用时的备用算法。质量差一些,会把影连着收下来,所以要多一步剥影。
- 剥影
- 在规则退路上,把贴着产品误收进来的影去掉。
- 中位背景
- 估计「某处如果没被产品挡住,该是什么颜色」的方法。取邻域中位再插值,别用外圈填充(原片背景是渐变,外圈读数会偏)。
- fail-closed
- 失败时保持关闭/报错,绝不悄悄降级放行。
- 中间带
- 像素与背景的差落在一个说不清的范围内。既不算产品也不算影 ⇒ 一个像素都不动。
- 真值
- 客户认可的成品图。所有数值参数都从真值量出来,不凭感觉。
- 三件套
- 缓存版本号
?v=、更新日志号、CUT_VER。
19视频任务
视频和图片的流程长得像,实现上完全不是一回事:图片是一次性返回,视频要轮询。
两段式:创建任务 → 轮询
「写死值」是这一块最大的坑
秒数与清晰度曾经直接写死在代码里(秒数恒为 5、清晰度恒为 720P),界面上看着一切正常,实际上用户根本选不了。 这类坑自检查不出来 —— 因为它不报错,只是默默把用户的意图丢掉。
教训:凡是「用户应该能选」的东西,都要去界面上真的能点到,并用探针抓一次真实请求体确认值真的传出去了。 只断言「函数被调用」不算数。
底栏在视频档的样子
- 显示:秒数、清晰度。
- 收起:尺寸、格式、张数(视频没有这些概念)。
- 锁死:比例固定 3:4,不给改。
- 自动切:引擎下拉落到「视频生成」那一档,优先用已激活的视频模型配置。
切回图片技能时,视频那两个下拉会自己收回去,不会留下。
20改动总结怎么写
每轮做完对话,除了改产品本身,还要给一份「这轮改了什么 + 改完是什么样」的可视化总结。 这是硬要求,不是可选项 —— 老板看图比看字快得多。
一份总结必须有的四块
| 块 | 写什么 | 硬要求 |
|---|---|---|
| 症状 → 根因 | 老板报的现象是什么,真实根因是哪一处 | 一个根因管多个症状时,要说清「同一根因」 |
| 改了什么 | 文件 + 改哪一段 + 改前是什么、改后是什么 | 写可对照的句子,别写「优化了」 |
| 实测读数 | 关键数字:外接框宽、背景色差、请求体字段、自检条数 | 🚨 只许来自真实日志 / 真实像素,不许手抄估算 |
| 踩了什么坑 | 这轮踩的坑 + 以前踩过、这轮又踩的(要特别标出) | 踩过的坑要进 §21,不能只留在对话里 |
生成方式:一键出图
不想每次现编 HTML,用脚本直接生成(自包含单文件,Apple 风、跟随系统明暗):
python tools/change-summary.py --out out/改动总结.html \
--title "本轮标题:症状 → 根因" \
--symptom "背景色值不对 / 产品偏下 / 图2图3产品小 / 形状被改" \
--cause "CEILING_HERO_ALLMODEL=true ⇒ 整张交模型" \
--file "js/skills.js:CEILING_HERO_ALLMODEL" \
--file "js/canvas.js:剥影判据改成正判法" \
--metric "抠图框 475px(v246 只有 389)" \
--metric "自检 336 绿 / 0 红" \
--pitfall "Write 工具可能写入替换字符" \
--pitfall "bash 双引号里的反引号会被执行"
--file / --metric / --pitfall 都能重复传,按传的顺序排。
数字从哪来
先跑探针 / 自检,把读数抄进 --metric。不允许「大概」「应该」「差不多」——
写不出精确数字,说明这一项还没测,那就不该写进总结里。
总结里不要出现的东西
内部常量名、密钥/口令/门禁字样、安全机制描述、以及任何「你放心」「应该没问题」这种没有读数支撑的断言。
21踩过的坑
这个项目踩过的坑,按代价排序。最贵的是「又踩了一次」—— 下面标了 又踩 的都是重犯过的。
〇、把整张图「缩小 + 补边」= 一眼假的贴图 又踩
现象:生成出来的方图第 2、3 张,产品缩成画面中间一小块,四周一圈硬边方框 —— 用户原话「为什么变成贴图了」。
根因:产品偏大时把整张图(连原始背景一起)缩小塞进画面中间,四周只能用近似底色补。补出来的底和原背景不是同一条渐变(哪怕逐行取平均色也一样),边缘必然出现可见方框。更糟的是同一张图被处理两次时缩放会相乘(乘两次就只剩一半多点),越修越小。
正解:只放大、不缩小。产品偏小 ⇒ 按目标宽度放大,图像溢出画面,四周天然无缝;产品偏大 ⇒ 保持模型原图不动,交回提示词去收。缩小时的那套填边代码整段删掉,并且处理过的图要记下来,绝不许处理第二次。
教训:「缩放 + 补背景」在视觉上永远比不过「只放大 + 裁切」 —— 图片是内容,不是可以随便平铺的底色。凡是需要「补一块背景」的方案,先想有没有不需要补的做法。
〇之一、拿「一列像素拉伸」去补背景,墙面噪点被拉成一道道竖纹
现象:「出图后按宽度归一」缩小产品时,四周会露出空白;第一版用「最近的一整列/一整行像素」拉伸填 —— 结果实拍墙面上的细碎噪点被纵向拉成几十条竖纹,一眼假。
正解:先铺一层逐行取左右边缘平均色的纯背景底(横向均匀、只保留纵向渐变),再把缩放后的图盖上去。牺牲一点横向纹理,换来完全看不出接缝。
教训:填边只能用「统计量」(平均值),不能用「单个像素样本」 —— 单个样本里一定混着噪声,一放大就变成结构。
〇、把「最长连续段」当产品外接框,整只灯被切掉一半 新
现象:做「出图后本机把产品挪到重心线」那个功能时,一开始用「所有判定为有产品的行里取最长的一段连续行」当外接框。实测吸顶灯在吸顶盘与灯罩交界处会断一两行,最长的那段只剩灯罩 ⇒ 顶部少算了近 200px、中心虚高 94px ⇒ 整张图被往错误方向移了 94px,比不动更糟。
正解:外接框取第一行到最后一行「有产品」的行;抗噪不靠切段,靠框内有产品的行占比 ≥ 55%(框里大半是空白就说明认错了,多半是把画面上边那道接缝当成了产品顶)⇒ 直接返回「不动」。
教训:检测类改动必须先把框画到图上肉眼过一遍,只看一个数字(中心 638 / 645)两个结果都「看着合理」,只有画出来才看得出漏了吸顶盘。
一、量测被界面元素污染
现象:从截图里量产品,量出「产品宽 84%、高 97%」这种明显离谱的数。红箭头、「查看大图」按钮、选中高亮都被当成了产品。
正解:① 先把掩膜画到图上肉眼验再取数;② 按色度/连通性分割,取最大连通块(红箭头是小块,会被自动排除);③ 采样只取四角那种没有 UI 的位置。
配套:别用「白底阈值」当 alpha 真值 —— 水晶罩的亮面接近白,会被判成断开,掩膜碎成一堆。alpha 才是金标准。
二、中文文件被 shell 写坏 又踩
现象一:用 python -c "..." 追加日志,字符串里的反引号被 bash 执行,日志里的话被删掉、还报错一堆 command not found。
现象二:用 heredoc 改含中文的源文件,中文被写花;严重时文件变 0 字节(先截断再抛错)。
正解:含中文的改动一律用编辑器工具(Write / Edit);shell 里要跑就先写成 .py 文件再执行,别在命令行拼代码。追加日志也走文件。
三、编辑器可能写入替换字符 新
现象:写进源文件的中文注释末尾出现 ��(U+FFFD)。
正解:写完含中文的文件,grep 一遍可疑字符;同时顺手查行尾,必须 CRLF=0。
四、自检的否定断言会盯上注释
现象:自检报某条守卫红,但代码逻辑没问题 —— 因为我在注释里写出了那个被否定的字面串,而否定断言跑的是未剥注释的源码。
正解:注释里引用旧写法要改写(不能原样抄);改完必跑自检。
五、桩没生效 / 探针写错参数名
现象一:桩里写 url.indexOf('/api/cutout'),结果一次都没命中 —— 真实请求是 api/cutout,没有前导斜杠。
现象二:探针里 getSkill('lamp-ceiling') 返回 null,脚本直接崩 —— 套系 ID 写错了。
正解:桩的 URL 匹配用子串(indexOf('cutout'));探针里取技能前先断言 ID 存在,并把探针的失败和产品的失败区分开(别把脚本 bug 报成产品 bug)。
六、「写死值」不报错,最难发现
现象:秒数恒为 5、清晰度恒为 720P,写死在两处。界面上一切正常、自检全绿,但用户根本选不了。
正解:凡是「用户应该能选」的东西,都要真的点得到,并用探针抓一次真实请求体确认值传出去了。只断言「函数被调用」等于没测。
七、发版形态
发成静态站 ⇒ /api/* 全挂(生图/抠图/对话全线不可用),而桩住接口的自检永远测不出这一条。参数见 §12。
八、体检脚本本身不可靠的两类假红
① 断言全过却不退出 ⇒ 伪装成超时,超时不算红;② 静默吞异常 ⇒ 把「没测到」伪装成「测出问题」。遇到可疑的红先单独复测一次。
九、写生成器 / 画 SVG 的四个坑 新
- 模板里 CSS 含
%⇒ 不能用%格式化(会当占位符炸掉)。改用__PLACEHOLDER__替换。 - 输出文件被预览进程占用 ⇒
PermissionError。换文件名,或输出名带时间戳。 - Python 数据模块名不能用连字符(
light-map.py导入直接ModuleNotFoundError,须写成light_map.py);手写sys.path.insert要os.path.abspath,相对路径换个 cwd 就失效。 - SVG viewBox 宽度要按最右列的 x+w 算,不是按列宽相加 —— 写小了整张图被放大、字巨大、溢出容器。连线起点与节点文字要错开,文字放盒子外侧会正好压在连线上糊成一团。
参考实现见 §16 更新记录里那两条;改动总结生成器:tools/change-summary.py
十、重命名要连图标一起看 新
菜单项的显示文字是「图标 + 名称」拼出来的。如果图标本身就是文字(比如 SKU),
把名称改短之后会渲染成 「SKU SKU」。⇒ 改名时把图标一起过一遍。
另外更新日志里写过的旧名要同步改,不能让日志和产品对不上。
十一、两个独立列表的下标不能混用 新
断言「分组顺序」时,我拿分组列表的下标去切技能列表,两套坐标系不同步 ⇒ 报出一堆假红。 ⇒ 只要涉及两个独立列表,统一在同一个列表上取下标。
十二、两套价格并存,混用必错 新
同一个视频模型,官方给两张价格表:按量付费(token 单价)和套餐积分折算(元/秒)。
两者差 1.5 倍,混起来算就会离谱。
规矩:只用按量付费口径,因为我们走的是 API 不是套餐。写价格表时在文件头注明口径。
十三、画幅的像素表必须逐格抄,不要自己推 新
我按「720p 就是短边 720」自己推了各画幅宽高 ⇒ 3:4 推出来比 16:9 便宜 25%;
官方实际是更贵(各画幅像素总量只差一个百分点以内)。差 25% 够把一次预算算崩。
规矩:任何对照表(分辨率 / 画幅 / 型号 / 价格)都必须逐格抄官方文档,不许用规律推导,
并把官方出处写进注释。
十四、整个模块没进自检清单 又踩
现象:「上传的图莫名变空白」连着几个版本都没查到原因 —— 因为出问题的那个模块 根本不在自检的文件清单里,里面再大的坑都不会被点亮(静态检查、体检也都绕开了它)。
正解:① 加新模块时同时把它写进自检清单;每轮收尾对一次「js/ 目录下的文件 == 清单」。
② 「我要改一份副本」的代码必须能证明副本与原件不共享任何可变结构 —— 只拷一层等于没拷,
对象里的数组/嵌套对象会一起被改到。③ 被调用的模块自己负责深拷贝,别指望调用方每次都记得。
十五、线上探针连不上,不是因为「站点挂了」新
现象:浏览器探针打线上链接直接 ERR_PROXY_CONNECTION_FAILED,
看着像站点不可用,其实同一秒 curl 能正常取到页面。
正解:本机直连会被拦,必须让浏览器显式走本机代理
(proxy={'server': 'http://127.0.0.1:7301'});
--no-proxy-server 与 direct:// 在线上探针里都连不通。
⚠️ 反过来,测 127.0.0.1 的本地页时要用 no-proxy,别把请求甩给代理。
判据:先 curl 一次 —— curl 通、浏览器不通,就是代理配置问题,不是站点。
十六、探针写错接口,会误判成「功能坏了」新
现象:探针报 Cannot read properties of undefined (reading 'push')
或 reading 'icon',看起来像画布坏了,其实是探针自己调错了口。
两个固定坑:① window.__yh.state 是函数不是对象
⇒ 必须写 __yh.state().nodes,直接点 .nodes 一定 undefined;
② 图片节点的类型名是 image,不是 img —— 写错会掉进
renderText 兜底分支,于是报 reading 'icon'(DEFS 里没这个类型)。
判据:报错栈里出现 nodes.js ... renderText,就是节点类型写错了,不是渲染逻辑坏了。
十六、提示词里两句话互相否定 新
现象:某一档要「生成场景」,结果却一直是原图。模型没坏 —— 同一份提示词里 一边写「生成场景图」、一边写「不许重造场景」,它只能听一条,而它选的是保守那条。
正解:① 一份提示词里同一件事只能有一种说法;② 不同档位的规则按张分流, 别用一句「第 2 / 3 / 4 张都怎样」把需求相反的档位捆在一起; ③ 后果写成可自检的判据(「去掉产品之后如果还像原图,就说明没生成」), 比写十句形容词都管用。
docs/长期规范速查.md → 工具坑