feat(images): 期2 图片趴三类 — 模特上身图/平台套图/自由创作 归类+成组+接模特库
后端: - 独立生图三类归类:model+product→model_tryon(模特上身图,引用模特库,不送审) / cover→platform_kit(平台套图) / image→free_create(自由创作);生成演员(model 无 product)仍→person(视频角色,留期3) - 成组:每次提交一个 batch_id 串起整批,落 asset.metadata;另记 mode + model_entity_id(上身图溯源模特库) - generate-image 端点透传 model_entity_id;资产库 _tab_q+summary 加 tryon/kits/creations 三类桶 - 单测 StandaloneCategoryTests 直跑 worker 验四态归类全绿(绕开异步 .delay) 前端: - 资产库 library.tsx 加三类 tab(模特上身图/平台套图/自由创作)+ 计数 + 筛选维度 - ai-tools 模特选择器数据源 person→模特库(listModels 映射,选中传形象图当参考 = 引用模特库),ActorLibrary 同源 - api.ts submitGenerateImage 加 model_entity_id 验收:tsc+build 全绿;无头 0 console error(资产库三类 tab 各归类正确、模特选择器 5 张取自模特库) 基线既有 3 失败(StandaloneImageReferenceTests 异步漂移)零新增 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
44e90233ff
commit
c8f3f91d38
@@ -1,4 +1,4 @@
|
||||
import { Fragment, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import type { ChangeEvent } from "react";
|
||||
import {
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
WandSparkles,
|
||||
X
|
||||
} from "lucide-react";
|
||||
import type { AITask, Asset, ModelConfig, Product } from "../types";
|
||||
import type { AITask, Asset, ModelConfig, ModelEntity, Product } from "../types";
|
||||
import { api } from "../api";
|
||||
import { ActorLibrary } from "../components/actor-library";
|
||||
import { SkeletonRows } from "../components/loading";
|
||||
@@ -580,16 +580,34 @@ export function ImageWorkbenchPage({
|
||||
|
||||
const imageModels = modelConfigs.filter((model) => model.capability.includes("image"));
|
||||
|
||||
/* 模特卡数据来源:服务端按 category=person 懒加载(不再吃全局 assets 全量数组);
|
||||
无则回退到基线占位模特卡 Ava/Luna/Mia/Zoe */
|
||||
/* 模特卡数据来源:期2 改为「模特库」(顶级实体,引用其形象图当上身图参考)。
|
||||
映射 ModelEntity→Asset 形:id=形象图资产 id(选中即作 model_id 参考图),metadata 记 model_entity_id 溯源。
|
||||
无模特则回退到基线占位卡 Ava/Luna/Mia/Zoe。深埋 ActorLibrary 弹窗同源,逻辑不动。 */
|
||||
const [personAssets, setPersonAssets] = useState<Asset[]>([]);
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
api.assetsPage({ category: "person", pageSize: 200 })
|
||||
.then((res) => { if (alive) setPersonAssets(res.results); })
|
||||
.catch(() => { if (alive) setPersonAssets([]); });
|
||||
return () => { alive = false; };
|
||||
const loadModels = useCallback(() => {
|
||||
api.listModels({ pageSize: 200 })
|
||||
.then((res) => {
|
||||
const mapped: Asset[] = res.results
|
||||
.filter((m: ModelEntity) => m.portrait_asset)
|
||||
.map((m: ModelEntity) => ({
|
||||
id: m.portrait_asset as string,
|
||||
name: m.name,
|
||||
asset_type: "image",
|
||||
source: m.source === "upload" ? "upload" : "ai_generated",
|
||||
category: "model_portrait",
|
||||
description: m.description || "",
|
||||
metadata: { model_entity_id: m.id },
|
||||
files: m.portrait
|
||||
? [{ id: m.portrait_asset as string, object_key: "", bucket: "", content_type: "image/png", size_bytes: 0, preview_url: m.portrait, is_primary: true }]
|
||||
: [],
|
||||
created_at: m.created_at,
|
||||
updated_at: m.updated_at,
|
||||
}));
|
||||
setPersonAssets(mapped);
|
||||
})
|
||||
.catch(() => setPersonAssets([]));
|
||||
}, []);
|
||||
useEffect(() => { loadModels(); }, [loadModels]);
|
||||
|
||||
useEffect(() => {
|
||||
if (product) setPrompt(meta.promptTemplate(product.title));
|
||||
@@ -1524,7 +1542,7 @@ export function ImageWorkbenchPage({
|
||||
onPick={(assetId, assetName) => {
|
||||
setPickedIds([assetId]);
|
||||
setPickedModelName(assetName || "");
|
||||
void api.assetsPage({ category: "person", pageSize: 200 }).then((res) => setPersonAssets(res.results)).catch(() => {});
|
||||
loadModels();
|
||||
setActorLibOpen(false);
|
||||
}}
|
||||
onGenerate={(p) => onGenerate({ prompt: p, mode: "model", count: 1 })}
|
||||
@@ -1537,7 +1555,7 @@ export function ImageWorkbenchPage({
|
||||
return api.uploadAsset(fd).catch(() => null);
|
||||
}}
|
||||
onRename={(assetId, name) => api.updateAsset(assetId, { name }).catch(() => null)}
|
||||
onRefresh={() => { void api.assetsPage({ category: "person", pageSize: 200 }).then((res) => setPersonAssets(res.results)).catch(() => {}); }}
|
||||
onRefresh={() => { loadModels(); }}
|
||||
/>
|
||||
|
||||
{/* P0④:商品库全屏选择器(pl-modal · 多选 + 右上勾 + 已选汇总 · restraint token) */}
|
||||
|
||||
Reference in New Issue
Block a user