模特上身图提示词重构 + 图片创作页 UI 调整

后端(模特上身图提示词):
- build_model_tryon_prompt_refs 重写:穿戴/非穿戴分流(穿戴=真实穿身替换原衣,
  非穿戴=手持/佩戴/使用不动原衣)、每张按 index 变化动作/场景/镜头、负面词尾接、
  多图参考序号自适应(参考图1~N=商品,参考图N+1=模特)
- 新增 _product_reference_urls:商品参考图真实上传图优先、排除 AI 生成图、可多张(≤3),
  无真实图回落 cover
- worker run_standalone_image_task 模特分支改用多图取图 + 传 index/n_product

前端(图片创作/工作室):
- 生成数量改 1/2/4;图片比例新增「手动输入」(宽:高 两输入框)
- 临时隐藏「商品库」按钮
- 模特卡:去掉 // 真人模特,标题改图片底部白字遮罩层 + 单行省略
- 工作室壳负边距对齐 .content padding,修复上下被遮挡/裁切

其他:并入此前未提交的商品页改动、脚本 Agent/格式实测文档与 demo
This commit is contained in:
zyc
2026-06-27 09:31:44 +08:00
parent 4c8b2d1902
commit de4b20cc7b
24 changed files with 2350 additions and 127 deletions
+54 -10
View File
@@ -432,7 +432,7 @@ const MODE_META: Record<
const RATIO_OPTIONS = ["1:1", "3:4", "4:5", "9:16", "16:9"];
const COUNT_OPTIONS = ["1", "2", "4"];
const MODEL_RATIO_OPTIONS = ["1:1", "3:4", "9:16"];
const MODEL_COUNT_OPTIONS = ["4", "8", "12"];
const MODEL_COUNT_OPTIONS = ["1", "2", "4"];
const COVER_COUNT_OPTIONS = ["4", "8", "12"];
/* 图片创作 · 空态提示词建议 chip(基线 image-optimize EXAMPLES) */
@@ -528,6 +528,10 @@ export function ImageWorkbenchPage({
const product = products.find((item) => item.id === productId) || products[0];
const [prompt, setPrompt] = useState(meta.promptTemplate(products[0]?.title || "商品"));
const [ratio, setRatio] = useState(meta.ratio);
// 手动输入比例:开启后用 W:H 两个输入框自定义,关闭则用预设 pill
const [ratioManual, setRatioManual] = useState(false);
const [ratioW, setRatioW] = useState("");
const [ratioH, setRatioH] = useState("");
const [style, setStyle] = useState("auto");
const [count, setCount] = useState(mode === "image" ? "4" : "4");
// 模特单选(长度恒 0/1);平台改回多选(P0③:可选多个平台,各出一组结果)
@@ -613,6 +617,9 @@ export function ImageWorkbenchPage({
if (product) setPrompt(meta.promptTemplate(product.title));
// mode 或商品切换都重置 prompt 与默认比例
setRatio(meta.ratio);
setRatioManual(false);
setRatioW("");
setRatioH("");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [productId, mode]);
@@ -1323,11 +1330,11 @@ export function ImageWorkbenchPage({
{product?.title || "未选择 · 请在左侧商品空间选一个"}
</span>
</div>
{/* P0④:商品库全屏选择器入口(多选商品,各起一批) */}
<button className="iw-pl-btn" type="button" onClick={() => { setPlDraft(productId ? [productId] : []); setPlQuery(""); setPlCat(""); setPlOpen(true); }} title="从商品库选择">
{/* P0④:商品库全屏选择器入口(多选商品,各起一批) — 临时屏蔽 */}
{/* <button className="iw-pl-btn" type="button" onClick={() => { setPlDraft(productId ? [productId] : []); setPlQuery(""); setPlCat(""); setPlOpen(true); }} title="从商品库选择">
<LayoutGrid size={13} />
商品库
</button>
</button> */}
<span className="spacer" />
<div className="tb-search-wrap">
{searchOpen && (
@@ -1402,9 +1409,8 @@ export function ImageWorkbenchPage({
<span className="ph-frame">{item.name.slice(0, 4)}</span>
</div>
)}
<div className="m-name">{item.name}</div>
</div>
<div className="m-name">{item.name}</div>
<div className="m-tag">// 真人模特</div>
</button>
);
})
@@ -1423,9 +1429,8 @@ export function ImageWorkbenchPage({
</span>
<div className="placeholder m-thumb">
<span className="ph-frame">{item.name}</span>
<div className="m-name">{item.name}</div>
</div>
<div className="m-name">{item.name}</div>
<div className="m-tag">{item.tag}</div>
</button>
))}
</div>
@@ -1481,13 +1486,52 @@ export function ImageWorkbenchPage({
<button
type="button"
key={value}
className={`opt ${ratio === value ? "active" : ""}`}
onClick={() => setRatio(value)}
className={`opt ${!ratioManual && ratio === value ? "active" : ""}`}
onClick={() => { setRatioManual(false); setRatio(value); }}
>
{value}
</button>
))}
<button
type="button"
className={`opt ${ratioManual ? "active" : ""}`}
onClick={() => {
setRatioManual(true);
if (ratioW && ratioH) setRatio(`${ratioW}:${ratioH}`);
}}
>
手动输入
</button>
</div>
{ratioManual && (
<div className="iw-ratio-manual">
<input
className="input"
type="number"
min={1}
placeholder="宽"
value={ratioW}
onChange={(event) => {
const w = event.target.value;
setRatioW(w);
if (w && ratioH) setRatio(`${w}:${ratioH}`);
}}
/>
<span className="sep">:</span>
<input
className="input"
type="number"
min={1}
placeholder="高"
value={ratioH}
onChange={(event) => {
const h = event.target.value;
setRatioH(h);
if (ratioW && h) setRatio(`${ratioW}:${h}`);
}}
/>
</div>
)}
</div>
)}
<div className="iw-sub">