fix(ai): 平台套图改走商品主图参考 image_edit · 模特上身图锁商品+模特双图
- 平台套图(cover)以前纯文生图,不参考真实商品主图 → 出图与商品对不上; 现以商品主图为参考图1走 image_edit,锁包装一致性,有模特则参考图2=模特 - 新增 build_platform_cover_prompt_refs;model_url 解析对 cover 模式同样生效 - 补回归测试:cover 传商品主图、model 传商品主图+模特图、无主图回落文生图 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -732,8 +732,9 @@
|
||||
/* ════════════════════════════════════════════════
|
||||
行31/32/33 · 批次卡 + 气泡菜单
|
||||
════════════════════════════════════════════════ */
|
||||
/* 批次卡:复用 .gen-card,批次间拉开间距 */
|
||||
.image-workbench .gen-batch-card + .gen-batch-card { margin-top: 18px; }
|
||||
/* 批次卡:复用 .gen-card。每批各带一个导航头(.iw-pv-h),批次间靠「卡片→下一个头」拉开间距;
|
||||
首个头紧跟 iw-preview 顶 padding,无需额外上间距(故只给跟在卡片后的头加 margin)。 */
|
||||
.image-workbench .gen-batch-card + .iw-pv-h { margin-top: 22px; }
|
||||
/* 批次头(数量 + 状态) */
|
||||
.image-workbench .gen-batch-h {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
|
||||
@@ -446,7 +446,7 @@ export const api = {
|
||||
return request<Paginated<AITask>>("/api/ai/tasks/");
|
||||
},
|
||||
// 异步生图:提交后秒级返回任务列表(慢出图在 worker 跑),再用 generateImageStatus 轮询取结果
|
||||
submitGenerateImage(payload: { prompt: string; mode?: "image" | "model" | "cover"; count?: number; product_id?: string; reference_product?: boolean }) {
|
||||
submitGenerateImage(payload: { prompt: string; mode?: "image" | "model" | "cover"; count?: number; product_id?: string; reference_product?: boolean; model_id?: string; ratio?: string }) {
|
||||
return request<{ tasks: { id: string; status: string }[] }>("/api/ai/generate-image/", { method: "POST", body: JSON.stringify(payload) });
|
||||
},
|
||||
generateImageStatus(ids: string[]) {
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
align-items: stretch;
|
||||
}
|
||||
.pd-overview .ov-card { height: 100%; box-sizing: border-box; }
|
||||
/* 编辑商品信息时左栏变高:右侧快速操作不跟随拉伸,而是锁定为进编辑前(查看态)的高度,
|
||||
整体大小保持不变。高度值由组件用 ref 实测后内联到 .ov-actions(见 products.tsx)。 */
|
||||
.pd-overview:has(.ov-main.editing) { align-items: start; }
|
||||
.pd-overview .ov-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-faint);
|
||||
|
||||
@@ -779,22 +779,6 @@ export function ImageWorkbenchPage({
|
||||
|
||||
const hasResults = batches.length > 0;
|
||||
|
||||
/* 按商品分组(行34):各商品各自一个导航头,避免多商品批次被并到同一个头里。
|
||||
保持首次出现顺序;productId 缺失的旧批次归到当前商品组。 */
|
||||
const batchGroups = (() => {
|
||||
const order: string[] = [];
|
||||
const map = new Map<string, { key: string; title: string; items: GenBatch[] }>();
|
||||
for (const b of batches) {
|
||||
const key = b.productId || product?.id || "—";
|
||||
if (!map.has(key)) {
|
||||
order.push(key);
|
||||
map.set(key, { key, title: b.productTitle || product?.title || "未选择", items: [] });
|
||||
}
|
||||
map.get(key)!.items.push(b);
|
||||
}
|
||||
return order.map((k) => map.get(k)!);
|
||||
})();
|
||||
|
||||
/* ── 单个批次的结果网格(行30:生成中转圈 icon;行32:单图 hover「更多」气泡菜单)── */
|
||||
function renderBatchGrid(batch: GenBatch) {
|
||||
const generating = batch.status === "generating";
|
||||
@@ -854,7 +838,15 @@ export function ImageWorkbenchPage({
|
||||
);
|
||||
}
|
||||
|
||||
/* ── 批次列表(行31/32/33):每个批次一行卡片,底部一组操作 + 底部「更多」气泡 ── */
|
||||
/* 批次所属商品名:优先生成时记下的 productTitle;旧批次缺失则用 productId 或成图的 asset.product
|
||||
(后端已解析归属商品)反查 products——绝不回退到「当前选中商品」(那正是会显示错名的根因)。 */
|
||||
function batchProductTitle(batch: GenBatch): string {
|
||||
if (batch.productTitle) return batch.productTitle;
|
||||
const pid = batch.productId || batch.results.find((a) => a.product)?.product || "";
|
||||
return products.find((p) => p.id === pid)?.title || "未选择";
|
||||
}
|
||||
|
||||
/* ── 批次列表(行31/32/33):每批各一个商品导航头 + 一张卡片 + 底部操作/气泡 ── */
|
||||
function renderBatchCards(list: GenBatch[]) {
|
||||
return (
|
||||
<>
|
||||
@@ -862,7 +854,32 @@ export function ImageWorkbenchPage({
|
||||
const generating = batch.status === "generating";
|
||||
const failed = batch.status === "failed";
|
||||
return (
|
||||
<div className="gen-card gen-batch-card" key={batch.id}>
|
||||
<Fragment key={batch.id}>
|
||||
{/* 行34:每批一个独立导航头——显示该批所属商品(多商品/多批不再合并到一个头) */}
|
||||
<div className="iw-pv-h">
|
||||
<Quote className="quote-icon" />
|
||||
<div className="pv-meta">
|
||||
<b>1 批</b>
|
||||
{mode === "model" ? ` · ${batch.ratio}` : ""}
|
||||
</div>
|
||||
<div className="pv-line">
|
||||
<span className="k">商品</span>
|
||||
<span className="v">{batchProductTitle(batch)}</span>
|
||||
</div>
|
||||
{mode === "cover" && (
|
||||
<div className="pv-line">
|
||||
<span className="k">平台</span>
|
||||
<span className="v">
|
||||
{pickedIds.length
|
||||
? PLATFORM_OPTIONS.filter((p) => pickedIds.includes(p.id))
|
||||
.map((p) => p.name)
|
||||
.join("、")
|
||||
: "未选择"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="gen-card gen-batch-card">
|
||||
<div className="gen-batch-h">
|
||||
<span className="b-pic">{batch.count}×</span>
|
||||
<div className="b-meta">
|
||||
@@ -902,6 +919,7 @@ export function ImageWorkbenchPage({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
@@ -1343,35 +1361,8 @@ export function ImageWorkbenchPage({
|
||||
) : (
|
||||
<>
|
||||
<MediaLightbox open={!!preview} src={preview?.src || ""} kind="image" name={preview?.name} close={() => setPreview(null)} />
|
||||
{/* 行34:按商品分组——每个商品一个独立导航头 + 自己的批次列表 */}
|
||||
{batchGroups.map((group) => (
|
||||
<Fragment key={group.key}>
|
||||
<div className="iw-pv-h">
|
||||
<Quote className="quote-icon" />
|
||||
<div className="pv-meta">
|
||||
<b>{group.items.length} 批</b>
|
||||
{mode === "model" ? ` · ${ratio}` : ""}
|
||||
</div>
|
||||
<div className="pv-line">
|
||||
<span className="k">商品</span>
|
||||
<span className="v">{group.title}</span>
|
||||
</div>
|
||||
{mode === "cover" && (
|
||||
<div className="pv-line">
|
||||
<span className="k">平台</span>
|
||||
<span className="v">
|
||||
{pickedIds.length
|
||||
? PLATFORM_OPTIONS.filter((p) => pickedIds.includes(p.id))
|
||||
.map((p) => p.name)
|
||||
.join("、")
|
||||
: "未选择"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{renderBatchCards(group.items)}
|
||||
</Fragment>
|
||||
))}
|
||||
{/* 行34:每批各自一个导航头(含所属商品)——见 renderBatchCards */}
|
||||
{renderBatchCards(batches)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1330,42 +1330,8 @@ nav button svg { width: 14px; height: 14px; color: var(--ink-3); }
|
||||
.modal-x { margin-left: auto; width: 28px; height: 28px; display: grid; place-items: center; border-radius: 7px; }
|
||||
.modal-x:hover { background: var(--bg-soft); }
|
||||
|
||||
.msg-workbench { display: grid; grid-template-columns: 380px minmax(0, 1fr); gap: 18px; min-height: 640px; }
|
||||
.msg-panel { background: var(--card); border: 1px solid var(--border); min-width: 0; }
|
||||
.msg-panel-h { display: flex; justify-content: space-between; align-items: center; padding: 14px 16px; border-bottom: 1px solid var(--border); }
|
||||
.msg-panel-h .ti { font-weight: 600; }
|
||||
.msg-filters { display: flex; gap: 6px; padding: 12px; flex-wrap: wrap; }
|
||||
.msg-filter { border: 1px solid var(--border); background: var(--card); padding: 5px 9px; font-size: 12px; color: var(--ink-2); }
|
||||
.msg-filter.active { background: var(--orange-tint); border-color: var(--orange-soft); color: var(--orange); font-weight: 600; }
|
||||
.msg-filter .ct { margin-left: 6px; color: var(--ink-3); font-family: 'JetBrains Mono', monospace; }
|
||||
.msg-search { display: flex; align-items: center; gap: 8px; margin: 0 12px 12px; border: 1px solid var(--border); background: var(--bg-soft); padding: 8px 10px; color: var(--ink-3); }
|
||||
.msg-search input { border: 0; background: transparent; flex: 1; }
|
||||
.msg-list { border-top: 1px solid var(--border); }
|
||||
.msg-item { width: 100%; display: grid; grid-template-columns: 28px 1fr; gap: 10px; padding: 12px; border-bottom: 1px solid var(--border); text-align: left; background: var(--card); }
|
||||
.msg-item:hover, .msg-item.active { background: var(--orange-tint); }
|
||||
.msg-type-ic { width: 28px; height: 28px; border: 1px solid var(--border); background: var(--bg-soft); display: grid; place-items: center; color: var(--orange); }
|
||||
.msg-item-row, .msg-item-foot { display: flex; align-items: center; gap: 8px; }
|
||||
.msg-dot { width: 6px; height: 6px; border-radius: 999px; background: var(--orange); }
|
||||
.msg-item-title { font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.msg-time, .msg-priority { color: var(--ink-3); font-family: 'JetBrains Mono', monospace; font-size: 12px; }
|
||||
.msg-brief { display: block; color: var(--ink-2); font-size: 12px; line-height: 1.5; margin: 4px 0 7px; }
|
||||
.msg-detail { display: flex; flex-direction: column; }
|
||||
.msg-detail-body { padding: 20px; flex: 1; }
|
||||
.msg-detail-top { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 16px; }
|
||||
.msg-detail-title h2 { font-size: 18px; line-height: 1.35; }
|
||||
.msg-detail-title .meta { color: var(--ink-3); font-family: 'JetBrains Mono', monospace; font-size: 12px; margin-top: 4px; display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.msg-body-text { color: var(--ink-2); line-height: 1.7; margin-bottom: 16px; }
|
||||
.msg-props { display: grid; grid-template-columns: 90px 1fr; gap: 0; border: 1px solid var(--border); margin-bottom: 16px; }
|
||||
.msg-props .k, .msg-props .v { padding: 9px 12px; border-bottom: 1px solid var(--border); font-size: 12px; }
|
||||
.msg-props .k { background: var(--bg-soft); color: var(--ink-3); font-family: 'JetBrains Mono', monospace; }
|
||||
.msg-timeline { border: 1px solid var(--border); }
|
||||
.msg-timeline-h { padding: 10px 12px; border-bottom: 1px solid var(--border); font-weight: 600; }
|
||||
.msg-step { display: grid; grid-template-columns: 70px 1fr; gap: 10px; padding: 8px 12px; border-bottom: 1px solid var(--border); }
|
||||
.msg-step:last-child { border-bottom: 0; }
|
||||
.msg-step .t { color: var(--orange); font-family: 'JetBrains Mono', monospace; font-size: 12px; }
|
||||
.msg-detail-f { display: flex; gap: 8px; align-items: center; padding: 14px 16px; border-top: 1px solid var(--border); background: var(--bg-soft); }
|
||||
.msg-foot-note { display: flex; justify-content: space-between; margin-top: 12px; color: var(--ink-3); font-family: 'JetBrains Mono', monospace; font-size: 12px; }
|
||||
.msg-foot-note button { color: var(--orange); }
|
||||
/* 消息中心样式统一由 messages-page.css(对齐 design 稿)提供;此处旧版 V1 .msg-* 规则
|
||||
用的是 --ink/--orange/--bg-soft 旧 token,会漏样式(灰底标签、搜索框描边、记录分隔线),已移除。 */
|
||||
|
||||
.factory-hero { display: grid; gap: 18px; margin-bottom: 26px; }
|
||||
.factory-card { background: var(--card); border: 1px solid var(--border); position: relative; padding: 24px; }
|
||||
@@ -1429,7 +1395,7 @@ nav button svg { width: 14px; height: 14px; color: var(--ink-3); }
|
||||
.switch input:checked + .slider::before { transform: translateX(18px); }
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.wizard, .create-product-layout, .product-detail-layout, .top-grid, .team-top, .msg-workbench, .factory-body, .tool-layout, .demo-layout, .settings-layout, .overview-grid {
|
||||
.wizard, .create-product-layout, .product-detail-layout, .top-grid, .team-top, .factory-body, .tool-layout, .demo-layout, .settings-layout, .overview-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.wiz-preview, .steps, .create-upload-zone, .settings-side { position: static; }
|
||||
|
||||
Reference in New Issue
Block a user