fix: sync ai detail material slots with workflow
This commit is contained in:
parent
01e124ebb1
commit
f97e96c5f9
@ -859,6 +859,9 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
const generatedImageKeysRef = useRef<Set<string>>(new Set());
|
||||
const lastSyncedBrandModelUrlRef = useRef<string | null>(null);
|
||||
const lastSyncedProductSlotKeysRef = useRef<Array<string | null>>([]);
|
||||
const lastSyncedDetailProductSlotKeysRef = useRef<Array<string | null>>([]);
|
||||
const lastSyncedDetailBundleSlotKeysRef = useRef<Array<string | null>>([]);
|
||||
const lastSyncedDetailModelSlotKeyRef = useRef<string | null>(null);
|
||||
const detailGenerationCancelRef = useRef(false);
|
||||
const workspaceStateSignatureRef = useRef<string>("");
|
||||
const [localAgentModeState, setLocalAgentModeState] = useState<AgentModeWorkspaceState>(
|
||||
@ -1147,6 +1150,30 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
detailReplacementState.slots.length === AGENT_DETAIL_SLOT_CONFIGS.length
|
||||
? detailReplacementState.slots
|
||||
: createEmptyAgentDetailSlots();
|
||||
const detailGenerationRequiredProductCount = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
isDetailReplacementPreset
|
||||
? presetModeConfig.detail.productImageSlotCount
|
||||
: productSlotCount,
|
||||
AGENT_DETAIL_PRODUCT_SLOT_INDEXES.length
|
||||
)
|
||||
);
|
||||
const detailGenerationProductSlotIndexes = useMemo(
|
||||
() =>
|
||||
AGENT_DETAIL_PRODUCT_SLOT_INDEXES.slice(0, detailGenerationRequiredProductCount),
|
||||
[detailGenerationRequiredProductCount]
|
||||
);
|
||||
const detailGenerationVisibleSlotIndexes = useMemo(
|
||||
() => [
|
||||
...detailGenerationProductSlotIndexes,
|
||||
...(modelSlotEnabled && AGENT_DETAIL_MODEL_SLOT_INDEX >= 0
|
||||
? [AGENT_DETAIL_MODEL_SLOT_INDEX]
|
||||
: []),
|
||||
...AGENT_DETAIL_BUNDLE_SLOT_INDEXES,
|
||||
],
|
||||
[detailGenerationProductSlotIndexes, modelSlotEnabled]
|
||||
);
|
||||
const detailGenerationProgress =
|
||||
(detailReplacementState.progress || {
|
||||
status: "idle",
|
||||
@ -1156,14 +1183,15 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
startedAt: null,
|
||||
finishedAt: null,
|
||||
}) as AgentDetailGenerationProgressState;
|
||||
const detailGenerationProductCount = AGENT_DETAIL_PRODUCT_SLOT_INDEXES.filter(
|
||||
const detailGenerationProductCount = detailGenerationProductSlotIndexes.filter(
|
||||
(index) => Boolean(detailGenerationSlots[index]?.url)
|
||||
).length;
|
||||
const detailGenerationBundleCount = AGENT_DETAIL_BUNDLE_SLOT_INDEXES.filter(
|
||||
(index) => Boolean(detailGenerationSlots[index]?.url)
|
||||
).length;
|
||||
const detailGenerationModelReady = Boolean(
|
||||
AGENT_DETAIL_MODEL_SLOT_INDEX >= 0 &&
|
||||
modelSlotEnabled &&
|
||||
AGENT_DETAIL_MODEL_SLOT_INDEX >= 0 &&
|
||||
detailGenerationSlots[AGENT_DETAIL_MODEL_SLOT_INDEX]?.url
|
||||
);
|
||||
const detailGenerationValidationIssues = useMemo(() => {
|
||||
@ -1171,10 +1199,12 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
if (!detailGenerationBrief.targetAudience) issues.push("缺少主购买人群");
|
||||
if (!detailGenerationBrief.usageScenario) issues.push("缺少需求场景");
|
||||
if (!detailGenerationBrief.coreNeed) issues.push("缺少核心需求");
|
||||
if (detailGenerationProductCount < 5) {
|
||||
issues.push(`产品图不足(${detailGenerationProductCount}/5)`);
|
||||
if (detailGenerationProductCount < detailGenerationRequiredProductCount) {
|
||||
issues.push(
|
||||
`产品图不足(${detailGenerationProductCount}/${detailGenerationRequiredProductCount})`
|
||||
);
|
||||
}
|
||||
if (!detailGenerationModelReady) {
|
||||
if (modelSlotEnabled && !detailGenerationModelReady) {
|
||||
issues.push("缺少模特图(1/1)");
|
||||
}
|
||||
return issues;
|
||||
@ -1184,6 +1214,8 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
detailGenerationBrief.usageScenario,
|
||||
detailGenerationModelReady,
|
||||
detailGenerationProductCount,
|
||||
detailGenerationRequiredProductCount,
|
||||
modelSlotEnabled,
|
||||
]);
|
||||
const skuReplacementState = resolvedAgentModeState.skuReplacement;
|
||||
const skuReplacementQueryInput = skuReplacementState.query;
|
||||
@ -2545,12 +2577,32 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!isDetailReplacementPreset) {
|
||||
lastSyncedDetailProductSlotKeysRef.current = [];
|
||||
lastSyncedDetailBundleSlotKeysRef.current = [];
|
||||
lastSyncedDetailModelSlotKeyRef.current = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const secondaryReferenceImages = referenceImages
|
||||
.filter((image) => image.source === "secondary")
|
||||
.slice(0, AGENT_DETAIL_BUNDLE_SLOT_INDEXES.length);
|
||||
const nextDetailProductSourceKeys = detailGenerationProductSlotIndexes.map((_, index) =>
|
||||
getAttachmentIdentityKey(selectedProductAttachments[index])
|
||||
);
|
||||
const nextDetailBundleSourceKeys = AGENT_DETAIL_BUNDLE_SLOT_INDEXES.map((_, index) =>
|
||||
getAttachmentIdentityKey(
|
||||
secondaryReferenceImages[index]
|
||||
? {
|
||||
url: secondaryReferenceImages[index]?.url,
|
||||
assetId: secondaryReferenceImages[index]?.assetId || null,
|
||||
}
|
||||
: null
|
||||
)
|
||||
);
|
||||
const nextDetailModelSourceKey =
|
||||
modelSlotEnabled && AGENT_DETAIL_MODEL_SLOT_INDEX >= 0
|
||||
? getAttachmentIdentityKey(selectedModelAttachment)
|
||||
: null;
|
||||
|
||||
updateDetailReplacementState((current) => {
|
||||
let changed = false;
|
||||
@ -2572,23 +2624,41 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
{ length: AGENT_DETAIL_SLOT_CONFIGS.length },
|
||||
(_, index) => current.slots?.[index] || null
|
||||
);
|
||||
AGENT_DETAIL_PRODUCT_SLOT_INDEXES.forEach((slotIndex, index) => {
|
||||
detailGenerationProductSlotIndexes.forEach((slotIndex, index) => {
|
||||
const attachment = selectedProductAttachments[index];
|
||||
if (!attachment || nextSlots[slotIndex]) return;
|
||||
nextSlots[slotIndex] = {
|
||||
url: attachment.url,
|
||||
name: attachment.name || AGENT_DETAIL_SLOT_CONFIGS[slotIndex]?.label || null,
|
||||
contentType: attachment.contentType || "image/jpeg",
|
||||
assetId: attachment.assetId || null,
|
||||
};
|
||||
changed = true;
|
||||
const currentSlot = nextSlots[slotIndex];
|
||||
const currentSlotKey = getAttachmentIdentityKey(currentSlot);
|
||||
const previousSyncedKey = lastSyncedDetailProductSlotKeysRef.current[index] || null;
|
||||
const currentIsPreviousAutoSelection =
|
||||
Boolean(previousSyncedKey) && currentSlotKey === previousSyncedKey;
|
||||
|
||||
if (attachment) {
|
||||
const nextAttachment = {
|
||||
url: attachment.url,
|
||||
name: attachment.name || AGENT_DETAIL_SLOT_CONFIGS[slotIndex]?.label || null,
|
||||
contentType: attachment.contentType || "image/jpeg",
|
||||
assetId: attachment.assetId || null,
|
||||
};
|
||||
if (!currentSlot || currentIsPreviousAutoSelection) {
|
||||
nextSlots[slotIndex] = nextAttachment;
|
||||
changed = true;
|
||||
}
|
||||
} else if (currentIsPreviousAutoSelection) {
|
||||
nextSlots[slotIndex] = null;
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (
|
||||
modelSlotEnabled &&
|
||||
AGENT_DETAIL_MODEL_SLOT_INDEX >= 0 &&
|
||||
selectedModelAttachment &&
|
||||
!nextSlots[AGENT_DETAIL_MODEL_SLOT_INDEX]
|
||||
selectedModelAttachment
|
||||
) {
|
||||
nextSlots[AGENT_DETAIL_MODEL_SLOT_INDEX] = {
|
||||
const currentSlot = nextSlots[AGENT_DETAIL_MODEL_SLOT_INDEX];
|
||||
const currentSlotKey = getAttachmentIdentityKey(currentSlot);
|
||||
const previousSyncedKey = lastSyncedDetailModelSlotKeyRef.current;
|
||||
const currentIsPreviousAutoSelection =
|
||||
Boolean(previousSyncedKey) && currentSlotKey === previousSyncedKey;
|
||||
const nextAttachment = {
|
||||
url: selectedModelAttachment.url,
|
||||
name:
|
||||
selectedModelAttachment.name ||
|
||||
@ -2597,28 +2667,61 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
contentType: selectedModelAttachment.contentType || "image/jpeg",
|
||||
assetId: selectedModelAttachment.assetId || null,
|
||||
};
|
||||
changed = true;
|
||||
if (!currentSlot || currentIsPreviousAutoSelection) {
|
||||
nextSlots[AGENT_DETAIL_MODEL_SLOT_INDEX] = nextAttachment;
|
||||
changed = true;
|
||||
}
|
||||
} else if (modelSlotEnabled && AGENT_DETAIL_MODEL_SLOT_INDEX >= 0) {
|
||||
const currentSlot = nextSlots[AGENT_DETAIL_MODEL_SLOT_INDEX];
|
||||
const currentSlotKey = getAttachmentIdentityKey(currentSlot);
|
||||
const previousSyncedKey = lastSyncedDetailModelSlotKeyRef.current;
|
||||
const currentIsPreviousAutoSelection =
|
||||
Boolean(previousSyncedKey) && currentSlotKey === previousSyncedKey;
|
||||
if (currentIsPreviousAutoSelection) {
|
||||
nextSlots[AGENT_DETAIL_MODEL_SLOT_INDEX] = null;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
AGENT_DETAIL_BUNDLE_SLOT_INDEXES.forEach((slotIndex, index) => {
|
||||
const image = secondaryReferenceImages[index];
|
||||
if (!image || nextSlots[slotIndex]) return;
|
||||
nextSlots[slotIndex] = {
|
||||
url: image.url,
|
||||
name:
|
||||
image.name ||
|
||||
image.label ||
|
||||
AGENT_DETAIL_SLOT_CONFIGS[slotIndex]?.label ||
|
||||
null,
|
||||
contentType: image.mimeType || "image/jpeg",
|
||||
assetId: image.assetId || null,
|
||||
};
|
||||
changed = true;
|
||||
const currentSlot = nextSlots[slotIndex];
|
||||
const currentSlotKey = getAttachmentIdentityKey(currentSlot);
|
||||
const previousSyncedKey = lastSyncedDetailBundleSlotKeysRef.current[index] || null;
|
||||
const currentIsPreviousAutoSelection =
|
||||
Boolean(previousSyncedKey) && currentSlotKey === previousSyncedKey;
|
||||
|
||||
if (image) {
|
||||
const nextAttachment = {
|
||||
url: image.url,
|
||||
name:
|
||||
image.name ||
|
||||
image.label ||
|
||||
AGENT_DETAIL_SLOT_CONFIGS[slotIndex]?.label ||
|
||||
null,
|
||||
contentType: image.mimeType || "image/jpeg",
|
||||
assetId: image.assetId || null,
|
||||
};
|
||||
if (!currentSlot || currentIsPreviousAutoSelection) {
|
||||
nextSlots[slotIndex] = nextAttachment;
|
||||
changed = true;
|
||||
}
|
||||
} else if (currentIsPreviousAutoSelection) {
|
||||
nextSlots[slotIndex] = null;
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!changed) {
|
||||
lastSyncedDetailProductSlotKeysRef.current = nextDetailProductSourceKeys;
|
||||
lastSyncedDetailBundleSlotKeysRef.current = nextDetailBundleSourceKeys;
|
||||
lastSyncedDetailModelSlotKeyRef.current = nextDetailModelSourceKey;
|
||||
return current;
|
||||
}
|
||||
|
||||
lastSyncedDetailProductSlotKeysRef.current = nextDetailProductSourceKeys;
|
||||
lastSyncedDetailBundleSlotKeysRef.current = nextDetailBundleSourceKeys;
|
||||
lastSyncedDetailModelSlotKeyRef.current = nextDetailModelSourceKey;
|
||||
|
||||
return {
|
||||
...current,
|
||||
brief: nextBrief,
|
||||
@ -2626,7 +2729,9 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
};
|
||||
});
|
||||
}, [
|
||||
detailGenerationProductSlotIndexes,
|
||||
isDetailReplacementPreset,
|
||||
modelSlotEnabled,
|
||||
referenceImages,
|
||||
selectedModelAttachment,
|
||||
selectedProductAttachments,
|
||||
@ -2734,14 +2839,14 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
return;
|
||||
}
|
||||
|
||||
const productAttachments = AGENT_DETAIL_PRODUCT_SLOT_INDEXES.map((index) =>
|
||||
const productAttachments = detailGenerationProductSlotIndexes.map((index) =>
|
||||
detailGenerationSlots[index]
|
||||
).filter((item): item is NonNullable<(typeof detailGenerationSlots)[number]> => Boolean(item?.url));
|
||||
const bundleAttachments = AGENT_DETAIL_BUNDLE_SLOT_INDEXES.map((index) =>
|
||||
detailGenerationSlots[index]
|
||||
).filter((item): item is NonNullable<(typeof detailGenerationSlots)[number]> => Boolean(item?.url));
|
||||
const modelAttachment =
|
||||
AGENT_DETAIL_MODEL_SLOT_INDEX >= 0
|
||||
modelSlotEnabled && AGENT_DETAIL_MODEL_SLOT_INDEX >= 0
|
||||
? detailGenerationSlots[AGENT_DETAIL_MODEL_SLOT_INDEX]
|
||||
: null;
|
||||
|
||||
@ -2760,9 +2865,10 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
: "";
|
||||
const detailPromptWithWorkflow = `${detailPrompt}${workflowHint}`.trim();
|
||||
const detailCount = extractAgentDetailFrameCount(detailPromptWithWorkflow);
|
||||
const referenceImageNotes = AGENT_DETAIL_SLOT_CONFIGS.map((config, index) => {
|
||||
const current = detailGenerationSlots[index];
|
||||
return `${index + 1}. ${config.label}${current?.name ? `(${current.name})` : ""}`;
|
||||
const referenceImageNotes = detailGenerationVisibleSlotIndexes.map((slotIndex, visibleIndex) => {
|
||||
const config = AGENT_DETAIL_SLOT_CONFIGS[slotIndex];
|
||||
const current = detailGenerationSlots[slotIndex];
|
||||
return `${visibleIndex + 1}. ${config?.label || `素材${visibleIndex + 1}`}${current?.name ? `(${current.name})` : ""}`;
|
||||
});
|
||||
const outputLanguage: "zh" | "en" = /[a-zA-Z]/.test(detailPromptWithWorkflow)
|
||||
? "en"
|
||||
@ -2802,6 +2908,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
count: detailCount,
|
||||
generationMode: "classic",
|
||||
referenceImages: detailGenerationSlots
|
||||
.filter((_, slotIndex) => detailGenerationVisibleSlotIndexes.includes(slotIndex))
|
||||
.map((slot) => slot?.url || "")
|
||||
.filter(Boolean)
|
||||
.slice(0, 12),
|
||||
@ -3429,12 +3536,16 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div className="text-xs font-medium text-white/82">素材中心</div>
|
||||
<div className="text-[11px] text-white/45">
|
||||
产品图 {detailGenerationProductCount}/5 · 模特图{" "}
|
||||
{detailGenerationModelReady ? "1/1" : "0/1"} · 搭配图 {detailGenerationBundleCount}/3
|
||||
产品图 {detailGenerationProductCount}/{detailGenerationRequiredProductCount}
|
||||
{modelSlotEnabled
|
||||
? ` · 模特图 ${detailGenerationModelReady ? "1/1" : "0/1"}`
|
||||
: ""}
|
||||
{` · 搭配图 ${detailGenerationBundleCount}/3`}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 xl:grid-cols-3">
|
||||
{AGENT_DETAIL_SLOT_CONFIGS.map((slot, index) => {
|
||||
{detailGenerationVisibleSlotIndexes.map((index) => {
|
||||
const slot = AGENT_DETAIL_SLOT_CONFIGS[index];
|
||||
const attachment = detailGenerationSlots[index];
|
||||
return (
|
||||
<div
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user