feat: 解耦角色与模特并完善模特库

This commit is contained in:
hh
2026-07-13 11:02:30 +08:00
parent 564215dc53
commit fc0f0ca370
22 changed files with 1328 additions and 314 deletions
+13 -14
View File
@@ -29,7 +29,7 @@ import {
} from "lucide-react";
import type { AITask, Asset, ImageConversation, ImageConversationTask, ModelConfig, ModelEntity, Product, WorkbenchTask } from "../types";
import { api } from "../api";
import { ActorLibrary } from "../components/actor-library";
import { ModelLibrary } from "../components/model-library";
import { SkeletonRows } from "../components/loading";
import { ConfirmModal, MediaLightbox } from "../components/overlays";
import { Pager } from "../components/pager";
@@ -664,8 +664,8 @@ export function ImageWorkbenchPage({
const [count, setCount] = useState(mode === "image" ? "4" : "4");
// 模特单选(长度恒 0/1);平台改回多选(P0③:可选多个平台,各出一组结果)
const [pickedIds, setPickedIds] = useState<string[]>([]);
// 模特库全屏弹窗(P0①:复用现成 ActorLibrary,mode=replace 选一个回填)
const [actorLibOpen, setActorLibOpen] = useState(false);
// 模特库全屏弹窗:选一个模特回填。
const [modelLibOpen, setModelLibOpen] = useState(false);
// 选中模特的展示名(从模特库/内联卡记下,导航头/CTA 显示用)
const [pickedModelName, setPickedModelName] = useState("");
// 商品库全屏选择器(P0④:pl-modal 多选 → 每个选中商品各起一批)
@@ -749,7 +749,7 @@ export function ImageWorkbenchPage({
/* 模特卡数据来源:期2 改为「模特库」(顶级实体,引用其形象图当上身图参考)。
映射 ModelEntity→Asset 形:id=形象图资产 id(选中即作 model_id 参考图),metadata 记 model_entity_id 溯源。
无模特则回退到基线占位卡 Ava/Luna/Mia/Zoe。深埋 ActorLibrary 弹窗同源,逻辑不动。 */
无模特则回退到基线占位卡 Ava/Luna/Mia/Zoe。深埋 ModelLibrary 弹窗同源。 */
const [personAssets, setPersonAssets] = useState<Asset[]>([]);
const loadModels = useCallback(() => {
api.listModels({ pageSize: 200 })
@@ -1854,7 +1854,7 @@ export function ImageWorkbenchPage({
const name = selected?.name || pickedModelName;
if (pickedIds[0]) {
return (
<button type="button" className="model-card selected" title="点击打开模特库更换模特" onClick={() => setActorLibOpen(true)}>
<button type="button" className="model-card selected" title="点击打开模特库更换模特" onClick={() => setModelLibOpen(true)}>
<div className="m-thumb">
{url ? (
<img className="m-thumb-img" src={url} alt={name} loading="lazy" />
@@ -1869,7 +1869,7 @@ export function ImageWorkbenchPage({
);
}
return (
<button type="button" className="model-card model-card-empty" title="从模特库选择模特" onClick={() => setActorLibOpen(true)}>
<button type="button" className="model-card model-card-empty" title="从模特库选择模特" onClick={() => setModelLibOpen(true)}>
<div className="m-thumb m-thumb-empty">
<Plus size={26} />
<span className="m-empty-label">从模特库选择</span>
@@ -2046,17 +2046,16 @@ export function ImageWorkbenchPage({
</section>
</div>
{/* P0①:模特库全屏弹窗(复用现成 ActorLibrary,mode=replace 选一个回填到「选择模特」) */}
<ActorLibrary
open={actorLibOpen}
{/* 模特库全屏弹窗:只选 Model,新增模特后刷新并回填。 */}
<ModelLibrary
open={modelLibOpen}
mode="replace"
assets={personAssets}
onClose={() => setActorLibOpen(false)}
onClose={() => setModelLibOpen(false)}
onPick={(assetId, assetName) => {
setPickedIds([assetId]);
setPickedModelName(assetName || "");
loadModels();
setActorLibOpen(false);
setModelLibOpen(false);
}}
onGenerate={(p) => onGenerate({ prompt: p, mode: "model", count: 1 })}
onUpload={(file) => {
@@ -2064,7 +2063,7 @@ export function ImageWorkbenchPage({
fd.append("file", file);
fd.append("name", file.name);
fd.append("asset_type", "image");
fd.append("category", "person");
fd.append("category", "model_portrait");
return api.uploadAsset(fd).catch(() => null);
}}
onRename={(assetId, name) => api.updateAsset(assetId, { name }).catch(() => null)}
@@ -2218,7 +2217,7 @@ function ProductLibraryModal({
onConfirm: () => void;
navigate?: (page: Page) => void;
}) {
// Esc 关闭 + body 滚动锁(与 ActorLibrary 一致体感)
// Esc 关闭 + body 滚动锁(与 ModelLibrary 一致体感)
useEffect(() => {
const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); };
window.addEventListener("keydown", onKey);