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