fix(core): 商品详情页三视图改走 image_edit 参考真实主图 + 该商品 AI 素材归属
- 商品详情页「AI 生成三视图」原走独立生图老链路(纯文生图),只凭商品名脑补, 出来通用图不像真品。改:前端传 reference_product;后端 run_standalone_image_task 据此取商品主图走 image_edit(复用 build_product_triview_prompt_refs 锁包装), 取不到主图优雅回落文生图。图片创作页(自由创作)不带标记,不受影响。 - 商品详情页素材区改为只显示「该商品」AI 素材(asset.product 归属)而非全团队。 - 首登水合带重试,失败不再静默(否则页面卡在全 0,要刷新才好)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -485,7 +485,7 @@ export function ImageWorkbenchPage({
|
||||
modelConfigs: ModelConfig[];
|
||||
onBack: () => void;
|
||||
navigate?: (page: Page) => void;
|
||||
onGenerate: (payload: { prompt: string; mode?: "image" | "model" | "cover"; count?: number }) => Promise<{ assets: Asset[] } | null>;
|
||||
onGenerate: (payload: { prompt: string; mode?: "image" | "model" | "cover"; count?: number; product_id?: string }) => Promise<{ assets: Asset[] } | null>;
|
||||
onResume?: (mode: "image" | "model" | "cover", ids: string[]) => Promise<{ assets: Asset[] } | null>;
|
||||
/** 行19:从商品页带入的初始商品 id(可选);未传则回退到第一个商品 */
|
||||
initialProductId?: string;
|
||||
@@ -574,7 +574,7 @@ export function ImageWorkbenchPage({
|
||||
// 追加新批次(行32②:不覆盖,在下面新增一行)
|
||||
setBatches((prev) => [...prev, newBatch]);
|
||||
try {
|
||||
const result = await onGenerate({ prompt: prompt.trim(), mode, count: candidateCount });
|
||||
const result = await onGenerate({ prompt: prompt.trim(), mode, count: candidateCount, product_id: product?.id });
|
||||
setBatches((prev) => {
|
||||
const next = prev.map((b) =>
|
||||
b.id === batchId
|
||||
@@ -608,7 +608,7 @@ export function ImageWorkbenchPage({
|
||||
};
|
||||
setBatches((prev) => [...prev, newBatch]);
|
||||
try {
|
||||
const result = await onGenerate({ prompt: src.prompt, mode, count: src.count });
|
||||
const result = await onGenerate({ prompt: src.prompt, mode, count: src.count, product_id: product?.id });
|
||||
setBatches((prev) => {
|
||||
const next = prev.map((b) =>
|
||||
b.id === batchId
|
||||
|
||||
@@ -668,7 +668,7 @@ export function ProductDetailPage({ product, projects, assets, initialTab = "ass
|
||||
onUpdate: (payload: Partial<Product>) => Promise<unknown> | void;
|
||||
onUploadImage?: (formData: FormData) => Promise<unknown> | void;
|
||||
onDeleteImage?: (imageId: string) => Promise<unknown> | void;
|
||||
onGenerateImages?: (payload: { prompt: string; mode?: "image" | "model" | "cover"; count?: number }) => Promise<{ assets: Asset[] } | null>;
|
||||
onGenerateImages?: (payload: { prompt: string; mode?: "image" | "model" | "cover"; count?: number; product_id?: string; reference_product?: boolean }) => Promise<{ assets: Asset[] } | null>;
|
||||
// 采用三视图版本:把该版本的 asset 作为商品图持久化进 AI 素材库(由 App.tsx 接线;不传则仅本地标记)
|
||||
onAdoptTriView?: (asset: Asset) => Promise<unknown> | void;
|
||||
}) {
|
||||
@@ -727,7 +727,10 @@ export function ProductDetailPage({ product, projects, assets, initialTab = "ass
|
||||
const res = await onGenerateImages({
|
||||
prompt: `${product.title || "商品"} 白底商品三视图,正面 / 侧面 / 背面三视图并排于同一张 16:9 横图,电商主图,干净白底,高清`,
|
||||
mode: "image",
|
||||
count: 1
|
||||
count: 1,
|
||||
product_id: product.id,
|
||||
// 锁真实商品主图:后端据此走 image_edit 以主图为参考,而非纯文生图脑补
|
||||
reference_product: true
|
||||
});
|
||||
const asset = res?.assets?.[0];
|
||||
const url = asset?.files?.[0]?.preview_url;
|
||||
@@ -756,10 +759,16 @@ export function ProductDetailPage({ product, projects, assets, initialTab = "ass
|
||||
const productImages = imageIds
|
||||
.map((id) => ({ id, url: pdAssetPreview(assetById.get(id)) }));
|
||||
|
||||
// AI 生成素材 · 团队资产中筛与该商品相关的类别(模特/场景/三视图/商品图/背景),取真图;无则回退到全部图片资产
|
||||
// AI 生成素材 · 只取「该商品」的 AI 素材,而非全团队资产。归属由后端解析的 asset.product 给出
|
||||
// (独立生图记 metadata.product_id;项目内生成的图回溯 origin_task→project→product,历史图也能归位);
|
||||
// 再纳入该商品上传的商品图(product.images 的 asset)。
|
||||
const AI_CATS = new Set(["product_image", "person", "scene", "tri_view", "background"]);
|
||||
const aiSource = assets.filter((asset) => AI_CATS.has(asset.category) || AI_CATS.has(asset.asset_type));
|
||||
const allImageAssets = aiSource.length ? aiSource : assets.filter((asset) => asset.asset_type === "image");
|
||||
const productImageIds = new Set(imageIds);
|
||||
const belongsToProduct = (asset: Asset) =>
|
||||
asset.product === product.id || productImageIds.has(asset.id);
|
||||
const allImageAssets = assets.filter(
|
||||
(asset) => belongsToProduct(asset) && (AI_CATS.has(asset.category) || AI_CATS.has(asset.asset_type) || asset.asset_type === "image")
|
||||
);
|
||||
// 类型筛选选项(当前素材里真实存在的 category)
|
||||
const typeOptions = Array.from(new Set(allImageAssets.map((a) => a.category).filter(Boolean)));
|
||||
const filteredAssets = allImageAssets
|
||||
@@ -1008,7 +1017,7 @@ export function ProductDetailPage({ product, projects, assets, initialTab = "ass
|
||||
<div className={`tab-pane${tab === "assets" ? " active" : ""}`} data-pane="assets">
|
||||
|
||||
<div className="pd-toolbar">
|
||||
<div className="total">全部 AI 素材 <span className="ct">({assetCount})</span></div>
|
||||
<div className="total">该商品 AI 素材 <span className="ct">({assetCount})</span></div>
|
||||
<div className={`chip-wrap${openFilter === "type" ? " open" : ""}`} style={{ display: "inline-flex" }} data-key="type">
|
||||
<button className="filter" type="button" onClick={() => setOpenFilter((f) => (f === "type" ? "" : "type"))}>
|
||||
{typeFilter ? (pdAssetTypeLabel({ category: typeFilter, asset_type: "" } as Asset) || typeFilter) : "全部类型"}
|
||||
|
||||
Reference in New Issue
Block a user